From 89c5f6cc832fd61deb9175696f351516a30913c9 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 25 Apr 2019 10:32:03 +0300 Subject: [PATCH 01/30] WIP --- .vscode/launch.json | 2 +- lib/bootstrap.ts | 2 + lib/commands/appstore-upload.ts | 6 +- lib/commands/build.ts | 6 +- lib/commands/clean-app.ts | 6 +- lib/commands/prepare.ts | 6 +- lib/common/declarations.d.ts | 1 + lib/common/helpers.ts | 5 +- lib/definitions/platform.d.ts | 9 +- lib/definitions/project-changes.d.ts | 2 - lib/services/livesync/livesync-service.ts | 129 ++++++----- .../platform-livesync-service-base.ts | 11 +- lib/services/local-build-service.ts | 4 + lib/services/platform-service.ts | 209 ++++++------------ lib/services/prepare-platform-js-service.ts | 29 ++- .../prepare-platform-native-service.ts | 88 ++++++-- lib/services/prepare-platform-service.ts | 4 +- lib/services/project-changes-service.ts | 26 +-- .../webpack/webpack-compiler-service.ts | 59 +++++ lib/services/webpack/webpack.d.ts | 22 ++ 20 files changed, 348 insertions(+), 278 deletions(-) create mode 100644 lib/services/webpack/webpack-compiler-service.ts create mode 100644 lib/services/webpack/webpack.d.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index e1dd227cc5..cfe5787073 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "program": "${workspaceRoot}/lib/nativescript-cli.js", // example commands - "args": [ "create", "cliapp", "--path", "${workspaceRoot}/scratch"] + "args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle"] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] // "args": [ "platform", "remove", "android", "--path", "cliapp"] diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index a8eda19224..5ee098ceac 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -201,3 +201,5 @@ $injector.require("testInitializationService", "./services/test-initialization-s $injector.require("networkConnectivityValidator", "./helpers/network-connectivity-validator"); $injector.requirePublic("cleanupService", "./services/cleanup-service"); + +$injector.require("webpackCompilerService", "./services/webpack/webpack-compiler-service"); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index c1055a2f92..f353a41509 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -65,7 +65,11 @@ export class PublishIOS implements ICommand { appFilesUpdaterOptions, projectData: this.$projectData, config: this.$options, - env: this.$options.env + env: this.$options.env, + webpackCompilerConfig: { + watch: false, + env: this.$options.env + } }; const buildConfig: IBuildConfig = { projectDir: this.$options.path, diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 95e709b873..01eaac5c9c 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -26,7 +26,11 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { appFilesUpdaterOptions, projectData: this.$projectData, config: this.$options, - env: this.$options.env + env: this.$options.env, + webpackCompilerConfig: { + watch: false, + env: this.$options.env + } }; await this.$platformService.preparePlatform(platformInfo); diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index b825b7e071..c822bcc980 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -26,7 +26,11 @@ export class CleanAppCommandBase extends ValidatePlatformCommandBase implements platform: this.platform.toLowerCase(), config: this.$options, projectData: this.$projectData, - env: this.$options.env + env: this.$options.env, + webpackCompilerConfig: { + watch: false, + env: this.$options.env + } }; return this.$platformService.cleanDestinationApp(platformInfo); diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index afa0387614..c37f99083d 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -23,7 +23,11 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm appFilesUpdaterOptions, projectData: this.$projectData, config: this.$options, - env: this.$options.env + env: this.$options.env, + webpackCompilerConfig: { + watch: false, + env: this.$options.env + } }; await this.$platformService.preparePlatform(platformInfo); diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index 5b0e0314a9..fb97c9c52c 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -1507,6 +1507,7 @@ interface IPromiseActions { interface IDeferPromise extends IPromiseActions { isRejected(): boolean; isPending(): boolean; + getResult(): any; promise: Promise; } diff --git a/lib/common/helpers.ts b/lib/common/helpers.ts index fd5b6044e4..82860f25a0 100644 --- a/lib/common/helpers.ts +++ b/lib/common/helpers.ts @@ -130,10 +130,12 @@ export function deferPromise(): IDeferPromise { let isResolved = false; let isRejected = false; let promise: Promise; + let result: T | PromiseLike; promise = new Promise((innerResolve, innerReject) => { resolve = (value?: T | PromiseLike) => { isResolved = true; + result = value; return innerResolve(value); }; @@ -151,7 +153,8 @@ export function deferPromise(): IDeferPromise { reject, isResolved: () => isResolved, isRejected: () => isRejected, - isPending: () => !isResolved && !isRejected + isPending: () => !isResolved && !isRejected, + getResult: () => result }; } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 650017d684..3501b9e091 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -307,11 +307,6 @@ interface IPlatformDataComposition { interface ICopyAppFilesData extends IProjectDataComposition, IAppFilesUpdaterOptionsComposition, IPlatformDataComposition, IOptionalFilesToSync, IOptionalFilesToRemove { } -interface IPreparePlatformService { - addPlatform(info: IAddPlatformInfo): Promise; - preparePlatform(config: IPreparePlatformJSInfo): Promise; -} - interface IAddPlatformInfo extends IProjectDataComposition, IPlatformDataComposition { frameworkDir: string; installedVersion: string; @@ -334,7 +329,9 @@ interface IPreparePlatformCoreInfo extends IPreparePlatformInfoBase, IOptionalPr platformSpecificData: IPlatformSpecificData; } -interface IPreparePlatformInfo extends IPreparePlatformInfoBase, IPlatformConfig, ISkipNativeCheckOptional { } +interface IPreparePlatformInfo extends IPreparePlatformInfoBase, IPlatformConfig, ISkipNativeCheckOptional { + webpackCompilerConfig: IWebpackCompilerConfig; +} interface IPlatformConfig { config: IPlatformOptions; diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index 7da52ca180..3d3f051999 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -13,13 +13,11 @@ interface IPrepareInfo extends IAddedNativePlatform, IAppFilesHashes { } interface IProjectChangesInfo extends IAddedNativePlatform { - appFilesChanged: boolean; appResourcesChanged: boolean; modulesChanged: boolean; configChanged: boolean; packageChanged: boolean; nativeChanged: boolean; - bundleChanged: boolean; signingChanged: boolean; readonly hasChanges: boolean; diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 5d5e800bb6..52eae4f75d 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,5 +1,5 @@ -import * as path from "path"; -import * as choki from "chokidar"; +// import * as path from "path"; +// import * as choki from "chokidar"; import { EOL } from "os"; import { EventEmitter } from "events"; import { hook } from "../../common/helpers"; @@ -329,6 +329,8 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi @hook('watchPatterns') public async getWatcherPatterns(liveSyncData: ILiveSyncInfo, projectData: IProjectData, platforms: string[]): Promise { // liveSyncData and platforms are used by plugins that make use of the watchPatterns hook + // TODO: ignore getAppDirectoryRelativePath + // TODO: watch platforms folder of node_modules e.g native source folders of nativescript plugins return [projectData.getAppDirectoryRelativePath(), projectData.getAppResourcesRelativeDirectoryPath()]; } @@ -413,26 +415,6 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi const appInstalledOnDeviceResult: IAppInstalledOnDeviceResult = { appInstalled: false }; if (options.preparedPlatforms.indexOf(platform) === -1) { options.preparedPlatforms.push(platform); - - const platformSpecificOptions = options.deviceBuildInfoDescriptor.platformSpecificOptions || {}; - const prepareInfo: IPreparePlatformInfo = { - platform, - appFilesUpdaterOptions: { - bundle: options.bundle, - release: options.release, - watchAllFiles: options.liveSyncData.watchAllFiles, - useHotModuleReload: options.liveSyncData.useHotModuleReload - }, - projectData: options.projectData, - env: options.env, - nativePrepare: nativePrepare, - filesToSync: options.filesToSync, - filesToRemove: options.filesToRemove, - skipModulesNativeCheck: options.skipModulesNativeCheck, - config: platformSpecificOptions - }; - - await this.$platformService.preparePlatform(prepareInfo); } const buildResult = await this.installedCachedAppPackage(platform, options); @@ -600,13 +582,18 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi let filesToRemove: string[] = []; let timeoutTimer: NodeJS.Timer; - const startSyncFilesTimeout = (platform?: string, opts?: { calledFromHook: boolean }) => { + const startSyncFilesTimeout = (files: string[], platform?: string, opts?: { calledFromHook: boolean }) => { timeoutTimer = setTimeout(async () => { if (platform && liveSyncData.bundle) { filesToSync = filesToSyncMap[platform]; } - if (filesToSync.length || filesToRemove.length) { + if (files) { + filesToSync = files; + } + + if ((filesToSync && filesToSync.length) || (filesToRemove && filesToRemove.length)) { + console.log("============================== FILES_TO_SYNC ==================== ", filesToSync); const currentFilesToSync = _.cloneDeep(filesToSync); filesToSync.splice(0, filesToSync.length); @@ -766,47 +753,82 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi filesToSyncMap, hmrData, filesToRemove, - startSyncFilesTimeout: async (platform: string) => { - const opts = { calledFromHook: true }; - if (platform) { - await startSyncFilesTimeout(platform, opts); - } else { - // This code is added for backwards compatibility with old versions of nativescript-dev-webpack plugin. - await startSyncFilesTimeout(null, opts); - } - } + // startSyncFilesTimeout: async (platform: string) => { + // const opts = { calledFromHook: true }; + // if (platform) { + // await startSyncFilesTimeout(platform, opts); + // } else { + // // This code is added for backwards compatibility with old versions of nativescript-dev-webpack plugin. + // await startSyncFilesTimeout(null, opts); + // } + // } } }); - const watcherOptions: choki.WatchOptions = { - ignoreInitial: true, - cwd: liveSyncData.projectDir, - awaitWriteFinish: { - pollInterval: 100, - stabilityThreshold: 500 + let isFirstSync = true; + this.$platformService.on("changedFiles", async files => { + console.log("===================== CHANGED FILES =============== ", files); + if (!isFirstSync) { + // filesToSyncMap["ios"] = files; + await startSyncFilesTimeout(files, "ios", { calledFromHook: true }); + } else { + isFirstSync = false; + } + }); + + // const platformSpecificOptions = options.deviceBuildInfoDescriptor.platformSpecificOptions || {}; + const prepareInfo: IPreparePlatformInfo = { + platform: "ios", + appFilesUpdaterOptions: { + bundle: true, + release: false, + watchAllFiles: false, + useHotModuleReload: false }, - ignored: ["**/.*", ".*"] // hidden files + projectData: this.$projectDataService.getProjectData(liveSyncData.projectDir), + env: liveSyncData.env, + nativePrepare: null, + // filesToSync: options.filesToSync, + // filesToRemove: options.filesToRemove, + // skipModulesNativeCheck: liveSyncData.skipModulesNativeCheck, + config: {}, + webpackCompilerConfig: { + watch: true, + env: liveSyncData.env + } }; - const watcher = choki.watch(patterns, watcherOptions) - .on("all", async (event: string, filePath: string) => { + await this.$platformService.preparePlatform(prepareInfo); - clearTimeout(timeoutTimer); + // const watcherOptions: choki.WatchOptions = { + // ignoreInitial: true, + // cwd: liveSyncData.projectDir, + // awaitWriteFinish: { + // pollInterval: 100, + // stabilityThreshold: 500 + // }, + // ignored: ["**/.*", ".*"] // hidden files + // }; - filePath = path.join(liveSyncData.projectDir, filePath); + // const watcher = choki.watch(patterns, watcherOptions) + // .on("all", async (event: string, filePath: string) => { - this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); + // clearTimeout(timeoutTimer); - if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) { - filesToSync.push(filePath); - } else if (event === "unlink" || event === "unlinkDir") { - filesToRemove.push(filePath); - } + // filePath = path.join(liveSyncData.projectDir, filePath); - startSyncFilesTimeout(); - }); + // this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); - this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, patterns }; + // if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) { + // filesToSync.push(filePath); + // } else if (event === "unlink" || event === "unlinkDir") { + // filesToRemove.push(filePath); + // } + + // startSyncFilesTimeout(); + // }); + + // this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, patterns }; this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; } } @@ -862,7 +884,6 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi this.$logger.trace(`Will emit event ${event} with data`, livesyncData); return this.emit(event, livesyncData); } - } $injector.register("liveSyncService", LiveSyncService); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index fac12ac588..d0fb91d933 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -10,8 +10,7 @@ export abstract class PlatformLiveSyncServiceBase { protected $logger: ILogger, protected $platformsData: IPlatformsData, protected $projectFilesManager: IProjectFilesManager, - private $devicePathProvider: IDevicePathProvider, - private $projectFilesProvider: IProjectFilesProvider) { } + private $devicePathProvider: IDevicePathProvider) { } public getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService { const platform = device.deviceInfo.platform.toLowerCase(); @@ -86,12 +85,12 @@ export abstract class PlatformLiveSyncServiceBase { let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; if (liveSyncInfo.filesToSync.length) { const filesToSync = liveSyncInfo.filesToSync; - const mappedFiles = _.map(filesToSync, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); + // const mappedFiles = _.map(filesToSync, filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)); // Some plugins modify platforms dir on afterPrepare (check nativescript-dev-sass) - we want to sync only existing file. - const existingFiles = mappedFiles.filter(m => m && this.$fs.exists(m)); + const existingFiles = filesToSync.filter(m => m && this.$fs.exists(m)); this.$logger.trace("Will execute livesync for files: ", existingFiles); - const skippedFiles = _.difference(mappedFiles, existingFiles); + const skippedFiles = _.difference(filesToSync, existingFiles); if (skippedFiles.length) { this.$logger.trace("The following files will not be synced as they do not exist:", skippedFiles); } @@ -111,7 +110,7 @@ export abstract class PlatformLiveSyncServiceBase { const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const mappedFiles = _(filePaths) - .map(filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)) + // .map(filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)) .filter(filePath => !!filePath) .value(); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); diff --git a/lib/services/local-build-service.ts b/lib/services/local-build-service.ts index ae50dfb717..f9abb165ea 100644 --- a/lib/services/local-build-service.ts +++ b/lib/services/local-build-service.ts @@ -29,6 +29,10 @@ export class LocalBuildService extends EventEmitter implements ILocalBuildServic sdk: null, frameworkPath: null, ignoreScripts: false + }, + webpackCompilerConfig: { + watch: false, + env: platformBuildOptions.env } }; diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index d6b935eca6..4f77ecdd0a 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -21,12 +21,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $errors: IErrors, private $fs: IFileSystem, private $logger: ILogger, - private $doctorService: IDoctorService, private $packageInstallationManager: IPackageInstallationManager, private $platformsData: IPlatformsData, private $projectDataService: IProjectDataService, private $pluginsService: IPluginsService, - private $projectFilesManager: IProjectFilesManager, + // private $projectFilesManager: IProjectFilesManager, private $mobileHelper: Mobile.IMobileHelper, private $hostInfo: IHostInfo, private $devicePathProvider: IDevicePathProvider, @@ -35,8 +34,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $analyticsService: IAnalyticsService, private $terminalSpinnerService: ITerminalSpinnerService, private $pacoteService: IPacoteService, - private $usbLiveSyncService: any, - public $hooksService: IHooksService + // private $usbLiveSyncService: any, + public $hooksService: IHooksService, ) { super(); } @@ -105,11 +104,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { const errorMessage = format(constants.AddPlaformErrors.InvalidFrameworkPathStringFormat, frameworkPath); this.$errors.fail(errorMessage); } - } else { - if (!version) { - version = this.getCurrentPlatformVersion(platform, projectData) || - await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); - } + } else if (!version) { + version = this.getCurrentPlatformVersion(platform, projectData) || await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); packageToInstall = `${platformData.frameworkPackageName}@${version}`; } @@ -142,17 +138,12 @@ export class PlatformService extends EventEmitter implements IPlatformService { const coreModuleData = this.$fs.readJson(path.join(frameworkDir, "..", "package.json")); const installedVersion = coreModuleData.version; - await this.$preparePlatformJSService.addPlatform({ - platformData, - frameworkDir, - installedVersion, - projectData, - config - }); + // JS platform add + const frameworkPackageNameData = { version: installedVersion }; + this.$projectDataService.setNSValue(projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData); if (!nativePrepare || !nativePrepare.skipNativePrepare) { - const platformDir = path.join(projectData.platformsDir, platformData.normalizedPlatformName.toLowerCase()); - this.$fs.deleteDirectory(platformDir); + await this.$preparePlatformNativeService.addPlatform({ platformData, frameworkDir, @@ -219,35 +210,56 @@ export class PlatformService extends EventEmitter implements IPlatformService { } @performanceLog() - public async preparePlatform(platformInfo: IPreparePlatformInfo): Promise { + public async preparePlatform(platformInfo: IPreparePlatformInfo, callback?: (shouldRebuild: boolean) => {}): Promise { + const { platform, projectData, webpackCompilerConfig, config, appFilesUpdaterOptions, filesToSync, filesToRemove, env } = platformInfo; const changesInfo = await this.getChangesInfo(platformInfo); - const shouldPrepare = await this.shouldPrepare({ platformInfo, changesInfo }); - - if (shouldPrepare) { - // Always clear up the app directory in platforms if `--bundle` value has changed in between builds or is passed in general - // this is done as user has full control over what goes in platforms when `--bundle` is passed - // and we may end up with duplicate symbols which would fail the build - if (changesInfo.bundleChanged) { - await this.cleanDestinationApp(platformInfo); + const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: appFilesUpdaterOptions.release }); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const prepareNativePlatformData = { + platform, + platformData, + appFilesUpdaterOptions, + projectData, + platformSpecificData: config, + changesInfo, + filesToSync, + filesToRemove, + projectFilesConfig, + env + }; + + this.$logger.out("Preparing project..."); + + const nativePromise = helpers.deferPromise(); + const jsPromise = helpers.deferPromise(); + const jsFiles: string[] = []; + + this.$preparePlatformJSService.on("jsFilesChanged", files => { + jsFiles.push(...files); + if (!jsPromise.isResolved()) { + jsPromise.resolve(jsFiles); } - this.$doctorService.checkForDeprecatedShortImportsInAppDir(platformInfo.projectData.projectDir); - - await this.preparePlatformCore( - platformInfo.platform, - platformInfo.appFilesUpdaterOptions, - platformInfo.projectData, - platformInfo.config, - platformInfo.env, - changesInfo, - platformInfo.filesToSync, - platformInfo.filesToRemove, - platformInfo.nativePrepare - ); - this.$projectChangesService.savePrepareInfo(platformInfo.platform, platformInfo.projectData); - } else { - this.$logger.out("Skipping prepare."); - } + if (nativePromise.isResolved()) { + this.emit("changedFiles", _.uniq(jsFiles)); + } + }); + await this.$preparePlatformJSService.startWatcher(platformData, projectData, webpackCompilerConfig); + + this.$preparePlatformNativeService.on("nativeFilesChanged", (files: any) => { + if (!nativePromise.isResolved()) { + nativePromise.resolve([]); + } + + if (jsPromise.isResolved()) { + this.emit("changedFiles", jsPromise.getResult()); + } + }); + await this.$preparePlatformNativeService.startWatcher(platformData, projectData, prepareNativePlatformData); + + await Promise.all([jsPromise, nativePromise]); + + this.$logger.out(`Project successfully prepared (${platform})`); return true; } @@ -300,62 +312,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, nativePrepare); } - /* Hooks are expected to use "filesToSync" parameter, as to give plugin authors additional information about the sync process.*/ - @performanceLog() - @helpers.hook('prepare') - private async preparePlatformCore(platform: string, - appFilesUpdaterOptions: IAppFilesUpdaterOptions, - projectData: IProjectData, - platformSpecificData: IPlatformSpecificData, - env: Object, - changesInfo?: IProjectChangesInfo, - filesToSync?: string[], - filesToRemove?: string[], - nativePrepare?: INativePrepare): Promise { - - this.$logger.out("Preparing project..."); - - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: appFilesUpdaterOptions.release }); - await this.$preparePlatformJSService.preparePlatform({ - platform, - platformData, - projectFilesConfig, - appFilesUpdaterOptions, - projectData, - platformSpecificData, - changesInfo, - filesToSync, - filesToRemove, - env - }); - - if (!nativePrepare || !nativePrepare.skipNativePrepare) { - await this.$preparePlatformNativeService.preparePlatform({ - platform, - platformData, - appFilesUpdaterOptions, - projectData, - platformSpecificData, - changesInfo, - filesToSync, - filesToRemove, - projectFilesConfig, - env - }); - } - - const directoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - const excludedDirs = [constants.APP_RESOURCES_FOLDER_NAME]; - if (!changesInfo || !changesInfo.modulesChanged) { - excludedDirs.push(constants.TNS_MODULES_FOLDER_NAME); - } - - this.$projectFilesManager.processPlatformSpecificFiles(directoryPath, platform, projectFilesConfig, excludedDirs); - - this.$logger.out(`Project successfully prepared (${platform})`); - } - public async shouldBuild(platform: string, projectData: IProjectData, buildConfig: IBuildConfig, outputPath?: string): Promise { if (buildConfig.release && this.$projectChangesService.currentChanges.hasChanges) { return true; @@ -537,7 +493,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { projectData: deployInfo.projectData, config: deployInfo.config, nativePrepare: deployInfo.nativePrepare, - env: deployInfo.env + env: deployInfo.env, + webpackCompilerConfig: { + watch: false, + env: deployInfo.env + } }); const options: Mobile.IDevicesServicesInitializationOptions = { platform: deployInfo.platform, deviceId: deployInfo.deployOptions.device, emulator: deployInfo.deployOptions.emulator @@ -769,54 +729,17 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async ensurePlatformInstalled(platform: string, projectData: IProjectData, config: IPlatformOptions, appFilesUpdaterOptions: IAppFilesUpdaterOptions, nativePrepare?: INativePrepare): Promise { - let requiresNativePlatformAdd = false; - - const platformData = this.$platformsData.getPlatformData(platform, projectData); + private async ensurePlatformInstalled(platform: string, projectData: IProjectData, config: IPlatformOptions, appFilesUpdaterOptions: IAppFilesUpdaterOptions, nativePrepare?: INativePrepare): Promise { const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - - // In case when no platform is added and webpack plugin is started it produces files in platforms folder. - // In this case {N} CLI needs to add platform and keeps the already produced files from webpack - const shouldPersistWebpackFiles = this.shouldPersistWebpackFiles(platform, projectData, prepareInfo, appFilesUpdaterOptions, nativePrepare); - if (shouldPersistWebpackFiles) { - await this.persistWebpackFiles(platform, projectData, config, platformData, nativePrepare); - return; - } - const hasPlatformDirectory = this.hasPlatformDirectory(platform, projectData); - if (hasPlatformDirectory) { - const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; - // In case there's no prepare info, it means only platform add had been executed. So we've come from CLI and we do not need to prepare natively. - requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; - if (requiresNativePlatformAdd && shouldAddNativePlatform) { - await this.addPlatform(platform, projectData, config, "", nativePrepare); - } - } else { + const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; + const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; + const shouldAddPlatform = !hasPlatformDirectory || (shouldAddNativePlatform && requiresNativePlatformAdd); + if (shouldAddPlatform) { await this.addPlatform(platform, projectData, config, "", nativePrepare); } } - private shouldPersistWebpackFiles(platform: string, projectData: IProjectData, prepareInfo: IPrepareInfo, appFilesUpdaterOptions: IAppFilesUpdaterOptions, nativePrepare: INativePrepare): boolean { - const hasPlatformDirectory = this.hasPlatformDirectory(platform, projectData); - const isWebpackWatcherStarted = this.$usbLiveSyncService.isInitialized; - const hasNativePlatformStatus = prepareInfo && prepareInfo.nativePlatformStatus; - const requiresPlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; - const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; - const shouldAddPlatform = !hasNativePlatformStatus || (requiresPlatformAdd && shouldAddNativePlatform); - const result = appFilesUpdaterOptions.bundle && isWebpackWatcherStarted && hasPlatformDirectory && shouldAddPlatform; - return result; - } - - private async persistWebpackFiles(platform: string, projectData: IProjectData, config: IPlatformOptions, platformData: IPlatformData, nativePrepare?: INativePrepare): Promise { - const tmpDirectoryPath = path.join(projectData.projectDir, "platforms", `tmp-${platform}`); - this.$fs.deleteDirectory(tmpDirectoryPath); - this.$fs.ensureDirectoryExists(tmpDirectoryPath); - this.$fs.copyFile(path.join(platformData.appDestinationDirectoryPath, "*"), tmpDirectoryPath); - await this.addPlatform(platform, projectData, config, "", nativePrepare); - this.$fs.copyFile(path.join(tmpDirectoryPath, "*"), platformData.appDestinationDirectoryPath); - this.$fs.deleteDirectory(tmpDirectoryPath); - } - private hasPlatformDirectory(platform: string, projectData: IProjectData): boolean { return this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase())); } diff --git a/lib/services/prepare-platform-js-service.ts b/lib/services/prepare-platform-js-service.ts index 0ea772d0bb..9ca1aa728c 100644 --- a/lib/services/prepare-platform-js-service.ts +++ b/lib/services/prepare-platform-js-service.ts @@ -1,22 +1,13 @@ -import * as temp from "temp"; import { hook } from "../common/helpers"; -import { PreparePlatformService } from "./prepare-platform-service"; import { performanceLog } from "./../common/decorators"; +import { EventEmitter } from "events"; -temp.track(); +export class PreparePlatformJSService extends EventEmitter implements IPreparePlatformService { -export class PreparePlatformJSService extends PreparePlatformService implements IPreparePlatformService { - - constructor($fs: IFileSystem, - $xmlValidator: IXmlValidator, - $hooksService: IHooksService, - private $projectDataService: IProjectDataService) { - super($fs, $hooksService, $xmlValidator); - } - - public async addPlatform(info: IAddPlatformInfo): Promise { - const frameworkPackageNameData: any = { version: info.installedVersion }; - this.$projectDataService.setNSValue(info.projectData.projectDir, info.platformData.frameworkPackageName, frameworkPackageNameData); + constructor( + private $webpackCompilerService: IWebpackCompilerService + ) { + super(); } @performanceLog() @@ -24,6 +15,14 @@ export class PreparePlatformJSService extends PreparePlatformService implements public async preparePlatform(config: IPreparePlatformJSInfo): Promise { // intentionally left blank, keep the support for before-prepareJSApp and after-prepareJSApp hooks } + + public async startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IPreparePlatformJSInfo): Promise { + this.$webpackCompilerService.on("webpackEmittedFiles", files => { + this.emit("jsFilesChanged", files); + }); + + await this.$webpackCompilerService.startWatcher(platformData, projectData, config); + } } $injector.register("preparePlatformJSService", PreparePlatformJSService); diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/prepare-platform-native-service.ts index 5afbbb5dc6..d0ea74e3f3 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/prepare-platform-native-service.ts @@ -1,31 +1,82 @@ -import * as constants from "../constants"; import * as path from "path"; -import { PreparePlatformService } from "./prepare-platform-service"; +import * as choki from "chokidar"; +import * as constants from "../constants"; import { performanceLog } from "../common/decorators"; +import { EventEmitter } from "events"; -export class PreparePlatformNativeService extends PreparePlatformService implements IPreparePlatformService { +export class PreparePlatformNativeService extends EventEmitter implements IPreparePlatformService { + private watchersInfo: IDictionary = {}; - constructor($fs: IFileSystem, - $xmlValidator: IXmlValidator, - $hooksService: IHooksService, + constructor( + private $fs: IFileSystem, + private $logger: ILogger, private $nodeModulesBuilder: INodeModulesBuilder, private $projectChangesService: IProjectChangesService, private $androidResourcesMigrationService: IAndroidResourcesMigrationService) { - super($fs, $hooksService, $xmlValidator); + super(); } @performanceLog() public async addPlatform(info: IAddPlatformInfo): Promise { - await info.platformData.platformProjectService.createProject(path.resolve(info.frameworkDir), info.installedVersion, info.projectData, info.config); - info.platformData.platformProjectService.ensureConfigurationFileInAppResources(info.projectData); - await info.platformData.platformProjectService.interpolateData(info.projectData, info.config); - info.platformData.platformProjectService.afterCreateProject(info.platformData.projectRoot, info.projectData); - this.$projectChangesService.setNativePlatformStatus(info.platformData.normalizedPlatformName, info.projectData, + const { platformData, projectData, frameworkDir, installedVersion, config } = info; + + const platformDir = path.join(projectData.platformsDir, platformData.normalizedPlatformName.toLowerCase()); + this.$fs.deleteDirectory(platformDir); + + await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, config); + platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); + await info.platformData.platformProjectService.interpolateData(projectData, config); + info.platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); + this.$projectChangesService.setNativePlatformStatus(platformData.normalizedPlatformName, projectData, { nativePlatformStatus: constants.NativePlatformStatus.requiresPrepare }); } + public async startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IPreparePlatformJSInfo): Promise { + const watcherOptions: choki.WatchOptions = { + ignoreInitial: true, + cwd: projectData.projectDir, + awaitWriteFinish: { + pollInterval: 100, + stabilityThreshold: 500 + }, + ignored: ["**/.*", ".*"] // hidden files + }; + + await this.preparePlatform(config); + + // TODO: node_modules/**/platforms -> when no platform is provided, + // node_modules/**/platforms/ios -> when iOS platform is provided + // node_modules/**/platforms/android -> when Android is provided + const patterns = [projectData.getAppResourcesRelativeDirectoryPath(), "node_modules/**/platforms/"]; + + // TODO: Add stopWatcher function + const watcher = choki.watch(patterns, watcherOptions) + .on("all", async (event: string, filePath: string) => { + filePath = path.join(projectData.projectDir, filePath); + this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); + await this.preparePlatform(config); + }); + + this.watchersInfo[projectData.projectDir] = watcher; + } + + public stopWatchers(): void { + // TODO: stop the watchers here + } + @performanceLog() - public async preparePlatform(config: IPreparePlatformJSInfo): Promise { + public async preparePlatform(config: IPreparePlatformJSInfo): Promise { // TODO: should return 3 states for nativeFilesChanged, hasChanges, noChanges, skipChanges + const shouldAddNativePlatform = !config.nativePrepare || !config.nativePrepare.skipNativePrepare; + if (!shouldAddNativePlatform) { + this.emit("nativeFilesChanged", false); + } + + const hasModulesChange = !config.changesInfo || config.changesInfo.modulesChanged; + const hasConfigChange = !config.changesInfo || config.changesInfo.configChanged; + const hasChangesRequirePrepare = !config.changesInfo || config.changesInfo.changesRequirePrepare; + + const hasChanges = hasModulesChange || hasConfigChange || hasChangesRequirePrepare; + if (config.changesInfo.hasChanges) { await this.cleanProject(config.platform, config.appFilesUpdaterOptions, config.platformData, config.projectData); } @@ -33,17 +84,12 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme // Move the native application resources from platforms/.../app/App_Resources // to the right places in the native project, // because webpack copies them on every build (not every change). - if (!config.changesInfo || config.changesInfo.changesRequirePrepare || config.appFilesUpdaterOptions.bundle) { - this.prepareAppResources(config.platformData, config.projectData); - } + this.prepareAppResources(config.platformData, config.projectData); - if (!config.changesInfo || config.changesInfo.changesRequirePrepare) { + if (hasChangesRequirePrepare) { await config.platformData.platformProjectService.prepareProject(config.projectData, config.platformSpecificData); } - const hasModulesChange = !config.changesInfo || config.changesInfo.modulesChanged; - const hasConfigChange = !config.changesInfo || config.changesInfo.configChanged; - if (hasModulesChange) { const appDestinationDirectoryPath = path.join(config.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); const lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; @@ -70,6 +116,8 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme config.platformData.platformProjectService.interpolateConfigurationFile(config.projectData, config.platformSpecificData); this.$projectChangesService.setNativePlatformStatus(config.platform, config.projectData, { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); + + this.emit("nativeFilesChanged", hasChanges); } private prepareAppResources(platformData: IPlatformData, projectData: IProjectData): void { diff --git a/lib/services/prepare-platform-service.ts b/lib/services/prepare-platform-service.ts index f6553e37b4..95e66f609d 100644 --- a/lib/services/prepare-platform-service.ts +++ b/lib/services/prepare-platform-service.ts @@ -1,11 +1,13 @@ import * as constants from "../constants"; import * as path from "path"; import { AppFilesUpdater } from "./app-files-updater"; +import { EventEmitter } from "events"; -export class PreparePlatformService { +export class PreparePlatformService extends EventEmitter { constructor(protected $fs: IFileSystem, public $hooksService: IHooksService, private $xmlValidator: IXmlValidator) { + super(); } protected async copyAppFiles(copyAppFilesData: ICopyAppFilesData): Promise { diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 82d96bd384..504c7e14bb 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -6,19 +6,16 @@ const prepareInfoFileName = ".nsprepareinfo"; class ProjectChangesInfo implements IProjectChangesInfo { - public appFilesChanged: boolean; public appResourcesChanged: boolean; public modulesChanged: boolean; public configChanged: boolean; public packageChanged: boolean; public nativeChanged: boolean; - public bundleChanged: boolean; public signingChanged: boolean; public nativePlatformStatus: NativePlatformStatus; public get hasChanges(): boolean { return this.packageChanged || - this.appFilesChanged || this.appResourcesChanged || this.modulesChanged || this.configChanged || @@ -49,7 +46,6 @@ export class ProjectChangesService implements IProjectChangesService { private $platformsData: IPlatformsData, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $fs: IFileSystem, - private $filesHashService: IFilesHashService, private $logger: ILogger, public $hooksService: IHooksService) { } @@ -67,7 +63,6 @@ export class ProjectChangesService implements IProjectChangesService { if (!isNewPrepareInfo) { this._newFiles = 0; - this._changesInfo.appFilesChanged = await this.hasChangedAppFiles(projectData); this._changesInfo.packageChanged = this.isProjectFileChanged(projectData, platform); @@ -109,10 +104,8 @@ export class ProjectChangesService implements IProjectChangesService { if (projectChangesOptions.bundle !== this._prepareInfo.bundle || projectChangesOptions.release !== this._prepareInfo.release) { this.$logger.trace(`Setting all setting to true. Current options are: `, projectChangesOptions, " old prepare info is: ", this._prepareInfo); - this._changesInfo.appFilesChanged = true; this._changesInfo.appResourcesChanged = true; this._changesInfo.modulesChanged = true; - this._changesInfo.bundleChanged = true; this._changesInfo.configChanged = true; this._prepareInfo.release = projectChangesOptions.release; this._prepareInfo.bundle = projectChangesOptions.bundle; @@ -199,14 +192,12 @@ export class ProjectChangesService implements IProjectChangesService { release: projectChangesOptions.release, changesRequireBuild: true, projectFileHash: this.getProjectFileStrippedHash(projectData, platform), - changesRequireBuildTime: null, - appFilesHashes: await this.$filesHashService.generateHashes(this.getAppFiles(projectData)) + changesRequireBuildTime: null }; this._outputProjectMtime = 0; this._outputProjectCTime = 0; this._changesInfo = this._changesInfo || new ProjectChangesInfo(); - this._changesInfo.appFilesChanged = true; this._changesInfo.appResourcesChanged = true; this._changesInfo.modulesChanged = true; this._changesInfo.configChanged = true; @@ -335,20 +326,5 @@ export class ProjectChangesService implements IProjectChangesService { } return false; } - - private getAppFiles(projectData: IProjectData): string[] { - return this.$fs.enumerateFilesInDirectorySync(projectData.appDirectoryPath, (filePath: string, stat: IFsStats) => filePath !== projectData.appResourcesDirectoryPath); - } - - private async hasChangedAppFiles(projectData: IProjectData): Promise { - const files = this.getAppFiles(projectData); - const changedFiles = await this.$filesHashService.getChanges(files, this._prepareInfo.appFilesHashes || {}); - const hasChanges = changedFiles && _.keys(changedFiles).length > 0; - if (hasChanges) { - this._prepareInfo.appFilesHashes = await this.$filesHashService.generateHashes(files); - } - - return hasChanges; - } } $injector.register("projectChangesService", ProjectChangesService); diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts new file mode 100644 index 0000000000..8718103c2e --- /dev/null +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -0,0 +1,59 @@ +import * as path from "path"; +import { EventEmitter } from "events"; + +export class WebpackCompilerService extends EventEmitter implements IWebpackCompilerService { + constructor( + private $childProcess: IChildProcess + ) { + super(); + } + + // TODO: Consider to introduce two methods -> compile and startWebpackWatcher + public async startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + const args = [ + path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), + "--preserve-symlinks", + `--config=${path.join(projectData.projectDir, "webpack.config.js")}`, + `--env.${platformData.normalizedPlatformName.toLowerCase()}` + ]; + + if (config.watch) { + args.push("--watch"); + } + + // TODO: provide env variables + + const stdio = config.watch ? ["inherit", "inherit", "inherit", "ipc"] : "inherit"; + + return new Promise((resolve, reject) => { + const childProcess = this.$childProcess.spawn("node", args, { cwd: projectData.projectDir, stdio }); + if (config.watch) { + childProcess.on("message", (message: any) => { + if (message === "Webpack compilation complete.") { + resolve(); + } + + if (message.emittedFiles) { + const files = message.emittedFiles + .filter((file: string) => file.indexOf("App_Resources") === -1) + .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)) + this.emit("webpackEmittedFiles", files); + } + }); + } + + childProcess.on("close", (arg: any) => { + const exitCode = typeof arg === "number" ? arg : arg && arg.code; + console.log("=========== WEBPACK EXIT WITH CODE ========== ", exitCode); + if (exitCode === 0) { + resolve(); + } else { + const error = new Error(`Executing webpack failed with exit code ${exitCode}.`); + error.code = exitCode; + reject(error); + } + }); + }); + } +} +$injector.register("webpackCompilerService", WebpackCompilerService); diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts new file mode 100644 index 0000000000..047d32e14a --- /dev/null +++ b/lib/services/webpack/webpack.d.ts @@ -0,0 +1,22 @@ +import { EventEmitter } from "events"; + +declare global { + interface IWebpackCompilerService extends EventEmitter { + startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; + } + + interface IWebpackCompilerConfig { + watch: boolean; + env: IWebpackEnvOptions; + } + + interface IWebpackEnvOptions { + + } + + interface IPreparePlatformService extends EventEmitter { + addPlatform?(info: IAddPlatformInfo): Promise; + preparePlatform(config: IPreparePlatformJSInfo): Promise; + startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IPreparePlatformJSInfo | IWebpackCompilerConfig): Promise; + } +} \ No newline at end of file From fb47b68a8218f11e9bc3057f039778400e434245 Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 28 Apr 2019 00:55:56 +0300 Subject: [PATCH 02/30] refactor: refactor platform add, platform prepare and platform build workflows --- .vscode/launch.json | 2 +- lib/bootstrap.ts | 18 +- lib/commands/add-platform.ts | 11 +- lib/commands/appstore-list.ts | 4 +- lib/commands/appstore-upload.ts | 35 +- lib/commands/build.ts | 49 +- lib/commands/clean-app.ts | 81 - lib/commands/command-base.ts | 4 +- lib/commands/debug.ts | 10 +- lib/commands/deploy.ts | 5 +- lib/commands/install.ts | 4 +- lib/commands/list-platforms.ts | 8 +- lib/commands/platform-clean.ts | 18 +- lib/commands/prepare.ts | 30 +- lib/commands/remove-platform.ts | 13 +- lib/commands/run.ts | 29 +- lib/commands/update-platform.ts | 15 +- lib/commands/update.ts | 25 +- lib/common/definitions/mobile.d.ts | 10 - lib/declarations.d.ts | 62 +- lib/definitions/livesync.d.ts | 19 +- lib/definitions/platform.d.ts | 91 +- lib/definitions/project-changes.d.ts | 13 +- lib/definitions/project.d.ts | 6 +- lib/factory/platform-workflow-data-factory.ts | 54 + lib/helpers/deploy-command-helper.ts | 2 +- lib/helpers/livesync-command-helper.ts | 9 +- lib/platform-command-param.ts | 4 +- lib/services/android-project-service.ts | 19 +- lib/services/app-files-updater.ts | 104 - lib/services/build-artefacts-service.ts | 90 + lib/services/bundle-workflow-service.ts | 92 + lib/services/emulator-settings-service.ts | 17 - lib/services/ios-project-service.ts | 12 +- .../livesync/android-livesync-service.ts | 5 +- lib/services/livesync/ios-livesync-service.ts | 5 +- lib/services/livesync/livesync-service.ts | 1804 ++++++++--------- lib/services/local-build-service.ts | 49 +- .../platform-environment-requirements.ts | 6 +- lib/services/platform-service.ts | 447 +--- lib/services/platform/platform-add-service.ts | 102 + .../platform/platform-build-service.ts | 74 + .../platform/platform-commands-service.ts | 178 ++ .../platform/platform-install-service.ts | 0 .../platform/platform-validation-service.ts | 75 + .../platform/platform-watcher-service.ts | 102 + lib/services/prepare-platform-js-service.ts | 25 +- .../prepare-platform-native-service.ts | 129 +- lib/services/prepare-platform-service.ts | 34 - lib/services/project-changes-service.ts | 11 +- lib/services/test-execution-service.ts | 6 +- .../webpack/webpack-compiler-service.ts | 89 +- lib/services/webpack/webpack.d.ts | 40 +- .../workflow/platform-workflow-service.ts | 50 + lib/services/workflow/workflow.d.ts | 16 + .../node-modules/node-modules-builder.ts | 26 +- .../node-modules/node-modules-dest-copy.ts | 23 - test/app-files-updates.ts | 87 - test/ios-project-service.ts | 307 ++- test/platform-commands.ts | 16 +- test/platform-service.ts | 107 +- test/project-changes-service.ts | 28 +- test/services/livesync-service.ts | 418 ++-- test/stubs.ts | 8 +- 64 files changed, 2639 insertions(+), 2593 deletions(-) delete mode 100644 lib/commands/clean-app.ts create mode 100644 lib/factory/platform-workflow-data-factory.ts delete mode 100644 lib/services/app-files-updater.ts create mode 100644 lib/services/build-artefacts-service.ts create mode 100644 lib/services/bundle-workflow-service.ts delete mode 100644 lib/services/emulator-settings-service.ts create mode 100644 lib/services/platform/platform-add-service.ts create mode 100644 lib/services/platform/platform-build-service.ts create mode 100644 lib/services/platform/platform-commands-service.ts create mode 100644 lib/services/platform/platform-install-service.ts create mode 100644 lib/services/platform/platform-validation-service.ts create mode 100644 lib/services/platform/platform-watcher-service.ts delete mode 100644 lib/services/prepare-platform-service.ts create mode 100644 lib/services/workflow/platform-workflow-service.ts create mode 100644 lib/services/workflow/workflow.d.ts delete mode 100644 lib/tools/node-modules/node-modules-dest-copy.ts delete mode 100644 test/app-files-updates.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index cfe5787073..5814d09efd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "program": "${workspaceRoot}/lib/nativescript-cli.js", // example commands - "args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle"] + "args": [ "build", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle"] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] // "args": [ "platform", "remove", "android", "--path", "cliapp"] diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 5ee098ceac..4d6b911264 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -34,8 +34,18 @@ $injector.require("tnsModulesService", "./services/tns-modules-service"); $injector.require("platformsData", "./platforms-data"); $injector.require("platformService", "./services/platform-service"); -$injector.require("preparePlatformJSService", "./services/prepare-platform-js-service"); -$injector.require("preparePlatformNativeService", "./services/prepare-platform-native-service"); +$injector.require("platformJSService", "./services/prepare-platform-js-service"); +$injector.require("platformNativeService", "./services/prepare-platform-native-service"); +$injector.require("platformAddService", "./services/platform/platform-add-service"); +$injector.require("platformBuildService", "./services/platform/platform-build-service"); +$injector.require("platformValidationService", "./services/platform/platform-validation-service"); +$injector.require("platformCommandsService", "./services/platform/platform-commands-service"); + +$injector.require("platformWorkflowService", "./services/workflow/platform-workflow-service"); + +$injector.require("platformWorkflowDataFactory", "./factory/platform-workflow-data-factory"); + +$injector.require("buildArtefactsService", "./services/build-artefacts-service"); $injector.require("debugDataService", "./services/debug-data-service"); $injector.requirePublicClass("debugService", "./services/debug-service"); @@ -47,8 +57,6 @@ $injector.requirePublic("analyticsSettingsService", "./services/analytics-settin $injector.require("analyticsService", "./services/analytics/analytics-service"); $injector.require("googleAnalyticsProvider", "./services/analytics/google-analytics-provider"); -$injector.require("emulatorSettingsService", "./services/emulator-settings-service"); - $injector.require("platformCommandParameter", "./platform-command-param"); $injector.requireCommand("create", "./commands/create-project"); $injector.requireCommand("generate", "./commands/generate"); @@ -66,8 +74,6 @@ $injector.requireCommand("debug|ios", "./commands/debug"); $injector.requireCommand("debug|android", "./commands/debug"); $injector.requireCommand("prepare", "./commands/prepare"); -$injector.requireCommand("clean-app|ios", "./commands/clean-app"); -$injector.requireCommand("clean-app|android", "./commands/clean-app"); $injector.requireCommand("build|ios", "./commands/build"); $injector.requireCommand("build|android", "./commands/build"); $injector.requireCommand("deploy", "./commands/deploy"); diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts index a75cff15bd..16fe4d13ab 100644 --- a/lib/commands/add-platform.ts +++ b/lib/commands/add-platform.ts @@ -4,16 +4,17 @@ export class AddPlatformCommand extends ValidatePlatformCommandBase implements I public allowedParameters: ICommandParameter[] = []; constructor($options: IOptions, - $platformService: IPlatformService, + private $platformCommandsService: IPlatformCommandsService, + $platformValidationService: IPlatformValidationService, $projectData: IProjectData, $platformsData: IPlatformsData, private $errors: IErrors) { - super($options, $platformsData, $platformService, $projectData); + super($options, $platformsData, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - await this.$platformService.addPlatforms(args, this.$projectData, this.$options, this.$options.frameworkPath); + await this.$platformCommandsService.addPlatforms(args, this.$projectData, this.$options.frameworkPath); } public async canExecute(args: string[]): Promise { @@ -23,9 +24,9 @@ export class AddPlatformCommand extends ValidatePlatformCommandBase implements I let canExecute = true; for (const arg of args) { - this.$platformService.validatePlatform(arg, this.$projectData); + this.$platformValidationService.validatePlatform(arg, this.$projectData); - if (!this.$platformService.isPlatformSupportedForOS(arg, this.$projectData)) { + if (!this.$platformValidationService.isPlatformSupportedForOS(arg, this.$projectData)) { this.$errors.fail(`Applications for platform ${arg} can not be built on this OS`); } diff --git a/lib/commands/appstore-list.ts b/lib/commands/appstore-list.ts index 6313a2f64b..128d9b8a3f 100644 --- a/lib/commands/appstore-list.ts +++ b/lib/commands/appstore-list.ts @@ -9,14 +9,14 @@ export class ListiOSApps implements ICommand { private $logger: ILogger, private $projectData: IProjectData, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $platformService: IPlatformService, + private $platformValidationService: IPlatformValidationService, private $errors: IErrors, private $prompter: IPrompter) { this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + if (!this.$platformValidationService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index f353a41509..730522545f 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -13,6 +13,8 @@ export class PublishIOS implements ICommand { private $options: IOptions, private $prompter: IPrompter, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $platformValidationService: IPlatformValidationService, + private $platformBuildService: IPlatformBuildService, private $xcodebuildService: IXcodebuildService) { this.$projectData.initializeProjectData(); } @@ -55,21 +57,10 @@ export class PublishIOS implements ICommand { if (!ipaFilePath) { const platform = this.$devicePlatformsConstants.iOS; // No .ipa path provided, build .ipa on out own. - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { - bundle: !!this.$options.bundle, + const preparePlatformData: IPreparePlatformData = { release: this.$options.release, - useHotModuleReload: false - }; - const platformInfo: IPreparePlatformInfo = { - platform, - appFilesUpdaterOptions, - projectData: this.$projectData, - config: this.$options, + useHotModuleReload: false, env: this.$options.env, - webpackCompilerConfig: { - watch: false, - env: this.$options.env - } }; const buildConfig: IBuildConfig = { projectDir: this.$options.path, @@ -83,22 +74,20 @@ export class PublishIOS implements ICommand { codeSignIdentity }; + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + if (mobileProvisionIdentifier || codeSignIdentity) { this.$logger.info("Building .ipa with the selected mobile provision and/or certificate."); // This is not very correct as if we build multiple targets we will try to sign all of them using the signing identity here. - await this.$platformService.preparePlatform(platformInfo); - await this.$platformService.buildPlatform(platform, buildConfig, this.$projectData); + await this.$platformService.preparePlatform(platformData, this.$projectData, preparePlatformData); + await this.$platformBuildService.buildPlatform(platformData, this.$projectData, buildConfig); ipaFilePath = this.$platformService.lastOutputPath(platform, buildConfig, this.$projectData); } else { this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); - await this.$platformService.preparePlatform(platformInfo); - - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); - - const exportPath = await this.$xcodebuildService.buildForAppStore(platformData, this.$projectData, buildConfig); - this.$logger.info("Export at: " + exportPath); + await this.$platformService.preparePlatform(platformData, this.$projectData, preparePlatformData); - ipaFilePath = exportPath; + ipaFilePath = await this.$xcodebuildService.buildForAppStore(platformData, this.$projectData, buildConfig); + this.$logger.info(`Export at: ${ipaFilePath}`); } } @@ -111,7 +100,7 @@ export class PublishIOS implements ICommand { } public async canExecute(args: string[]): Promise { - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + if (!this.$platformValidationService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 01eaac5c9c..eb40694acb 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -7,33 +7,18 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { $projectData: IProjectData, $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $platformService: IPlatformService, + protected $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, + private $platformWorkflowService: IPlatformWorkflowService, + $platformValidationService: IPlatformValidationService, private $bundleValidatorHelper: IBundleValidatorHelper, protected $logger: ILogger) { - super($options, $platformsData, $platformService, $projectData); + super($options, $platformsData, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } public async executeCore(args: string[]): Promise { const platform = args[0].toLowerCase(); - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { - bundle: !!this.$options.bundle, - release: this.$options.release, - useHotModuleReload: this.$options.hmr - }; - const platformInfo: IPreparePlatformInfo = { - platform, - appFilesUpdaterOptions, - projectData: this.$projectData, - config: this.$options, - env: this.$options.env, - webpackCompilerConfig: { - watch: false, - env: this.$options.env - } - }; - await this.$platformService.preparePlatform(platformInfo); const buildConfig: IBuildConfig = { buildForDevice: this.$options.forDevice, iCloudContainerEnvironment: this.$options.iCloudContainerEnvironment, @@ -50,19 +35,15 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { androidBundle: this.$options.aab }; - const outputPath = await this.$platformService.buildPlatform(platform, buildConfig, this.$projectData); - - if (this.$options.copyTo) { - this.$platformService.copyLastOutput(platform, this.$options.copyTo, buildConfig, this.$projectData); - } else { - this.$logger.info(`The build result is located at: ${outputPath}`); - } + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const workflowData = this.$platformWorkflowDataFactory.createPlatformWorkflowData(platform, this.$options); + const outputPath = await this.$platformWorkflowService.buildPlatform(platformData, this.$projectData, workflowData, buildConfig); return outputPath; } protected validatePlatform(platform: string): void { - if (!this.$platformService.isPlatformSupportedForOS(platform, this.$projectData)) { + if (!this.$platformValidationService.isPlatformSupportedForOS(platform, this.$projectData)) { this.$errors.fail(`Applications for platform ${platform} can not be built on this OS`); } @@ -82,7 +63,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { return false; } - const result = await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); + const result = await this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); return result; } } @@ -95,10 +76,12 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $platformService: IPlatformService, + $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, + $platformWorkflowService: IPlatformWorkflowService, + $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $logger); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformWorkflowDataFactory, $platformWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); } public async execute(args: string[]): Promise { @@ -129,11 +112,13 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $platformService: IPlatformService, + $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, + $platformWorkflowService: IPlatformWorkflowService, + $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, protected $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $logger); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformWorkflowDataFactory, $platformWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); } public async execute(args: string[]): Promise { diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts deleted file mode 100644 index c822bcc980..0000000000 --- a/lib/commands/clean-app.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { ValidatePlatformCommandBase } from "./command-base"; - -export class CleanAppCommandBase extends ValidatePlatformCommandBase implements ICommand { - public allowedParameters: ICommandParameter[] = []; - - protected platform: string; - - constructor($options: IOptions, - $projectData: IProjectData, - $platformService: IPlatformService, - protected $errors: IErrors, - protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $platformsData: IPlatformsData) { - super($options, $platformsData, $platformService, $projectData); - this.$projectData.initializeProjectData(); - } - - public async execute(args: string[]): Promise { - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { - bundle: !!this.$options.bundle, - release: this.$options.release, - useHotModuleReload: false - }; - const platformInfo: IPreparePlatformInfo = { - appFilesUpdaterOptions, - platform: this.platform.toLowerCase(), - config: this.$options, - projectData: this.$projectData, - env: this.$options.env, - webpackCompilerConfig: { - watch: false, - env: this.$options.env - } - }; - - return this.$platformService.cleanDestinationApp(platformInfo); - } - - public async canExecute(args: string[]): Promise { - if (!this.$platformService.isPlatformSupportedForOS(this.platform, this.$projectData)) { - this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); - } - - const result = await super.canExecuteCommandBase(this.platform); - return result; - } -} - -export class CleanAppIosCommand extends CleanAppCommandBase implements ICommand { - constructor(protected $options: IOptions, - protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - protected $platformsData: IPlatformsData, - protected $errors: IErrors, - $platformService: IPlatformService, - $projectData: IProjectData) { - super($options, $projectData, $platformService, $errors, $devicePlatformsConstants, $platformsData); - } - - protected get platform(): string { - return this.$devicePlatformsConstants.iOS; - } -} - -$injector.registerCommand("clean-app|ios", CleanAppIosCommand); - -export class CleanAppAndroidCommand extends CleanAppCommandBase implements ICommand { - constructor(protected $options: IOptions, - protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - protected $platformsData: IPlatformsData, - protected $errors: IErrors, - $platformService: IPlatformService, - $projectData: IProjectData) { - super($options, $projectData, $platformService, $errors, $devicePlatformsConstants, $platformsData); - } - - protected get platform(): string { - return this.$devicePlatformsConstants.Android; - } -} - -$injector.registerCommand("clean-app|android", CleanAppAndroidCommand); diff --git a/lib/commands/command-base.ts b/lib/commands/command-base.ts index e4f778336e..27eb6a78fb 100644 --- a/lib/commands/command-base.ts +++ b/lib/commands/command-base.ts @@ -1,7 +1,7 @@ export abstract class ValidatePlatformCommandBase { constructor(protected $options: IOptions, protected $platformsData: IPlatformsData, - protected $platformService: IPlatformService, + protected $platformValidationService: IPlatformValidationService, protected $projectData: IProjectData) { } abstract allowedParameters: ICommandParameter[]; @@ -14,7 +14,7 @@ export abstract class ValidatePlatformCommandBase { let result = { canExecute, suppressCommandHelp: !canExecute }; if (canExecute && options.validateOptions) { - const validateOptionsOutput = await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); + const validateOptionsOutput = await this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); result = { canExecute: validateOptionsOutput, suppressCommandHelp: false }; } diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index ab62e8ac56..ab386e23c4 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -9,7 +9,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements private $bundleValidatorHelper: IBundleValidatorHelper, private $debugService: IDebugService, protected $devicesService: Mobile.IDevicesService, - $platformService: IPlatformService, + $platformValidationService: IPlatformValidationService, $projectData: IProjectData, $options: IOptions, $platformsData: IPlatformsData, @@ -19,7 +19,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements private $liveSyncService: IDebugLiveSyncService, private $liveSyncCommandHelper: ILiveSyncCommandHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { - super($options, $platformsData, $platformService, $projectData); + super($options, $platformsData, $platformValidationService, $projectData); } public async execute(args: string[]): Promise { @@ -58,7 +58,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements public async canExecute(args: string[]): Promise { this.$androidBundleValidatorHelper.validateNoAab(); - if (!this.$platformService.isPlatformSupportedForOS(this.platform, this.$projectData)) { + if (!this.$platformValidationService.isPlatformSupportedForOS(this.platform, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); } @@ -85,7 +85,7 @@ export class DebugIOSCommand implements ICommand { constructor(protected $errors: IErrors, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $platformService: IPlatformService, + private $platformValidationService: IPlatformValidationService, private $options: IOptions, private $injector: IInjector, private $sysInfo: ISysInfo, @@ -108,7 +108,7 @@ export class DebugIOSCommand implements ICommand { } public async canExecute(args: string[]): Promise { - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { + if (!this.$platformValidationService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index c83b357ca0..cfc1485cc0 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -4,7 +4,8 @@ import { ValidatePlatformCommandBase } from "./command-base"; export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor($platformService: IPlatformService, + constructor($platformValidationService: IPlatformValidationService, + private $platformService: IPlatformService, private $platformCommandParameter: ICommandParameter, $options: IOptions, $projectData: IProjectData, @@ -14,7 +15,7 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement $platformsData: IPlatformsData, private $bundleValidatorHelper: IBundleValidatorHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { - super($options, $platformsData, $platformService, $projectData); + super($options, $platformsData, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } diff --git a/lib/commands/install.ts b/lib/commands/install.ts index 655097d6a8..bdbfd2921b 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -6,7 +6,7 @@ export class InstallCommand implements ICommand { constructor(private $options: IOptions, private $platformsData: IPlatformsData, - private $platformService: IPlatformService, + private $platformCommandsService: IPlatformCommandsService, private $projectData: IProjectData, private $projectDataService: IProjectDataService, private $pluginsService: IPluginsService, @@ -34,7 +34,7 @@ export class InstallCommand implements ICommand { const platformProjectService = platformData.platformProjectService; await platformProjectService.validate(this.$projectData, this.$options); - await this.$platformService.addPlatforms([`${platform}@${frameworkPackageData.version}`], this.$projectData, this.$options, this.$options.frameworkPath); + await this.$platformCommandsService.addPlatforms([`${platform}@${frameworkPackageData.version}`], this.$projectData, this.$options.frameworkPath); } catch (err) { error = `${error}${EOL}${err}`; } diff --git a/lib/commands/list-platforms.ts b/lib/commands/list-platforms.ts index 7210609be8..d47b1cf21d 100644 --- a/lib/commands/list-platforms.ts +++ b/lib/commands/list-platforms.ts @@ -3,17 +3,17 @@ import * as helpers from "../common/helpers"; export class ListPlatformsCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $platformService: IPlatformService, + constructor(private $platformCommandsService: IPlatformCommandsService, private $projectData: IProjectData, private $logger: ILogger) { this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - const installedPlatforms = this.$platformService.getInstalledPlatforms(this.$projectData); + const installedPlatforms = this.$platformCommandsService.getInstalledPlatforms(this.$projectData); if (installedPlatforms.length > 0) { - const preparedPlatforms = this.$platformService.getPreparedPlatforms(this.$projectData); + const preparedPlatforms = this.$platformCommandsService.getPreparedPlatforms(this.$projectData); if (preparedPlatforms.length > 0) { this.$logger.out("The project is prepared for: ", helpers.formatListOfNames(preparedPlatforms, "and")); } else { @@ -22,7 +22,7 @@ export class ListPlatformsCommand implements ICommand { this.$logger.out("Installed platforms: ", helpers.formatListOfNames(installedPlatforms, "and")); } else { - const formattedPlatformsList = helpers.formatListOfNames(this.$platformService.getAvailablePlatforms(this.$projectData), "and"); + const formattedPlatformsList = helpers.formatListOfNames(this.$platformCommandsService.getAvailablePlatforms(this.$projectData), "and"); this.$logger.out("Available platforms for this OS: ", formattedPlatformsList); this.$logger.out("No installed platforms found. Use $ tns platform add"); } diff --git a/lib/commands/platform-clean.ts b/lib/commands/platform-clean.ts index 9837902c8e..056132c496 100644 --- a/lib/commands/platform-clean.ts +++ b/lib/commands/platform-clean.ts @@ -1,16 +1,20 @@ export class CleanCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $options: IOptions, - private $projectData: IProjectData, - private $platformService: IPlatformService, + constructor( private $errors: IErrors, - private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { + private $options: IOptions, + private $platformCommandsService: IPlatformCommandsService, + private $platformValidationService: IPlatformValidationService, + private $platformService: IPlatformService, + private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, + private $projectData: IProjectData + ) { this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - await this.$platformService.cleanPlatforms(args, this.$projectData, this.$options); + await this.$platformCommandsService.cleanPlatforms(args, this.$projectData, this.$options.frameworkPath); } public async canExecute(args: string[]): Promise { @@ -19,11 +23,11 @@ export class CleanCommand implements ICommand { } _.each(args, platform => { - this.$platformService.validatePlatform(platform, this.$projectData); + this.$platformValidationService.validatePlatform(platform, this.$projectData); }); for (const platform of args) { - this.$platformService.validatePlatformInstalled(platform, this.$projectData); + this.$platformValidationService.validatePlatformInstalled(platform, this.$projectData); const currentRuntimeVersion = this.$platformService.getCurrentPlatformVersion(platform, this.$projectData); await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index c37f99083d..108af3b7d7 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -4,38 +4,28 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm public allowedParameters = [this.$platformCommandParameter]; constructor($options: IOptions, - $platformService: IPlatformService, + private $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, + private $platformWorkflowService: IPlatformWorkflowService, + $platformValidationService: IPlatformValidationService, $projectData: IProjectData, private $platformCommandParameter: ICommandParameter, $platformsData: IPlatformsData) { - super($options, $platformsData, $platformService, $projectData); + super($options, $platformsData, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { - bundle: !!this.$options.bundle, - release: this.$options.release, - useHotModuleReload: this.$options.hmr - }; - const platformInfo: IPreparePlatformInfo = { - platform: args[0], - appFilesUpdaterOptions, - projectData: this.$projectData, - config: this.$options, - env: this.$options.env, - webpackCompilerConfig: { - watch: false, - env: this.$options.env - } - }; + const platform = args[0]; + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const workflowData = this.$platformWorkflowDataFactory.createPlatformWorkflowData(platform, this.$options); - await this.$platformService.preparePlatform(platformInfo); + await this.$platformWorkflowService.preparePlatform(platformData, this.$projectData, workflowData); } public async canExecute(args: string[]): Promise { const platform = args[0]; - const result = await this.$platformCommandParameter.validate(platform) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); + const result = await this.$platformCommandParameter.validate(platform) && + await this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); if (!result) { return false; } diff --git a/lib/commands/remove-platform.ts b/lib/commands/remove-platform.ts index 3ee9b90dc7..97805861dd 100644 --- a/lib/commands/remove-platform.ts +++ b/lib/commands/remove-platform.ts @@ -1,14 +1,17 @@ export class RemovePlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $platformService: IPlatformService, - private $projectData: IProjectData, - private $errors: IErrors) { + constructor( + private $errors: IErrors, + private $platformCommandsService: IPlatformCommandsService, + private $platformValidationService: IPlatformValidationService, + private $projectData: IProjectData + ) { this.$projectData.initializeProjectData(); } public execute(args: string[]): Promise { - return this.$platformService.removePlatforms(args, this.$projectData); + return this.$platformCommandsService.removePlatforms(args, this.$projectData); } public async canExecute(args: string[]): Promise { @@ -17,7 +20,7 @@ export class RemovePlatformCommand implements ICommand { } _.each(args, platform => { - this.$platformService.validatePlatform(platform, this.$projectData); + this.$platformValidationService.validatePlatform(platform, this.$projectData); }); return true; diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 2e87e58376..a283a5b03f 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -8,12 +8,13 @@ export class RunCommandBase implements ICommand { public platform: string; constructor( private $analyticsService: IAnalyticsService, - private $projectData: IProjectData, + private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $errors: IErrors, private $hostInfo: IHostInfo, private $liveSyncCommandHelper: ILiveSyncCommandHelper, - private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { } + private $projectData: IProjectData, + ) { } public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { @@ -61,13 +62,15 @@ export class RunIosCommand implements ICommand { return this.$devicePlatformsConstants.iOS; } - constructor(private $platformsData: IPlatformsData, + constructor( private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $errors: IErrors, private $injector: IInjector, - private $platformService: IPlatformService, + private $options: IOptions, + private $platformsData: IPlatformsData, + private $platformValidationService: IPlatformValidationService, private $projectDataService: IProjectDataService, - private $options: IOptions) { + ) { } public async execute(args: string[]): Promise { @@ -77,11 +80,11 @@ export class RunIosCommand implements ICommand { public async canExecute(args: string[]): Promise { const projectData = this.$projectDataService.getProjectData(); - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, projectData)) { + if (!this.$platformValidationService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - const result = await this.runCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, projectData, this.$platformsData.availablePlatforms.iOS); + const result = await this.runCommand.canExecute(args) && await this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, projectData, this.$platformsData.availablePlatforms.iOS); return result; } } @@ -102,13 +105,15 @@ export class RunAndroidCommand implements ICommand { return this.$devicePlatformsConstants.Android; } - constructor(private $platformsData: IPlatformsData, + constructor( private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $errors: IErrors, private $injector: IInjector, - private $platformService: IPlatformService, + private $options: IOptions, + private $platformsData: IPlatformsData, + private $platformValidationService: IPlatformValidationService, private $projectData: IProjectData, - private $options: IOptions) { } + ) { } public execute(args: string[]): Promise { return this.runCommand.execute(args); @@ -117,7 +122,7 @@ export class RunAndroidCommand implements ICommand { public async canExecute(args: string[]): Promise { await this.runCommand.canExecute(args); - if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { + if (!this.$platformValidationService.isPlatformSupportedForOS(this.$devicePlatformsConstants.Android, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.Android} can not be built on this OS`); } @@ -125,7 +130,7 @@ export class RunAndroidCommand implements ICommand { this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } - return this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); + return this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); } } diff --git a/lib/commands/update-platform.ts b/lib/commands/update-platform.ts index 61d62f779c..51431949d4 100644 --- a/lib/commands/update-platform.ts +++ b/lib/commands/update-platform.ts @@ -1,16 +1,19 @@ export class UpdatePlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $options: IOptions, - private $projectData: IProjectData, - private $platformService: IPlatformService, + constructor( + private $errors: IErrors, + private $options: IOptions, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, - private $errors: IErrors) { + private $platformCommandsService: IPlatformCommandsService, + private $platformValidationService: IPlatformValidationService, + private $projectData: IProjectData, + ) { this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - await this.$platformService.updatePlatforms(args, this.$projectData, this.$options); + await this.$platformCommandsService.updatePlatforms(args, this.$projectData); } public async canExecute(args: string[]): Promise { @@ -20,7 +23,7 @@ export class UpdatePlatformCommand implements ICommand { _.each(args, arg => { const platform = arg.split("@")[0]; - this.$platformService.validatePlatform(platform, this.$projectData); + this.$platformValidationService.validatePlatform(platform, this.$projectData); }); for (const arg of args) { diff --git a/lib/commands/update.ts b/lib/commands/update.ts index afbd5f7c0a..be4f45f27c 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -5,15 +5,18 @@ import { ValidatePlatformCommandBase } from "./command-base"; export class UpdateCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor($options: IOptions, - $projectData: IProjectData, - $platformService: IPlatformService, + constructor( + private $fs: IFileSystem, + private $logger: ILogger, + $options: IOptions, + private $platformCommandsService: IPlatformCommandsService, $platformsData: IPlatformsData, + $platformValidationService: IPlatformValidationService, private $pluginsService: IPluginsService, + $projectData: IProjectData, private $projectDataService: IProjectDataService, - private $fs: IFileSystem, - private $logger: ILogger) { - super($options, $platformsData, $platformService, $projectData); + ) { + super($options, $platformsData, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } @@ -82,7 +85,7 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma this.$projectDataService.removeNSProperty(this.$projectData.projectDir, platformData.frameworkPackageName); } - await this.$platformService.removePlatforms(platforms.installed, this.$projectData); + await this.$platformCommandsService.removePlatforms(platforms.installed, this.$projectData); await this.$pluginsService.remove(constants.TNS_CORE_MODULES_NAME, this.$projectData); if (!!this.$projectData.dependencies[constants.TNS_CORE_MODULES_WIDGETS_NAME]) { await this.$pluginsService.remove(constants.TNS_CORE_MODULES_WIDGETS_NAME, this.$projectData); @@ -94,12 +97,12 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma if (args.length === 1) { for (const platform of platforms.packagePlatforms) { - await this.$platformService.addPlatforms([platform + "@" + args[0]], this.$projectData, this.$options, this.$options.frameworkPath); + await this.$platformCommandsService.addPlatforms([platform + "@" + args[0]], this.$projectData, this.$options.frameworkPath); } await this.$pluginsService.add(`${constants.TNS_CORE_MODULES_NAME}@${args[0]}`, this.$projectData); } else { - await this.$platformService.addPlatforms(platforms.packagePlatforms, this.$projectData, this.$options, this.$options.frameworkPath); + await this.$platformCommandsService.addPlatforms(platforms.packagePlatforms, this.$projectData, this.$options.frameworkPath); await this.$pluginsService.add(constants.TNS_CORE_MODULES_NAME, this.$projectData); } @@ -107,8 +110,8 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma } private getPlatforms(): { installed: string[], packagePlatforms: string[] } { - const installedPlatforms = this.$platformService.getInstalledPlatforms(this.$projectData); - const availablePlatforms = this.$platformService.getAvailablePlatforms(this.$projectData); + const installedPlatforms = this.$platformCommandsService.getInstalledPlatforms(this.$projectData); + const availablePlatforms = this.$platformCommandsService.getAvailablePlatforms(this.$projectData); const packagePlatforms: string[] = []; for (const platform of availablePlatforms) { diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 1ce531beef..9f0a5bd788 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -897,16 +897,6 @@ declare module Mobile { connectToPort(connectToPortData: IConnectToPortData): Promise; } - interface IEmulatorSettingsService { - /** - * Gives information if current project can be started in emulator. - * @param {string} platform The mobile platform of the emulator (android, ios, wp8). - * @returns {boolean} true in case the project can be started in emulator. In case not, the method will throw error. - */ - canStart(platform: string): boolean; - minVersion: number; - } - interface IRunApplicationOnEmulatorOptions { /** * The identifier of the application that will be started on device. diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 1be467010f..ef02ac6a93 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -578,9 +578,9 @@ interface IHasAndroidBundle { androidBundle?: boolean; } -interface IAppFilesUpdaterOptions extends IBundle, IRelease, IOptionalWatchAllFiles, IHasUseHotModuleReloadOption { } +interface IAppFilesUpdaterOptions { } -interface IPlatformBuildData extends IAppFilesUpdaterOptions, IBuildConfig, IEnvOptions { } +interface IPlatformBuildData extends IRelease, IHasUseHotModuleReloadOption, IBuildConfig, IEnvOptions { } interface IDeviceEmulator extends IHasEmulatorOption, IDeviceIdentifier { } @@ -1026,3 +1026,61 @@ interface IRuntimeGradleVersions { interface INetworkConnectivityValidator { validate(): Promise; } + +interface IBundleWorkflowService { + +} + +interface IPlatformValidationService { + /** + * Ensures the passed platform is a valid one (from the supported ones) + */ + validatePlatform(platform: string, projectData: IProjectData): void; + + /** + * Gets first chance to validate the options provided as command line arguments. + * If no platform is provided or a falsy (null, undefined, "", false...) platform is provided, + * the options will be validated for all available platforms. + */ + validateOptions(provision: true | string, teamId: true | string, projectData: IProjectData, platform?: string): Promise; + + + validatePlatformInstalled(platform: string, projectData: IProjectData): void; + + /** + * Checks whether passed platform can be built on the current OS + * @param {string} platform The mobile platform. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {boolean} Whether the platform is supported for current OS or not. + */ + isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean; +} + +interface IBuildArtefactsService { + getLastBuiltPackagePath(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): Promise; +} + +interface IPlatformAddService { + addPlatform(addPlatformData: IAddPlatformData, projectData: IProjectData): Promise; +} + +interface IPlatformCommandsService { + addPlatforms(platforms: string[], projectData: IProjectData, frameworkPath: string): Promise; + cleanPlatforms(platforms: string[], projectData: IProjectData, framworkPath: string): Promise; + removePlatforms(platforms: string[], projectData: IProjectData): Promise; + updatePlatforms(platforms: string[], projectData: IProjectData): Promise; + getInstalledPlatforms(projectData: IProjectData): string[]; + getAvailablePlatforms(projectData: IProjectData): string[]; + getPreparedPlatforms(projectData: IProjectData): string[]; +} + +interface IAddPlatformData { + platformParam: string; + frameworkPath?: string; + nativePrepare?: INativePrepare; +} + +interface IPlatformBuildService { + buildPlatform(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; + saveBuildInfoFile(platformData: IPlatformData, projectData: IProjectData, buildInfoFileDirname: string): void; +} diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index b71cb07b93..e79a2b10ef 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -129,11 +129,6 @@ declare global { * Whether debugging has been enabled for this device or not */ debugggingEnabled?: boolean; - - /** - * Describes options specific for each platform, like provision for iOS, target sdk for Android, etc. - */ - platformSpecificOptions?: IPlatformOptions; } interface IOptionalSkipWatcher { @@ -146,13 +141,9 @@ declare global { /** * Describes a LiveSync operation. */ - interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { - /** - * Defines if all project files should be watched for changes. In case it is not passed, only `app` dir of the project will be watched for changes. - * In case it is set to true, the package.json of the project and node_modules directory will also be watched, so any change there will be transferred to device(s). - */ - watchAllFiles?: boolean; - + interface ILiveSyncInfo extends IProjectDir, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { + webpackCompilerConfig: IWebpackCompilerConfig; + /** * Forces a build before the initial livesync. */ @@ -170,6 +161,8 @@ declare global { * If not provided, defaults to 10seconds. */ timeout?: string; + + nativePrepare?: INativePrepare; } interface IHasSyncToPreviewAppOption { @@ -213,7 +206,7 @@ declare global { /** * Desribes object that can be passed to ensureLatestAppPackageIsInstalledOnDevice method. */ - interface IEnsureLatestAppPackageIsInstalledOnDeviceOptions extends IProjectDataComposition, IEnvOptions, IBundle, IRelease, ISkipNativeCheckOptional, IOptionalFilesToRemove, IOptionalFilesToSync { + interface IEnsureLatestAppPackageIsInstalledOnDeviceOptions extends IProjectDataComposition, IEnvOptions, IBundle, IRelease, IOptionalFilesToRemove, IOptionalFilesToSync { device: Mobile.IDevice; preparedPlatforms: string[]; rebuiltInformation: ILiveSyncBuildInfo[]; diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 3501b9e091..bf0ee2e0a9 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -15,41 +15,6 @@ interface IBuildPlatformAction { } interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { - cleanPlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions, framework?: string): Promise; - - addPlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string): Promise; - - /** - * Gets list of all installed platforms (the ones for which /platforms/ exists). - * @param {IProjectData} projectData DTO with information about the project. - * @returns {string[]} List of currently installed platforms. - */ - getInstalledPlatforms(projectData: IProjectData): string[]; - - /** - * Gets a list of all platforms that can be used on current OS, but are not installed at the moment. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {string[]} List of all available platforms. - */ - getAvailablePlatforms(projectData: IProjectData): string[]; - - /** - * Returns a list of all currently prepared platforms. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {string[]} List of all prepared platforms. - */ - getPreparedPlatforms(projectData: IProjectData): string[]; - - /** - * Remove platforms from specified project (`/platforms/` dir). - * @param {string[]} platforms Platforms to be removed. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {Promise} - */ - removePlatforms(platforms: string[], projectData: IProjectData): Promise; - - updatePlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions): Promise; - /** * Ensures that the specified platform and its dependencies are installed. * When there are changes to be prepared, it prepares the native project for the specified platform. @@ -58,7 +23,7 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { * @param {IPreparePlatformInfo} platformInfo Options to control the preparation. * @returns {boolean} true indicates that the platform was prepared. */ - preparePlatform(platformInfo: IPreparePlatformInfo): Promise; + preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise; /** * Determines whether a build is necessary. A build is necessary when one of the following is true: @@ -91,13 +56,6 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { */ validateInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise; - /** - * Determines whether the project should undergo the prepare process. - * @param {IShouldPrepareInfo} shouldPrepareInfo Options needed to decide whether to prepare. - * @returns {Promise} true indicates that the project should be prepared. - */ - shouldPrepare(shouldPrepareInfo: IShouldPrepareInfo): Promise; - /** * Installs the application on specified device. * When finishes, saves .nsbuildinfo in application root folder to indicate the prepare that was used to build the app. @@ -111,13 +69,6 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { */ installApplication(device: Mobile.IDevice, options: IBuildConfig, projectData: IProjectData, pathToBuiltApp?: string, outputPath?: string): Promise; - /** - * Gets first chance to validate the options provided as command line arguments. - * If no platform is provided or a falsy (null, undefined, "", false...) platform is provided, - * the options will be validated for all available platforms. - */ - validateOptions(provision: true | string, teamId: true | string, projectData: IProjectData, platform?: string): Promise; - /** * Executes prepare, build and installOnPlatform when necessary to ensure that the latest version of the app is installed on specified platform. * - When --clean option is specified it builds the app on every change. If not, build is executed only when there are native changes. @@ -135,22 +86,6 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { */ startApplication(platform: string, runOptions: IRunPlatformOptions, appData: Mobile.IStartApplicationData): Promise; - cleanDestinationApp(platformInfo: IPreparePlatformInfo): Promise; - validatePlatformInstalled(platform: string, projectData: IProjectData): void; - - /** - * Ensures the passed platform is a valid one (from the supported ones) - */ - validatePlatform(platform: string, projectData: IProjectData): void; - - /** - * Checks whether passed platform can be built on the current OS - * @param {string} platform The mobile platform. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {boolean} Whether the platform is supported for current OS or not. - */ - isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean; - /** * Returns information about the latest built application for device in the current project. * @param {IPlatformData} platformData Data describing the current platform. @@ -238,6 +173,7 @@ interface IPlatformData { platformProjectService: IPlatformProjectService; projectRoot: string; normalizedPlatformName: string; + platformNameLowerCase: string; appDestinationDirectoryPath: string; getBuildOutputPath(options: IBuildOutputOptions): string; bundleBuildOutputPath?: string; @@ -275,14 +211,8 @@ interface INodeModulesData extends IPlatform, IProjectDataComposition, IAppFiles projectFilesConfig: IProjectFilesConfig; } -interface INodeModulesBuilderData { - nodeModulesData: INodeModulesData; - release: boolean; - copyNodeModules?: boolean; -} - interface INodeModulesBuilder { - prepareNodeModules(opts: INodeModulesBuilderData): Promise; + prepareNodeModules(platformData: IPlatformData, projectData: IProjectData): Promise; } interface INodeModulesDependenciesBuilder { @@ -307,16 +237,13 @@ interface IPlatformDataComposition { interface ICopyAppFilesData extends IProjectDataComposition, IAppFilesUpdaterOptionsComposition, IPlatformDataComposition, IOptionalFilesToSync, IOptionalFilesToRemove { } -interface IAddPlatformInfo extends IProjectDataComposition, IPlatformDataComposition { - frameworkDir: string; - installedVersion: string; - config: IPlatformOptions; -} - interface IPreparePlatformJSInfo extends IPreparePlatformCoreInfo, ICopyAppFilesData { projectFilesConfig?: IProjectFilesConfig; } +interface IPlatformOptions extends IRelease, IHasUseHotModuleReloadOption { +} + interface IShouldPrepareInfo extends IOptionalProjectChangesInfoComposition { platformInfo: IPreparePlatformInfo; } @@ -329,7 +256,7 @@ interface IPreparePlatformCoreInfo extends IPreparePlatformInfoBase, IOptionalPr platformSpecificData: IPlatformSpecificData; } -interface IPreparePlatformInfo extends IPreparePlatformInfoBase, IPlatformConfig, ISkipNativeCheckOptional { +interface IPreparePlatformInfo extends IPreparePlatformInfoBase, IPlatformConfig { webpackCompilerConfig: IWebpackCompilerConfig; } @@ -351,10 +278,6 @@ interface IOptionalNativePrepareComposition { nativePrepare?: INativePrepare; } -interface IOptionalWatchAllFiles { - watchAllFiles?: boolean; -} - interface IDeployPlatformInfo extends IPlatform, IAppFilesUpdaterOptionsComposition, IProjectDataComposition, IPlatformConfig, IEnvOptions, IOptionalNativePrepareComposition, IOptionalOutputPath, IBuildPlatformAction { deployOptions: IDeployPlatformOptions } diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index 3d3f051999..b3ad6137c3 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -25,17 +25,8 @@ interface IProjectChangesInfo extends IAddedNativePlatform { readonly changesRequirePrepare: boolean; } -/** - * Describes interface for controlling checking node_modules for native changes. - */ -interface ISkipNativeCheckOptional { - /** - * Designates node_modules should not be checked for native changes. - */ - skipModulesNativeCheck?: boolean; -} - -interface IProjectChangesOptions extends IAppFilesUpdaterOptions, IProvision, ITeamIdentifier, ISkipNativeCheckOptional { +interface IProjectChangesOptions extends IRelease, IHasUseHotModuleReloadOption { + signingOptions: IiOSSigningOptions | IAndroidSigningOptions; nativePlatformStatus?: "1" | "2" | "3"; } diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index f68e51b970..5d336a0060 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -358,7 +358,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS validate(projectData: IProjectData, options: IOptions, notConfiguredEnvOptions?: INotConfiguredEnvOptions): Promise; createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise; interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; - interpolateConfigurationFile(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): void; + interpolateConfigurationFile(projectData: IProjectData, signingOptions: IiOSSigningOptions | IAndroidSigningOptions): void; /** * Executes additional actions after native project is created. @@ -384,7 +384,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS * @param {any} platformSpecificData Platform specific data required for project preparation. * @returns {void} */ - prepareProject(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; + prepareProject(projectData: IProjectData, signingOptions: IiOSSigningOptions | IAndroidSigningOptions): Promise; /** * Prepares App_Resources in the native project by clearing data from other platform and applying platform specific rules. @@ -460,7 +460,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS * Check the current state of the project, and validate against the options. * If there are parts in the project that are inconsistent with the desired options, marks them in the changeset flags. */ - checkForChanges(changeset: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): Promise; + checkForChanges(changeset: IProjectChangesInfo, signingOptions: IiOSSigningOptions, projectData: IProjectData): Promise; /** * Get the deployment target's version diff --git a/lib/factory/platform-workflow-data-factory.ts b/lib/factory/platform-workflow-data-factory.ts new file mode 100644 index 0000000000..eb0ca11352 --- /dev/null +++ b/lib/factory/platform-workflow-data-factory.ts @@ -0,0 +1,54 @@ +export class PlatformWorkflowDataFactory implements IPlatformWorkflowDataFactory { + public createPlatformWorkflowData(platformParam: string, options: IOptions, nativePrepare?: INativePrepare): IPlatformWorkflowData { + if (platformParam.toLowerCase() === "ios") { + return (new IOSWorkflowData(platformParam, options, nativePrepare)).data; + } else if (platformParam.toLowerCase() === "android") { + return (new AndroidWorkflowData(platformParam, options, nativePrepare)).data; + } else { + throw new Error("Invalid workflowData!!!"); + } + } +} +$injector.register("platformWorkflowDataFactory", PlatformWorkflowDataFactory); + +abstract class WorkflowDataBase { + constructor(protected platformParam: string, protected $options: IOptions, protected nativePrepare?: INativePrepare) { } + + public abstract signingOptions: TSigningOptions; + + public get data() { + return { ...this.baseData, signingOptions: this.signingOptions }; + } + + private baseData = { + platformParam: this.platformParam, + release: this.$options.release, + useHotModuleReload: this.$options.hmr, + env: this.$options.env, + nativePrepare: this.nativePrepare + }; +} + +class AndroidWorkflowData extends WorkflowDataBase { + constructor(platformParam: string, $options: IOptions, nativePrepare?: INativePrepare) { + super(platformParam, $options, nativePrepare); + } + + public signingOptions: IAndroidSigningOptions = { + keyStoreAlias: this.$options.keyStoreAlias, + keyStorePath: this.$options.keyStorePath, + keyStoreAliasPassword: this.$options.keyStoreAliasPassword, + keyStorePassword: this.$options.keyStorePassword, + }; +} + +class IOSWorkflowData extends WorkflowDataBase { + constructor(platformParam: string, $options: IOptions, nativePrepare?: INativePrepare) { + super(platformParam, $options, nativePrepare); + } + + public signingOptions: IiOSSigningOptions = { + teamId: this.$options.teamId, + provision: this.$options.provision, + }; +} diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index a5a0e9d652..28b71f3a72 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -33,7 +33,7 @@ export class DeployCommandHelper implements IDeployCommandHelper { deployOptions, projectData: this.$projectData, buildPlatform: this.$platformService.buildPlatform.bind(this.$platformService), - config: this.$options, + config: this.$options, env: this.$options.env, }; diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 8b5c648298..ae5af9c560 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -100,7 +100,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const info: ILiveSyncDeviceInfo = { identifier: d.deviceInfo.identifier, - platformSpecificOptions: this.$options, buildAction, debugggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], debugOptions: this.$options, @@ -118,11 +117,11 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch, - watchAllFiles: this.$options.syncAllFiles, clean: this.$options.clean, - bundle: !!this.$options.bundle, release: this.$options.release, - env: this.$options.env, + webpackCompilerConfig: { + env: this.$options.env + }, timeout: this.$options.timeout, useHotModuleReload: this.$options.hmr, force: this.$options.force @@ -181,7 +180,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { deployOptions, buildPlatform: this.$platformService.buildPlatform.bind(this.$platformService), projectData: this.$projectData, - config: this.$options, + config: this.$options, env: this.$options.env }; diff --git a/lib/platform-command-param.ts b/lib/platform-command-param.ts index b6ec67eb6b..b53ad9979c 100644 --- a/lib/platform-command-param.ts +++ b/lib/platform-command-param.ts @@ -1,10 +1,10 @@ export class PlatformCommandParameter implements ICommandParameter { - constructor(private $platformService: IPlatformService, + constructor(private $platformValidationService: IPlatformValidationService, private $projectData: IProjectData) { } mandatory = true; async validate(value: string): Promise { this.$projectData.initializeProjectData(); - this.$platformService.validatePlatform(value, this.$projectData); + this.$platformValidationService.validatePlatform(value, this.$projectData); return true; } } diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index a73447b5a1..427d5b6c61 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -46,10 +46,17 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this._platformData = { frameworkPackageName: constants.TNS_ANDROID_RUNTIME_NAME, normalizedPlatformName: "Android", + platformNameLowerCase: "android", appDestinationDirectoryPath: path.join(...appDestinationDirectoryArr), platformProjectService: this, projectRoot: projectRoot, - getBuildOutputPath: () => path.join(...deviceBuildOutputArr), + getBuildOutputPath: (buildConfig: IBuildConfig) => { + if (buildConfig.androidBundle) { + return path.join(projectRoot, constants.APP_FOLDER_NAME, constants.BUILD_DIR, constants.OUTPUTS_DIR, constants.BUNDLE_DIR); + } + + return path.join(...deviceBuildOutputArr); + }, bundleBuildOutputPath: path.join(projectRoot, constants.APP_FOLDER_NAME, constants.BUILD_DIR, constants.OUTPUTS_DIR, constants.BUNDLE_DIR), getValidBuildOutputData: (buildOptions: IBuildOutputOptions): IValidBuildOutputData => { const buildMode = buildOptions.release ? Configurations.Release.toLowerCase() : Configurations.Debug.toLowerCase(); @@ -160,9 +167,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject _.map(directoriesToClean, dir => this.$fs.deleteDirectory(dir)); } - public async interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { + public async interpolateData(projectData: IProjectData, signingOptions: any): Promise { // Interpolate the apilevel and package - this.interpolateConfigurationFile(projectData, platformSpecificData); + this.interpolateConfigurationFile(projectData, signingOptions); const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); let stringsFilePath: string; @@ -192,11 +199,11 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - public interpolateConfigurationFile(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): void { + public interpolateConfigurationFile(projectData: IProjectData, signingOptions: IAndroidSigningOptions): void { const manifestPath = this.getPlatformData(projectData).configurationFilePath; shell.sed('-i', /__PACKAGE__/, projectData.projectIdentifiers.android, manifestPath); if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { - const sdk = (platformSpecificData && platformSpecificData.sdk) || (this.$androidToolsInfo.getToolsInfo().compileSdkVersion || "").toString(); + const sdk = (signingOptions && signingOptions.sdk) || (this.$androidToolsInfo.getToolsInfo().compileSdkVersion || "").toString(); shell.sed('-i', /__APILEVEL__/, sdk, manifestPath); } } @@ -368,7 +375,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject await adb.executeShellCommand(["rm", "-rf", deviceRootPath]); } - public async checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): Promise { + public async checkForChanges(): Promise { // Nothing android specific to check yet. } diff --git a/lib/services/app-files-updater.ts b/lib/services/app-files-updater.ts deleted file mode 100644 index 0e14011477..0000000000 --- a/lib/services/app-files-updater.ts +++ /dev/null @@ -1,104 +0,0 @@ -import * as path from "path"; -import * as minimatch from "minimatch"; -import * as constants from "../constants"; -// TODO: ?? -import * as fs from "fs"; - -export class AppFilesUpdater { - constructor(private appSourceDirectoryPath: string, - private appDestinationDirectoryPath: string, - public options: IAppFilesUpdaterOptions, - public fileSystem: IFileSystem - ) { - } - - public updateApp(updateAppOptions: IUpdateAppOptions, projectData: IProjectData): void { - this.cleanDestinationApp(updateAppOptions); - let sourceFiles = updateAppOptions.filesToSync || this.resolveAppSourceFiles(projectData); - - // exclude the app_resources directory from being enumerated - // for copying if it is present in the application sources dir - const appResourcesPathNormalized = path.normalize(projectData.appResourcesDirectoryPath + path.sep); - sourceFiles = sourceFiles.filter(dirName => !path.normalize(dirName).startsWith(appResourcesPathNormalized)); - - updateAppOptions.beforeCopyAction(sourceFiles); - this.copyAppSourceFiles(sourceFiles); - } - - public cleanDestinationApp(updateAppOptions?: IUpdateAppOptions): void { - let itemsToRemove: string[]; - - if (updateAppOptions && updateAppOptions.filesToRemove) { - // We get here during LiveSync - we only want to get rid of files, that the file system watcher detected were deleted - itemsToRemove = updateAppOptions.filesToRemove.map(fileToRemove => path.relative(this.appSourceDirectoryPath, fileToRemove)); - } else { - // We get here during the initial sync before the file system watcher is even started - // delete everything and prepare everything anew just to be sure - // Delete the destination app in order to prevent EEXIST errors when symlinks are used. - itemsToRemove = this.readDestinationDir(); - itemsToRemove = itemsToRemove.filter( - (directoryName: string) => directoryName !== constants.TNS_MODULES_FOLDER_NAME); - - } - - _(itemsToRemove).each((directoryItem: string) => { - this.deleteDestinationItem(directoryItem); - }); - } - - protected readDestinationDir(): string[] { - if (this.fileSystem.exists(this.appDestinationDirectoryPath)) { - return this.fileSystem.readDirectory(this.appDestinationDirectoryPath); - } else { - return []; - } - } - - protected deleteDestinationItem(directoryItem: string): void { - this.fileSystem.deleteDirectory(path.join(this.appDestinationDirectoryPath, directoryItem)); - } - - protected readSourceDir(projectData: IProjectData): string[] { - const tnsDir = path.join(this.appSourceDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); - - return this.fileSystem.enumerateFilesInDirectorySync(this.appSourceDirectoryPath, null, { includeEmptyDirectories: true }).filter(dirName => dirName !== tnsDir); - } - - protected resolveAppSourceFiles(projectData: IProjectData): string[] { - if (this.options.bundle) { - return []; - } - - // Copy all files from app dir, but make sure to exclude tns_modules and application resources - let sourceFiles = this.readSourceDir(projectData); - - if (this.options.release) { - const testsFolderPath = path.join(this.appSourceDirectoryPath, 'tests'); - sourceFiles = sourceFiles.filter(source => source.indexOf(testsFolderPath) === -1); - } - - // Remove .ts and .js.map files in release - if (this.options.release) { - constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, { nocase: true }))); - } - - return sourceFiles; - } - - protected copyAppSourceFiles(sourceFiles: string[]): void { - sourceFiles.map(source => { - const destinationPath = path.join(this.appDestinationDirectoryPath, path.relative(this.appSourceDirectoryPath, source)); - - let exists = fs.lstatSync(source); - if (exists.isSymbolicLink()) { - source = fs.realpathSync(source); - exists = fs.lstatSync(source); - } - if (exists.isDirectory()) { - return this.fileSystem.createDirectory(destinationPath); - } - - return this.fileSystem.copyFile(source, destinationPath); - }); - } -} diff --git a/lib/services/build-artefacts-service.ts b/lib/services/build-artefacts-service.ts new file mode 100644 index 0000000000..dc34999735 --- /dev/null +++ b/lib/services/build-artefacts-service.ts @@ -0,0 +1,90 @@ +import * as path from "path"; + +export class BuildArtefactsService implements IBuildArtefactsService { + constructor( + private $errors: IErrors, + private $fs: IFileSystem, + private $logger: ILogger + ) { } + + public async getLastBuiltPackagePath(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): Promise { + const packageFile = buildConfig.buildForDevice ? + this.getLatestApplicationPackageForDevice(platformData, buildConfig, outputPath).packageName : + this.getLatestApplicationPackageForEmulator(platformData, buildConfig, outputPath).packageName; + + if (!packageFile || !this.$fs.exists(packageFile)) { + this.$errors.failWithoutHelp(`Unable to find built application. Try 'tns build ${platformData.platformNameLowerCase}'.`); + } + + return packageFile; + } + + private getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage { + outputPath = outputPath || platformData.getBuildOutputPath(buildConfig); + const buildOutputOptions = { buildForDevice: true, release: buildConfig.release, androidBundle: buildConfig.androidBundle }; + return this.getLatestApplicationPackage(outputPath, platformData.getValidBuildOutputData(buildOutputOptions)); + } + + private getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage { + outputPath = outputPath || platformData.getBuildOutputPath(buildConfig); + const buildOutputOptions = { buildForDevice: false, release: buildConfig.release, androidBundle: buildConfig.androidBundle }; + return this.getLatestApplicationPackage(outputPath, platformData.getValidBuildOutputData(buildOutputOptions)); + } + + private getLatestApplicationPackage(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage { + let packages = this.getApplicationPackages(buildOutputPath, validBuildOutputData); + const packageExtName = path.extname(validBuildOutputData.packageNames[0]); + if (packages.length === 0) { + this.$errors.fail(`No ${packageExtName} found in ${buildOutputPath} directory.`); + } + + if (packages.length > 1) { + this.$logger.warn(`More than one ${packageExtName} found in ${buildOutputPath} directory. Using the last one produced from build.`); + } + + packages = _.sortBy(packages, pkg => pkg.time).reverse(); // We need to reverse because sortBy always sorts in ascending order + + return packages[0]; + } + + private getApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[] { + // Get latest package` that is produced from build + let result = this.getApplicationPackagesCore(this.$fs.readDirectory(buildOutputPath).map(filename => path.join(buildOutputPath, filename)), validBuildOutputData.packageNames); + if (result) { + return result; + } + + const candidates = this.$fs.enumerateFilesInDirectorySync(buildOutputPath); + result = this.getApplicationPackagesCore(candidates, validBuildOutputData.packageNames); + if (result) { + return result; + } + + if (validBuildOutputData.regexes && validBuildOutputData.regexes.length) { + return this.createApplicationPackages(candidates.filter(filepath => _.some(validBuildOutputData.regexes, regex => regex.test(path.basename(filepath))))); + } + + return []; + } + + private getApplicationPackagesCore(candidates: string[], validPackageNames: string[]): IApplicationPackage[] { + const packages = candidates.filter(filePath => _.includes(validPackageNames, path.basename(filePath))); + if (packages.length > 0) { + return this.createApplicationPackages(packages); + } + + return null; + } + + private createApplicationPackages(packages: string[]): IApplicationPackage[] { + return packages.map(filepath => this.createApplicationPackage(filepath)); + } + + private createApplicationPackage(packageName: string): IApplicationPackage { + return { + packageName, + time: this.$fs.getFsStats(packageName).mtime + }; + } +} +$injector.register("buildArtefactsService", BuildArtefactsService); diff --git a/lib/services/bundle-workflow-service.ts b/lib/services/bundle-workflow-service.ts new file mode 100644 index 0000000000..e8e8811d67 --- /dev/null +++ b/lib/services/bundle-workflow-service.ts @@ -0,0 +1,92 @@ +import * as path from "path"; +import * as constants from "../constants"; + +export class BundleWorkflowService implements IBundleWorkflowService { + constructor( + private $devicesService: Mobile.IDevicesService, + private $errors: IErrors, + private $fs: IFileSystem, + private $logger: ILogger, + private $platformAddService: IPlatformAddService, + private $platformsData: IPlatformsData, + private $platformWatcherService: IPlatformWatcherService, + private $pluginsService: IPluginsService, + private $projectChangesService: IProjectChangesService + ) { } + + // processInfo[projectDir] = { + // deviceDescriptors, nativeFilesWatcher, jsFilesWatcher + // } + + public async start(projectData: IProjectData, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + await this.initializeSetup(projectData); + + const platforms = _(deviceDescriptors) + .map(device => this.$devicesService.getDeviceByIdentifier(device.identifier)) + .map(device => device.deviceInfo.platform) + .uniq() + .value(); + for (const platform in platforms) { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + + const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, liveSyncInfo.nativePrepare); + if (shouldAddPlatform) { + await this.$platformAddService.addPlatform({ + platformParam: (liveSyncInfo).platformParam, + frameworkPath: (liveSyncInfo).frameworkPath, + nativePrepare: liveSyncInfo.nativePrepare + }, projectData); + } + + this.$platformWatcherService.on("onInitialSync", async () => { + console.log("================= RECEIVED INITIAL SYNC ============= "); + // check if we should build, install, transfer files + // AddActionToChain + }); + this.$platformWatcherService.on("onFilesChange", () => { + console.log("=================== RECEIVED FILES CHANGE ================ "); + // Emitted when webpack compilatation is done and native prepare is done + // console.log("--------- ========= ---------- ", data); + // AddActionToChain + }); + + await this.$platformWatcherService.startWatcher(platformData, projectData, { + webpackCompilerConfig: liveSyncInfo.webpackCompilerConfig, + preparePlatformData: { + release: liveSyncInfo.release, + useHotModuleReload: liveSyncInfo.useHotModuleReload, + nativePrepare: liveSyncInfo.nativePrepare, + signingOptions: { + teamId: (liveSyncInfo).teamId, + provision: (liveSyncInfo).provision + } + } + }); + } + + for (const deviceDescriptor in deviceDescriptors) { + console.log("============ DEVICE DESCRIPTOR ============== ", deviceDescriptor); + } + } + + private async initializeSetup(projectData: IProjectData): Promise { + try { + await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); + } catch (err) { + this.$logger.trace(err); + this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); + } + } + + private shouldAddPlatform(platformData: IPlatformData, projectData: IProjectData, nativePrepare: INativePrepare): boolean { + const platformName = platformData.normalizedPlatformName.toLowerCase(); + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformName, projectData); + const hasPlatformDirectory = this.$fs.exists(path.join(projectData.platformsDir, platformName)); + const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; + const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; + const result = !hasPlatformDirectory || (shouldAddNativePlatform && requiresNativePlatformAdd); + + return result; + } +} +$injector.register("bundleWorkflowService", BundleWorkflowService); diff --git a/lib/services/emulator-settings-service.ts b/lib/services/emulator-settings-service.ts deleted file mode 100644 index d9bd1b6f26..0000000000 --- a/lib/services/emulator-settings-service.ts +++ /dev/null @@ -1,17 +0,0 @@ -export class EmulatorSettingsService implements Mobile.IEmulatorSettingsService { - private static REQURED_ANDROID_APILEVEL = 17; - - constructor(private $injector: IInjector) { } - - public canStart(platform: string): boolean { - const platformService = this.$injector.resolve("platformService"); // this should be resolved here due to cyclic dependency - - const installedPlatforms = platformService.getInstalledPlatforms(); - return _.includes(installedPlatforms, platform.toLowerCase()); - } - - public get minVersion(): number { - return EmulatorSettingsService.REQURED_ANDROID_APILEVEL; - } -} -$injector.register("emulatorSettingsService", EmulatorSettingsService); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 33fd672742..0490fc0ad0 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -65,6 +65,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this._platformData = { frameworkPackageName: constants.TNS_IOS_RUNTIME_NAME, normalizedPlatformName: "iOS", + platformNameLowerCase: "ios", appDestinationDirectoryPath: path.join(projectRoot, projectData.projectName), platformProjectService: this, projectRoot: projectRoot, @@ -275,13 +276,13 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return contentIsTheSame; } - public async prepareProject(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { + public async prepareProject(projectData: IProjectData, signingOptions: IiOSSigningOptions): Promise { const projectRoot = path.join(projectData.platformsDir, "ios"); - const provision = platformSpecificData && platformSpecificData.provision; - const teamId = platformSpecificData && platformSpecificData.teamId; + const provision = signingOptions && signingOptions.provision; + const teamId = signingOptions && signingOptions.teamId; if (provision) { - await this.$iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision, platformSpecificData.mobileProvisionData); + await this.$iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision, signingOptions.mobileProvisionData); } if (teamId) { await this.$iOSSigningService.setupSigningFromTeam(projectRoot, projectData, teamId); @@ -506,7 +507,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return Promise.resolve(); } - public async checkForChanges(changesInfo: IProjectChangesInfo, { provision, teamId }: IProjectChangesOptions, projectData: IProjectData): Promise { + public async checkForChanges(changesInfo: IProjectChangesInfo, signingOptions: IiOSSigningOptions, projectData: IProjectData): Promise { + const { provision, teamId } = signingOptions; const hasProvision = provision !== undefined; const hasTeamId = teamId !== undefined; if (hasProvision || hasTeamId) { diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index 70f765ae3e..4c776fa564 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -11,9 +11,8 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen private $injector: IInjector, $devicePathProvider: IDevicePathProvider, $fs: IFileSystem, - $logger: ILogger, - $projectFilesProvider: IProjectFilesProvider) { - super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider, $projectFilesProvider); + $logger: ILogger) { + super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider); } protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir, frameworkVersion: string): INativeScriptDeviceLiveSyncService { diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index 16566740b2..effdf26868 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -12,9 +12,8 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I protected $projectFilesManager: IProjectFilesManager, private $injector: IInjector, $devicePathProvider: IDevicePathProvider, - $logger: ILogger, - $projectFilesProvider: IProjectFilesProvider) { - super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider, $projectFilesProvider); + $logger: ILogger) { + super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider); } @performanceLog() diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 52eae4f75d..f788696fed 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,902 +1,902 @@ -// import * as path from "path"; -// import * as choki from "chokidar"; -import { EOL } from "os"; -import { EventEmitter } from "events"; -import { hook } from "../../common/helpers"; -import { - PACKAGE_JSON_FILE_NAME, - USER_INTERACTION_NEEDED_EVENT_NAME, - DEBUGGER_ATTACHED_EVENT_NAME, - DEBUGGER_DETACHED_EVENT_NAME, - TrackActionNames, - LiveSyncEvents -} from "../../constants"; -import { DeviceTypes, DeviceDiscoveryEventNames, HmrConstants } from "../../common/constants"; -import { cache } from "../../common/decorators"; -import { performanceLog } from "../../common/decorators"; - -const deviceDescriptorPrimaryKey = "identifier"; - -export class LiveSyncService extends EventEmitter implements IDebugLiveSyncService { - // key is projectDir - protected liveSyncProcessesInfo: IDictionary = {}; - - constructor(private $platformService: IPlatformService, - private $projectDataService: IProjectDataService, - private $devicesService: Mobile.IDevicesService, - private $mobileHelper: Mobile.IMobileHelper, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, - private $logger: ILogger, - private $hooksService: IHooksService, - private $pluginsService: IPluginsService, - private $debugService: IDebugService, - private $errors: IErrors, - private $debugDataService: IDebugDataService, - private $analyticsService: IAnalyticsService, - private $usbLiveSyncService: DeprecatedUsbLiveSyncService, - private $previewAppLiveSyncService: IPreviewAppLiveSyncService, - private $previewQrCodeService: IPreviewQrCodeService, - private $previewSdkService: IPreviewSdkService, - private $hmrStatusService: IHmrStatusService, - private $injector: IInjector) { - super(); - } - - public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { - const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); - await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); - await this.liveSyncOperation(deviceDescriptors, liveSyncData, projectData); - } - - public async liveSyncToPreviewApp(data: IPreviewAppLiveSyncData): Promise { - this.attachToPreviewAppLiveSyncError(); - - await this.liveSync([], { - syncToPreviewApp: true, - projectDir: data.projectDir, - bundle: data.bundle, - useHotModuleReload: data.useHotModuleReload, - release: false, - env: data.env, - }); - - const url = this.$previewSdkService.getQrCodeUrl({ projectDir: data.projectDir, useHotModuleReload: data.useHotModuleReload }); - const result = await this.$previewQrCodeService.getLiveSyncQrCode(url); - return result; - } - - public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { - const liveSyncProcessInfo = this.liveSyncProcessesInfo[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. - const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; - - const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); - - const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) - .map(descriptor => descriptor.identifier); - - // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. - if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { - if (liveSyncProcessInfo.timer) { - clearTimeout(liveSyncProcessInfo.timer); - } - - if (liveSyncProcessInfo.watcherInfo && liveSyncProcessInfo.watcherInfo.watcher) { - liveSyncProcessInfo.watcherInfo.watcher.close(); - } - - liveSyncProcessInfo.watcherInfo = null; - liveSyncProcessInfo.isStopped = true; - - if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) { - await liveSyncProcessInfo.actionsChain; - } - - liveSyncProcessInfo.deviceDescriptors = []; - - if (liveSyncProcessInfo.syncToPreviewApp) { - await this.$previewAppLiveSyncService.stopLiveSync(); - this.$previewAppLiveSyncService.removeAllListeners(); - } - - // Kill typescript watcher - const projectData = this.$projectDataService.getProjectData(projectDir); - await this.$hooksService.executeAfterHooks('watch', { - hookArgs: { - projectData - } - }); - - // In case we are stopping the LiveSync we must set usbLiveSyncService.isInitialized to false, - // as in case we execute nativescript-dev-typescript's before-prepare hook again in the same process, it MUST transpile the files. - this.$usbLiveSyncService.isInitialized = false; - } else if (liveSyncProcessInfo.currentSyncAction && shouldAwaitPendingOperation) { - await liveSyncProcessInfo.currentSyncAction; - } - - // Emit LiveSync stopped when we've really stopped. - _.each(removedDeviceIdentifiers, deviceIdentifier => { - this.emitLivesyncEvent(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier }); - }); - } - } - - public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { - const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; - const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; - return currentDescriptors || []; - } - - private attachToPreviewAppLiveSyncError(): void { - if (!this.$usbLiveSyncService.isInitialized) { - this.$previewAppLiveSyncService.on(LiveSyncEvents.previewAppLiveSyncError, liveSyncData => { - this.$logger.error(liveSyncData.error); - this.emit(LiveSyncEvents.previewAppLiveSyncError, liveSyncData); - }); - } - } - - @performanceLog() - private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { - const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir); - - return deviceDescriptor && deviceDescriptor.debugggingEnabled ? - this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) : - this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath); - } - - private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IRefreshApplicationSettings): Promise { - const result = { didRestart: false }; - const platform = liveSyncResultInfo.deviceAppData.platform; - const platformLiveSyncService = this.getLiveSyncService(platform); - const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; - try { - let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); - if (!shouldRestart) { - shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); - } - - if (shouldRestart) { - const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; - this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); - await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); - result.didRestart = true; - } - } catch (err) { - 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); - if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { - this.emitLivesyncEvent(LiveSyncEvents.liveSyncNotification, { - projectDir: projectData.projectDir, - applicationIdentifier, - deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - notification: msg - }); - } - - if (settings && settings.shouldCheckDeveloperDiscImage) { - this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); - } - } - - this.emitLivesyncEvent(LiveSyncEvents.liveSyncExecuted, { - projectDir: projectData.projectDir, - applicationIdentifier, - syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), - deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - isFullSync: liveSyncResultInfo.isFullSync - }); - - return result; - } - - @performanceLog() - private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { - debugOptions = debugOptions || {}; - if (debugOptions.debugBrk) { - liveSyncResultInfo.waitForDebugger = true; - } - - const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); - - // 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: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - debugOptions: debugOptions, - }; - - return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); - } - - private handleDeveloperDiskImageError(err: any, liveSyncResultInfo: ILiveSyncResultInfo, projectData: IProjectData, debugOpts: IDebugOptions, outputPath: string) { - if ((err.message || err) === "Could not find developer disk image") { - const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; - const attachDebuggerOptions: IAttachDebuggerOptions = { - platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, - isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, - projectDir: projectData.projectDir, - deviceIdentifier, - debugOptions: debugOpts, - outputPath - }; - this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); - } - } - - public async attachDebugger(settings: IAttachDebuggerOptions): Promise { - // Default values - if (settings.debugOptions) { - settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; - settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; - } else { - settings.debugOptions = { - chrome: true, - start: true - }; - } - - const projectData = this.$projectDataService.getProjectData(settings.projectDir); - const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); - - // Of the properties below only `buildForDevice` and `release` are currently used. - // Leaving the others with placeholder values so that they may not be forgotten in future implementations. - const buildConfig = this.getInstallApplicationBuildConfig(settings.deviceIdentifier, settings.projectDir, { isEmulator: settings.isEmulator }); - debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath); - const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); - const result = this.printDebugInformation(debugInfo, settings.debugOptions.forceDebuggerAttachedEvent); - return result; - } - - 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 enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { - return _.map(deviceOpts, d => this.enableDebuggingCore(d, debuggingAdditionalOptions)); - } - - private getDeviceDescriptor(deviceIdentifier: string, projectDir: string) { - const deviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); - - return _.find(deviceDescriptors, d => d.identifier === deviceIdentifier); - } - - @performanceLog() - private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { - const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); - if (!currentDeviceDescriptor) { - this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); - } - - currentDeviceDescriptor.debugggingEnabled = true; - currentDeviceDescriptor.debugOptions = deviceOption.debugOptions; - const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); - const attachDebuggerOptions: IAttachDebuggerOptions = { - deviceIdentifier: deviceOption.deviceIdentifier, - isEmulator: currentDeviceInstance.isEmulator, - outputPath: currentDeviceDescriptor.outputPath, - platform: currentDeviceInstance.deviceInfo.platform, - projectDir: debuggingAdditionalOptions.projectDir, - debugOptions: deviceOption.debugOptions - }; - - let debugInformation: IDebugInformation; - try { - debugInformation = await this.attachDebugger(attachDebuggerOptions); - } catch (err) { - this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); - attachDebuggerOptions.debugOptions.start = false; - try { - debugInformation = await this.attachDebugger(attachDebuggerOptions); - } catch (innerErr) { - this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); - throw err; - } - } - - return debugInformation; - } - - private async enableDebuggingCore(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { - const liveSyncProcessInfo: ILiveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; - if (liveSyncProcessInfo && liveSyncProcessInfo.currentSyncAction) { - await liveSyncProcessInfo.currentSyncAction; - } - - return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, debuggingAdditionalOptions); - } - - public disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { - return _.map(deviceOptions, d => this.disableDebuggingCore(d, debuggingAdditionalOptions)); - } - - @hook('watchPatterns') - public async getWatcherPatterns(liveSyncData: ILiveSyncInfo, projectData: IProjectData, platforms: string[]): Promise { - // liveSyncData and platforms are used by plugins that make use of the watchPatterns hook - // TODO: ignore getAppDirectoryRelativePath - // TODO: watch platforms folder of node_modules e.g native source folders of nativescript plugins - return [projectData.getAppDirectoryRelativePath(), projectData.getAppResourcesRelativeDirectoryPath()]; - } - - public async disableDebuggingCore(deviceOption: IDisableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { - const liveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; - if (liveSyncProcessInfo.currentSyncAction) { - await liveSyncProcessInfo.currentSyncAction; - } - - const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); - if (currentDeviceDescriptor) { - currentDeviceDescriptor.debugggingEnabled = false; - } else { - this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}`); - } - - const currentDevice = this.$devicesService.getDeviceByIdentifier(currentDeviceDescriptor.identifier); - if (!currentDevice) { - this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}. Could not find device.`); - } - - await this.$debugService.debugStop(currentDevice.deviceInfo.identifier); - this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier: currentDeviceDescriptor.identifier }); - } - - @hook("liveSync") - private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise { - let deviceDescriptorsForInitialSync: ILiveSyncDeviceInfo[] = []; - - if (liveSyncData.syncToPreviewApp) { - await this.$previewAppLiveSyncService.initialize({ - projectDir: projectData.projectDir, - bundle: liveSyncData.bundle, - useHotModuleReload: liveSyncData.useHotModuleReload, - env: liveSyncData.env - }); - } else { - // In case liveSync is called for a second time for the same projectDir. - const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; - - // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. - const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir); - deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; - } - - this.setLiveSyncProcessInfo(liveSyncData, deviceDescriptors); - - const shouldStartWatcher = !liveSyncData.skipWatcher && (liveSyncData.syncToPreviewApp || this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length); - if (shouldStartWatcher) { - // Should be set after prepare - this.$usbLiveSyncService.isInitialized = true; - await this.startWatcher(projectData, liveSyncData, deviceDescriptors); - } - - await this.initialSync(projectData, liveSyncData, deviceDescriptorsForInitialSync); - } - - private setLiveSyncProcessInfo(liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): void { - const { projectDir } = liveSyncData; - this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); - this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); - this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; - this.liveSyncProcessesInfo[projectDir].isStopped = false; - this.liveSyncProcessesInfo[projectDir].syncToPreviewApp = liveSyncData.syncToPreviewApp; - - const currentDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); - this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); - } - - private getLiveSyncService(platform: string): IPlatformLiveSyncService { - if (this.$mobileHelper.isiOSPlatform(platform)) { - return this.$injector.resolve("iOSLiveSyncService"); - } else if (this.$mobileHelper.isAndroidPlatform(platform)) { - return this.$injector.resolve("androidLiveSyncService"); - } - - this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); - } - - private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions, nativePrepare?: INativePrepare): Promise { - const platform = options.device.deviceInfo.platform; - const appInstalledOnDeviceResult: IAppInstalledOnDeviceResult = { appInstalled: false }; - if (options.preparedPlatforms.indexOf(platform) === -1) { - options.preparedPlatforms.push(platform); - } - - const buildResult = await this.installedCachedAppPackage(platform, options); - if (buildResult) { - appInstalledOnDeviceResult.appInstalled = true; - return appInstalledOnDeviceResult; - } - - const shouldBuild = await this.$platformService.shouldBuild(platform, - options.projectData, - { buildForDevice: !options.device.isEmulator, clean: options.liveSyncData && options.liveSyncData.clean }, - options.deviceBuildInfoDescriptor.outputPath); - let pathToBuildItem = null; - if (shouldBuild) { - pathToBuildItem = await options.deviceBuildInfoDescriptor.buildAction(); - options.rebuiltInformation.push({ isEmulator: options.device.isEmulator, platform, pathToBuildItem }); - } else { - await this.$analyticsService.trackEventActionInGoogleAnalytics({ - action: TrackActionNames.LiveSync, - device: options.device, - projectDir: options.projectData.projectDir - }); - } - - await this.$platformService.validateInstall(options.device, options.projectData, options, options.deviceBuildInfoDescriptor.outputPath); - const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options, options.deviceBuildInfoDescriptor.outputPath); - if (shouldInstall) { - const buildConfig = this.getInstallApplicationBuildConfig(options.device.deviceInfo.identifier, options.projectData.projectDir, { isEmulator: options.device.isEmulator }); - await this.$platformService.installApplication(options.device, buildConfig, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); - appInstalledOnDeviceResult.appInstalled = true; - } - - return appInstalledOnDeviceResult; - } - - private async installedCachedAppPackage(platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { - const rebuildInfo = _.find(options.rebuiltInformation, info => info.platform === platform && (this.$mobileHelper.isAndroidPlatform(platform) || info.isEmulator === options.device.isEmulator)); - - if (rebuildInfo) { - // Case where we have three devices attached, a change that requires build is found, - // we'll rebuild the app only for the first device, but we should install new package on all three devices. - const buildConfig = this.getInstallApplicationBuildConfig(options.device.deviceInfo.identifier, options.projectData.projectDir, { isEmulator: options.device.isEmulator }); - await this.$platformService.installApplication(options.device, buildConfig, options.projectData, rebuildInfo.pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); - return rebuildInfo.pathToBuildItem; - } - - return null; - } - - private async initialSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { - if (!liveSyncData.syncToPreviewApp) { - await this.initialCableSync(projectData, liveSyncData, deviceDescriptors); - } - } - - private async initialCableSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { - const preparedPlatforms: string[] = []; - const rebuiltInformation: ILiveSyncBuildInfo[] = []; - - const settings = this.getDefaultLatestAppPackageInstalledSettings(); - // Now fullSync - const deviceAction = async (device: Mobile.IDevice): Promise => { - const platform = device.deviceInfo.platform; - try { - const platformLiveSyncService = this.getLiveSyncService(platform); - - const deviceBuildInfoDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - - await this.ensureLatestAppPackageIsInstalledOnDevice({ - device, - preparedPlatforms, - rebuiltInformation, - projectData, - deviceBuildInfoDescriptor, - liveSyncData, - settings, - bundle: liveSyncData.bundle, - release: liveSyncData.release, - env: liveSyncData.env - }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); - - const liveSyncResultInfo = await platformLiveSyncService.fullSync({ - projectData, - device, - syncAllFiles: liveSyncData.watchAllFiles, - useHotModuleReload: liveSyncData.useHotModuleReload, - watch: !liveSyncData.skipWatcher, - force: liveSyncData.force, - liveSyncDeviceInfo: deviceBuildInfoDescriptor - }); - - await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - - this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - - this.emitLivesyncEvent(LiveSyncEvents.liveSyncStarted, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[platform.toLowerCase()] - }); - } catch (err) { - this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); - - this.emitLivesyncEvent(LiveSyncEvents.liveSyncError, { - error: err, - deviceIdentifier: device.deviceInfo.identifier, - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectIdentifiers[platform.toLowerCase()] - }); - - await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier], { shouldAwaitAllActions: false }); - } - }; - - // Execute the action only on the deviceDescriptors passed to initialSync. - // In case where we add deviceDescriptors to already running application, we've already executed initialSync for them. - await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); - - this.attachDeviceLostHandler(); - } - - private getDefaultLatestAppPackageInstalledSettings(): ILatestAppPackageInstalledSettings { - return { - [this.$devicePlatformsConstants.Android]: { - [DeviceTypes.Device]: false, - [DeviceTypes.Emulator]: false - }, - [this.$devicePlatformsConstants.iOS]: { - [DeviceTypes.Device]: false, - [DeviceTypes.Emulator]: false - } - }; - } - - private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { - const devicesIds = deviceDescriptors.map(dd => dd.identifier); - const devices = _.filter(this.$devicesService.getDeviceInstances(), device => _.includes(devicesIds, device.deviceInfo.identifier)); - const platforms = _(devices).map(device => device.deviceInfo.platform).uniq().value(); - const patterns = await this.getWatcherPatterns(liveSyncData, projectData, platforms); - - if (liveSyncData.useHotModuleReload) { - this.$hmrStatusService.attachToHmrStatusEvent(); - } - - if (liveSyncData.watchAllFiles) { - const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); - patterns.push(PACKAGE_JSON_FILE_NAME); - - // watch only production node_module/packages same one prepare uses - for (const index in productionDependencies) { - patterns.push(productionDependencies[index].directory); - } - } - - const currentWatcherInfo = this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo; - const areWatcherPatternsDifferent = () => _.xor(currentWatcherInfo.patterns, patterns).length; - if (!currentWatcherInfo || areWatcherPatternsDifferent()) { - if (currentWatcherInfo) { - currentWatcherInfo.watcher.close(); - } - - let filesToSync: string[] = []; - const hmrData: IDictionary = {}; - const filesToSyncMap: IDictionary = {}; - let filesToRemove: string[] = []; - let timeoutTimer: NodeJS.Timer; - - const startSyncFilesTimeout = (files: string[], platform?: string, opts?: { calledFromHook: boolean }) => { - timeoutTimer = setTimeout(async () => { - if (platform && liveSyncData.bundle) { - filesToSync = filesToSyncMap[platform]; - } - - if (files) { - filesToSync = files; - } - - if ((filesToSync && filesToSync.length) || (filesToRemove && filesToRemove.length)) { - console.log("============================== FILES_TO_SYNC ==================== ", filesToSync); - const currentFilesToSync = _.cloneDeep(filesToSync); - filesToSync.splice(0, filesToSync.length); - - const currentFilesToRemove = _.cloneDeep(filesToRemove); - filesToRemove = []; - - if (liveSyncData.syncToPreviewApp) { - await this.addActionToChain(projectData.projectDir, async () => { - await this.$previewAppLiveSyncService.syncFiles({ - projectDir: projectData.projectDir, - bundle: liveSyncData.bundle, - useHotModuleReload: liveSyncData.useHotModuleReload, - env: liveSyncData.env - }, currentFilesToSync, currentFilesToRemove); - }); - } else { - // Push actions to the queue, do not start them simultaneously - await this.addActionToChain(projectData.projectDir, async () => { - try { - const currentHmrData = _.cloneDeep(hmrData); - - const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); - - const preparedPlatforms: string[] = []; - const rebuiltInformation: ILiveSyncBuildInfo[] = []; - - const latestAppPackageInstalledSettings = this.getDefaultLatestAppPackageInstalledSettings(); - - await this.$devicesService.execute(async (device: Mobile.IDevice) => { - const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; - const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - const platformHmrData = (currentHmrData && currentHmrData[device.deviceInfo.platform]) || {}; - - const settings: ILiveSyncWatchInfo = { - liveSyncDeviceInfo: deviceBuildInfoDescriptor, - projectData, - filesToRemove: currentFilesToRemove, - filesToSync: currentFilesToSync, - isReinstalled: false, - syncAllFiles: liveSyncData.watchAllFiles, - hmrData: platformHmrData, - useHotModuleReload: liveSyncData.useHotModuleReload, - force: liveSyncData.force, - connectTimeout: 1000 - }; - - const service = this.getLiveSyncService(device.deviceInfo.platform); - - const watchAction = async (watchInfo: ILiveSyncWatchInfo): Promise => { - const isInHMRMode = liveSyncData.useHotModuleReload && platformHmrData.hash; - if (isInHMRMode) { - this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); - } - - let liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); - - await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - - // If didRecover is true, this means we were in ErrorActivity and fallback files were already transferred and app will be restarted. - if (!liveSyncResultInfo.didRecover && isInHMRMode) { - const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); - if (status === HmrConstants.HMR_ERROR_STATUS) { - watchInfo.filesToSync = platformHmrData.fallbackFiles; - liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); - // We want to force a restart of the application. - liveSyncResultInfo.isFullSync = true; - await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - } - } - - this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - }; - - if (liveSyncData.useHotModuleReload && opts && opts.calledFromHook) { - try { - this.$logger.trace("Try executing watch action without any preparation of files."); - await watchAction(settings); - this.$logger.trace("Successfully executed watch action without any preparation of files."); - return; - } catch (err) { - this.$logger.trace(`Error while trying to execute fast sync. Now we'll check the state of the app and we'll try to resurrect from the error. The error is: ${err}`); - } - } - - const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({ - device, - preparedPlatforms, - rebuiltInformation, - projectData, - deviceBuildInfoDescriptor, - // the clean option should be respected only during initial sync - liveSyncData: _.assign({}, liveSyncData, { clean: false }), - settings: latestAppPackageInstalledSettings, - modifiedFiles: allModifiedFiles, - filesToRemove: currentFilesToRemove, - filesToSync: currentFilesToSync, - bundle: liveSyncData.bundle, - release: liveSyncData.release, - env: liveSyncData.env, - skipModulesNativeCheck: !liveSyncData.watchAllFiles - }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); - - settings.isReinstalled = appInstalledOnDeviceResult.appInstalled; - settings.connectTimeout = null; - - if (liveSyncData.useHotModuleReload && appInstalledOnDeviceResult.appInstalled) { - _.each(platformHmrData.fallbackFiles, fileToSync => currentFilesToSync.push(fileToSync)); - } - - await watchAction(settings); - }, - (device: Mobile.IDevice) => { - const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; - return (!platform || platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); - } - ); - } catch (err) { - const allErrors = (err).allErrors; - - if (allErrors && _.isArray(allErrors)) { - for (const deviceError of allErrors) { - this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); - const device = this.$devicesService.getDeviceByIdentifier(deviceError.deviceIdentifier); - this.emitLivesyncEvent(LiveSyncEvents.liveSyncError, { - error: deviceError, - deviceIdentifier: deviceError.deviceIdentifier, - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] - }); - - await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier], { shouldAwaitAllActions: false }); - } - } - } - }); - } - } - }, liveSyncData.useHotModuleReload ? 0 : 250); - - this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; - }; - - await this.$hooksService.executeBeforeHooks('watch', { - hookArgs: { - projectData, - config: { - env: liveSyncData.env, - appFilesUpdaterOptions: { - bundle: liveSyncData.bundle, - release: liveSyncData.release, - watchAllFiles: liveSyncData.watchAllFiles, - useHotModuleReload: liveSyncData.useHotModuleReload - }, - platforms - }, - filesToSync, - filesToSyncMap, - hmrData, - filesToRemove, - // startSyncFilesTimeout: async (platform: string) => { - // const opts = { calledFromHook: true }; - // if (platform) { - // await startSyncFilesTimeout(platform, opts); - // } else { - // // This code is added for backwards compatibility with old versions of nativescript-dev-webpack plugin. - // await startSyncFilesTimeout(null, opts); - // } - // } - } - }); - - let isFirstSync = true; - this.$platformService.on("changedFiles", async files => { - console.log("===================== CHANGED FILES =============== ", files); - if (!isFirstSync) { - // filesToSyncMap["ios"] = files; - await startSyncFilesTimeout(files, "ios", { calledFromHook: true }); - } else { - isFirstSync = false; - } - }); - - // const platformSpecificOptions = options.deviceBuildInfoDescriptor.platformSpecificOptions || {}; - const prepareInfo: IPreparePlatformInfo = { - platform: "ios", - appFilesUpdaterOptions: { - bundle: true, - release: false, - watchAllFiles: false, - useHotModuleReload: false - }, - projectData: this.$projectDataService.getProjectData(liveSyncData.projectDir), - env: liveSyncData.env, - nativePrepare: null, - // filesToSync: options.filesToSync, - // filesToRemove: options.filesToRemove, - // skipModulesNativeCheck: liveSyncData.skipModulesNativeCheck, - config: {}, - webpackCompilerConfig: { - watch: true, - env: liveSyncData.env - } - }; - - await this.$platformService.preparePlatform(prepareInfo); - - // const watcherOptions: choki.WatchOptions = { - // ignoreInitial: true, - // cwd: liveSyncData.projectDir, - // awaitWriteFinish: { - // pollInterval: 100, - // stabilityThreshold: 500 - // }, - // ignored: ["**/.*", ".*"] // hidden files - // }; - - // const watcher = choki.watch(patterns, watcherOptions) - // .on("all", async (event: string, filePath: string) => { - - // clearTimeout(timeoutTimer); - - // filePath = path.join(liveSyncData.projectDir, filePath); - - // this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); - - // if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) { - // filesToSync.push(filePath); - // } else if (event === "unlink" || event === "unlinkDir") { - // filesToRemove.push(filePath); - // } - - // startSyncFilesTimeout(); - // }); - - // this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, patterns }; - this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; - } - } - - @cache() - private attachDeviceLostHandler(): void { - 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.liveSyncProcessesInfo) { - try { - if (_.find(this.liveSyncProcessesInfo[projectDir].deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { - await this.stopLiveSync(projectDir, [device.deviceInfo.identifier]); - } - } catch (err) { - this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); - } - } - }); - } - - private async addActionToChain(projectDir: string, action: () => Promise): Promise { - const liveSyncInfo = this.liveSyncProcessesInfo[projectDir]; - if (liveSyncInfo) { - liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { - if (!liveSyncInfo.isStopped) { - liveSyncInfo.currentSyncAction = action(); - const res = await liveSyncInfo.currentSyncAction; - return res; - } - }); - - const result = await liveSyncInfo.actionsChain; - return result; - } - } - - private getInstallApplicationBuildConfig(deviceIdentifier: string, projectDir: string, opts: { isEmulator: boolean }): IBuildConfig { - const buildConfig: IBuildConfig = { - buildForDevice: !opts.isEmulator, - iCloudContainerEnvironment: null, - release: false, - device: deviceIdentifier, - provision: null, - teamId: null, - projectDir - }; - - return buildConfig; - } - - public emitLivesyncEvent(event: string, livesyncData: ILiveSyncEventData): boolean { - this.$logger.trace(`Will emit event ${event} with data`, livesyncData); - return this.emit(event, livesyncData); - } -} - -$injector.register("liveSyncService", LiveSyncService); - -/** - * This class is used only for old versions of nativescript-dev-typescript plugin. - * It should be replaced with liveSyncService.isInitalized. - * Consider adding get and set methods for isInitialized, - * so whenever someone tries to access the value of isInitialized, - * they'll get a warning to update the plugins (like nativescript-dev-typescript). - */ -export class DeprecatedUsbLiveSyncService { - public isInitialized = false; -} - -$injector.register("usbLiveSyncService", DeprecatedUsbLiveSyncService); +// // import * as path from "path"; +// // import * as choki from "chokidar"; +// import { EOL } from "os"; +// import { EventEmitter } from "events"; +// import { hook } from "../../common/helpers"; +// import { +// PACKAGE_JSON_FILE_NAME, +// USER_INTERACTION_NEEDED_EVENT_NAME, +// DEBUGGER_ATTACHED_EVENT_NAME, +// DEBUGGER_DETACHED_EVENT_NAME, +// TrackActionNames, +// LiveSyncEvents +// } from "../../constants"; +// import { DeviceTypes, DeviceDiscoveryEventNames, HmrConstants } from "../../common/constants"; +// import { cache } from "../../common/decorators"; +// import { performanceLog } from "../../common/decorators"; + +// const deviceDescriptorPrimaryKey = "identifier"; + +// export class LiveSyncService extends EventEmitter implements IDebugLiveSyncService { +// // key is projectDir +// protected liveSyncProcessesInfo: IDictionary = {}; + +// constructor(private $platformService: IPlatformService, +// private $projectDataService: IProjectDataService, +// private $devicesService: Mobile.IDevicesService, +// private $mobileHelper: Mobile.IMobileHelper, +// private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, +// private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, +// private $logger: ILogger, +// private $hooksService: IHooksService, +// private $pluginsService: IPluginsService, +// private $debugService: IDebugService, +// private $errors: IErrors, +// private $debugDataService: IDebugDataService, +// private $analyticsService: IAnalyticsService, +// private $usbLiveSyncService: DeprecatedUsbLiveSyncService, +// private $previewAppLiveSyncService: IPreviewAppLiveSyncService, +// private $previewQrCodeService: IPreviewQrCodeService, +// private $previewSdkService: IPreviewSdkService, +// private $hmrStatusService: IHmrStatusService, +// private $injector: IInjector) { +// super(); +// } + +// public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { +// const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); +// await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); +// await this.liveSyncOperation(deviceDescriptors, liveSyncData, projectData); +// } + +// public async liveSyncToPreviewApp(data: IPreviewAppLiveSyncData): Promise { +// this.attachToPreviewAppLiveSyncError(); + +// await this.liveSync([], { +// syncToPreviewApp: true, +// projectDir: data.projectDir, +// bundle: data.bundle, +// useHotModuleReload: data.useHotModuleReload, +// release: false, +// env: data.env, +// }); + +// const url = this.$previewSdkService.getQrCodeUrl({ projectDir: data.projectDir, useHotModuleReload: data.useHotModuleReload }); +// const result = await this.$previewQrCodeService.getLiveSyncQrCode(url); +// return result; +// } + +// public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { +// const liveSyncProcessInfo = this.liveSyncProcessesInfo[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. +// const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; + +// const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); + +// const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) +// .map(descriptor => descriptor.identifier); + +// // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. +// if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { +// if (liveSyncProcessInfo.timer) { +// clearTimeout(liveSyncProcessInfo.timer); +// } + +// if (liveSyncProcessInfo.watcherInfo && liveSyncProcessInfo.watcherInfo.watcher) { +// liveSyncProcessInfo.watcherInfo.watcher.close(); +// } + +// liveSyncProcessInfo.watcherInfo = null; +// liveSyncProcessInfo.isStopped = true; + +// if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) { +// await liveSyncProcessInfo.actionsChain; +// } + +// liveSyncProcessInfo.deviceDescriptors = []; + +// if (liveSyncProcessInfo.syncToPreviewApp) { +// await this.$previewAppLiveSyncService.stopLiveSync(); +// this.$previewAppLiveSyncService.removeAllListeners(); +// } + +// // Kill typescript watcher +// const projectData = this.$projectDataService.getProjectData(projectDir); +// await this.$hooksService.executeAfterHooks('watch', { +// hookArgs: { +// projectData +// } +// }); + +// // In case we are stopping the LiveSync we must set usbLiveSyncService.isInitialized to false, +// // as in case we execute nativescript-dev-typescript's before-prepare hook again in the same process, it MUST transpile the files. +// this.$usbLiveSyncService.isInitialized = false; +// } else if (liveSyncProcessInfo.currentSyncAction && shouldAwaitPendingOperation) { +// await liveSyncProcessInfo.currentSyncAction; +// } + +// // Emit LiveSync stopped when we've really stopped. +// _.each(removedDeviceIdentifiers, deviceIdentifier => { +// this.emitLivesyncEvent(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier }); +// }); +// } +// } + +// public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { +// const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; +// const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; +// return currentDescriptors || []; +// } + +// private attachToPreviewAppLiveSyncError(): void { +// if (!this.$usbLiveSyncService.isInitialized) { +// this.$previewAppLiveSyncService.on(LiveSyncEvents.previewAppLiveSyncError, liveSyncData => { +// this.$logger.error(liveSyncData.error); +// this.emit(LiveSyncEvents.previewAppLiveSyncError, liveSyncData); +// }); +// } +// } + +// @performanceLog() +// private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { +// const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir); + +// return deviceDescriptor && deviceDescriptor.debugggingEnabled ? +// this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) : +// this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath); +// } + +// private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IRefreshApplicationSettings): Promise { +// const result = { didRestart: false }; +// const platform = liveSyncResultInfo.deviceAppData.platform; +// const platformLiveSyncService = this.getLiveSyncService(platform); +// const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; +// try { +// let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); +// if (!shouldRestart) { +// shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); +// } + +// if (shouldRestart) { +// const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; +// this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); +// await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); +// result.didRestart = true; +// } +// } catch (err) { +// 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); +// if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { +// this.emitLivesyncEvent(LiveSyncEvents.liveSyncNotification, { +// projectDir: projectData.projectDir, +// applicationIdentifier, +// deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, +// notification: msg +// }); +// } + +// if (settings && settings.shouldCheckDeveloperDiscImage) { +// this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); +// } +// } + +// this.emitLivesyncEvent(LiveSyncEvents.liveSyncExecuted, { +// projectDir: projectData.projectDir, +// applicationIdentifier, +// syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), +// deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, +// isFullSync: liveSyncResultInfo.isFullSync +// }); + +// return result; +// } + +// @performanceLog() +// private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { +// debugOptions = debugOptions || {}; +// if (debugOptions.debugBrk) { +// liveSyncResultInfo.waitForDebugger = true; +// } + +// const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); + +// // 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: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, +// debugOptions: debugOptions, +// }; + +// return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); +// } + +// private handleDeveloperDiskImageError(err: any, liveSyncResultInfo: ILiveSyncResultInfo, projectData: IProjectData, debugOpts: IDebugOptions, outputPath: string) { +// if ((err.message || err) === "Could not find developer disk image") { +// const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; +// const attachDebuggerOptions: IAttachDebuggerOptions = { +// platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, +// isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, +// projectDir: projectData.projectDir, +// deviceIdentifier, +// debugOptions: debugOpts, +// outputPath +// }; +// this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); +// } +// } + +// public async attachDebugger(settings: IAttachDebuggerOptions): Promise { +// // Default values +// if (settings.debugOptions) { +// settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; +// settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; +// } else { +// settings.debugOptions = { +// chrome: true, +// start: true +// }; +// } + +// const projectData = this.$projectDataService.getProjectData(settings.projectDir); +// const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); + +// // Of the properties below only `buildForDevice` and `release` are currently used. +// // Leaving the others with placeholder values so that they may not be forgotten in future implementations. +// const buildConfig = this.getInstallApplicationBuildConfig(settings.deviceIdentifier, settings.projectDir, { isEmulator: settings.isEmulator }); +// debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath); +// const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); +// const result = this.printDebugInformation(debugInfo, settings.debugOptions.forceDebuggerAttachedEvent); +// return result; +// } + +// 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 enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { +// return _.map(deviceOpts, d => this.enableDebuggingCore(d, debuggingAdditionalOptions)); +// } + +// private getDeviceDescriptor(deviceIdentifier: string, projectDir: string) { +// const deviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); + +// return _.find(deviceDescriptors, d => d.identifier === deviceIdentifier); +// } + +// @performanceLog() +// private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { +// const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); +// if (!currentDeviceDescriptor) { +// this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); +// } + +// currentDeviceDescriptor.debugggingEnabled = true; +// currentDeviceDescriptor.debugOptions = deviceOption.debugOptions; +// const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); +// const attachDebuggerOptions: IAttachDebuggerOptions = { +// deviceIdentifier: deviceOption.deviceIdentifier, +// isEmulator: currentDeviceInstance.isEmulator, +// outputPath: currentDeviceDescriptor.outputPath, +// platform: currentDeviceInstance.deviceInfo.platform, +// projectDir: debuggingAdditionalOptions.projectDir, +// debugOptions: deviceOption.debugOptions +// }; + +// let debugInformation: IDebugInformation; +// try { +// debugInformation = await this.attachDebugger(attachDebuggerOptions); +// } catch (err) { +// this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); +// attachDebuggerOptions.debugOptions.start = false; +// try { +// debugInformation = await this.attachDebugger(attachDebuggerOptions); +// } catch (innerErr) { +// this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); +// throw err; +// } +// } + +// return debugInformation; +// } + +// private async enableDebuggingCore(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { +// const liveSyncProcessInfo: ILiveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; +// if (liveSyncProcessInfo && liveSyncProcessInfo.currentSyncAction) { +// await liveSyncProcessInfo.currentSyncAction; +// } + +// return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, debuggingAdditionalOptions); +// } + +// public disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { +// return _.map(deviceOptions, d => this.disableDebuggingCore(d, debuggingAdditionalOptions)); +// } + +// @hook('watchPatterns') +// public async getWatcherPatterns(liveSyncData: ILiveSyncInfo, projectData: IProjectData, platforms: string[]): Promise { +// // liveSyncData and platforms are used by plugins that make use of the watchPatterns hook +// // TODO: ignore getAppDirectoryRelativePath +// // TODO: watch platforms folder of node_modules e.g native source folders of nativescript plugins +// return [projectData.getAppDirectoryRelativePath(), projectData.getAppResourcesRelativeDirectoryPath()]; +// } + +// public async disableDebuggingCore(deviceOption: IDisableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { +// const liveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; +// if (liveSyncProcessInfo.currentSyncAction) { +// await liveSyncProcessInfo.currentSyncAction; +// } + +// const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); +// if (currentDeviceDescriptor) { +// currentDeviceDescriptor.debugggingEnabled = false; +// } else { +// this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}`); +// } + +// const currentDevice = this.$devicesService.getDeviceByIdentifier(currentDeviceDescriptor.identifier); +// if (!currentDevice) { +// this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}. Could not find device.`); +// } + +// await this.$debugService.debugStop(currentDevice.deviceInfo.identifier); +// this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier: currentDeviceDescriptor.identifier }); +// } + +// @hook("liveSync") +// private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise { +// let deviceDescriptorsForInitialSync: ILiveSyncDeviceInfo[] = []; + +// if (liveSyncData.syncToPreviewApp) { +// await this.$previewAppLiveSyncService.initialize({ +// projectDir: projectData.projectDir, +// bundle: liveSyncData.bundle, +// useHotModuleReload: liveSyncData.useHotModuleReload, +// env: liveSyncData.env +// }); +// } else { +// // In case liveSync is called for a second time for the same projectDir. +// const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; + +// // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. +// const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir); +// deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; +// } + +// this.setLiveSyncProcessInfo(liveSyncData, deviceDescriptors); + +// const shouldStartWatcher = !liveSyncData.skipWatcher && (liveSyncData.syncToPreviewApp || this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length); +// if (shouldStartWatcher) { +// // Should be set after prepare +// this.$usbLiveSyncService.isInitialized = true; +// await this.startWatcher(projectData, liveSyncData, deviceDescriptors); +// } + +// await this.initialSync(projectData, liveSyncData, deviceDescriptorsForInitialSync); +// } + +// private setLiveSyncProcessInfo(liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): void { +// const { projectDir } = liveSyncData; +// this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); +// this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); +// this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; +// this.liveSyncProcessesInfo[projectDir].isStopped = false; +// this.liveSyncProcessesInfo[projectDir].syncToPreviewApp = liveSyncData.syncToPreviewApp; + +// const currentDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); +// this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); +// } + +// private getLiveSyncService(platform: string): IPlatformLiveSyncService { +// if (this.$mobileHelper.isiOSPlatform(platform)) { +// return this.$injector.resolve("iOSLiveSyncService"); +// } else if (this.$mobileHelper.isAndroidPlatform(platform)) { +// return this.$injector.resolve("androidLiveSyncService"); +// } + +// this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); +// } + +// private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions, nativePrepare?: INativePrepare): Promise { +// const platform = options.device.deviceInfo.platform; +// const appInstalledOnDeviceResult: IAppInstalledOnDeviceResult = { appInstalled: false }; +// if (options.preparedPlatforms.indexOf(platform) === -1) { +// options.preparedPlatforms.push(platform); +// } + +// const buildResult = await this.installedCachedAppPackage(platform, options); +// if (buildResult) { +// appInstalledOnDeviceResult.appInstalled = true; +// return appInstalledOnDeviceResult; +// } + +// const shouldBuild = await this.$platformService.shouldBuild(platform, +// options.projectData, +// { buildForDevice: !options.device.isEmulator, clean: options.liveSyncData && options.liveSyncData.clean }, +// options.deviceBuildInfoDescriptor.outputPath); +// let pathToBuildItem = null; +// if (shouldBuild) { +// pathToBuildItem = await options.deviceBuildInfoDescriptor.buildAction(); +// options.rebuiltInformation.push({ isEmulator: options.device.isEmulator, platform, pathToBuildItem }); +// } else { +// await this.$analyticsService.trackEventActionInGoogleAnalytics({ +// action: TrackActionNames.LiveSync, +// device: options.device, +// projectDir: options.projectData.projectDir +// }); +// } + +// await this.$platformService.validateInstall(options.device, options.projectData, options, options.deviceBuildInfoDescriptor.outputPath); +// const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options, options.deviceBuildInfoDescriptor.outputPath); +// if (shouldInstall) { +// const buildConfig = this.getInstallApplicationBuildConfig(options.device.deviceInfo.identifier, options.projectData.projectDir, { isEmulator: options.device.isEmulator }); +// await this.$platformService.installApplication(options.device, buildConfig, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); +// appInstalledOnDeviceResult.appInstalled = true; +// } + +// return appInstalledOnDeviceResult; +// } + +// private async installedCachedAppPackage(platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { +// const rebuildInfo = _.find(options.rebuiltInformation, info => info.platform === platform && (this.$mobileHelper.isAndroidPlatform(platform) || info.isEmulator === options.device.isEmulator)); + +// if (rebuildInfo) { +// // Case where we have three devices attached, a change that requires build is found, +// // we'll rebuild the app only for the first device, but we should install new package on all three devices. +// const buildConfig = this.getInstallApplicationBuildConfig(options.device.deviceInfo.identifier, options.projectData.projectDir, { isEmulator: options.device.isEmulator }); +// await this.$platformService.installApplication(options.device, buildConfig, options.projectData, rebuildInfo.pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); +// return rebuildInfo.pathToBuildItem; +// } + +// return null; +// } + +// private async initialSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { +// if (!liveSyncData.syncToPreviewApp) { +// await this.initialCableSync(projectData, liveSyncData, deviceDescriptors); +// } +// } + +// private async initialCableSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { +// const preparedPlatforms: string[] = []; +// const rebuiltInformation: ILiveSyncBuildInfo[] = []; + +// const settings = this.getDefaultLatestAppPackageInstalledSettings(); +// // Now fullSync +// const deviceAction = async (device: Mobile.IDevice): Promise => { +// const platform = device.deviceInfo.platform; +// try { +// const platformLiveSyncService = this.getLiveSyncService(platform); + +// const deviceBuildInfoDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + +// await this.ensureLatestAppPackageIsInstalledOnDevice({ +// device, +// preparedPlatforms, +// rebuiltInformation, +// projectData, +// deviceBuildInfoDescriptor, +// liveSyncData, +// settings, +// bundle: liveSyncData.bundle, +// release: liveSyncData.release, +// env: liveSyncData.env +// }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); + +// const liveSyncResultInfo = await platformLiveSyncService.fullSync({ +// projectData, +// device, +// useHotModuleReload: liveSyncData.useHotModuleReload, +// watch: !liveSyncData.skipWatcher, +// force: liveSyncData.force, +// liveSyncDeviceInfo: deviceBuildInfoDescriptor +// }); + +// await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + +// this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); + +// this.emitLivesyncEvent(LiveSyncEvents.liveSyncStarted, { +// projectDir: projectData.projectDir, +// deviceIdentifier: device.deviceInfo.identifier, +// applicationIdentifier: projectData.projectIdentifiers[platform.toLowerCase()] +// }); +// } catch (err) { +// this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); + +// this.emitLivesyncEvent(LiveSyncEvents.liveSyncError, { +// error: err, +// deviceIdentifier: device.deviceInfo.identifier, +// projectDir: projectData.projectDir, +// applicationIdentifier: projectData.projectIdentifiers[platform.toLowerCase()] +// }); + +// await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier], { shouldAwaitAllActions: false }); +// } +// }; + +// // Execute the action only on the deviceDescriptors passed to initialSync. +// // In case where we add deviceDescriptors to already running application, we've already executed initialSync for them. +// await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); + +// this.attachDeviceLostHandler(); +// } + +// private getDefaultLatestAppPackageInstalledSettings(): ILatestAppPackageInstalledSettings { +// return { +// [this.$devicePlatformsConstants.Android]: { +// [DeviceTypes.Device]: false, +// [DeviceTypes.Emulator]: false +// }, +// [this.$devicePlatformsConstants.iOS]: { +// [DeviceTypes.Device]: false, +// [DeviceTypes.Emulator]: false +// } +// }; +// } + +// private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { +// const devicesIds = deviceDescriptors.map(dd => dd.identifier); +// const devices = _.filter(this.$devicesService.getDeviceInstances(), device => _.includes(devicesIds, device.deviceInfo.identifier)); +// const platforms = _(devices).map(device => device.deviceInfo.platform).uniq().value(); +// const patterns = await this.getWatcherPatterns(liveSyncData, projectData, platforms); + +// if (liveSyncData.useHotModuleReload) { +// this.$hmrStatusService.attachToHmrStatusEvent(); +// } + +// if (liveSyncData.watchAllFiles) { +// const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); +// patterns.push(PACKAGE_JSON_FILE_NAME); + +// // watch only production node_module/packages same one prepare uses +// for (const index in productionDependencies) { +// patterns.push(productionDependencies[index].directory); +// } +// } + +// const currentWatcherInfo = this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo; +// const areWatcherPatternsDifferent = () => _.xor(currentWatcherInfo.patterns, patterns).length; +// if (!currentWatcherInfo || areWatcherPatternsDifferent()) { +// if (currentWatcherInfo) { +// currentWatcherInfo.watcher.close(); +// } + +// let filesToSync: string[] = []; +// const hmrData: IDictionary = {}; +// const filesToSyncMap: IDictionary = {}; +// let filesToRemove: string[] = []; +// let timeoutTimer: NodeJS.Timer; + +// const startSyncFilesTimeout = (files: string[], platform?: string, opts?: { calledFromHook: boolean }) => { +// timeoutTimer = setTimeout(async () => { +// if (platform && liveSyncData.bundle) { +// filesToSync = filesToSyncMap[platform]; +// } + +// if (files) { +// filesToSync = files; +// } + +// if ((filesToSync && filesToSync.length) || (filesToRemove && filesToRemove.length)) { +// console.log("============================== FILES_TO_SYNC ==================== ", filesToSync); +// const currentFilesToSync = _.cloneDeep(filesToSync); +// filesToSync.splice(0, filesToSync.length); + +// const currentFilesToRemove = _.cloneDeep(filesToRemove); +// filesToRemove = []; + +// if (liveSyncData.syncToPreviewApp) { +// await this.addActionToChain(projectData.projectDir, async () => { +// await this.$previewAppLiveSyncService.syncFiles({ +// projectDir: projectData.projectDir, +// bundle: liveSyncData.bundle, +// useHotModuleReload: liveSyncData.useHotModuleReload, +// env: liveSyncData.env +// }, currentFilesToSync, currentFilesToRemove); +// }); +// } else { +// // Push actions to the queue, do not start them simultaneously +// await this.addActionToChain(projectData.projectDir, async () => { +// try { +// const currentHmrData = _.cloneDeep(hmrData); + +// const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); + +// const preparedPlatforms: string[] = []; +// const rebuiltInformation: ILiveSyncBuildInfo[] = []; + +// const latestAppPackageInstalledSettings = this.getDefaultLatestAppPackageInstalledSettings(); + +// await this.$devicesService.execute(async (device: Mobile.IDevice) => { +// const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; +// const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); +// const platformHmrData = (currentHmrData && currentHmrData[device.deviceInfo.platform]) || {}; + +// const settings: ILiveSyncWatchInfo = { +// liveSyncDeviceInfo: deviceBuildInfoDescriptor, +// projectData, +// filesToRemove: currentFilesToRemove, +// filesToSync: currentFilesToSync, +// isReinstalled: false, +// syncAllFiles: liveSyncData.watchAllFiles, +// hmrData: platformHmrData, +// useHotModuleReload: liveSyncData.useHotModuleReload, +// force: liveSyncData.force, +// connectTimeout: 1000 +// }; + +// const service = this.getLiveSyncService(device.deviceInfo.platform); + +// const watchAction = async (watchInfo: ILiveSyncWatchInfo): Promise => { +// const isInHMRMode = liveSyncData.useHotModuleReload && platformHmrData.hash; +// if (isInHMRMode) { +// this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); +// } + +// let liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); + +// await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + +// // If didRecover is true, this means we were in ErrorActivity and fallback files were already transferred and app will be restarted. +// if (!liveSyncResultInfo.didRecover && isInHMRMode) { +// const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); +// if (status === HmrConstants.HMR_ERROR_STATUS) { +// watchInfo.filesToSync = platformHmrData.fallbackFiles; +// liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); +// // We want to force a restart of the application. +// liveSyncResultInfo.isFullSync = true; +// await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); +// } +// } + +// this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); +// }; + +// if (liveSyncData.useHotModuleReload && opts && opts.calledFromHook) { +// try { +// this.$logger.trace("Try executing watch action without any preparation of files."); +// await watchAction(settings); +// this.$logger.trace("Successfully executed watch action without any preparation of files."); +// return; +// } catch (err) { +// this.$logger.trace(`Error while trying to execute fast sync. Now we'll check the state of the app and we'll try to resurrect from the error. The error is: ${err}`); +// } +// } + +// const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({ +// device, +// preparedPlatforms, +// rebuiltInformation, +// projectData, +// deviceBuildInfoDescriptor, +// // the clean option should be respected only during initial sync +// liveSyncData: _.assign({}, liveSyncData, { clean: false }), +// settings: latestAppPackageInstalledSettings, +// modifiedFiles: allModifiedFiles, +// filesToRemove: currentFilesToRemove, +// filesToSync: currentFilesToSync, +// bundle: liveSyncData.bundle, +// release: liveSyncData.release, +// env: liveSyncData.env, +// skipModulesNativeCheck: !liveSyncData.watchAllFiles +// }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); + +// settings.isReinstalled = appInstalledOnDeviceResult.appInstalled; +// settings.connectTimeout = null; + +// if (liveSyncData.useHotModuleReload && appInstalledOnDeviceResult.appInstalled) { +// _.each(platformHmrData.fallbackFiles, fileToSync => currentFilesToSync.push(fileToSync)); +// } + +// await watchAction(settings); +// }, +// // Ensure the livesync process will be triggered only for the initial devices +// (device: Mobile.IDevice) => { +// const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; +// return (!platform || platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); +// } +// ); +// } catch (err) { +// const allErrors = (err).allErrors; + +// if (allErrors && _.isArray(allErrors)) { +// for (const deviceError of allErrors) { +// this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); +// const device = this.$devicesService.getDeviceByIdentifier(deviceError.deviceIdentifier); +// this.emitLivesyncEvent(LiveSyncEvents.liveSyncError, { +// error: deviceError, +// deviceIdentifier: deviceError.deviceIdentifier, +// projectDir: projectData.projectDir, +// applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] +// }); + +// await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier], { shouldAwaitAllActions: false }); +// } +// } +// } +// }); +// } +// } +// }, liveSyncData.useHotModuleReload ? 0 : 250); + +// this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; +// }; + +// await this.$hooksService.executeBeforeHooks('watch', { +// hookArgs: { +// projectData, +// config: { +// env: liveSyncData.env, +// appFilesUpdaterOptions: { +// bundle: liveSyncData.bundle, +// release: liveSyncData.release, +// watchAllFiles: liveSyncData.watchAllFiles, +// useHotModuleReload: liveSyncData.useHotModuleReload +// }, +// platforms +// }, +// filesToSync, +// filesToSyncMap, +// hmrData, +// filesToRemove, +// // startSyncFilesTimeout: async (platform: string) => { +// // const opts = { calledFromHook: true }; +// // if (platform) { +// // await startSyncFilesTimeout(platform, opts); +// // } else { +// // // This code is added for backwards compatibility with old versions of nativescript-dev-webpack plugin. +// // await startSyncFilesTimeout(null, opts); +// // } +// // } +// } +// }); + +// let isFirstSync = true; +// this.$platformService.on("changedFiles", async files => { +// console.log("===================== CHANGED FILES =============== ", files); +// if (!isFirstSync) { +// // filesToSyncMap["ios"] = files; +// await startSyncFilesTimeout(files, "ios", { calledFromHook: true }); +// } else { +// isFirstSync = false; +// } +// }); + +// // const platformSpecificOptions = options.deviceBuildInfoDescriptor.platformSpecificOptions || {}; +// const prepareInfo: IPreparePlatformInfo = { +// platform: "ios", +// appFilesUpdaterOptions: { +// bundle: true, +// release: false, +// watchAllFiles: false, +// useHotModuleReload: false +// }, +// projectData: this.$projectDataService.getProjectData(liveSyncData.projectDir), +// env: liveSyncData.env, +// nativePrepare: null, +// // filesToSync: options.filesToSync, +// // filesToRemove: options.filesToRemove, +// // skipModulesNativeCheck: liveSyncData.skipModulesNativeCheck, +// config: {}, +// webpackCompilerConfig: { +// watch: true, +// env: liveSyncData.env +// } +// }; + +// await this.$platformService.preparePlatform(prepareInfo); + +// // const watcherOptions: choki.WatchOptions = { +// // ignoreInitial: true, +// // cwd: liveSyncData.projectDir, +// // awaitWriteFinish: { +// // pollInterval: 100, +// // stabilityThreshold: 500 +// // }, +// // ignored: ["**/.*", ".*"] // hidden files +// // }; + +// // const watcher = choki.watch(patterns, watcherOptions) +// // .on("all", async (event: string, filePath: string) => { + +// // clearTimeout(timeoutTimer); + +// // filePath = path.join(liveSyncData.projectDir, filePath); + +// // this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); + +// // if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) { +// // filesToSync.push(filePath); +// // } else if (event === "unlink" || event === "unlinkDir") { +// // filesToRemove.push(filePath); +// // } + +// // startSyncFilesTimeout(); +// // }); + +// // this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, patterns }; +// this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; +// } +// } + +// @cache() +// private attachDeviceLostHandler(): void { +// 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.liveSyncProcessesInfo) { +// try { +// if (_.find(this.liveSyncProcessesInfo[projectDir].deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { +// await this.stopLiveSync(projectDir, [device.deviceInfo.identifier]); +// } +// } catch (err) { +// this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); +// } +// } +// }); +// } + +// private async addActionToChain(projectDir: string, action: () => Promise): Promise { +// const liveSyncInfo = this.liveSyncProcessesInfo[projectDir]; +// if (liveSyncInfo) { +// liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { +// if (!liveSyncInfo.isStopped) { +// liveSyncInfo.currentSyncAction = action(); +// const res = await liveSyncInfo.currentSyncAction; +// return res; +// } +// }); + +// const result = await liveSyncInfo.actionsChain; +// return result; +// } +// } + +// private getInstallApplicationBuildConfig(deviceIdentifier: string, projectDir: string, opts: { isEmulator: boolean }): IBuildConfig { +// const buildConfig: IBuildConfig = { +// buildForDevice: !opts.isEmulator, +// iCloudContainerEnvironment: null, +// release: false, +// device: deviceIdentifier, +// provision: null, +// teamId: null, +// projectDir +// }; + +// return buildConfig; +// } + +// public emitLivesyncEvent(event: string, livesyncData: ILiveSyncEventData): boolean { +// this.$logger.trace(`Will emit event ${event} with data`, livesyncData); +// return this.emit(event, livesyncData); +// } +// } + +// $injector.register("liveSyncService", LiveSyncService); + +// /** +// * This class is used only for old versions of nativescript-dev-typescript plugin. +// * It should be replaced with liveSyncService.isInitalized. +// * Consider adding get and set methods for isInitialized, +// * so whenever someone tries to access the value of isInitialized, +// * they'll get a warning to update the plugins (like nativescript-dev-typescript). +// */ +// export class DeprecatedUsbLiveSyncService { +// public isInitialized = false; +// } + +// $injector.register("usbLiveSyncService", DeprecatedUsbLiveSyncService); diff --git a/lib/services/local-build-service.ts b/lib/services/local-build-service.ts index f9abb165ea..ad09c8a3e7 100644 --- a/lib/services/local-build-service.ts +++ b/lib/services/local-build-service.ts @@ -1,16 +1,16 @@ import { EventEmitter } from "events"; -import { BUILD_OUTPUT_EVENT_NAME, ANDROID_RELEASE_BUILD_ERROR_MESSAGE } from "../constants"; -import { attachAwaitDetach } from "../common/helpers"; +import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE } from "../constants"; export class LocalBuildService extends EventEmitter implements ILocalBuildService { - constructor(private $projectData: IProjectData, - private $mobileHelper: Mobile.IMobileHelper, + constructor( private $errors: IErrors, + private $mobileHelper: Mobile.IMobileHelper, private $platformsData: IPlatformsData, - private $platformService: IPlatformService, - private $projectDataService: IProjectDataService) { - super(); - } + private $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, + private $platformWorkflowService: IPlatformWorkflowService, + private $projectData: IProjectData, + private $projectDataService: IProjectDataService + ) { super(); } public async build(platform: string, platformBuildOptions: IPlatformBuildData): Promise { if (this.$mobileHelper.isAndroidPlatform(platform) && platformBuildOptions.release && (!platformBuildOptions.keyStorePath || !platformBuildOptions.keyStorePassword || !platformBuildOptions.keyStoreAlias || !platformBuildOptions.keyStoreAliasPassword)) { @@ -18,33 +18,16 @@ export class LocalBuildService extends EventEmitter implements ILocalBuildServic } this.$projectData.initializeProjectData(platformBuildOptions.projectDir); - const prepareInfo: IPreparePlatformInfo = { - platform, - appFilesUpdaterOptions: platformBuildOptions, - projectData: this.$projectData, - env: platformBuildOptions.env, - config: { - provision: platformBuildOptions.provision, - teamId: platformBuildOptions.teamId, - sdk: null, - frameworkPath: null, - ignoreScripts: false - }, - webpackCompilerConfig: { - watch: false, - env: platformBuildOptions.env - } - }; - - await this.$platformService.preparePlatform(prepareInfo); - const handler = (data: any) => { - data.projectDir = platformBuildOptions.projectDir; - this.emit(BUILD_OUTPUT_EVENT_NAME, data); - }; + + const projectData = this.$projectDataService.getProjectData(platformBuildOptions.projectDir); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const workflowData = this.$platformWorkflowDataFactory.createPlatformWorkflowData(platform, platformBuildOptions, (platformBuildOptions).nativePrepare); + platformBuildOptions.buildOutputStdio = "pipe"; - await attachAwaitDetach(BUILD_OUTPUT_EVENT_NAME, this.$platformService, handler, this.$platformService.buildPlatform(platform, platformBuildOptions, this.$projectData)); - return this.$platformService.lastOutputPath(platform, platformBuildOptions, this.$projectData); + const result = await this.$platformWorkflowService.buildPlatform(platformData, projectData, workflowData, platformBuildOptions); + + return result; } public async cleanNativeApp(data: ICleanNativeAppData): Promise { diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index e30353eded..6ef70bcfda 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -185,11 +185,11 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ syncToPreviewApp: true, projectDir, skipWatcher: !options.watch, - watchAllFiles: options.syncAllFiles, clean: options.clean, - bundle: !!options.bundle, release: options.release, - env: options.env, + webpackCompilerConfig: { + env: options.env, + }, timeout: options.timeout, useHotModuleReload: options.hmr }); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 4f77ecdd0a..17290ac31f 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -4,9 +4,7 @@ import * as constants from "../constants"; import { Configurations } from "../common/constants"; import * as helpers from "../common/helpers"; import * as semver from "semver"; -import { format } from "util"; import { EventEmitter } from "events"; -import { AppFilesUpdater } from "./app-files-updater"; import { attachAwaitDetach } from "../common/helpers"; import * as temp from "temp"; import { performanceLog } from ".././common/decorators"; @@ -16,302 +14,45 @@ const buildInfoFileName = ".nsbuildinfo"; export class PlatformService extends EventEmitter implements IPlatformService { constructor(private $devicesService: Mobile.IDevicesService, - private $preparePlatformNativeService: IPreparePlatformService, - private $preparePlatformJSService: IPreparePlatformService, private $errors: IErrors, private $fs: IFileSystem, private $logger: ILogger, - private $packageInstallationManager: IPackageInstallationManager, private $platformsData: IPlatformsData, private $projectDataService: IProjectDataService, - private $pluginsService: IPluginsService, - // private $projectFilesManager: IProjectFilesManager, + private $webpackCompilerService: IWebpackCompilerService, + // private $platformJSService: IPreparePlatformService, + private $platformNativeService: IPreparePlatformService, private $mobileHelper: Mobile.IMobileHelper, private $hostInfo: IHostInfo, private $devicePathProvider: IDevicePathProvider, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $projectChangesService: IProjectChangesService, private $analyticsService: IAnalyticsService, - private $terminalSpinnerService: ITerminalSpinnerService, - private $pacoteService: IPacoteService, - // private $usbLiveSyncService: any, public $hooksService: IHooksService, - ) { - super(); - } - - public async cleanPlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions, framworkPath?: string): Promise { - for (const platform of platforms) { - const version: string = this.getCurrentPlatformVersion(platform, projectData); - - let platformWithVersion: string = platform; - if (version !== undefined) { - platformWithVersion += "@" + version; - } - - await this.removePlatforms([platform], projectData); - await this.addPlatforms([platformWithVersion], projectData, config); - } - } - - public async addPlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string): Promise { - const platformsDir = projectData.platformsDir; - this.$fs.ensureDirectoryExists(platformsDir); - - for (const platform of platforms) { - this.validatePlatform(platform, projectData); - const platformPath = path.join(projectData.platformsDir, platform); - - const isPlatformAdded = this.isPlatformAdded(platform, platformPath, projectData); - if (isPlatformAdded) { - this.$errors.failWithoutHelp(`Platform ${platform} already added`); - } - - await this.addPlatform(platform.toLowerCase(), projectData, config, frameworkPath); - } - } + ) { super(); } public getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { const platformData = this.$platformsData.getPlatformData(platform, projectData); const currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); - let version: string; - if (currentPlatformData && currentPlatformData[constants.VERSION_STRING]) { - version = currentPlatformData[constants.VERSION_STRING]; - } + const version = currentPlatformData && currentPlatformData.version; return version; } - private async addPlatform(platformParam: string, projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string, nativePrepare?: INativePrepare): Promise { - const data = platformParam.split("@"); - const platform = data[0].toLowerCase(); - let version = data[1]; - - const platformData = this.$platformsData.getPlatformData(platform, projectData); - - // Log the values for project - this.$logger.trace("Creating NativeScript project for the %s platform", platform); - this.$logger.trace("Path: %s", platformData.projectRoot); - this.$logger.trace("Package: %s", projectData.projectIdentifiers[platform]); - this.$logger.trace("Name: %s", projectData.projectName); - - this.$logger.out("Copying template files..."); - - let packageToInstall = ""; - if (frameworkPath) { - packageToInstall = path.resolve(frameworkPath); - if (!this.$fs.exists(packageToInstall)) { - const errorMessage = format(constants.AddPlaformErrors.InvalidFrameworkPathStringFormat, frameworkPath); - this.$errors.fail(errorMessage); - } - } else if (!version) { - version = this.getCurrentPlatformVersion(platform, projectData) || await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); - - packageToInstall = `${platformData.frameworkPackageName}@${version}`; - } - - const spinner = this.$terminalSpinnerService.createSpinner(); - const platformPath = path.join(projectData.platformsDir, platform); - let installedPlatformVersion; - - try { - spinner.start(); - const downloadedPackagePath = temp.mkdirSync("runtimeDir"); - temp.track(); - await this.$pacoteService.extractPackage(packageToInstall, downloadedPackagePath); - let frameworkDir = path.join(downloadedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME); - frameworkDir = path.resolve(frameworkDir); - installedPlatformVersion = - await this.addPlatformCore(platformData, frameworkDir, projectData, config, nativePrepare); - } catch (err) { - this.$fs.deleteDirectory(platformPath); - throw err; - } finally { - spinner.stop(); - } - - this.$fs.ensureDirectoryExists(platformPath); - this.$logger.out(`Platform ${platform} successfully added. v${installedPlatformVersion}`); - } - - private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, projectData: IProjectData, config: IPlatformOptions, nativePrepare?: INativePrepare): Promise { - const coreModuleData = this.$fs.readJson(path.join(frameworkDir, "..", "package.json")); - const installedVersion = coreModuleData.version; - - // JS platform add - const frameworkPackageNameData = { version: installedVersion }; - this.$projectDataService.setNSValue(projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData); - - if (!nativePrepare || !nativePrepare.skipNativePrepare) { - - await this.$preparePlatformNativeService.addPlatform({ - platformData, - frameworkDir, - installedVersion, - projectData, - config - }); - } - - return installedVersion; - } - - public getInstalledPlatforms(projectData: IProjectData): string[] { - if (!this.$fs.exists(projectData.platformsDir)) { - return []; - } - - const subDirs = this.$fs.readDirectory(projectData.platformsDir); - return _.filter(subDirs, p => this.$platformsData.platformsNames.indexOf(p) > -1); - } - - public getAvailablePlatforms(projectData: IProjectData): string[] { - const installedPlatforms = this.getInstalledPlatforms(projectData); - return _.filter(this.$platformsData.platformsNames, p => { - return installedPlatforms.indexOf(p) < 0 && this.isPlatformSupportedForOS(p, projectData); // Only those not already installed - }); - } - - public getPreparedPlatforms(projectData: IProjectData): string[] { - return _.filter(this.$platformsData.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); - } - - @performanceLog() - @helpers.hook('shouldPrepare') - public async shouldPrepare(shouldPrepareInfo: IShouldPrepareInfo): Promise { - shouldPrepareInfo.changesInfo = shouldPrepareInfo.changesInfo || await this.getChangesInfo(shouldPrepareInfo.platformInfo); - const requiresNativePrepare = (!shouldPrepareInfo.platformInfo.nativePrepare || !shouldPrepareInfo.platformInfo.nativePrepare.skipNativePrepare) && shouldPrepareInfo.changesInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPrepare; - - return shouldPrepareInfo.changesInfo.hasChanges || requiresNativePrepare; - } - - private async getChangesInfo(preparePlatformInfo: IPreparePlatformInfo): Promise { - await this.initialPrepare(preparePlatformInfo); - - const { platform, appFilesUpdaterOptions, projectData, config, nativePrepare } = preparePlatformInfo; - const bundle = appFilesUpdaterOptions.bundle; - const nativePlatformStatus = (nativePrepare && nativePrepare.skipNativePrepare) ? constants.NativePlatformStatus.requiresPlatformAdd : constants.NativePlatformStatus.requiresPrepare; - const changesInfo = await this.$projectChangesService.checkForChanges({ - platform, - projectData, - projectChangesOptions: { - bundle, - release: appFilesUpdaterOptions.release, - provision: config.provision, - teamId: config.teamId, - nativePlatformStatus, - skipModulesNativeCheck: preparePlatformInfo.skipModulesNativeCheck, - useHotModuleReload: appFilesUpdaterOptions.useHotModuleReload - } - }); - - this.$logger.trace("Changes info in prepare platform:", changesInfo); - return changesInfo; - } - @performanceLog() - public async preparePlatform(platformInfo: IPreparePlatformInfo, callback?: (shouldRebuild: boolean) => {}): Promise { - const { platform, projectData, webpackCompilerConfig, config, appFilesUpdaterOptions, filesToSync, filesToRemove, env } = platformInfo; - const changesInfo = await this.getChangesInfo(platformInfo); - const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: appFilesUpdaterOptions.release }); - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const prepareNativePlatformData = { - platform, - platformData, - appFilesUpdaterOptions, - projectData, - platformSpecificData: config, - changesInfo, - filesToSync, - filesToRemove, - projectFilesConfig, - env - }; - + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { this.$logger.out("Preparing project..."); - const nativePromise = helpers.deferPromise(); - const jsPromise = helpers.deferPromise(); - const jsFiles: string[] = []; - - this.$preparePlatformJSService.on("jsFilesChanged", files => { - jsFiles.push(...files); - if (!jsPromise.isResolved()) { - jsPromise.resolve(jsFiles); - } - - if (nativePromise.isResolved()) { - this.emit("changedFiles", _.uniq(jsFiles)); - } - }); - await this.$preparePlatformJSService.startWatcher(platformData, projectData, webpackCompilerConfig); - - this.$preparePlatformNativeService.on("nativeFilesChanged", (files: any) => { - if (!nativePromise.isResolved()) { - nativePromise.resolve([]); - } - - if (jsPromise.isResolved()) { - this.emit("changedFiles", jsPromise.getResult()); - } - }); - await this.$preparePlatformNativeService.startWatcher(platformData, projectData, prepareNativePlatformData); + await this.$webpackCompilerService.compile(platformData, projectData, { watch: false, env: preparePlatformData.env }); + await this.$platformNativeService.preparePlatform(platformData, projectData, preparePlatformData); - await Promise.all([jsPromise, nativePromise]); + this.$projectChangesService.savePrepareInfo(platformData.platformNameLowerCase, projectData); - this.$logger.out(`Project successfully prepared (${platform})`); + this.$logger.out(`Project successfully prepared (${platformData.platformNameLowerCase})`); return true; } - public async validateOptions(provision: true | string, teamId: true | string, projectData: IProjectData, platform?: string, aab?: boolean): Promise { - if (platform && !this.$mobileHelper.isAndroidPlatform(platform) && aab) { - this.$errors.failWithoutHelp("The --aab option is supported only for the Android platform."); - } - - if (platform) { - platform = this.$mobileHelper.normalizePlatformName(platform); - this.$logger.trace("Validate options for platform: " + platform); - const platformData = this.$platformsData.getPlatformData(platform, projectData); - - const result = await platformData.platformProjectService.validateOptions( - projectData.projectIdentifiers[platform.toLowerCase()], - provision, - teamId - ); - - return result; - } else { - let valid = true; - for (const availablePlatform in this.$platformsData.availablePlatforms) { - this.$logger.trace("Validate options for platform: " + availablePlatform); - const platformData = this.$platformsData.getPlatformData(availablePlatform, projectData); - valid = valid && await platformData.platformProjectService.validateOptions( - projectData.projectIdentifiers[availablePlatform.toLowerCase()], - provision, - teamId - ); - } - - return valid; - } - } - - private async initialPrepare(preparePlatformInfo: IPreparePlatformInfo) { - const { platform, appFilesUpdaterOptions, projectData, config, nativePrepare } = preparePlatformInfo; - this.validatePlatform(platform, projectData); - - // We need dev-dependencies here, so before-prepare hooks will be executed correctly. - try { - await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); - } catch (err) { - this.$logger.trace(err); - this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); - } - - await this.ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, nativePrepare); - } - public async shouldBuild(platform: string, projectData: IProjectData, buildConfig: IBuildConfig, outputPath?: string): Promise { if (buildConfig.release && this.$projectChangesService.currentChanges.hasChanges) { return true; @@ -487,20 +228,23 @@ export class PlatformService extends EventEmitter implements IPlatformService { } public async deployPlatform(deployInfo: IDeployPlatformInfo): Promise { - await this.preparePlatform({ - platform: deployInfo.platform, - appFilesUpdaterOptions: deployInfo.appFilesUpdaterOptions, - projectData: deployInfo.projectData, - config: deployInfo.config, - nativePrepare: deployInfo.nativePrepare, - env: deployInfo.env, - webpackCompilerConfig: { - watch: false, - env: deployInfo.env - } - }); + // TODO: Refactor deploy platform command + // await this.preparePlatform({ + // platform: deployInfo.platform, + // appFilesUpdaterOptions: deployInfo.appFilesUpdaterOptions, + // projectData: deployInfo.projectData, + // config: deployInfo.config, + // nativePrepare: deployInfo.nativePrepare, + // env: deployInfo.env, + // webpackCompilerConfig: { + // watch: false, + // env: deployInfo.env + // } + // }); const options: Mobile.IDevicesServicesInitializationOptions = { - platform: deployInfo.platform, deviceId: deployInfo.deployOptions.device, emulator: deployInfo.deployOptions.emulator + platform: deployInfo.platform, + deviceId: deployInfo.deployOptions.device, + emulator: deployInfo.deployOptions.emulator }; await this.$devicesService.initialize(options); const action = async (device: Mobile.IDevice): Promise => { @@ -606,16 +350,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { return null; } - @helpers.hook('cleanApp') - public async cleanDestinationApp(platformInfo: IPreparePlatformInfo): Promise { - await this.ensurePlatformInstalled(platformInfo.platform, platformInfo.projectData, platformInfo.config, platformInfo.appFilesUpdaterOptions, platformInfo.nativePrepare); - - const platformData = this.$platformsData.getPlatformData(platformInfo.platform, platformInfo.projectData); - const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - const appUpdater = new AppFilesUpdater(platformInfo.projectData.appDirectoryPath, appDestinationDirectoryPath, platformInfo.appFilesUpdaterOptions, this.$fs); - appUpdater.cleanDestinationApp(); - } - public lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData, outputPath?: string): string { let packageFile: string; const platformData = this.$platformsData.getPlatformData(platform, projectData); @@ -647,48 +381,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.info(`Copied file '${packageFile}' to '${targetPath}'.`); } - public async removePlatforms(platforms: string[], projectData: IProjectData): Promise { - for (const platform of platforms) { - this.validatePlatformInstalled(platform, projectData); - const platformData = this.$platformsData.getPlatformData(platform, projectData); - let errorMessage; - - try { - await platformData.platformProjectService.stopServices(platformData.projectRoot); - } catch (err) { - errorMessage = err.message; - } - - try { - const platformDir = path.join(projectData.platformsDir, platform.toLowerCase()); - this.$fs.deleteDirectory(platformDir); - this.$projectDataService.removeNSProperty(projectData.projectDir, platformData.frameworkPackageName); - - this.$logger.out(`Platform ${platform} successfully removed.`); - } catch (err) { - this.$logger.error(`Failed to remove ${platform} platform with errors:`); - if (errorMessage) { - this.$logger.error(errorMessage); - } - this.$errors.failWithoutHelp(err.message); - } - } - } - - public async updatePlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions): Promise { - for (const platformParam of platforms) { - const data = platformParam.split("@"), - platform = data[0], - version = data[1]; - - if (this.hasPlatformDirectory(platform, projectData)) { - await this.updatePlatform(platform, version, projectData, config); - } else { - await this.addPlatform(platformParam, projectData, config); - } - } - } - private getCanExecuteAction(platform: string, options: IDeviceEmulator): any { const canExecute = (currentDevice: Mobile.IDevice): boolean => { if (options.device && currentDevice && currentDevice.deviceInfo) { @@ -721,40 +413,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public validatePlatformInstalled(platform: string, projectData: IProjectData): void { - this.validatePlatform(platform, projectData); - - if (!this.hasPlatformDirectory(platform, projectData)) { - this.$errors.fail("The platform %s is not added to this project. Please use 'tns platform add '", platform); - } - } - - private async ensurePlatformInstalled(platform: string, projectData: IProjectData, config: IPlatformOptions, appFilesUpdaterOptions: IAppFilesUpdaterOptions, nativePrepare?: INativePrepare): Promise { - const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - const hasPlatformDirectory = this.hasPlatformDirectory(platform, projectData); - const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; - const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; - const shouldAddPlatform = !hasPlatformDirectory || (shouldAddNativePlatform && requiresNativePlatformAdd); - if (shouldAddPlatform) { - await this.addPlatform(platform, projectData, config, "", nativePrepare); - } - } - - private hasPlatformDirectory(platform: string, projectData: IProjectData): boolean { - return this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase())); - } - - public isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean { - const targetedOS = this.$platformsData.getPlatformData(platform, projectData).targetedOS; - const res = !targetedOS || targetedOS.indexOf("*") >= 0 || targetedOS.indexOf(process.platform) >= 0; - return res; - } - - private isPlatformPrepared(platform: string, projectData: IProjectData): boolean { - const platformData = this.$platformsData.getPlatformData(platform, projectData); - return platformData.platformProjectService.isPlatformPrepared(platformData.projectRoot, projectData); - } - private getApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[] { // Get latest package` that is produced from build let result = this.getApplicationPackagesCore(this.$fs.readDirectory(buildOutputPath).map(filename => path.join(buildOutputPath, filename)), validBuildOutputData.packageNames); @@ -821,46 +479,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { return this.getLatestApplicationPackage(outputPath || platformData.getBuildOutputPath(buildConfig), platformData.getValidBuildOutputData(buildOutputOptions)); } - private async updatePlatform(platform: string, version: string, projectData: IProjectData, config: IPlatformOptions): Promise { - const platformData = this.$platformsData.getPlatformData(platform, projectData); - - const data = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); - const currentVersion = data && data.version ? data.version : "0.2.0"; - - const installedModuleDir = temp.mkdirSync("runtime-to-update"); - let newVersion = version === constants.PackageVersion.NEXT ? - await this.$packageInstallationManager.getNextVersion(platformData.frameworkPackageName) : - version || await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); - await this.$pacoteService.extractPackage(`${platformData.frameworkPackageName}@${newVersion}`, installedModuleDir); - const cachedPackageData = this.$fs.readJson(path.join(installedModuleDir, "package.json")); - newVersion = (cachedPackageData && cachedPackageData.version) || newVersion; - - const canUpdate = platformData.platformProjectService.canUpdatePlatform(installedModuleDir, projectData); - if (canUpdate) { - if (!semver.valid(newVersion)) { - this.$errors.fail("The version %s is not valid. The version should consists from 3 parts separated by dot.", newVersion); - } - - if (!semver.gt(currentVersion, newVersion)) { - await this.updatePlatformCore(platformData, { currentVersion, newVersion, canUpdate }, projectData, config); - } else if (semver.eq(currentVersion, newVersion)) { - this.$errors.fail("Current and new version are the same."); - } else { - this.$errors.fail(`Your current version: ${currentVersion} is higher than the one you're trying to install ${newVersion}.`); - } - } else { - this.$errors.failWithoutHelp("Native Platform cannot be updated."); - } - } - - private async updatePlatformCore(platformData: IPlatformData, updateOptions: IUpdatePlatformOptions, projectData: IProjectData, config: IPlatformOptions): Promise { - let packageName = platformData.normalizedPlatformName.toLowerCase(); - await this.removePlatforms([packageName], projectData); - packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; - await this.addPlatform(packageName, projectData, config); - this.$logger.out("Successfully updated to version ", updateOptions.newVersion); - } - // TODO: Remove this method from here. It has nothing to do with platform public async readFile(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise { temp.track(); @@ -880,19 +498,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { return null; } - - private isPlatformAdded(platform: string, platformPath: string, projectData: IProjectData): boolean { - if (!this.$fs.exists(platformPath)) { - return false; - } - - const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - if (!prepareInfo) { - return true; - } - - return prepareInfo.nativePlatformStatus !== constants.NativePlatformStatus.requiresPlatformAdd; - } } $injector.register("platformService", PlatformService); diff --git a/lib/services/platform/platform-add-service.ts b/lib/services/platform/platform-add-service.ts new file mode 100644 index 0000000000..f51d8fa91a --- /dev/null +++ b/lib/services/platform/platform-add-service.ts @@ -0,0 +1,102 @@ +import * as path from "path"; +import * as temp from "temp"; +import { PROJECT_FRAMEWORK_FOLDER_NAME } from "../../constants"; + +export class PlatformAddService implements IPlatformAddService { + constructor( + private $errors: IErrors, + private $fs: IFileSystem, + private $logger: ILogger, + private $packageInstallationManager: IPackageInstallationManager, + private $pacoteService: IPacoteService, + private $platformsData: IPlatformsData, + private $platformJSService: IPreparePlatformService, + private $platformNativeService: IPreparePlatformService, + private $projectDataService: IProjectDataService, + private $terminalSpinnerService: ITerminalSpinnerService + ) { } + + public async addPlatform(addPlatformData: IAddPlatformData, projectData: IProjectData): Promise { + const { platformParam, frameworkPath, nativePrepare } = addPlatformData; + const [ platform, version ] = platformParam.toLowerCase().split("@"); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + + this.$logger.trace(`Creating NativeScript project for the ${platformData.platformNameLowerCase} platform`); + this.$logger.trace(`Path: ${platformData.projectRoot}`); + this.$logger.trace(`Package: ${projectData.projectIdentifiers[platformData.platformNameLowerCase]}`); + this.$logger.trace(`Name: ${projectData.projectName}`); + + this.$logger.out("Copying template files..."); + + const packageToInstall = await this.getPackageToInstall(platformData, projectData, frameworkPath, version); + + const installedPlatformVersion = await this.addPlatformSafe(platformData, projectData, packageToInstall, nativePrepare); + + this.$fs.ensureDirectoryExists(path.join(projectData.platformsDir, platform)); + this.$logger.out(`Platform ${platform} successfully added. v${installedPlatformVersion}`); + } + + private async getPackageToInstall(platformData: IPlatformData, projectData: IProjectData, frameworkPath?: string, version?: string): Promise { + let result = null; + if (frameworkPath) { + if (!this.$fs.exists(frameworkPath)) { + this.$errors.fail(`Invalid frameworkPath: ${frameworkPath}. Please ensure the specified frameworkPath exists.`); + } + result = path.resolve(frameworkPath); + } else { + if (!version) { + version = this.getCurrentPlatformVersion(platformData.platformNameLowerCase, projectData) || + await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); + } + + result = `${platformData.frameworkPackageName}@${version}`; + } + + return result; + } + + private async addPlatformSafe(platformData: IPlatformData, projectData: IProjectData, packageToInstall: string, nativePrepare: INativePrepare): Promise { + const spinner = this.$terminalSpinnerService.createSpinner(); + + try { + spinner.start(); + + const frameworkDirPath = await this.extractPackage(packageToInstall); + const frameworkPackageJsonContent = this.$fs.readJson(path.join(frameworkDirPath, "..", "package.json")); + const frameworkVersion = frameworkPackageJsonContent.version; + + await this.$platformJSService.addPlatform(platformData, projectData, frameworkDirPath, frameworkVersion); + + if (!nativePrepare || !nativePrepare.skipNativePrepare) { + await this.$platformNativeService.addPlatform(platformData, projectData, frameworkDirPath, frameworkVersion); + } + + return frameworkVersion; + } catch (err) { + const platformPath = path.join(projectData.platformsDir, platformData.platformNameLowerCase); + this.$fs.deleteDirectory(platformPath); + throw err; + } finally { + spinner.stop(); + } + } + + private async extractPackage(pkg: string): Promise { + temp.track(); + const downloadedPackagePath = temp.mkdirSync("runtimeDir"); + await this.$pacoteService.extractPackage(pkg, downloadedPackagePath); + const frameworkDir = path.join(downloadedPackagePath, PROJECT_FRAMEWORK_FOLDER_NAME); + + return path.resolve(frameworkDir); + } + + // TODO: There is the same method in platformService. Consider to reuse it + private getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); + const version = currentPlatformData && currentPlatformData.version; + + return version; + } +} +$injector.register("platformAddService", PlatformAddService); diff --git a/lib/services/platform/platform-build-service.ts b/lib/services/platform/platform-build-service.ts new file mode 100644 index 0000000000..e4f70aa6d0 --- /dev/null +++ b/lib/services/platform/platform-build-service.ts @@ -0,0 +1,74 @@ +import * as constants from "../../constants"; +import { Configurations } from "../../common/constants"; +import { EventEmitter } from "events"; +import { attachAwaitDetach } from "../../common/helpers"; +import * as path from "path"; + +const buildInfoFileName = ".nsbuildinfo"; + +export class PlatformBuildService extends EventEmitter implements IPlatformBuildService { + constructor( + private $analyticsService: IAnalyticsService, + private $buildArtefactsService: IBuildArtefactsService, + private $fs: IFileSystem, + private $logger: ILogger, + private $mobileHelper: Mobile.IMobileHelper, + private $projectChangesService: IProjectChangesService + ) { super(); } + + public async buildPlatform(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + this.$logger.out("Building project..."); + + const platform = platformData.platformNameLowerCase; + + const action = constants.TrackActionNames.Build; + const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildConfig && buildConfig.buildForDevice; + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action, + isForDevice, + platform, + projectDir: projectData.projectDir, + additionalData: `${buildConfig.release ? Configurations.Release : Configurations.Debug}_${buildConfig.clean ? constants.BuildStates.Clean : constants.BuildStates.Incremental}` + }); + + if (buildConfig.clean) { + await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); + } + + const handler = (data: any) => { + this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); + this.$logger.printInfoMessageOnSameLine(data.data.toString()); + }; + + await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildConfig)); + + const buildInfoFileDirname = platformData.getBuildOutputPath(buildConfig); + this.saveBuildInfoFile(platformData, projectData, buildInfoFileDirname); + + this.$logger.out("Project successfully built."); + + const result = await this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildConfig); + + // if (this.$options.copyTo) { + // this.$platformService.copyLastOutput(platform, this.$options.copyTo, buildConfig, this.$projectData); + // } else { + // this.$logger.info(`The build result is located at: ${outputPath}`); + // } + + return result; + } + + public saveBuildInfoFile(platformData: IPlatformData, projectData: IProjectData, buildInfoFileDirname: string): void { + const buildInfoFile = path.join(buildInfoFileDirname, buildInfoFileName); + + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData.platformNameLowerCase, projectData); + const buildInfo: IBuildInfo = { + prepareTime: prepareInfo.changesRequireBuildTime, + buildTime: new Date().toString() + }; + + this.$fs.writeJson(buildInfoFile, buildInfo); + } +} +$injector.register("platformBuildService", PlatformBuildService); diff --git a/lib/services/platform/platform-commands-service.ts b/lib/services/platform/platform-commands-service.ts new file mode 100644 index 0000000000..1c1a2e058c --- /dev/null +++ b/lib/services/platform/platform-commands-service.ts @@ -0,0 +1,178 @@ +import * as path from "path"; +import * as semver from "semver"; +import * as temp from "temp"; +import * as constants from "../../constants"; + +export class PlatformCommandsService implements IPlatformCommandsService { + constructor( + private $fs: IFileSystem, + private $errors: IErrors, + private $logger: ILogger, + private $packageInstallationManager: IPackageInstallationManager, + private $pacoteService: IPacoteService, + private $platformAddService: IPlatformAddService, + private $platformsData: IPlatformsData, + private $platformValidationService: IPlatformValidationService, + private $projectChangesService: IProjectChangesService, + private $projectDataService: IProjectDataService + ) { } + + public async addPlatforms(platforms: string[], projectData: IProjectData, frameworkPath: string): Promise { + const platformsDir = projectData.platformsDir; + this.$fs.ensureDirectoryExists(platformsDir); + + for (const platform of platforms) { + this.$platformValidationService.validatePlatform(platform, projectData); + const platformPath = path.join(projectData.platformsDir, platform); + + const isPlatformAdded = this.isPlatformAdded(platform, platformPath, projectData); + if (isPlatformAdded) { + this.$errors.failWithoutHelp(`Platform ${platform} already added`); + } + + const addPlatformData = { platformParam: platform.toLowerCase(), frameworkPath }; + await this.$platformAddService.addPlatform(addPlatformData, projectData); + } + } + + public async cleanPlatforms(platforms: string[], projectData: IProjectData, framworkPath: string): Promise { + for (const platform of platforms) { + await this.removePlatforms([platform], projectData); + + const version: string = this.getCurrentPlatformVersion(platform, projectData); + const platformParam = version ? `${platform}@${version}` : platform; + await this.addPlatforms([platformParam], projectData, framworkPath); + } + } + + public async removePlatforms(platforms: string[], projectData: IProjectData): Promise { + for (const platform of platforms) { + this.$platformValidationService.validatePlatformInstalled(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + let errorMessage; + + try { + await platformData.platformProjectService.stopServices(platformData.projectRoot); + } catch (err) { + errorMessage = err.message; + } + + try { + const platformDir = path.join(projectData.platformsDir, platform.toLowerCase()); + this.$fs.deleteDirectory(platformDir); + this.$projectDataService.removeNSProperty(projectData.projectDir, platformData.frameworkPackageName); + + this.$logger.out(`Platform ${platform} successfully removed.`); + } catch (err) { + this.$logger.error(`Failed to remove ${platform} platform with errors:`); + if (errorMessage) { + this.$logger.error(errorMessage); + } + this.$errors.failWithoutHelp(err.message); + } + } + } + + public async updatePlatforms(platforms: string[], projectData: IProjectData): Promise { + for (const platformParam of platforms) { + const data = platformParam.split("@"), + platform = data[0], + version = data[1]; + + const hasPlatformDirectory = this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase())); + if (hasPlatformDirectory) { + await this.updatePlatform(platform, version, projectData); + } else { + await this.$platformAddService.addPlatform({ platformParam }, projectData); + } + } + } + + public getInstalledPlatforms(projectData: IProjectData): string[] { + if (!this.$fs.exists(projectData.platformsDir)) { + return []; + } + + const subDirs = this.$fs.readDirectory(projectData.platformsDir); + return _.filter(subDirs, p => this.$platformsData.platformsNames.indexOf(p) > -1); + } + + public getAvailablePlatforms(projectData: IProjectData): string[] { + const installedPlatforms = this.getInstalledPlatforms(projectData); + return _.filter(this.$platformsData.platformsNames, p => { + return installedPlatforms.indexOf(p) < 0 && this.$platformValidationService.isPlatformSupportedForOS(p, projectData); // Only those not already installed + }); + } + + public getPreparedPlatforms(projectData: IProjectData): string[] { + return _.filter(this.$platformsData.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); + } + + private isPlatformAdded(platform: string, platformPath: string, projectData: IProjectData): boolean { + if (!this.$fs.exists(platformPath)) { + return false; + } + + const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + if (!prepareInfo) { + return true; + } + + return prepareInfo.nativePlatformStatus !== constants.NativePlatformStatus.requiresPlatformAdd; + } + + private async updatePlatform(platform: string, version: string, projectData: IProjectData): Promise { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + + const data = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); + const currentVersion = data && data.version ? data.version : "0.2.0"; + + const installedModuleDir = temp.mkdirSync("runtime-to-update"); + let newVersion = version === constants.PackageVersion.NEXT ? + await this.$packageInstallationManager.getNextVersion(platformData.frameworkPackageName) : + version || await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); + await this.$pacoteService.extractPackage(`${platformData.frameworkPackageName}@${newVersion}`, installedModuleDir); + const cachedPackageData = this.$fs.readJson(path.join(installedModuleDir, "package.json")); + newVersion = (cachedPackageData && cachedPackageData.version) || newVersion; + + const canUpdate = platformData.platformProjectService.canUpdatePlatform(installedModuleDir, projectData); + if (canUpdate) { + if (!semver.valid(newVersion)) { + this.$errors.fail("The version %s is not valid. The version should consists from 3 parts separated by dot.", newVersion); + } + + if (!semver.gt(currentVersion, newVersion)) { + await this.updatePlatformCore(platformData, { currentVersion, newVersion, canUpdate }, projectData); + } else if (semver.eq(currentVersion, newVersion)) { + this.$errors.fail("Current and new version are the same."); + } else { + this.$errors.fail(`Your current version: ${currentVersion} is higher than the one you're trying to install ${newVersion}.`); + } + } else { + this.$errors.failWithoutHelp("Native Platform cannot be updated."); + } + } + + private async updatePlatformCore(platformData: IPlatformData, updateOptions: IUpdatePlatformOptions, projectData: IProjectData): Promise { + let packageName = platformData.normalizedPlatformName.toLowerCase(); + await this.removePlatforms([packageName], projectData); + packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; + const addPlatformData = { platformParam: packageName }; + await this.$platformAddService.addPlatform(addPlatformData, projectData); + this.$logger.out("Successfully updated to version ", updateOptions.newVersion); + } + + private getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); + const version = currentPlatformData && currentPlatformData.version; + + return version; + } + + private isPlatformPrepared(platform: string, projectData: IProjectData): boolean { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + return platformData.platformProjectService.isPlatformPrepared(platformData.projectRoot, projectData); + } +} +$injector.register("platformCommandsService", PlatformCommandsService); diff --git a/lib/services/platform/platform-install-service.ts b/lib/services/platform/platform-install-service.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/services/platform/platform-validation-service.ts b/lib/services/platform/platform-validation-service.ts new file mode 100644 index 0000000000..05c100ecde --- /dev/null +++ b/lib/services/platform/platform-validation-service.ts @@ -0,0 +1,75 @@ +import * as helpers from "../../common/helpers"; +import * as path from "path"; + +export class PlatformValidationService implements IPlatformValidationService { + + constructor( + private $errors: IErrors, + private $fs: IFileSystem, + private $logger: ILogger, + private $mobileHelper: Mobile.IMobileHelper, + private $platformsData: IPlatformsData + ) { } + + public validatePlatform(platform: string, projectData: IProjectData): void { + if (!platform) { + this.$errors.fail("No platform specified."); + } + + platform = platform.split("@")[0].toLowerCase(); + + if (!this.$platformsData.getPlatformData(platform, projectData)) { + const platformNames = helpers.formatListOfNames(this.$platformsData.platformsNames); + this.$errors.fail(`Invalid platform ${platform}. Valid platforms are ${platformNames}.`); + } + } + + public validatePlatformInstalled(platform: string, projectData: IProjectData): void { + this.validatePlatform(platform, projectData); + + const hasPlatformDirectory = this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase())); + if (!hasPlatformDirectory) { + this.$errors.fail("The platform %s is not added to this project. Please use 'tns platform add '", platform); + } + } + + public async validateOptions(provision: true | string, teamId: true | string, projectData: IProjectData, platform?: string, aab?: boolean): Promise { + if (platform && !this.$mobileHelper.isAndroidPlatform(platform) && aab) { + this.$errors.failWithoutHelp("The --aab option is supported only for the Android platform."); + } + + if (platform) { + platform = this.$mobileHelper.normalizePlatformName(platform); + this.$logger.trace("Validate options for platform: " + platform); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + + const result = await platformData.platformProjectService.validateOptions( + projectData.projectIdentifiers[platform.toLowerCase()], + provision, + teamId + ); + + return result; + } else { + let valid = true; + for (const availablePlatform in this.$platformsData.availablePlatforms) { + this.$logger.trace("Validate options for platform: " + availablePlatform); + const platformData = this.$platformsData.getPlatformData(availablePlatform, projectData); + valid = valid && await platformData.platformProjectService.validateOptions( + projectData.projectIdentifiers[availablePlatform.toLowerCase()], + provision, + teamId + ); + } + + return valid; + } + } + + public isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean { + const targetedOS = this.$platformsData.getPlatformData(platform, projectData).targetedOS; + const res = !targetedOS || targetedOS.indexOf("*") >= 0 || targetedOS.indexOf(process.platform) >= 0; + return res; + } +} +$injector.register("platformValidationService", PlatformValidationService); diff --git a/lib/services/platform/platform-watcher-service.ts b/lib/services/platform/platform-watcher-service.ts new file mode 100644 index 0000000000..92757ad296 --- /dev/null +++ b/lib/services/platform/platform-watcher-service.ts @@ -0,0 +1,102 @@ +import { EventEmitter } from "events"; +import * as choki from "chokidar"; +import * as path from "path"; + +interface IPlatformWatcherData { + nativeWatcher: any; + webpackCompilerProcess: any; +} + +interface IFilesChangeData { + files: string[]; + hasNativeChange: boolean; +} + +export class PlatformWatcherService extends EventEmitter implements IPlatformWatcherService { + private watchersData: IDictionary> = {}; + private isInitialSyncEventEmitted = false; + private persistedFilesChangeData: IFilesChangeData[] = []; + + constructor( + private $logger: ILogger, + private $platformNativeService: IPreparePlatformService, + private $webpackCompilerService: IWebpackCompilerService + ) { super(); } + + public async startWatcher(platformData: IPlatformData, projectData: IProjectData, startWatcherData: IStartWatcherData): Promise { + const { webpackCompilerConfig, preparePlatformData } = startWatcherData; + + this.$logger.out("Starting watchers..."); + + if (!this.watchersData[projectData.projectDir]) { + this.watchersData[projectData.projectDir] = {}; + } + + if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase]) { + this.watchersData[projectData.projectDir][platformData.platformNameLowerCase] = { + nativeWatcher: null, + webpackCompilerProcess: null + }; + } + + await this.startJsWatcher(platformData, projectData, webpackCompilerConfig); // -> start watcher + initial compilation + await this.startNativeWatcher(platformData, projectData, preparePlatformData); // -> start watcher + initial prepare + + this.emitInitialSyncEvent(); + } + + private async startJsWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { + this.$webpackCompilerService.on("webpackEmittedFiles", files => { + this.emitFilesChangeEvent({ files, hasNativeChange: false }); + }); + + const childProcess = await this.$webpackCompilerService.startWatcher(platformData, projectData, config); + this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess = childProcess; + } + } + + private async startNativeWatcher(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { + if ((!preparePlatformData.nativePrepare || !preparePlatformData.nativePrepare.skipNativePrepare) && + !this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeWatcher) { + const patterns = [ + path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), + `node_modules/**/platforms/${platformData.platformNameLowerCase}/` + ]; + const watcherOptions: choki.WatchOptions = { + ignoreInitial: true, + cwd: projectData.projectDir, + awaitWriteFinish: { + pollInterval: 100, + stabilityThreshold: 500 + }, + ignored: ["**/.*", ".*"] // hidden files + }; + const watcher = choki.watch(patterns, watcherOptions) + .on("all", async (event: string, filePath: string) => { + filePath = path.join(projectData.projectDir, filePath); + this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); + this.emitFilesChangeEvent({ files: [], hasNativeChange: true }); + }); + + this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeWatcher = watcher; + + await this.$platformNativeService.preparePlatform(platformData, projectData, preparePlatformData); + } + } + + private emitFilesChangeEvent(filesChangeData: IFilesChangeData) { + if (this.isInitialSyncEventEmitted) { + this.emit("fileChangeData", filesChangeData); + } else { + this.persistedFilesChangeData.push(filesChangeData); + } + } + + private emitInitialSyncEvent() { + // TODO: Check the persisted data and add them in emitted event's data + this.emit("onInitialSync", ({})); + this.isInitialSyncEventEmitted = true; + } +} +$injector.register("platformWatcherService", PlatformWatcherService); diff --git a/lib/services/prepare-platform-js-service.ts b/lib/services/prepare-platform-js-service.ts index 9ca1aa728c..fee21c364c 100644 --- a/lib/services/prepare-platform-js-service.ts +++ b/lib/services/prepare-platform-js-service.ts @@ -2,27 +2,24 @@ import { hook } from "../common/helpers"; import { performanceLog } from "./../common/decorators"; import { EventEmitter } from "events"; -export class PreparePlatformJSService extends EventEmitter implements IPreparePlatformService { +export class PlatformJSService extends EventEmitter implements IPreparePlatformService { constructor( - private $webpackCompilerService: IWebpackCompilerService - ) { - super(); + private $projectDataService: IProjectDataService, + // private $webpackCompilerService: IWebpackCompilerService + ) { super(); } + + public async addPlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise { + const frameworkPackageNameData = { version: frameworkVersion }; + this.$projectDataService.setNSValue(projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData); } @performanceLog() @hook('prepareJSApp') - public async preparePlatform(config: IPreparePlatformJSInfo): Promise { + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { // intentionally left blank, keep the support for before-prepareJSApp and after-prepareJSApp hooks - } - - public async startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IPreparePlatformJSInfo): Promise { - this.$webpackCompilerService.on("webpackEmittedFiles", files => { - this.emit("jsFilesChanged", files); - }); - - await this.$webpackCompilerService.startWatcher(platformData, projectData, config); + return true; } } -$injector.register("preparePlatformJSService", PreparePlatformJSService); +$injector.register("platformJSService", PlatformJSService); diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/prepare-platform-native-service.ts index d0ea74e3f3..55b9552bf7 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/prepare-platform-native-service.ts @@ -1,123 +1,82 @@ import * as path from "path"; -import * as choki from "chokidar"; import * as constants from "../constants"; import { performanceLog } from "../common/decorators"; -import { EventEmitter } from "events"; - -export class PreparePlatformNativeService extends EventEmitter implements IPreparePlatformService { - private watchersInfo: IDictionary = {}; +export class PlatformNativeService implements IPreparePlatformService { constructor( private $fs: IFileSystem, - private $logger: ILogger, private $nodeModulesBuilder: INodeModulesBuilder, private $projectChangesService: IProjectChangesService, - private $androidResourcesMigrationService: IAndroidResourcesMigrationService) { - super(); - } + private $androidResourcesMigrationService: IAndroidResourcesMigrationService + ) { } @performanceLog() - public async addPlatform(info: IAddPlatformInfo): Promise { - const { platformData, projectData, frameworkDir, installedVersion, config } = info; + public async addPlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise { + const config = {}; const platformDir = path.join(projectData.platformsDir, platformData.normalizedPlatformName.toLowerCase()); this.$fs.deleteDirectory(platformDir); - await platformData.platformProjectService.createProject(path.resolve(frameworkDir), installedVersion, projectData, config); + await platformData.platformProjectService.createProject(path.resolve(frameworkDirPath), frameworkVersion, projectData, config); platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); - await info.platformData.platformProjectService.interpolateData(projectData, config); - info.platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); + await platformData.platformProjectService.interpolateData(projectData, config); + platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); this.$projectChangesService.setNativePlatformStatus(platformData.normalizedPlatformName, projectData, { nativePlatformStatus: constants.NativePlatformStatus.requiresPrepare }); } - public async startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IPreparePlatformJSInfo): Promise { - const watcherOptions: choki.WatchOptions = { - ignoreInitial: true, - cwd: projectData.projectDir, - awaitWriteFinish: { - pollInterval: 100, - stabilityThreshold: 500 - }, - ignored: ["**/.*", ".*"] // hidden files - }; - - await this.preparePlatform(config); - - // TODO: node_modules/**/platforms -> when no platform is provided, - // node_modules/**/platforms/ios -> when iOS platform is provided - // node_modules/**/platforms/android -> when Android is provided - const patterns = [projectData.getAppResourcesRelativeDirectoryPath(), "node_modules/**/platforms/"]; - - // TODO: Add stopWatcher function - const watcher = choki.watch(patterns, watcherOptions) - .on("all", async (event: string, filePath: string) => { - filePath = path.join(projectData.projectDir, filePath); - this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); - await this.preparePlatform(config); - }); - - this.watchersInfo[projectData.projectDir] = watcher; - } - - public stopWatchers(): void { - // TODO: stop the watchers here - } - @performanceLog() - public async preparePlatform(config: IPreparePlatformJSInfo): Promise { // TODO: should return 3 states for nativeFilesChanged, hasChanges, noChanges, skipChanges - const shouldAddNativePlatform = !config.nativePrepare || !config.nativePrepare.skipNativePrepare; - if (!shouldAddNativePlatform) { - this.emit("nativeFilesChanged", false); + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { + const { nativePrepare, release, useHotModuleReload, signingOptions } = preparePlatformData; + if (nativePrepare && nativePrepare.skipNativePrepare) { + return false; } - const hasModulesChange = !config.changesInfo || config.changesInfo.modulesChanged; - const hasConfigChange = !config.changesInfo || config.changesInfo.configChanged; - const hasChangesRequirePrepare = !config.changesInfo || config.changesInfo.changesRequirePrepare; + const nativePlatformStatus = (nativePrepare && nativePrepare.skipNativePrepare) ? constants.NativePlatformStatus.requiresPlatformAdd : constants.NativePlatformStatus.requiresPrepare; + const changesInfo = await this.$projectChangesService.checkForChanges({ + platform: platformData.platformNameLowerCase, + projectData, + projectChangesOptions: { + signingOptions, + release, + nativePlatformStatus, + useHotModuleReload + } + }); + + const hasModulesChange = !changesInfo || changesInfo.modulesChanged; + const hasConfigChange = !changesInfo || changesInfo.configChanged; + const hasChangesRequirePrepare = !changesInfo || changesInfo.changesRequirePrepare; const hasChanges = hasModulesChange || hasConfigChange || hasChangesRequirePrepare; - if (config.changesInfo.hasChanges) { - await this.cleanProject(config.platform, config.appFilesUpdaterOptions, config.platformData, config.projectData); + if (changesInfo.hasChanges) { + await this.cleanProject(platformData, projectData, { release }); } // Move the native application resources from platforms/.../app/App_Resources // to the right places in the native project, // because webpack copies them on every build (not every change). - this.prepareAppResources(config.platformData, config.projectData); + this.prepareAppResources(platformData, projectData); if (hasChangesRequirePrepare) { - await config.platformData.platformProjectService.prepareProject(config.projectData, config.platformSpecificData); + await platformData.platformProjectService.prepareProject(projectData, signingOptions); } if (hasModulesChange) { - const appDestinationDirectoryPath = path.join(config.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - const lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; - - const tnsModulesDestinationPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); - const nodeModulesData: INodeModulesData = { - absoluteOutputPath: tnsModulesDestinationPath, - appFilesUpdaterOptions: config.appFilesUpdaterOptions, - lastModifiedTime, - platform: config.platform, - projectData: config.projectData, - projectFilesConfig: config.projectFilesConfig - }; - - // Process node_modules folder - await this.$nodeModulesBuilder.prepareNodeModules({ nodeModulesData, release: config.appFilesUpdaterOptions.release }); + await this.$nodeModulesBuilder.prepareNodeModules(platformData, projectData); } if (hasModulesChange || hasConfigChange) { - await config.platformData.platformProjectService.processConfigurationFilesFromAppResources(config.projectData, { release: config.appFilesUpdaterOptions.release }); - await config.platformData.platformProjectService.handleNativeDependenciesChange(config.projectData, { release: config.appFilesUpdaterOptions.release }); + await platformData.platformProjectService.processConfigurationFilesFromAppResources(projectData, { release }); + await platformData.platformProjectService.handleNativeDependenciesChange(projectData, { release }); } - config.platformData.platformProjectService.interpolateConfigurationFile(config.projectData, config.platformSpecificData); - this.$projectChangesService.setNativePlatformStatus(config.platform, config.projectData, + platformData.platformProjectService.interpolateConfigurationFile(projectData, signingOptions); + this.$projectChangesService.setNativePlatformStatus(platformData.platformNameLowerCase, projectData, { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - this.emit("nativeFilesChanged", hasChanges); + return hasChanges; } private prepareAppResources(platformData: IPlatformData, projectData: IProjectData): void { @@ -157,24 +116,24 @@ export class PreparePlatformNativeService extends EventEmitter implements IPrepa } } - private async cleanProject(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, platformData: IPlatformData, projectData: IProjectData): Promise { + private async cleanProject(platformData: IPlatformData, projectData: IProjectData, options: { release: boolean }): Promise { // android build artifacts need to be cleaned up // when switching between debug, release and webpack builds - if (platform.toLowerCase() !== "android") { + if (platformData.platformNameLowerCase !== "android") { return; } - const previousPrepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + const previousPrepareInfo = this.$projectChangesService.getPrepareInfo(platformData.platformNameLowerCase, projectData); if (!previousPrepareInfo || previousPrepareInfo.nativePlatformStatus !== constants.NativePlatformStatus.alreadyPrepared) { return; } - const { release: previousWasRelease, bundle: previousWasBundle } = previousPrepareInfo; - const { release: currentIsRelease, bundle: currentIsBundle } = appFilesUpdaterOptions; - if ((previousWasRelease !== currentIsRelease) || (previousWasBundle !== currentIsBundle)) { + const { release: previousWasRelease } = previousPrepareInfo; + const { release: currentIsRelease } = options; + if (previousWasRelease !== currentIsRelease) { await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); } } } -$injector.register("preparePlatformNativeService", PreparePlatformNativeService); +$injector.register("platformNativeService", PlatformNativeService); diff --git a/lib/services/prepare-platform-service.ts b/lib/services/prepare-platform-service.ts deleted file mode 100644 index 95e66f609d..0000000000 --- a/lib/services/prepare-platform-service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as constants from "../constants"; -import * as path from "path"; -import { AppFilesUpdater } from "./app-files-updater"; -import { EventEmitter } from "events"; - -export class PreparePlatformService extends EventEmitter { - constructor(protected $fs: IFileSystem, - public $hooksService: IHooksService, - private $xmlValidator: IXmlValidator) { - super(); - } - - protected async copyAppFiles(copyAppFilesData: ICopyAppFilesData): Promise { - copyAppFilesData.platformData.platformProjectService.ensureConfigurationFileInAppResources(copyAppFilesData.projectData); - const appDestinationDirectoryPath = path.join(copyAppFilesData.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - - // Copy app folder to native project - this.$fs.ensureDirectoryExists(appDestinationDirectoryPath); - - const appUpdater = new AppFilesUpdater(copyAppFilesData.projectData.appDirectoryPath, appDestinationDirectoryPath, copyAppFilesData.appFilesUpdaterOptions, this.$fs); - const appUpdaterOptions: IUpdateAppOptions = { - beforeCopyAction: sourceFiles => { - this.$xmlValidator.validateXmlFiles(sourceFiles); - }, - filesToRemove: copyAppFilesData.filesToRemove - }; - // TODO: consider passing filesToSync in appUpdaterOptions - // this would currently lead to the following problem: imagine changing two files rapidly one after the other (transpilation for example) - // the first file would trigger the whole LiveSync process and the second will be queued - // after the first LiveSync is done the .nsprepare file is written and the second file is later on wrongly assumed as having been prepared - // because .nsprepare was written after both file changes - appUpdater.updateApp(appUpdaterOptions, copyAppFilesData.projectData); - } -} diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 504c7e14bb..f9a71266f9 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -63,19 +63,18 @@ export class ProjectChangesService implements IProjectChangesService { if (!isNewPrepareInfo) { this._newFiles = 0; - this._changesInfo.packageChanged = this.isProjectFileChanged(projectData, platform); const platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName); this._changesInfo.appResourcesChanged = this.containsNewerFiles(platformResourcesDir, null, projectData); /*done because currently all node_modules are traversed, a possible improvement could be traversing only the production dependencies*/ - this._changesInfo.nativeChanged = projectChangesOptions.skipModulesNativeCheck ? false : this.containsNewerFiles( + this._changesInfo.nativeChanged = this.containsNewerFiles( path.join(projectData.projectDir, NODE_MODULES_FOLDER_NAME), path.join(projectData.projectDir, NODE_MODULES_FOLDER_NAME, "tns-ios-inspector"), projectData, this.fileChangeRequiresBuild); - this.$logger.trace(`Set nativeChanged to ${this._changesInfo.nativeChanged}. skipModulesNativeCheck is: ${projectChangesOptions.skipModulesNativeCheck}`); + this.$logger.trace(`Set nativeChanged to ${this._changesInfo.nativeChanged}.`); if (this._newFiles > 0 || this._changesInfo.nativeChanged) { this.$logger.trace(`Setting modulesChanged to true, newFiles: ${this._newFiles}, nativeChanged: ${this._changesInfo.nativeChanged}`); @@ -99,16 +98,15 @@ export class ProjectChangesService implements IProjectChangesService { if (checkForChangesOpts.projectChangesOptions.nativePlatformStatus !== NativePlatformStatus.requiresPlatformAdd) { const projectService = platformData.platformProjectService; - await projectService.checkForChanges(this._changesInfo, projectChangesOptions, projectData); + await projectService.checkForChanges(this._changesInfo, projectChangesOptions.signingOptions, projectData); } - if (projectChangesOptions.bundle !== this._prepareInfo.bundle || projectChangesOptions.release !== this._prepareInfo.release) { + if (projectChangesOptions.release !== this._prepareInfo.release) { this.$logger.trace(`Setting all setting to true. Current options are: `, projectChangesOptions, " old prepare info is: ", this._prepareInfo); this._changesInfo.appResourcesChanged = true; this._changesInfo.modulesChanged = true; this._changesInfo.configChanged = true; this._prepareInfo.release = projectChangesOptions.release; - this._prepareInfo.bundle = projectChangesOptions.bundle; } if (this._changesInfo.packageChanged) { this.$logger.trace("Set modulesChanged to true as packageChanged is true"); @@ -188,7 +186,6 @@ export class ProjectChangesService implements IProjectChangesService { this._prepareInfo = { time: "", nativePlatformStatus: projectChangesOptions.nativePlatformStatus, - bundle: projectChangesOptions.bundle, release: projectChangesOptions.release, changesRequireBuild: true, projectFileHash: this.getProjectFileStrippedHash(projectData, platform), diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 2a7a9d7689..71412b4979 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -114,10 +114,10 @@ export class TestExecutionService implements ITestExecutionService { const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, - watchAllFiles: this.$options.syncAllFiles, - bundle: !!this.$options.bundle, release: this.$options.release, - env, + webpackCompilerConfig: { + env, + }, timeout: this.$options.timeout, useHotModuleReload: this.$options.hmr }; diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 8718103c2e..e465bdd769 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -1,47 +1,54 @@ import * as path from "path"; +import * as child_process from "child_process"; import { EventEmitter } from "events"; export class WebpackCompilerService extends EventEmitter implements IWebpackCompilerService { constructor( private $childProcess: IChildProcess - ) { - super(); - } + ) { super(); } // TODO: Consider to introduce two methods -> compile and startWebpackWatcher - public async startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { - const args = [ - path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), - "--preserve-symlinks", - `--config=${path.join(projectData.projectDir, "webpack.config.js")}`, - `--env.${platformData.normalizedPlatformName.toLowerCase()}` - ]; - - if (config.watch) { - args.push("--watch"); - } - - // TODO: provide env variables + // TODO: persist webpack per platform and persist watchers per projectDir + public async startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + return new Promise((resolve, reject) => { + let isFirstWebpackWatchCompilation = true; + const childProcess = this.startWebpackProcess(platformData, projectData, config); - const stdio = config.watch ? ["inherit", "inherit", "inherit", "ipc"] : "inherit"; + childProcess.on("message", (message: any) => { + if (message === "Webpack compilation complete.") { + resolve(childProcess); + } - return new Promise((resolve, reject) => { - const childProcess = this.$childProcess.spawn("node", args, { cwd: projectData.projectDir, stdio }); - if (config.watch) { - childProcess.on("message", (message: any) => { - if (message === "Webpack compilation complete.") { - resolve(); + if (message.emittedFiles) { + if (isFirstWebpackWatchCompilation) { + isFirstWebpackWatchCompilation = false; + return; } - if (message.emittedFiles) { - const files = message.emittedFiles - .filter((file: string) => file.indexOf("App_Resources") === -1) - .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)) - this.emit("webpackEmittedFiles", files); - } - }); - } + const files = message.emittedFiles + .filter((file: string) => file.indexOf("App_Resources") === -1) + .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)); + this.emit("webpackEmittedFiles", files); + } + }); + childProcess.on("close", (arg: any) => { + const exitCode = typeof arg === "number" ? arg : arg && arg.code; + console.log("=========== WEBPACK EXIT WITH CODE ========== ", exitCode); + if (exitCode === 0) { + resolve(childProcess); + } else { + const error = new Error(`Executing webpack failed with exit code ${exitCode}.`); + error.code = exitCode; + reject(error); + } + }); + }); + } + + public async compile(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + const childProcess = this.startWebpackProcess(platformData, projectData, config); + return new Promise((resolve, reject) => { childProcess.on("close", (arg: any) => { const exitCode = typeof arg === "number" ? arg : arg && arg.code; console.log("=========== WEBPACK EXIT WITH CODE ========== ", exitCode); @@ -55,5 +62,25 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp }); }); } + + private startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): child_process.ChildProcess { + const args = [ + path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), + "--preserve-symlinks", + `--config=${path.join(projectData.projectDir, "webpack.config.js")}`, + `--env.${platformData.normalizedPlatformName.toLowerCase()}` + ]; + + if (config.watch) { + args.push("--watch"); + } + + // TODO: provide env variables + + const stdio = config.watch ? ["inherit", "inherit", "inherit", "ipc"] : "inherit"; + const childProcess = this.$childProcess.spawn("node", args, { cwd: projectData.projectDir, stdio }); + + return childProcess; + } } $injector.register("webpackCompilerService", WebpackCompilerService); diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index 047d32e14a..01b889611b 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -2,21 +2,49 @@ import { EventEmitter } from "events"; declare global { interface IWebpackCompilerService extends EventEmitter { - startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; + startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; + compile(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; } interface IWebpackCompilerConfig { - watch: boolean; env: IWebpackEnvOptions; + watch?: boolean; } interface IWebpackEnvOptions { } - interface IPreparePlatformService extends EventEmitter { - addPlatform?(info: IAddPlatformInfo): Promise; - preparePlatform(config: IPreparePlatformJSInfo): Promise; - startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IPreparePlatformJSInfo | IWebpackCompilerConfig): Promise; + interface IPreparePlatformService { + addPlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise; + preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise; + } + + interface IPreparePlatformData extends IRelease, IHasUseHotModuleReloadOption { + signingOptions?: IiOSSigningOptions | IAndroidSigningOptions; + nativePrepare?: INativePrepare; + env?: any; + frameworkPath?: string; + } + + interface IiOSSigningOptions extends ITeamIdentifier, IProvision { + mobileProvisionData?: any; + } + + interface IAndroidSigningOptions { + keyStoreAlias: string; + keyStorePath: string; + keyStoreAliasPassword: string; + keyStorePassword: string; + sdk?: string; + } + + interface IPlatformWatcherService extends EventEmitter { + startWatcher(platformData: IPlatformData, projectData: IProjectData, startWatcherData: IStartWatcherData): Promise; + } + + interface IStartWatcherData { + webpackCompilerConfig: IWebpackCompilerConfig; + preparePlatformData: IPreparePlatformData; } } \ No newline at end of file diff --git a/lib/services/workflow/platform-workflow-service.ts b/lib/services/workflow/platform-workflow-service.ts new file mode 100644 index 0000000000..8692ba70a7 --- /dev/null +++ b/lib/services/workflow/platform-workflow-service.ts @@ -0,0 +1,50 @@ +import * as path from "path"; +import { NativePlatformStatus } from "../../constants"; + +export class PlatformWorkflowService implements IPlatformWorkflowService { + constructor ( + private $fs: IFileSystem, + private $platformAddService: IPlatformAddService, + private $platformBuildService: IPlatformBuildService, + private $platformService: IPreparePlatformService, + private $projectChangesService: IProjectChangesService + ) { } + + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise { + await this.addPlatformIfNeeded(platformData, projectData, workflowData); + await this.$platformService.preparePlatform(platformData, projectData, workflowData); + } + + public async buildPlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData, buildConfig: IBuildConfig): Promise { + await this.preparePlatform(platformData, projectData, workflowData); + const result = await this.$platformBuildService.buildPlatform(platformData, projectData, buildConfig); + + return result; + } + + public async runPlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData) { + return; + // await this.buildPlatformIfNeeded() + } + + private async addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise { + const { platformParam, frameworkPath, nativePrepare } = workflowData; + + const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, nativePrepare); + if (shouldAddPlatform) { + await this.$platformAddService.addPlatform({ platformParam, frameworkPath, nativePrepare }, projectData); + } + } + + private shouldAddPlatform(platformData: IPlatformData, projectData: IProjectData, nativePrepare: INativePrepare): boolean { + const platformName = platformData.platformNameLowerCase; + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformName, projectData); + const hasPlatformDirectory = this.$fs.exists(path.join(projectData.platformsDir, platformName)); + const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; + const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === NativePlatformStatus.requiresPlatformAdd; + const result = !hasPlatformDirectory || (shouldAddNativePlatform && requiresNativePlatformAdd); + + return !!result; + } +} +$injector.register("platformWorkflowService", PlatformWorkflowService); diff --git a/lib/services/workflow/workflow.d.ts b/lib/services/workflow/workflow.d.ts new file mode 100644 index 0000000000..e6751a11c7 --- /dev/null +++ b/lib/services/workflow/workflow.d.ts @@ -0,0 +1,16 @@ +interface IPlatformWorkflowData extends IRelease, IHasUseHotModuleReloadOption { + platformParam: string; + frameworkPath?: string; + nativePrepare?: INativePrepare; + env?: any; + signingOptions: IiOSSigningOptions | IAndroidSigningOptions; +} + +interface IPlatformWorkflowService { + preparePlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise; + buildPlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData, buildConfig: IBuildConfig): Promise; +} + +interface IPlatformWorkflowDataFactory { + createPlatformWorkflowData(platformParam: string, options: IOptions, nativePrepare?: INativePrepare): IPlatformWorkflowData; +} \ No newline at end of file diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index c9af8361fd..0d1143dfa8 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -1,15 +1,25 @@ -import { NpmPluginPrepare } from "./node-modules-dest-copy"; - export class NodeModulesBuilder implements INodeModulesBuilder { constructor( - private $injector: IInjector, - private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder + private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, + private $pluginsService: IPluginsService ) { } - public async prepareNodeModules(opts: INodeModulesBuilderData): Promise { - const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(opts.nodeModulesData.projectData.projectDir); - const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); - await npmPluginPrepare.preparePlugins(productionDependencies, opts.nodeModulesData.platform, opts.nodeModulesData.projectData); + public async prepareNodeModules(platformData: IPlatformData, projectData: IProjectData): Promise { + const dependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); + if (_.isEmpty(dependencies)) { + return; + } + + await platformData.platformProjectService.beforePrepareAllPlugins(projectData, dependencies); + + for (const dependencyKey in dependencies) { + const dependency = dependencies[dependencyKey]; + const isPlugin = !!dependency.nativescript; + if (isPlugin) { + const pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); + await this.$pluginsService.preparePluginNativeCode(pluginData, platformData.normalizedPlatformName.toLowerCase(), projectData); + } + } } } diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts deleted file mode 100644 index 45af036501..0000000000 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ /dev/null @@ -1,23 +0,0 @@ -export class NpmPluginPrepare { - constructor( - private $pluginsService: IPluginsService, - private $platformsData: IPlatformsData, - ) { } - - public async preparePlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { - if (_.isEmpty(dependencies)) { - return; - } - - await this.$platformsData.getPlatformData(platform, projectData).platformProjectService.beforePrepareAllPlugins(projectData, dependencies); - - for (const dependencyKey in dependencies) { - const dependency = dependencies[dependencyKey]; - const isPlugin = !!dependency.nativescript; - if (isPlugin) { - const pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); - await this.$pluginsService.preparePluginNativeCode(pluginData, platform, projectData); - } - } - } -} diff --git a/test/app-files-updates.ts b/test/app-files-updates.ts deleted file mode 100644 index 7ac8f94b39..0000000000 --- a/test/app-files-updates.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { assert } from "chai"; -import { AppFilesUpdater } from "../lib/services/app-files-updater"; -import * as yok from "../lib/common/yok"; - -require("should"); - -function createTestInjector(): IInjector { - const testInjector = new yok.Yok(); - - testInjector.register("projectData", { appResourcesDirectoryPath: "App_Resources" }); - - return testInjector; -} - -describe("App files cleanup", () => { - class CleanUpAppFilesUpdater extends AppFilesUpdater { - public deletedDestinationItems: string[] = []; - - constructor( - public destinationFiles: string[], - options: any - ) { - super("", "", options, null); - } - - public clean() { - this.cleanDestinationApp(); - } - - protected readDestinationDir(): string[] { - return this.destinationFiles; - } - - protected deleteDestinationItem(directoryItem: string): void { - this.deletedDestinationItems.push(directoryItem); - } - } - - _.each([true, false], bundle => { - it(`cleans up entire app when bundle is ${bundle}`, () => { - const updater = new CleanUpAppFilesUpdater([ - "file1", "dir1/file2", "App_Resources/Android/blah.png" - ], { bundle }); - updater.clean(); - assert.deepEqual(["file1", "dir1/file2", "App_Resources/Android/blah.png"], updater.deletedDestinationItems); - }); - }); -}); - -describe("App files copy", () => { - class CopyAppFilesUpdater extends AppFilesUpdater { - public copiedDestinationItems: string[] = []; - - constructor( - public sourceFiles: string[], - options: any - ) { - super("", "", options, null); - } - - protected readSourceDir(): string[] { - return this.sourceFiles; - } - - public copy(): void { - const injector = createTestInjector(); - const projectData = injector.resolve("projectData"); - this.copiedDestinationItems = this.resolveAppSourceFiles(projectData); - } - } - - it("copies all app files but app_resources when not bundling", () => { - const updater = new CopyAppFilesUpdater([ - "file1", "dir1/file2" - ], { bundle: false }); - updater.copy(); - assert.deepEqual(["file1", "dir1/file2"], updater.copiedDestinationItems); - }); - - it("skips copying files when bundling", () => { - const updater = new CopyAppFilesUpdater([ - "file1", "dir1/file2", "App_Resources/Android/blah.png" - ], { bundle: true }); - updater.copy(); - assert.deepEqual([], updater.copiedDestinationItems); - }); -}); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index be0b2b1fb7..f443b533a6 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -28,7 +28,6 @@ import { NodePackageManager } from "../lib/node-package-manager"; import { YarnPackageManager } from "../lib/yarn-package-manager"; import { assert } from "chai"; -import { IOSProvisionService } from "../lib/services/ios-provision-service"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; import { BUILD_XCCONFIG_FILE_NAME } from "../lib/constants"; import { ProjectDataStub } from "./stubs"; @@ -711,161 +710,157 @@ describe("Relative paths", () => { }); }); -describe("iOS Project Service Signing", () => { - let testInjector: IInjector; - let projectName: string; - let projectDirName: string; - let projectPath: string; - let files: any; - let iOSProjectService: IPlatformProjectService; - let projectData: any; - let pbxproj: string; - let iOSProvisionService: IOSProvisionService; - let pbxprojDomXcode: IPbxprojDomXcode; - - beforeEach(() => { - files = {}; - projectName = "TNSApp" + Math.ceil(Math.random() * 1000); - projectDirName = projectName + "Dir"; - projectPath = temp.mkdirSync(projectDirName); - testInjector = createTestInjector(projectPath, projectDirName); - testInjector.register("fs", { - files: {}, - readJson(path: string): any { - if (this.exists(path)) { - return JSON.stringify(files[path]); - } else { - return null; - } - }, - exists(path: string): boolean { - return path in files; - } - }); - testInjector.register("pbxprojDomXcode", { Xcode: {} }); - pbxproj = join(projectPath, `platforms/ios/${projectDirName}.xcodeproj/project.pbxproj`); - iOSProjectService = testInjector.resolve("iOSProjectService"); - iOSProvisionService = testInjector.resolve("iOSProvisionService"); - pbxprojDomXcode = testInjector.resolve("pbxprojDomXcode"); - projectData = testInjector.resolve("projectData"); - iOSProvisionService.pick = async (uuidOrName: string, projId: string) => { - return ({ - "NativeScriptDev": { - Name: "NativeScriptDev", - CreationDate: null, - ExpirationDate: null, - TeamName: "Telerik AD", - TeamIdentifier: ["TKID101"], - ProvisionedDevices: [], - Entitlements: { - "application-identifier": "*", - "com.apple.developer.team-identifier": "ABC" - }, - UUID: "12345", - ProvisionsAllDevices: false, - ApplicationIdentifierPrefix: null, - DeveloperCertificates: null, - Type: "Development" - }, - "NativeScriptDist": { - Name: "NativeScriptDist", - CreationDate: null, - ExpirationDate: null, - TeamName: "Telerik AD", - TeamIdentifier: ["TKID202"], - ProvisionedDevices: [], - Entitlements: { - "application-identifier": "*", - "com.apple.developer.team-identifier": "ABC" - }, - UUID: "6789", - ProvisionsAllDevices: true, - ApplicationIdentifierPrefix: null, - DeveloperCertificates: null, - Type: "Distribution" - }, - "NativeScriptAdHoc": { - Name: "NativeScriptAdHoc", - CreationDate: null, - ExpirationDate: null, - TeamName: "Telerik AD", - TeamIdentifier: ["TKID303"], - ProvisionedDevices: [], - Entitlements: { - "application-identifier": "*", - "com.apple.developer.team-identifier": "ABC" - }, - UUID: "1010", - ProvisionsAllDevices: true, - ApplicationIdentifierPrefix: null, - DeveloperCertificates: null, - Type: "Distribution" - } - })[uuidOrName]; - }; - }); - - describe("Check for Changes", () => { - it("sets signingChanged if no Xcode project exists", async () => { - const changes = {}; - await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined, useHotModuleReload: false }, projectData); - assert.isTrue(!!changes.signingChanged); - }); - it("sets signingChanged if the Xcode projects is configured with Automatic signing, but proivsion is specified", async () => { - files[pbxproj] = ""; - pbxprojDomXcode.Xcode.open = function (path: string) { - assert.equal(path, pbxproj); - return { - getSigning(x: string) { - return { style: "Automatic" }; - } - }; - }; - const changes = {}; - await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined, useHotModuleReload: false }, projectData); - assert.isTrue(!!changes.signingChanged); - }); - it("sets signingChanged if the Xcode projects is configured with Manual signing, but the proivsion specified differs the selected in the pbxproj", async () => { - files[pbxproj] = ""; - pbxprojDomXcode.Xcode.open = function (path: string) { - assert.equal(path, pbxproj); - return { - getSigning() { - return { - style: "Manual", configurations: { - Debug: { name: "NativeScriptDev2" }, - Release: { name: "NativeScriptDev2" } - } - }; - } - }; - }; - const changes = {}; - await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined, useHotModuleReload: false }, projectData); - assert.isTrue(!!changes.signingChanged); - }); - it("does not set signingChanged if the Xcode projects is configured with Manual signing and proivsion matches", async () => { - files[pbxproj] = ""; - pbxprojDomXcode.Xcode.open = function (path: string) { - assert.equal(path, pbxproj); - return { - getSigning() { - return { - style: "Manual", configurations: { - Debug: { name: "NativeScriptDev" }, - Release: { name: "NativeScriptDev" } - } - }; - } - }; - }; - const changes = {}; - await iOSProjectService.checkForChanges(changes, { bundle: false, release: false, provision: "NativeScriptDev", teamId: undefined, useHotModuleReload: false }, projectData); - console.log("CHANGES !!!! ", changes); - assert.isFalse(!!changes.signingChanged); - }); - }); -}); +// describe("iOS Project Service Signing", () => { +// let testInjector: IInjector; +// let projectName: string; +// let projectDirName: string; +// let projectPath: string; +// let files: any; +// let iOSProvisionService: IOSProvisionService; + +// // beforeEach(() => { +// // files = {}; +// // projectName = "TNSApp" + Math.ceil(Math.random() * 1000); +// // projectDirName = projectName + "Dir"; +// // projectPath = temp.mkdirSync(projectDirName); +// // testInjector = createTestInjector(projectPath, projectDirName); +// // testInjector.register("fs", { +// // files: {}, +// // readJson(path: string): any { +// // if (this.exists(path)) { +// // return JSON.stringify(files[path]); +// // } else { +// // return null; +// // } +// // }, +// // exists(path: string): boolean { +// // return path in files; +// // } +// // }); +// // testInjector.register("pbxprojDomXcode", { Xcode: {} }); +// // pbxproj = join(projectPath, `platforms/ios/${projectDirName}.xcodeproj/project.pbxproj`); +// // iOSProjectService = testInjector.resolve("iOSProjectService"); +// // iOSProvisionService = testInjector.resolve("iOSProvisionService"); +// // pbxprojDomXcode = testInjector.resolve("pbxprojDomXcode"); +// // projectData = testInjector.resolve("projectData"); +// // iOSProvisionService.pick = async (uuidOrName: string, projId: string) => { +// // return ({ +// // "NativeScriptDev": { +// // Name: "NativeScriptDev", +// // CreationDate: null, +// // ExpirationDate: null, +// // TeamName: "Telerik AD", +// // TeamIdentifier: ["TKID101"], +// // ProvisionedDevices: [], +// // Entitlements: { +// // "application-identifier": "*", +// // "com.apple.developer.team-identifier": "ABC" +// // }, +// // UUID: "12345", +// // ProvisionsAllDevices: false, +// // ApplicationIdentifierPrefix: null, +// // DeveloperCertificates: null, +// // Type: "Development" +// // }, +// // "NativeScriptDist": { +// // Name: "NativeScriptDist", +// // CreationDate: null, +// // ExpirationDate: null, +// // TeamName: "Telerik AD", +// // TeamIdentifier: ["TKID202"], +// // ProvisionedDevices: [], +// // Entitlements: { +// // "application-identifier": "*", +// // "com.apple.developer.team-identifier": "ABC" +// // }, +// // UUID: "6789", +// // ProvisionsAllDevices: true, +// // ApplicationIdentifierPrefix: null, +// // DeveloperCertificates: null, +// // Type: "Distribution" +// // }, +// // "NativeScriptAdHoc": { +// // Name: "NativeScriptAdHoc", +// // CreationDate: null, +// // ExpirationDate: null, +// // TeamName: "Telerik AD", +// // TeamIdentifier: ["TKID303"], +// // ProvisionedDevices: [], +// // Entitlements: { +// // "application-identifier": "*", +// // "com.apple.developer.team-identifier": "ABC" +// // }, +// // UUID: "1010", +// // ProvisionsAllDevices: true, +// // ApplicationIdentifierPrefix: null, +// // DeveloperCertificates: null, +// // Type: "Distribution" +// // } +// // })[uuidOrName]; +// // }; +// // }); + +// // describe("Check for Changes", () => { +// // it("sets signingChanged if no Xcode project exists", async () => { +// // const changes = {}; +// // await iOSProjectService.checkForChanges(changes, { release: false, provision: "NativeScriptDev", teamId: undefined, useHotModuleReload: false }, projectData); +// // assert.isTrue(!!changes.signingChanged); +// // }); +// // it("sets signingChanged if the Xcode projects is configured with Automatic signing, but proivsion is specified", async () => { +// // files[pbxproj] = ""; +// // pbxprojDomXcode.Xcode.open = function (path: string) { +// // assert.equal(path, pbxproj); +// // return { +// // getSigning(x: string) { +// // return { style: "Automatic" }; +// // } +// // }; +// // }; +// // const changes = {}; +// // await iOSProjectService.checkForChanges(changes, { release: false, provision: "NativeScriptDev", teamId: undefined, useHotModuleReload: false }, projectData); +// // assert.isTrue(!!changes.signingChanged); +// // }); +// // it("sets signingChanged if the Xcode projects is configured with Manual signing, but the proivsion specified differs the selected in the pbxproj", async () => { +// // files[pbxproj] = ""; +// // pbxprojDomXcode.Xcode.open = function (path: string) { +// // assert.equal(path, pbxproj); +// // return { +// // getSigning() { +// // return { +// // style: "Manual", configurations: { +// // Debug: { name: "NativeScriptDev2" }, +// // Release: { name: "NativeScriptDev2" } +// // } +// // }; +// // } +// // }; +// // }; +// // const changes = {}; +// // await iOSProjectService.checkForChanges(changes, { release: false, provision: "NativeScriptDev", teamId: undefined, useHotModuleReload: false }, projectData); +// // assert.isTrue(!!changes.signingChanged); +// // }); +// // it("does not set signingChanged if the Xcode projects is configured with Manual signing and proivsion matches", async () => { +// // files[pbxproj] = ""; +// // pbxprojDomXcode.Xcode.open = function (path: string) { +// // assert.equal(path, pbxproj); +// // return { +// // getSigning() { +// // return { +// // style: "Manual", configurations: { +// // Debug: { name: "NativeScriptDev" }, +// // Release: { name: "NativeScriptDev" } +// // } +// // }; +// // } +// // }; +// // }; +// // const changes = {}; +// // await iOSProjectService.checkForChanges(changes, { release: false, provision: "NativeScriptDev", teamId: undefined, useHotModuleReload: false }, projectData); +// // console.log("CHANGES !!!! ", changes); +// // assert.isFalse(!!changes.signingChanged); +// // }); +// // }); +// }); describe("Merge Project XCConfig files", () => { if (require("os").platform() !== "darwin") { diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 5bba06ea46..f8b416e68d 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -4,7 +4,6 @@ import * as PlatformAddCommandLib from "../lib/commands/add-platform"; import * as PlatformRemoveCommandLib from "../lib/commands/remove-platform"; import * as PlatformUpdateCommandLib from "../lib/commands/update-platform"; import * as PlatformCleanCommandLib from "../lib/commands/platform-clean"; -import * as PlatformServiceLib from '../lib/services/platform-service'; import * as StaticConfigLib from "../lib/config"; import * as CommandsServiceLib from "../lib/common/services/commands-service"; import * as optionsLib from "../lib/options"; @@ -20,12 +19,15 @@ import * as ChildProcessLib from "../lib/common/child-process"; import ProjectChangesLib = require("../lib/services/project-changes-service"); import { Messages } from "../lib/common/messages/messages"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; +import { PlatformCommandsService } from "../lib/services/platform/platform-commands-service"; +import { PlatformValidationService } from "../lib/services/platform/platform-validation-service"; let isCommandExecuted = true; class PlatformData implements IPlatformData { frameworkPackageName = "tns-android"; normalizedPlatformName = "Android"; + platformNameLowerCase = "android"; platformProjectService: IPlatformProjectService = { validate: async (projectData: IProjectData): Promise => { return { @@ -100,7 +102,9 @@ function createTestInjector() { testInjector.register("hooksService", stubs.HooksServiceStub); testInjector.register("staticConfig", StaticConfigLib.StaticConfig); testInjector.register("nodeModulesDependenciesBuilder", {}); - testInjector.register('platformService', PlatformServiceLib.PlatformService); + // testInjector.register('platformService', PlatformServiceLib.PlatformService); + testInjector.register('platformCommandsService', PlatformCommandsService); + testInjector.register('platformValidationService', PlatformValidationService); testInjector.register('errors', ErrorsNoFailStub); testInjector.register('logger', stubs.LoggerStub); testInjector.register('packageInstallationManager', stubs.PackageInstallationManagerStub); @@ -187,14 +191,14 @@ function createTestInjector() { } describe('Platform Service Tests', () => { - let platformService: IPlatformService, testInjector: IInjector; + let platformCommandsService: IPlatformCommandsService, testInjector: IInjector; let commandsService: ICommandsService; let fs: IFileSystem; beforeEach(() => { testInjector = createTestInjector(); testInjector.register("fs", stubs.FileSystemStub); commandsService = testInjector.resolve("commands-service"); - platformService = testInjector.resolve("platformService"); + platformCommandsService = testInjector.resolve("platformCommandsService"); fs = testInjector.resolve("fs"); }); @@ -476,11 +480,11 @@ describe('Platform Service Tests', () => { const platformActions: { action: string, platforms: string[] }[] = []; const cleanCommand = testInjector.resolveCommand("platform|clean"); - platformService.removePlatforms = async (platforms: string[]) => { + platformCommandsService.removePlatforms = async (platforms: string[]) => { platformActions.push({ action: "removePlatforms", platforms }); }; - platformService.addPlatforms = async (platforms: string[]) => { + platformCommandsService.addPlatforms = async (platforms: string[]) => { platformActions.push({ action: "addPlatforms", platforms }); diff --git a/test/platform-service.ts b/test/platform-service.ts index 1cdac296f0..2e2ae670fb 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -1,7 +1,6 @@ import * as yok from "../lib/common/yok"; import * as stubs from "./stubs"; import * as PlatformServiceLib from "../lib/services/platform-service"; -import * as StaticConfigLib from "../lib/config"; import { VERSION_STRING, PACKAGE_JSON_FILE_NAME, AddPlaformErrors } from "../lib/constants"; import * as fsLib from "../lib/common/file-system"; import * as optionsLib from "../lib/options"; @@ -15,14 +14,16 @@ import { MobileHelper } from "../lib/common/mobile/mobile-helper"; import { ProjectFilesProvider } from "../lib/providers/project-files-provider"; import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; import { XmlValidator } from "../lib/xml-validator"; -import { PreparePlatformNativeService } from "../lib/services/prepare-platform-native-service"; -import { PreparePlatformJSService } from "../lib/services/prepare-platform-js-service"; +import { PlatformJSService } from "../lib/services/prepare-platform-js-service"; import * as ChildProcessLib from "../lib/common/child-process"; import ProjectChangesLib = require("../lib/services/project-changes-service"); import { Messages } from "../lib/common/messages/messages"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; import { mkdir } from "shelljs"; import * as constants from "../lib/constants"; +import { PlatformCommandsService } from "../lib/services/platform/platform-commands-service"; +import { StaticConfig } from "../lib/config"; +import { PlatformNativeService } from "../lib/services/prepare-platform-native-service"; require("should"); const temp = require("temp"); @@ -32,6 +33,7 @@ function createTestInjector() { const testInjector = new yok.Yok(); testInjector.register('platformService', PlatformServiceLib.PlatformService); + testInjector.register("platformCommandsService", PlatformCommandsService); testInjector.register('errors', stubs.ErrorsStub); testInjector.register('logger', stubs.LoggerStub); testInjector.register("nodeModulesDependenciesBuilder", {}); @@ -49,7 +51,7 @@ function createTestInjector() { }); testInjector.register("options", optionsLib.Options); testInjector.register("hostInfo", hostInfoLib.HostInfo); - testInjector.register("staticConfig", StaticConfigLib.StaticConfig); + testInjector.register("staticConfig", StaticConfig); testInjector.register("nodeModulesBuilder", { prepareNodeModules: () => { return Promise.resolve(); @@ -76,8 +78,8 @@ function createTestInjector() { testInjector.register("projectFilesProvider", ProjectFilesProvider); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("xmlValidator", XmlValidator); - testInjector.register("preparePlatformNativeService", PreparePlatformNativeService); - testInjector.register("preparePlatformJSService", PreparePlatformJSService); + testInjector.register("platformNativeService", PlatformNativeService); + testInjector.register("platformJSService", PlatformJSService); testInjector.register("packageManager", { uninstall: async () => { return true; @@ -128,19 +130,13 @@ function createTestInjector() { } describe('Platform Service Tests', () => { - let platformService: IPlatformService, testInjector: IInjector; - const config: IPlatformOptions = { - ignoreScripts: false, - provision: null, - teamId: null, - sdk: null, - frameworkPath: null - }; + let platformCommandsService: IPlatformCommandsService, platformService: IPlatformService, testInjector: IInjector; beforeEach(() => { testInjector = createTestInjector(); testInjector.register("fs", stubs.FileSystemStub); testInjector.resolve("projectData").initializeProjectData(); + platformCommandsService = testInjector.resolve("platformCommandsService"); platformService = testInjector.resolve("platformService"); }); @@ -150,22 +146,22 @@ describe('Platform Service Tests', () => { const fs = testInjector.resolve("fs"); fs.exists = () => false; const projectData: IProjectData = testInjector.resolve("projectData"); - await platformService.addPlatforms(["Android"], projectData, config); - await platformService.addPlatforms(["ANDROID"], projectData, config); - await platformService.addPlatforms(["AnDrOiD"], projectData, config); - await platformService.addPlatforms(["androiD"], projectData, config); - - await platformService.addPlatforms(["iOS"], projectData, config); - await platformService.addPlatforms(["IOS"], projectData, config); - await platformService.addPlatforms(["IoS"], projectData, config); - await platformService.addPlatforms(["iOs"], projectData, config); + await platformCommandsService.addPlatforms(["Android"], projectData, ""); + await platformCommandsService.addPlatforms(["ANDROID"], projectData, ""); + await platformCommandsService.addPlatforms(["AnDrOiD"], projectData, ""); + await platformCommandsService.addPlatforms(["androiD"], projectData, ""); + + await platformCommandsService.addPlatforms(["iOS"], projectData, ""); + await platformCommandsService.addPlatforms(["IOS"], projectData, ""); + await platformCommandsService.addPlatforms(["IoS"], projectData, ""); + await platformCommandsService.addPlatforms(["iOs"], projectData, ""); }); it("should fail if platform is already installed", async () => { const projectData: IProjectData = testInjector.resolve("projectData"); // By default fs.exists returns true, so the platforms directory should exists - await assert.isRejected(platformService.addPlatforms(["android"], projectData, config), "Platform android already added"); - await assert.isRejected(platformService.addPlatforms(["ios"], projectData, config), "Platform ios already added"); + await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); + await assert.isRejected(platformCommandsService.addPlatforms(["ios"], projectData, ""), "Platform ios already added"); }); it("should fail if unable to extract runtime package", async () => { @@ -179,7 +175,7 @@ describe('Platform Service Tests', () => { }; const projectData: IProjectData = testInjector.resolve("projectData"); - await assert.isRejected(platformService.addPlatforms(["android"], projectData, config), errorMessage); + await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), errorMessage); }); it("fails when path passed to frameworkPath does not exist", async () => { @@ -189,7 +185,7 @@ describe('Platform Service Tests', () => { const projectData: IProjectData = testInjector.resolve("projectData"); const frameworkPath = "invalidPath"; const errorMessage = format(AddPlaformErrors.InvalidFrameworkPathStringFormat, frameworkPath); - await assert.isRejected(platformService.addPlatforms(["android"], projectData, config, frameworkPath), errorMessage); + await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, frameworkPath), errorMessage); }); const assertCorrectDataIsPassedToPacoteService = async (versionString: string): Promise => { @@ -207,6 +203,7 @@ describe('Platform Service Tests', () => { platformsData.getPlatformData = (platform: string, pData: IProjectData): IPlatformData => { return { frameworkPackageName: packageName, + platformNameLowerCase: "", platformProjectService: new stubs.PlatformProjectServiceStub(), projectRoot: "", normalizedPlatformName: "", @@ -220,9 +217,9 @@ describe('Platform Service Tests', () => { }; const projectData: IProjectData = testInjector.resolve("projectData"); - await platformService.addPlatforms(["android"], projectData, config); + await platformCommandsService.addPlatforms(["android"], projectData, ""); assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); - await platformService.addPlatforms(["ios"], projectData, config); + await platformCommandsService.addPlatforms(["ios"], projectData, ""); assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); }; it("should respect platform version in package.json's nativescript key", async () => { @@ -264,7 +261,7 @@ describe('Platform Service Tests', () => { const preparePlatformNativeService = testInjector.resolve("preparePlatformNativeService"); preparePlatformNativeService.addPlatform = async () => isNativePlatformAdded = true; - await platformService.addPlatforms(["android"], projectData, config); + await platformCommandsService.addPlatforms(["android"], projectData, ""); assert.isTrue(isJsPlatformAdded); assert.isTrue(isNativePlatformAdded); @@ -278,7 +275,7 @@ describe('Platform Service Tests', () => { projectChangesService.getPrepareInfo = () => null; const projectData = testInjector.resolve("projectData"); - await assert.isRejected(platformService.addPlatforms(["android"], projectData, config), "Platform android already added"); + await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); }); // Workflow: tns run; tns platform add @@ -289,7 +286,7 @@ describe('Platform Service Tests', () => { projectChangesService.getPrepareInfo = () => ({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); const projectData = testInjector.resolve("projectData"); - await assert.isRejected(platformService.addPlatforms(["android"], projectData, config), "Platform android already added"); + await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); }); }); }); @@ -302,13 +299,13 @@ describe('Platform Service Tests', () => { testInjector.resolve("fs").exists = () => false; try { - await platformService.removePlatforms(["android"], projectData); + await platformCommandsService.removePlatforms(["android"], projectData); } catch (e) { errorsCaught++; } try { - await platformService.removePlatforms(["ios"], projectData); + await platformCommandsService.removePlatforms(["ios"], projectData); } catch (e) { errorsCaught++; } @@ -318,10 +315,10 @@ describe('Platform Service Tests', () => { it("shouldn't fail when platforms are added", async () => { const projectData: IProjectData = testInjector.resolve("projectData"); testInjector.resolve("fs").exists = () => false; - await platformService.addPlatforms(["android"], projectData, config); + await platformCommandsService.addPlatforms(["android"], projectData, ""); testInjector.resolve("fs").exists = () => true; - await platformService.removePlatforms(["android"], projectData); + await platformCommandsService.removePlatforms(["android"], projectData); }); }); @@ -343,15 +340,15 @@ describe('Platform Service Tests', () => { }; const projectData: IProjectData = testInjector.resolve("projectData"); - platformService.removePlatforms = (platforms: string[], prjctData: IProjectData): Promise => { + platformCommandsService.removePlatforms = (platforms: string[], prjctData: IProjectData): Promise => { nsValueObject[VERSION_STRING] = undefined; return Promise.resolve(); }; - await platformService.cleanPlatforms(["android"], projectData, config); + await platformCommandsService.cleanPlatforms(["android"], projectData, ""); nsValueObject[VERSION_STRING] = versionString; - await platformService.cleanPlatforms(["ios"], projectData, config); + await platformCommandsService.cleanPlatforms(["ios"], projectData, ""); }); }); @@ -369,7 +366,7 @@ describe('Platform Service Tests', () => { packageInstallationManager.getLatestVersion = async () => "0.2.0"; const projectData: IProjectData = testInjector.resolve("projectData"); - await assert.isRejected(platformService.updatePlatforms(["android"], projectData, null)); + await assert.isRejected(platformCommandsService.updatePlatforms(["android"], projectData)); }); }); }); @@ -467,7 +464,7 @@ describe('Platform Service Tests', () => { // appFilesUpdaterOptions, // platformTemplate: "", // projectData, - // config: { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, + // "": { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, // env: {} // }); // } @@ -906,7 +903,7 @@ describe('Platform Service Tests', () => { // appFilesUpdaterOptions, // platformTemplate: "", // projectData, - // config: { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, + // "": { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, // env: {} // }); // } finally { @@ -1014,8 +1011,8 @@ describe('Platform Service Tests', () => { beforeEach(() => { reset(); - (platformService).addPlatform = () => { /** */ }; - (platformService).persistWebpackFiles = () => areWebpackFilesPersisted = true; + (platformCommandsService).addPlatform = () => { /** */ }; + (platformCommandsService).persistWebpackFiles = () => areWebpackFilesPersisted = true; projectData = testInjector.resolve("projectData"); usbLiveSyncService = testInjector.resolve("usbLiveSyncService"); @@ -1081,7 +1078,7 @@ describe('Platform Service Tests', () => { usbLiveSyncService.isInitialized = testCase.isWebpackWatcherStarted === undefined ? true : testCase.isWebpackWatcherStarted; mockPrepareInfo(testCase.prepareInfo); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, testCase.nativePrepare); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, testCase.nativePrepare); assert.deepEqual(areWebpackFilesPersisted, testCase.areWebpackFilesPersisted); }); }); @@ -1089,64 +1086,64 @@ describe('Platform Service Tests', () => { it("should not persist webpack files after the second execution of `tns preview --bundle` or `tns cloud run --bundle`", async () => { // First execution of `tns preview --bundle` mockPrepareInfo(null); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); assert.isTrue(areWebpackFilesPersisted); // Second execution of `tns preview --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); assert.isFalse(areWebpackFilesPersisted); }); it("should not persist webpack files after the second execution of `tns run --bundle`", async () => { // First execution of `tns run --bundle` mockPrepareInfo(null); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); assert.isTrue(areWebpackFilesPersisted); // Second execution of `tns run --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); assert.isFalse(areWebpackFilesPersisted); }); it("should handle correctly the following sequence of commands: `tns preview --bundle`, `tns run --bundle` and `tns preview --bundle`", async () => { // First execution of `tns preview --bundle` mockPrepareInfo(null); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); assert.isTrue(areWebpackFilesPersisted); // Execution of `tns run --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); assert.isTrue(areWebpackFilesPersisted); // Execution of `tns preview --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); assert.isFalse(areWebpackFilesPersisted); }); it("should handle correctly the following sequence of commands: `tns preview --bundle`, `tns run --bundle` and `tns build --bundle`", async () => { // Execution of `tns preview --bundle` mockPrepareInfo(null); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); assert.isTrue(areWebpackFilesPersisted); // Execution of `tns run --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); assert.isTrue(areWebpackFilesPersisted); // Execution of `tns build --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions); + await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); assert.isFalse(areWebpackFilesPersisted); }); }); diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index b42b5d318e..16ea3bd7cd 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -153,10 +153,11 @@ describe("Project Changes Service Tests", () => { platform: "ios", projectData: serviceTest.projectData, projectChangesOptions: { - bundle: false, release: false, - provision: undefined, - teamId: undefined, + signingOptions: { + provision: undefined, + teamId: undefined, + }, useHotModuleReload: false } }); @@ -166,10 +167,11 @@ describe("Project Changes Service Tests", () => { platform: "android", projectData: serviceTest.projectData, projectChangesOptions: { - bundle: false, release: false, - provision: undefined, - teamId: undefined, + signingOptions: { + provision: undefined, + teamId: undefined, + }, useHotModuleReload: false } }); @@ -194,10 +196,11 @@ describe("Project Changes Service Tests", () => { platform, projectData: serviceTest.projectData, projectChangesOptions: { - bundle: false, release: false, - provision: undefined, - teamId: undefined, + signingOptions: { + provision: undefined, + teamId: undefined, + }, useHotModuleReload: false } }); @@ -219,10 +222,11 @@ describe("Project Changes Service Tests", () => { platform, projectData: serviceTest.projectData, projectChangesOptions: { - bundle: false, release: false, - provision: undefined, - teamId: undefined, + signingOptions: { + provision: undefined, + teamId: undefined, + }, useHotModuleReload: false } }); diff --git a/test/services/livesync-service.ts b/test/services/livesync-service.ts index 9a4008ce8c..653e3d0f4b 100644 --- a/test/services/livesync-service.ts +++ b/test/services/livesync-service.ts @@ -1,209 +1,209 @@ -import { Yok } from "../../lib/common/yok"; -import { assert } from "chai"; -import { LiveSyncService, DeprecatedUsbLiveSyncService } from "../../lib/services/livesync/livesync-service"; -import { LoggerStub } from "../stubs"; - -const createTestInjector = (): IInjector => { - const testInjector = new Yok(); - - testInjector.register("platformService", {}); - testInjector.register("hmrStatusService", {}); - testInjector.register("projectDataService", { - getProjectData: (projectDir: string): IProjectData => ({}) - }); - - testInjector.register("devicesService", {}); - testInjector.register("mobileHelper", {}); - testInjector.register("devicePlatformsConstants", {}); - testInjector.register("nodeModulesDependenciesBuilder", {}); - testInjector.register("logger", LoggerStub); - testInjector.register("debugService", {}); - testInjector.register("errors", {}); - testInjector.register("debugDataService", {}); - testInjector.register("hooksService", { - executeAfterHooks: (commandName: string, hookArguments?: IDictionary): Promise => Promise.resolve() - }); - - testInjector.register("pluginsService", {}); - testInjector.register("analyticsService", {}); - testInjector.register("injector", testInjector); - testInjector.register("usbLiveSyncService", { - isInitialized: false - }); - testInjector.register("platformsData", { - availablePlatforms: { - Android: "Android", - iOS: "iOS" - } - }); - testInjector.register("previewAppLiveSyncService", {}); - testInjector.register("previewQrCodeService", {}); - testInjector.register("previewSdkService", {}); - - return testInjector; -}; - -class LiveSyncServiceInheritor extends LiveSyncService { - constructor($platformService: IPlatformService, - $projectDataService: IProjectDataService, - $devicesService: Mobile.IDevicesService, - $mobileHelper: Mobile.IMobileHelper, - $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, - $logger: ILogger, - $hooksService: IHooksService, - $pluginsService: IPluginsService, - $debugService: IDebugService, - $errors: IErrors, - $debugDataService: IDebugDataService, - $analyticsService: IAnalyticsService, - $usbLiveSyncService: DeprecatedUsbLiveSyncService, - $injector: IInjector, - $previewAppLiveSyncService: IPreviewAppLiveSyncService, - $previewQrCodeService: IPreviewQrCodeService, - $previewSdkService: IPreviewSdkService, - $hmrStatusService: IHmrStatusService, - $platformsData: IPlatformsData) { - - super( - $platformService, - $projectDataService, - $devicesService, - $mobileHelper, - $devicePlatformsConstants, - $nodeModulesDependenciesBuilder, - $logger, - $hooksService, - $pluginsService, - $debugService, - $errors, - $debugDataService, - $analyticsService, - $usbLiveSyncService, - $previewAppLiveSyncService, - $previewQrCodeService, - $previewSdkService, - $hmrStatusService, - $injector - ); - } - - public liveSyncProcessesInfo: IDictionary = {}; -} - -interface IStopLiveSyncTestCase { - name: string; - currentDeviceIdentifiers: string[]; - expectedDeviceIdentifiers: string[]; - deviceIdentifiersToBeStopped?: string[]; -} - -describe("liveSyncService", () => { - describe("stopLiveSync", () => { - const getLiveSyncProcessInfo = (): ILiveSyncProcessInfo => ({ - actionsChain: Promise.resolve(), - currentSyncAction: Promise.resolve(), - isStopped: false, - timer: setTimeout(() => undefined, 1000), - watcherInfo: { - watcher: { - close: (): any => undefined - }, - patterns: ["pattern"] - }, - deviceDescriptors: [], - syncToPreviewApp: false - }); - - const getDeviceDescriptor = (identifier: string): ILiveSyncDeviceInfo => ({ - identifier, - outputPath: "", - skipNativePrepare: false, - platformSpecificOptions: null, - buildAction: () => Promise.resolve("") - }); - - const testCases: IStopLiveSyncTestCase[] = [ - { - name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", - currentDeviceIdentifiers: ["device1", "device2", "device3"], - expectedDeviceIdentifiers: ["device1", "device2", "device3"] - }, - { - name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", - currentDeviceIdentifiers: ["device1"], - expectedDeviceIdentifiers: ["device1"] - }, - { - name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", - currentDeviceIdentifiers: ["device1"], - expectedDeviceIdentifiers: ["device1"], - deviceIdentifiersToBeStopped: ["device1"] - }, - { - name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", - currentDeviceIdentifiers: ["device1", "device2", "device3"], - expectedDeviceIdentifiers: ["device1", "device3"], - deviceIdentifiersToBeStopped: ["device1", "device3"] - }, - { - name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", - currentDeviceIdentifiers: ["device1", "device2", "device3"], - expectedDeviceIdentifiers: ["device1"], - deviceIdentifiersToBeStopped: ["device1", "device4"] - } - ]; - - for (const testCase of testCases) { - it(testCase.name, async () => { - const testInjector = createTestInjector(); - const liveSyncService = testInjector.resolve(LiveSyncServiceInheritor); - const projectDir = "projectDir"; - const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; - liveSyncService.on("liveSyncStopped", (data: { projectDir: string, deviceIdentifier: string }) => { - assert.equal(data.projectDir, projectDir); - emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); - }); - - // Setup liveSyncProcessesInfo for current test - liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo(); - const deviceDescriptors = testCase.currentDeviceIdentifiers.map(d => getDeviceDescriptor(d)); - liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors); - - await liveSyncService.stopLiveSync(projectDir, testCase.deviceIdentifiersToBeStopped); - - assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); - }); - } - - const prepareTestForUsbLiveSyncService = (): any => { - const testInjector = createTestInjector(); - const liveSyncService = testInjector.resolve(LiveSyncServiceInheritor); - const projectDir = "projectDir"; - const usbLiveSyncService = testInjector.resolve("usbLiveSyncService"); - usbLiveSyncService.isInitialized = true; - - // Setup liveSyncProcessesInfo for current test - liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo(); - const deviceDescriptors = ["device1", "device2", "device3"].map(d => getDeviceDescriptor(d)); - liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors); - return { projectDir, liveSyncService, usbLiveSyncService }; - }; - - it("sets usbLiveSyncService.isInitialized to false when LiveSync is stopped for all devices", async () => { - const { projectDir, liveSyncService, usbLiveSyncService } = prepareTestForUsbLiveSyncService(); - await liveSyncService.stopLiveSync(projectDir, ["device1", "device2", "device3"]); - - assert.isFalse(usbLiveSyncService.isInitialized, "When the LiveSync process is stopped, we must set usbLiveSyncService.isInitialized to false"); - }); - - it("does not set usbLiveSyncService.isInitialized to false when LiveSync is stopped for some of devices only", async () => { - const { projectDir, liveSyncService, usbLiveSyncService } = prepareTestForUsbLiveSyncService(); - await liveSyncService.stopLiveSync(projectDir, ["device1", "device2"]); - - assert.isTrue(usbLiveSyncService.isInitialized, "When the LiveSync process is stopped only for some of the devices, we must not set usbLiveSyncService.isInitialized to false"); - }); - - }); - -}); +// import { Yok } from "../../lib/common/yok"; +// import { assert } from "chai"; +// import { LiveSyncService, DeprecatedUsbLiveSyncService } from "../../lib/services/livesync/livesync-service"; +// import { LoggerStub } from "../stubs"; + +// const createTestInjector = (): IInjector => { +// const testInjector = new Yok(); + +// testInjector.register("platformService", {}); +// testInjector.register("hmrStatusService", {}); +// testInjector.register("projectDataService", { +// getProjectData: (projectDir: string): IProjectData => ({}) +// }); + +// testInjector.register("devicesService", {}); +// testInjector.register("mobileHelper", {}); +// testInjector.register("devicePlatformsConstants", {}); +// testInjector.register("nodeModulesDependenciesBuilder", {}); +// testInjector.register("logger", LoggerStub); +// testInjector.register("debugService", {}); +// testInjector.register("errors", {}); +// testInjector.register("debugDataService", {}); +// testInjector.register("hooksService", { +// executeAfterHooks: (commandName: string, hookArguments?: IDictionary): Promise => Promise.resolve() +// }); + +// testInjector.register("pluginsService", {}); +// testInjector.register("analyticsService", {}); +// testInjector.register("injector", testInjector); +// testInjector.register("usbLiveSyncService", { +// isInitialized: false +// }); +// testInjector.register("platformsData", { +// availablePlatforms: { +// Android: "Android", +// iOS: "iOS" +// } +// }); +// testInjector.register("previewAppLiveSyncService", {}); +// testInjector.register("previewQrCodeService", {}); +// testInjector.register("previewSdkService", {}); + +// return testInjector; +// }; + +// class LiveSyncServiceInheritor extends LiveSyncService { +// constructor($platformService: IPlatformService, +// $projectDataService: IProjectDataService, +// $devicesService: Mobile.IDevicesService, +// $mobileHelper: Mobile.IMobileHelper, +// $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, +// $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, +// $logger: ILogger, +// $hooksService: IHooksService, +// $pluginsService: IPluginsService, +// $debugService: IDebugService, +// $errors: IErrors, +// $debugDataService: IDebugDataService, +// $analyticsService: IAnalyticsService, +// $usbLiveSyncService: DeprecatedUsbLiveSyncService, +// $injector: IInjector, +// $previewAppLiveSyncService: IPreviewAppLiveSyncService, +// $previewQrCodeService: IPreviewQrCodeService, +// $previewSdkService: IPreviewSdkService, +// $hmrStatusService: IHmrStatusService, +// $platformsData: IPlatformsData) { + +// super( +// $platformService, +// $projectDataService, +// $devicesService, +// $mobileHelper, +// $devicePlatformsConstants, +// $nodeModulesDependenciesBuilder, +// $logger, +// $hooksService, +// $pluginsService, +// $debugService, +// $errors, +// $debugDataService, +// $analyticsService, +// $usbLiveSyncService, +// $previewAppLiveSyncService, +// $previewQrCodeService, +// $previewSdkService, +// $hmrStatusService, +// $injector +// ); +// } + +// public liveSyncProcessesInfo: IDictionary = {}; +// } + +// interface IStopLiveSyncTestCase { +// name: string; +// currentDeviceIdentifiers: string[]; +// expectedDeviceIdentifiers: string[]; +// deviceIdentifiersToBeStopped?: string[]; +// } + +// describe("liveSyncService", () => { +// describe("stopLiveSync", () => { +// const getLiveSyncProcessInfo = (): ILiveSyncProcessInfo => ({ +// actionsChain: Promise.resolve(), +// currentSyncAction: Promise.resolve(), +// isStopped: false, +// timer: setTimeout(() => undefined, 1000), +// watcherInfo: { +// watcher: { +// close: (): any => undefined +// }, +// patterns: ["pattern"] +// }, +// deviceDescriptors: [], +// syncToPreviewApp: false +// }); + +// const getDeviceDescriptor = (identifier: string): ILiveSyncDeviceInfo => ({ +// identifier, +// outputPath: "", +// skipNativePrepare: false, +// platformSpecificOptions: null, +// buildAction: () => Promise.resolve("") +// }); + +// const testCases: IStopLiveSyncTestCase[] = [ +// { +// name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", +// currentDeviceIdentifiers: ["device1", "device2", "device3"], +// expectedDeviceIdentifiers: ["device1", "device2", "device3"] +// }, +// { +// name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", +// currentDeviceIdentifiers: ["device1"], +// expectedDeviceIdentifiers: ["device1"] +// }, +// { +// name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", +// currentDeviceIdentifiers: ["device1"], +// expectedDeviceIdentifiers: ["device1"], +// deviceIdentifiersToBeStopped: ["device1"] +// }, +// { +// name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", +// currentDeviceIdentifiers: ["device1", "device2", "device3"], +// expectedDeviceIdentifiers: ["device1", "device3"], +// deviceIdentifiersToBeStopped: ["device1", "device3"] +// }, +// { +// name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", +// currentDeviceIdentifiers: ["device1", "device2", "device3"], +// expectedDeviceIdentifiers: ["device1"], +// deviceIdentifiersToBeStopped: ["device1", "device4"] +// } +// ]; + +// for (const testCase of testCases) { +// it(testCase.name, async () => { +// const testInjector = createTestInjector(); +// const liveSyncService = testInjector.resolve(LiveSyncServiceInheritor); +// const projectDir = "projectDir"; +// const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; +// liveSyncService.on("liveSyncStopped", (data: { projectDir: string, deviceIdentifier: string }) => { +// assert.equal(data.projectDir, projectDir); +// emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); +// }); + +// // Setup liveSyncProcessesInfo for current test +// liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo(); +// const deviceDescriptors = testCase.currentDeviceIdentifiers.map(d => getDeviceDescriptor(d)); +// liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors); + +// await liveSyncService.stopLiveSync(projectDir, testCase.deviceIdentifiersToBeStopped); + +// assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); +// }); +// } + +// const prepareTestForUsbLiveSyncService = (): any => { +// const testInjector = createTestInjector(); +// const liveSyncService = testInjector.resolve(LiveSyncServiceInheritor); +// const projectDir = "projectDir"; +// const usbLiveSyncService = testInjector.resolve("usbLiveSyncService"); +// usbLiveSyncService.isInitialized = true; + +// // Setup liveSyncProcessesInfo for current test +// liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo(); +// const deviceDescriptors = ["device1", "device2", "device3"].map(d => getDeviceDescriptor(d)); +// liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors); +// return { projectDir, liveSyncService, usbLiveSyncService }; +// }; + +// it("sets usbLiveSyncService.isInitialized to false when LiveSync is stopped for all devices", async () => { +// const { projectDir, liveSyncService, usbLiveSyncService } = prepareTestForUsbLiveSyncService(); +// await liveSyncService.stopLiveSync(projectDir, ["device1", "device2", "device3"]); + +// assert.isFalse(usbLiveSyncService.isInitialized, "When the LiveSync process is stopped, we must set usbLiveSyncService.isInitialized to false"); +// }); + +// it("does not set usbLiveSyncService.isInitialized to false when LiveSync is stopped for some of devices only", async () => { +// const { projectDir, liveSyncService, usbLiveSyncService } = prepareTestForUsbLiveSyncService(); +// await liveSyncService.stopLiveSync(projectDir, ["device1", "device2"]); + +// assert.isTrue(usbLiveSyncService.isInitialized, "When the LiveSync process is stopped only for some of the devices, we must not set usbLiveSyncService.isInitialized to false"); +// }); + +// }); + +// }); diff --git a/test/stubs.ts b/test/stubs.ts index 28add17f06..502d2f32c9 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -370,9 +370,10 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor return { frameworkPackageName: "", normalizedPlatformName: "", + platformNameLowerCase: "", platformProjectService: this, projectRoot: "", - getBuildOutputPath: () => "", + getBuildOutputPath: (buildConfig: IBuildConfig) => "", getValidBuildOutputData: (buildOptions: IBuildOutputOptions) => ({ packageNames: [] }), frameworkFilesExtensions: [], appDestinationDirectoryPath: "", @@ -450,7 +451,7 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor async cleanProject(projectRoot: string, projectData: IProjectData): Promise { return Promise.resolve(); } - async checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): Promise { + async checkForChanges(changesInfo: IProjectChangesInfo, options: any, projectData: IProjectData): Promise { // Nothing yet. } getFrameworkVersion(projectData: IProjectData): string { @@ -471,6 +472,7 @@ export class PlatformsDataStub extends EventEmitter implements IPlatformsData { return { frameworkPackageName: "", platformProjectService: new PlatformProjectServiceStub(), + platformNameLowerCase: "", projectRoot: "", normalizedPlatformName: "", appDestinationDirectoryPath: "", @@ -805,7 +807,7 @@ export class PlatformServiceStub extends EventEmitter implements IPlatformServic return Promise.resolve(); } - public preparePlatform(platformInfo: IPreparePlatformInfo): Promise { + public preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { return Promise.resolve(true); } From e295df862759f0b07346c1ed641474f386ccc0f7 Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 28 Apr 2019 15:33:40 +0300 Subject: [PATCH 03/30] feat: introduce initialSync workflow --- .vscode/launch.json | 2 +- lib/bootstrap.ts | 4 + lib/declarations.d.ts | 4 +- lib/definitions/livesync.d.ts | 5 +- lib/helpers/livesync-command-helper.ts | 23 ++- lib/services/build-artefacts-service.ts | 57 +++--- lib/services/bundle-workflow-service.ts | 166 +++++++++++----- .../device/device-installation-service.ts | 0 lib/services/livesync/ios-livesync-service.ts | 6 +- lib/services/livesync/livesync-service.ts | 177 ++++++++++++++++++ .../platform/platform-build-service.ts | 15 ++ .../platform/platform-watcher-service.ts | 2 + .../webpack/webpack-compiler-service.ts | 21 ++- .../workflow/device-workflow-service.ts | 139 ++++++++++++++ .../workflow/platform-workflow-service.ts | 65 ++++++- lib/services/workflow/workflow.d.ts | 7 + 16 files changed, 587 insertions(+), 106 deletions(-) create mode 100644 lib/services/device/device-installation-service.ts create mode 100644 lib/services/workflow/device-workflow-service.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 5814d09efd..cfe5787073 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "program": "${workspaceRoot}/lib/nativescript-cli.js", // example commands - "args": [ "build", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle"] + "args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle"] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] // "args": [ "platform", "remove", "android", "--path", "cliapp"] diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 4d6b911264..0c2e3e02fa 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -40,8 +40,12 @@ $injector.require("platformAddService", "./services/platform/platform-add-servic $injector.require("platformBuildService", "./services/platform/platform-build-service"); $injector.require("platformValidationService", "./services/platform/platform-validation-service"); $injector.require("platformCommandsService", "./services/platform/platform-commands-service"); +$injector.require("platformWatcherService", "./services/platform/platform-watcher-service"); $injector.require("platformWorkflowService", "./services/workflow/platform-workflow-service"); +$injector.require("deviceWorkflowService", "./services/workflow/device-workflow-service"); +$injector.require("runWorkflowService", "./services/workflow/run-workflow-service"); +$injector.require("bundleWorkflowService", "./services/bundle-workflow-service"); $injector.require("platformWorkflowDataFactory", "./factory/platform-workflow-data-factory"); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index ef02ac6a93..e7985922ff 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -1028,7 +1028,7 @@ interface INetworkConnectivityValidator { } interface IBundleWorkflowService { - + start(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise; } interface IPlatformValidationService { @@ -1058,6 +1058,7 @@ interface IPlatformValidationService { interface IBuildArtefactsService { getLastBuiltPackagePath(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): Promise; + getAllBuiltApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[]; } interface IPlatformAddService { @@ -1083,4 +1084,5 @@ interface IAddPlatformData { interface IPlatformBuildService { buildPlatform(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; saveBuildInfoFile(platformData: IPlatformData, projectData: IProjectData, buildInfoFileDirname: string): void; + getBuildInfoFromFile(platformData: IPlatformData, buildConfig: IBuildConfig, buildOutputPath?: string): IBuildInfo; } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index e79a2b10ef..827098a53f 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -264,6 +264,10 @@ declare global { getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; } + interface ILiveSyncService2 { + fullSync(device: Mobile.IDevice, deviceBuildInfoDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise; + } + /** * Describes LiveSync operations while debuggging. */ @@ -374,7 +378,6 @@ declare global { interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { device: Mobile.IDevice; watch: boolean; - syncAllFiles: boolean; liveSyncDeviceInfo: ILiveSyncDeviceInfo; force?: boolean; } diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index ae5af9c560..6f6b49502e 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,4 +1,4 @@ -import { LiveSyncEvents } from "../constants"; +// import { LiveSyncEvents } from "../constants"; export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; @@ -6,7 +6,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { constructor(private $platformService: IPlatformService, private $projectData: IProjectData, private $options: IOptions, - private $liveSyncService: ILiveSyncService, + private $bundleWorkflowService: IBundleWorkflowService, + // private $liveSyncService: ILiveSyncService, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $devicesService: Mobile.IDevicesService, @@ -127,16 +128,18 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { force: this.$options.force }; - const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); - this.$liveSyncService.on(LiveSyncEvents.liveSyncStopped, (data: { projectDir: string, deviceIdentifier: string }) => { - _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); + await this.$bundleWorkflowService.start(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); - if (remainingDevicesToSync.length === 0) { - process.exit(ErrorCodes.ALL_DEVICES_DISCONNECTED); - } - }); + // const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); + // this.$liveSyncService.on(LiveSyncEvents.liveSyncStopped, (data: { projectDir: string, deviceIdentifier: string }) => { + // _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); + + // if (remainingDevicesToSync.length === 0) { + // process.exit(ErrorCodes.ALL_DEVICES_DISCONNECTED); + // } + // }); - await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + // await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } public async validatePlatform(platform: string): Promise> { diff --git a/lib/services/build-artefacts-service.ts b/lib/services/build-artefacts-service.ts index dc34999735..2ca1bc9afe 100644 --- a/lib/services/build-artefacts-service.ts +++ b/lib/services/build-artefacts-service.ts @@ -19,6 +19,27 @@ export class BuildArtefactsService implements IBuildArtefactsService { return packageFile; } + public getAllBuiltApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[] { + const rootFiles = this.$fs.readDirectory(buildOutputPath).map(filename => path.join(buildOutputPath, filename)); + let result = this.getApplicationPackagesCore(rootFiles, validBuildOutputData.packageNames); + if (result) { + return result; + } + + const candidates = this.$fs.enumerateFilesInDirectorySync(buildOutputPath); + result = this.getApplicationPackagesCore(candidates, validBuildOutputData.packageNames); + if (result) { + return result; + } + + if (validBuildOutputData.regexes && validBuildOutputData.regexes.length) { + const packages = candidates.filter(filepath => _.some(validBuildOutputData.regexes, regex => regex.test(path.basename(filepath)))); + return this.createApplicationPackages(packages); + } + + return []; + } + private getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage { outputPath = outputPath || platformData.getBuildOutputPath(buildConfig); const buildOutputOptions = { buildForDevice: true, release: buildConfig.release, androidBundle: buildConfig.androidBundle }; @@ -32,7 +53,7 @@ export class BuildArtefactsService implements IBuildArtefactsService { } private getLatestApplicationPackage(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage { - let packages = this.getApplicationPackages(buildOutputPath, validBuildOutputData); + let packages = this.getAllBuiltApplicationPackages(buildOutputPath, validBuildOutputData); const packageExtName = path.extname(validBuildOutputData.packageNames[0]); if (packages.length === 0) { this.$errors.fail(`No ${packageExtName} found in ${buildOutputPath} directory.`); @@ -47,26 +68,6 @@ export class BuildArtefactsService implements IBuildArtefactsService { return packages[0]; } - private getApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[] { - // Get latest package` that is produced from build - let result = this.getApplicationPackagesCore(this.$fs.readDirectory(buildOutputPath).map(filename => path.join(buildOutputPath, filename)), validBuildOutputData.packageNames); - if (result) { - return result; - } - - const candidates = this.$fs.enumerateFilesInDirectorySync(buildOutputPath); - result = this.getApplicationPackagesCore(candidates, validBuildOutputData.packageNames); - if (result) { - return result; - } - - if (validBuildOutputData.regexes && validBuildOutputData.regexes.length) { - return this.createApplicationPackages(candidates.filter(filepath => _.some(validBuildOutputData.regexes, regex => regex.test(path.basename(filepath))))); - } - - return []; - } - private getApplicationPackagesCore(candidates: string[], validPackageNames: string[]): IApplicationPackage[] { const packages = candidates.filter(filePath => _.includes(validPackageNames, path.basename(filePath))); if (packages.length > 0) { @@ -77,14 +78,12 @@ export class BuildArtefactsService implements IBuildArtefactsService { } private createApplicationPackages(packages: string[]): IApplicationPackage[] { - return packages.map(filepath => this.createApplicationPackage(filepath)); - } - - private createApplicationPackage(packageName: string): IApplicationPackage { - return { - packageName, - time: this.$fs.getFsStats(packageName).mtime - }; + return packages.map(packageName => { + return { + packageName, + time: this.$fs.getFsStats(packageName).mtime + }; + }); } } $injector.register("buildArtefactsService", BuildArtefactsService); diff --git a/lib/services/bundle-workflow-service.ts b/lib/services/bundle-workflow-service.ts index e8e8811d67..b56bce9008 100644 --- a/lib/services/bundle-workflow-service.ts +++ b/lib/services/bundle-workflow-service.ts @@ -1,24 +1,34 @@ -import * as path from "path"; -import * as constants from "../constants"; +// import * as path from "path"; +// import * as constants from "../constants"; +const deviceDescriptorPrimaryKey = "identifier"; + +// TODO: Rename this class to RunWorkflowService export class BundleWorkflowService implements IBundleWorkflowService { + private liveSyncProcessesInfo: IDictionary = {}; + constructor( private $devicesService: Mobile.IDevicesService, + private $deviceWorkflowService: IDeviceWorkflowService, private $errors: IErrors, - private $fs: IFileSystem, + private $liveSyncService: ILiveSyncService2, + // private $fs: IFileSystem, private $logger: ILogger, - private $platformAddService: IPlatformAddService, + // private $platformAddService: IPlatformAddService, private $platformsData: IPlatformsData, private $platformWatcherService: IPlatformWatcherService, + private $platformWorkflowService: IPlatformWorkflowService, private $pluginsService: IPluginsService, - private $projectChangesService: IProjectChangesService + private $projectDataService: IProjectDataService, + // private $projectChangesService: IProjectChangesService ) { } // processInfo[projectDir] = { // deviceDescriptors, nativeFilesWatcher, jsFilesWatcher // } - public async start(projectData: IProjectData, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + public async start(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + const projectData = this.$projectDataService.getProjectData(projectDir); await this.initializeSetup(projectData); const platforms = _(deviceDescriptors) @@ -26,47 +36,102 @@ export class BundleWorkflowService implements IBundleWorkflowService { .map(device => device.deviceInfo.platform) .uniq() .value(); - for (const platform in platforms) { - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, liveSyncInfo.nativePrepare); - if (shouldAddPlatform) { - await this.$platformAddService.addPlatform({ - platformParam: (liveSyncInfo).platformParam, - frameworkPath: (liveSyncInfo).frameworkPath, - nativePrepare: liveSyncInfo.nativePrepare - }, projectData); + const workflowData: IPlatformWorkflowData = { + platformParam: null, + nativePrepare: liveSyncInfo.nativePrepare, + release: liveSyncInfo.release, + useHotModuleReload: liveSyncInfo.useHotModuleReload, + signingOptions: { + teamId: (liveSyncInfo).teamId, + provision: (liveSyncInfo).provision } + }; - this.$platformWatcherService.on("onInitialSync", async () => { - console.log("================= RECEIVED INITIAL SYNC ============= "); - // check if we should build, install, transfer files - // AddActionToChain - }); - this.$platformWatcherService.on("onFilesChange", () => { - console.log("=================== RECEIVED FILES CHANGE ================ "); - // Emitted when webpack compilatation is done and native prepare is done - // console.log("--------- ========= ---------- ", data); - // AddActionToChain - }); + // Ensure platform is added before starting JS(webpack) and native prepare + for (const platform of platforms) { + const platformNameLowerCase = platform.toLowerCase(); + const platformData = this.$platformsData.getPlatformData(platformNameLowerCase, projectData); + workflowData.platformParam = platformNameLowerCase; + await this.$platformWorkflowService.addPlatformIfNeeded(platformData, projectData, workflowData); + } + + this.setLiveSyncProcessInfo(projectDir, liveSyncInfo, deviceDescriptors); + + const initalSyncDeviceAction = async (device: Mobile.IDevice): Promise => { + console.log("================== INITIAL SYNC DEVICE ACTION ================"); + const deviceBuildInfoDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + const platform = device.deviceInfo.platform; + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const buildConfig = { + buildForDevice: !device.isEmulator, + device: device.deviceInfo.identifier, + release: liveSyncInfo.release, + clean: liveSyncInfo.clean, + iCloudContainerEnvironment: "", + projectDir: projectData.projectDir, + teamId: null, + provision: null, + }; + const outputPath = deviceBuildInfoDescriptor.outputPath || platformData.getBuildOutputPath(buildConfig); + const packageFilePath = await this.$platformWorkflowService.buildPlatformIfNeeded(platformData, projectData, workflowData, buildConfig, outputPath); + + await this.$deviceWorkflowService.installOnDeviceIfNeeded(device, platformData, projectData, buildConfig, packageFilePath, outputPath); + + await this.$liveSyncService.fullSync(device, deviceBuildInfoDescriptor, projectData, liveSyncInfo); + }; + + // const filesChangeDeviceAction = async (device: Mobile.IDevice): Promise { + // // test + // }; + + this.$platformWatcherService.on("onInitialSync", async () => { // TODO: emit correct initialSyncData -> platform + hasNativeChange + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(initalSyncDeviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); + }); + this.$platformWatcherService.on("fileChangeData", () => { + console.log("=================== RECEIVED FILES CHANGE ================ "); + // Emitted when webpack compilatation is done and native prepare is done + // console.log("--------- ========= ---------- ", data); + // AddActionToChain + }); - await this.$platformWatcherService.startWatcher(platformData, projectData, { - webpackCompilerConfig: liveSyncInfo.webpackCompilerConfig, - preparePlatformData: { - release: liveSyncInfo.release, - useHotModuleReload: liveSyncInfo.useHotModuleReload, - nativePrepare: liveSyncInfo.nativePrepare, - signingOptions: { - teamId: (liveSyncInfo).teamId, - provision: (liveSyncInfo).provision + const shouldStartWatcher = !liveSyncInfo.skipWatcher && (liveSyncInfo.syncToPreviewApp || this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length); + + if (shouldStartWatcher) { + // TODO: Extract the preparePlatformData to separate variable + for (const platform of platforms) { + const platformData = this.$platformsData.getPlatformData(platform.toLocaleLowerCase(), projectData); + await this.$platformWatcherService.startWatcher(platformData, projectData, { + webpackCompilerConfig: liveSyncInfo.webpackCompilerConfig, + preparePlatformData: { + release: liveSyncInfo.release, + useHotModuleReload: liveSyncInfo.useHotModuleReload, + nativePrepare: liveSyncInfo.nativePrepare, + signingOptions: { + teamId: (liveSyncInfo).teamId, + provision: (liveSyncInfo).provision + } } - } - }); + }); + } } + } - for (const deviceDescriptor in deviceDescriptors) { - console.log("============ DEVICE DESCRIPTOR ============== ", deviceDescriptor); - } + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; + const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; + return currentDescriptors || []; + } + + private setLiveSyncProcessInfo(projectDir: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): void { + this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); + this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); + this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; + this.liveSyncProcessesInfo[projectDir].isStopped = false; + this.liveSyncProcessesInfo[projectDir].syncToPreviewApp = liveSyncInfo.syncToPreviewApp; + + const currentDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); + this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); } private async initializeSetup(projectData: IProjectData): Promise { @@ -78,15 +143,20 @@ export class BundleWorkflowService implements IBundleWorkflowService { } } - private shouldAddPlatform(platformData: IPlatformData, projectData: IProjectData, nativePrepare: INativePrepare): boolean { - const platformName = platformData.normalizedPlatformName.toLowerCase(); - const prepareInfo = this.$projectChangesService.getPrepareInfo(platformName, projectData); - const hasPlatformDirectory = this.$fs.exists(path.join(projectData.platformsDir, platformName)); - const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; - const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; - const result = !hasPlatformDirectory || (shouldAddNativePlatform && requiresNativePlatformAdd); + private async addActionToChain(projectDir: string, action: () => Promise): Promise { + const liveSyncInfo = this.liveSyncProcessesInfo[projectDir]; + if (liveSyncInfo) { + liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { + if (!liveSyncInfo.isStopped) { + liveSyncInfo.currentSyncAction = action(); + const res = await liveSyncInfo.currentSyncAction; + return res; + } + }); - return result; + const result = await liveSyncInfo.actionsChain; + return result; + } } } $injector.register("bundleWorkflowService", BundleWorkflowService); diff --git a/lib/services/device/device-installation-service.ts b/lib/services/device/device-installation-service.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index effdf26868..b24ffc93eb 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -34,9 +34,7 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I this.$logger.trace("Creating zip file: " + tempZip); this.$fs.copyFile(path.join(path.dirname(projectFilesPath), `${APP_FOLDER_NAME}/*`), tempApp); - if (!syncInfo.syncAllFiles) { - this.$fs.deleteDirectory(path.join(tempApp, TNS_MODULES_FOLDER_NAME)); - } + this.$fs.deleteDirectory(path.join(tempApp, TNS_MODULES_FOLDER_NAME)); await this.$fs.zipFiles(tempZip, this.$fs.enumerateFilesInDirectorySync(tempApp), (res) => { return path.join(APP_FOLDER_NAME, path.relative(tempApp, res)); @@ -63,7 +61,7 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I // In this case we should execute fullsync because iOS Runtime requires the full content of app dir to be extracted in the root of sync dir. return this.fullSync({ projectData: liveSyncInfo.projectData, - device, syncAllFiles: liveSyncInfo.syncAllFiles, + device, liveSyncDeviceInfo: liveSyncInfo.liveSyncDeviceInfo, watch: true, useHotModuleReload: liveSyncInfo.useHotModuleReload diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index f788696fed..d419883710 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,3 +1,179 @@ +import { performanceLog } from "../../common/decorators"; + +export class LiveSyncService implements ILiveSyncService2 { + constructor( + // private $devicesService: Mobile.IDevicesService, + private $errors: IErrors, + private $injector: IInjector, + private $logger: ILogger, + private $mobileHelper: Mobile.IMobileHelper, + ) { } + + public async fullSync(device: Mobile.IDevice, liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { + const platformLiveSyncService = this.getLiveSyncService("ios"); + const liveSyncResultInfo = await platformLiveSyncService.fullSync({ + projectData, + device, + useHotModuleReload: liveSyncInfo.useHotModuleReload, + watch: !liveSyncInfo.skipWatcher, + force: liveSyncInfo.force, + liveSyncDeviceInfo + }); + + return liveSyncResultInfo; + } + + @performanceLog() + public async refreshApplication(liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { + // const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir); + + return liveSyncDeviceInfo && liveSyncDeviceInfo.debugggingEnabled ? + this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) : + this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath); + } + + public async attachDebugger(settings: IAttachDebuggerOptions): Promise { + return null; + // Default values + // if (settings.debugOptions) { + // settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; + // settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; + // } else { + // settings.debugOptions = { + // chrome: true, + // start: true + // }; + // } + + // const projectData = this.$projectDataService.getProjectData(settings.projectDir); + // const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); + + // // Of the properties below only `buildForDevice` and `release` are currently used. + // // Leaving the others with placeholder values so that they may not be forgotten in future implementations. + // const buildConfig = this.getInstallApplicationBuildConfig(settings.deviceIdentifier, settings.projectDir, { isEmulator: settings.isEmulator }); + // debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath); + // const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); + // const result = this.printDebugInformation(debugInfo, settings.debugOptions.forceDebuggerAttachedEvent); + // return result; + } + + @performanceLog() + private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { + debugOptions = debugOptions || {}; + if (debugOptions.debugBrk) { + liveSyncResultInfo.waitForDebugger = true; + } + + const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); + + // 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: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + debugOptions: debugOptions, + }; + + return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); + } + + private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IRefreshApplicationSettings): Promise { + const result = { didRestart: false }; + const platform = liveSyncResultInfo.deviceAppData.platform; + const platformLiveSyncService = this.getLiveSyncService(platform); + const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; + try { + let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); + if (!shouldRestart) { + shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); + } + + if (shouldRestart) { + // const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; + // this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); + await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); + result.didRestart = true; + } + } catch (err) { + 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); + if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { + // this.emitLivesyncEvent(LiveSyncEvents.liveSyncNotification, { + // projectDir: projectData.projectDir, + // applicationIdentifier, + // deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + // notification: msg + // }); + } + + if (settings && settings.shouldCheckDeveloperDiscImage) { + // this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); + } + } + + // this.emitLivesyncEvent(LiveSyncEvents.liveSyncExecuted, { + // projectDir: projectData.projectDir, + // applicationIdentifier, + // syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + // deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + // isFullSync: liveSyncResultInfo.isFullSync + // }); + + return result; + } + + @performanceLog() + private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + return null; + // const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); + // if (!currentDeviceDescriptor) { + // this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); + // } + + // currentDeviceDescriptor.debugggingEnabled = true; + // currentDeviceDescriptor.debugOptions = deviceOption.debugOptions; + // const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); + // const attachDebuggerOptions: IAttachDebuggerOptions = { + // deviceIdentifier: deviceOption.deviceIdentifier, + // isEmulator: currentDeviceInstance.isEmulator, + // outputPath: currentDeviceDescriptor.outputPath, + // platform: currentDeviceInstance.deviceInfo.platform, + // projectDir: debuggingAdditionalOptions.projectDir, + // debugOptions: deviceOption.debugOptions + // }; + + // let debugInformation: IDebugInformation; + // try { + // debugInformation = await this.attachDebugger(attachDebuggerOptions); + // } catch (err) { + // this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); + // attachDebuggerOptions.debugOptions.start = false; + // try { + // debugInformation = await this.attachDebugger(attachDebuggerOptions); + // } catch (innerErr) { + // this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); + // throw err; + // } + // } + + // return debugInformation; + } + + private getLiveSyncService(platform: string): IPlatformLiveSyncService { + if (this.$mobileHelper.isiOSPlatform(platform)) { + return this.$injector.resolve("iOSLiveSyncService"); + } else if (this.$mobileHelper.isAndroidPlatform(platform)) { + return this.$injector.resolve("androidLiveSyncService"); + } + + this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); + } +} +$injector.register("liveSyncService", LiveSyncService); + // // import * as path from "path"; // // import * as choki from "chokidar"; // import { EOL } from "os"; @@ -900,3 +1076,4 @@ // } // $injector.register("usbLiveSyncService", DeprecatedUsbLiveSyncService); + diff --git a/lib/services/platform/platform-build-service.ts b/lib/services/platform/platform-build-service.ts index e4f70aa6d0..7a90b4b69e 100644 --- a/lib/services/platform/platform-build-service.ts +++ b/lib/services/platform/platform-build-service.ts @@ -70,5 +70,20 @@ export class PlatformBuildService extends EventEmitter implements IPlatformBuild this.$fs.writeJson(buildInfoFile, buildInfo); } + + public getBuildInfoFromFile(platformData: IPlatformData, buildConfig: IBuildConfig, buildOutputPath?: string): IBuildInfo { + buildOutputPath = buildOutputPath || platformData.getBuildOutputPath(buildConfig); + const buildInfoFile = path.join(buildOutputPath, buildInfoFileName); + if (this.$fs.exists(buildInfoFile)) { + try { + const buildInfo = this.$fs.readJson(buildInfoFile); + return buildInfo; + } catch (e) { + return null; + } + } + + return null; + } } $injector.register("platformBuildService", PlatformBuildService); diff --git a/lib/services/platform/platform-watcher-service.ts b/lib/services/platform/platform-watcher-service.ts index 92757ad296..f9210b343d 100644 --- a/lib/services/platform/platform-watcher-service.ts +++ b/lib/services/platform/platform-watcher-service.ts @@ -48,6 +48,7 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat private async startJsWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { this.$webpackCompilerService.on("webpackEmittedFiles", files => { + console.log("RECEIVED webpackEmittedFiles ================="); this.emitFilesChangeEvent({ files, hasNativeChange: false }); }); @@ -86,6 +87,7 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat } private emitFilesChangeEvent(filesChangeData: IFilesChangeData) { + console.log("================ emitFilesChangeEvent ================ ", this.isInitialSyncEventEmitted); if (this.isInitialSyncEventEmitted) { this.emit("fileChangeData", filesChangeData); } else { diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index e465bdd769..8f4554c0b9 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -3,15 +3,22 @@ import * as child_process from "child_process"; import { EventEmitter } from "events"; export class WebpackCompilerService extends EventEmitter implements IWebpackCompilerService { + private webpackProcesses: IDictionary = {}; + constructor( private $childProcess: IChildProcess ) { super(); } - // TODO: Consider to introduce two methods -> compile and startWebpackWatcher - // TODO: persist webpack per platform and persist watchers per projectDir + // TODO: Rename this to compileWithWatch() public async startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { return new Promise((resolve, reject) => { + if (this.webpackProcesses[platformData.platformNameLowerCase]) { + resolve(); + return; + } + let isFirstWebpackWatchCompilation = true; + config.watch = true; const childProcess = this.startWebpackProcess(platformData, projectData, config); childProcess.on("message", (message: any) => { @@ -46,9 +53,15 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp }); } + // TODO: Rename this to compileWithoutWatch() public async compile(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { - const childProcess = this.startWebpackProcess(platformData, projectData, config); return new Promise((resolve, reject) => { + if (this.webpackProcesses[platformData.platformNameLowerCase]) { + resolve(); + return; + } + + const childProcess = this.startWebpackProcess(platformData, projectData, config); childProcess.on("close", (arg: any) => { const exitCode = typeof arg === "number" ? arg : arg && arg.code; console.log("=========== WEBPACK EXIT WITH CODE ========== ", exitCode); @@ -80,6 +93,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const stdio = config.watch ? ["inherit", "inherit", "inherit", "ipc"] : "inherit"; const childProcess = this.$childProcess.spawn("node", args, { cwd: projectData.projectDir, stdio }); + this.webpackProcesses[platformData.platformNameLowerCase] = childProcess; + return childProcess; } } diff --git a/lib/services/workflow/device-workflow-service.ts b/lib/services/workflow/device-workflow-service.ts new file mode 100644 index 0000000000..3f8ff48f06 --- /dev/null +++ b/lib/services/workflow/device-workflow-service.ts @@ -0,0 +1,139 @@ +import * as constants from "../../constants"; +import * as helpers from "../../common/helpers"; +import * as path from "path"; +import * as shell from "shelljs"; +import * as temp from "temp"; + +// TODO: Extract it as common constant for this service and platform-build-service.ts +const buildInfoFileName = ".nsbuildinfo"; + +export class DeviceWorkflowService implements IDeviceWorkflowService { + constructor( + private $analyticsService: IAnalyticsService, + private $devicePathProvider: IDevicePathProvider, + private $fs: IFileSystem, + private $logger: ILogger, + private $mobileHelper: Mobile.IMobileHelper, + private $platformBuildService: IPlatformBuildService + ) { } + + // TODO: Extract this method to device-installation-service + public async installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise { + this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: constants.TrackActionNames.Deploy, + device, + projectDir: projectData.projectDir + }); + + // TODO: Get latest built applicationPackage when no applicationPackage is provided + // const packageFile = applicationPackage.packageName; + // const outputFilePath = applicationPackage.packagePath; + + // if (!packageFile) { + // if (this.$devicesService.isiOSSimulator(device)) { + // packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig, outputFilePath).packageName; + // } else { + // packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig, outputFilePath).packageName; + // } + // } + + await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); + + const platform = device.deviceInfo.platform.toLowerCase(); + await device.applicationManager.reinstallApplication(projectData.projectIdentifiers[platform], packageFile); + + await this.updateHashesOnDevice({ + device, + appIdentifier: projectData.projectIdentifiers[platform], + outputFilePath, + platformData + }); + + if (!buildConfig.release) { + const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); + const options = buildConfig; + options.buildForDevice = !device.isEmulator; + const buildInfoFilePath = outputFilePath || platformData.getBuildOutputPath(buildConfig); + const appIdentifier = projectData.projectIdentifiers[platform]; + + await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); + } + + this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); + } + + public async installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise { + const shouldInstall = await this.shouldInstall(device, platformData, projectData, buildConfig); + if (shouldInstall) { + await this.installOnDevice(device, platformData, projectData, buildConfig, packageFile, outputFilePath); + } + } + + private async updateHashesOnDevice(data: { device: Mobile.IDevice, appIdentifier: string, outputFilePath: string, platformData: IPlatformData }): Promise { + const { device, appIdentifier, platformData, outputFilePath } = data; + + if (!this.$mobileHelper.isAndroidPlatform(platformData.normalizedPlatformName)) { + return; + } + + let hashes = {}; + const hashesFilePath = path.join(outputFilePath || platformData.getBuildOutputPath(null), constants.HASHES_FILE_NAME); + if (this.$fs.exists(hashesFilePath)) { + hashes = this.$fs.readJson(hashesFilePath); + } + + await device.fileSystem.updateHashesOnDevice(hashes, appIdentifier); + } + + private async getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise { + const platform = device.deviceInfo.platform.toLowerCase(); + const deviceRootPath = await this.$devicePathProvider.getDeviceProjectRootPath(device, { + appIdentifier: projectData.projectIdentifiers[platform], + getDirname: true + }); + return helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, buildInfoFileName)); + } + + private async shouldInstall(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, release: IRelease, outputPath?: string): Promise { + const platform = device.deviceInfo.platform; + if (!(await device.applicationManager.isApplicationInstalled(projectData.projectIdentifiers[platform.toLowerCase()]))) { + return true; + } + + const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); + const localBuildInfo = this.$platformBuildService.getBuildInfoFromFile(platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); + + return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; + } + + private async getDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData): Promise { + const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); + try { + return JSON.parse(await this.readFile(device, deviceFilePath, projectData)); + } catch (e) { + return null; + } + } + + public async readFile(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise { + temp.track(); + const uniqueFilePath = temp.path({ suffix: ".tmp" }); + const platform = device.deviceInfo.platform.toLowerCase(); + try { + await device.fileSystem.getFile(deviceFilePath, projectData.projectIdentifiers[platform], uniqueFilePath); + } catch (e) { + return null; + } + + if (this.$fs.exists(uniqueFilePath)) { + const text = this.$fs.readText(uniqueFilePath); + shell.rm(uniqueFilePath); + return text; + } + + return null; + } +} +$injector.register("deviceWorkflowService", DeviceWorkflowService); diff --git a/lib/services/workflow/platform-workflow-service.ts b/lib/services/workflow/platform-workflow-service.ts index 8692ba70a7..d6cf2aa629 100644 --- a/lib/services/workflow/platform-workflow-service.ts +++ b/lib/services/workflow/platform-workflow-service.ts @@ -3,6 +3,7 @@ import { NativePlatformStatus } from "../../constants"; export class PlatformWorkflowService implements IPlatformWorkflowService { constructor ( + private $buildArtefactsService: IBuildArtefactsService, private $fs: IFileSystem, private $platformAddService: IPlatformAddService, private $platformBuildService: IPlatformBuildService, @@ -10,6 +11,15 @@ export class PlatformWorkflowService implements IPlatformWorkflowService { private $projectChangesService: IProjectChangesService ) { } + public async addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise { + const { platformParam, frameworkPath, nativePrepare } = workflowData; + + const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, nativePrepare); + if (shouldAddPlatform) { + await this.$platformAddService.addPlatform({ platformParam, frameworkPath, nativePrepare }, projectData); + } + } + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise { await this.addPlatformIfNeeded(platformData, projectData, workflowData); await this.$platformService.preparePlatform(platformData, projectData, workflowData); @@ -22,18 +32,18 @@ export class PlatformWorkflowService implements IPlatformWorkflowService { return result; } - public async runPlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData) { - return; - // await this.buildPlatformIfNeeded() - } + public async buildPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData, buildConfig: IBuildConfig, outputPath?: string): Promise { + await this.preparePlatform(platformData, projectData, workflowData); - private async addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise { - const { platformParam, frameworkPath, nativePrepare } = workflowData; + let result = null; - const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, nativePrepare); - if (shouldAddPlatform) { - await this.$platformAddService.addPlatform({ platformParam, frameworkPath, nativePrepare }, projectData); + outputPath = outputPath || platformData.getBuildOutputPath(buildConfig); + const shouldBuildPlatform = await this.shouldBuildPlatform(platformData, projectData, buildConfig, outputPath); + if (shouldBuildPlatform) { + result = await this.$platformBuildService.buildPlatform(platformData, projectData, buildConfig); } + + return result; } private shouldAddPlatform(platformData: IPlatformData, projectData: IProjectData, nativePrepare: INativePrepare): boolean { @@ -46,5 +56,42 @@ export class PlatformWorkflowService implements IPlatformWorkflowService { return !!result; } + + private async shouldBuildPlatform(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, outputPath: string): Promise { + const platform = platformData.platformNameLowerCase; + if (buildConfig.release && this.$projectChangesService.currentChanges.hasChanges) { + return true; + } + + if (this.$projectChangesService.currentChanges.changesRequireBuild) { + return true; + } + + if (!this.$fs.exists(outputPath)) { + return true; + } + + const validBuildOutputData = platformData.getValidBuildOutputData(buildConfig); + const packages = this.$buildArtefactsService.getAllBuiltApplicationPackages(outputPath, validBuildOutputData); + if (packages.length === 0) { + return true; + } + + const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + const buildInfo = this.$platformBuildService.getBuildInfoFromFile(platformData, buildConfig, outputPath); + if (!prepareInfo || !buildInfo) { + return true; + } + + if (buildConfig.clean) { + return true; + } + + if (prepareInfo.time === buildInfo.prepareTime) { + return false; + } + + return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; + } } $injector.register("platformWorkflowService", PlatformWorkflowService); diff --git a/lib/services/workflow/workflow.d.ts b/lib/services/workflow/workflow.d.ts index e6751a11c7..dfbe8698ba 100644 --- a/lib/services/workflow/workflow.d.ts +++ b/lib/services/workflow/workflow.d.ts @@ -7,10 +7,17 @@ interface IPlatformWorkflowData extends IRelease, IHasUseHotModuleReloadOption { } interface IPlatformWorkflowService { + addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise; preparePlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise; buildPlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData, buildConfig: IBuildConfig): Promise; + buildPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData, buildConfig: IBuildConfig, outputPath?: string): Promise; } interface IPlatformWorkflowDataFactory { createPlatformWorkflowData(platformParam: string, options: IOptions, nativePrepare?: INativePrepare): IPlatformWorkflowData; +} + +interface IDeviceWorkflowService { + installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise; + installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise; } \ No newline at end of file From 3e446064baf1c072fd1685ac99f505da756853b0 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 1 May 2019 16:05:10 +0300 Subject: [PATCH 04/30] feat: introduce workflowDataService in order to handle the creation of all data related objects in one place --- lib/bootstrap.ts | 8 +- lib/commands/appstore-upload.ts | 90 ++--- lib/commands/build.ts | 30 +- lib/commands/prepare.ts | 5 +- lib/common/definitions/mobile.d.ts | 1 + lib/common/mobile/mobile-helper.ts | 22 ++ lib/constants.ts | 3 + lib/declarations.d.ts | 11 +- lib/definitions/livesync.d.ts | 5 +- lib/definitions/platform.d.ts | 138 -------- lib/definitions/project-changes.d.ts | 18 - lib/definitions/project.d.ts | 116 ------- lib/factory/platform-workflow-data-factory.ts | 54 --- lib/helpers/livesync-command-helper.ts | 16 +- lib/services/android-project-service.ts | 14 +- lib/services/bundle-workflow-service.ts | 161 +++++---- .../device/device-installation-service.ts | 111 +++++++ .../device-restart-application-service.ts | 82 +++++ lib/services/ios-project-service.ts | 25 +- lib/services/livesync/livesync-service.ts | 46 ++- lib/services/local-build-service.ts | 18 +- lib/services/platform-service.ts | 23 +- lib/services/platform/platform-add-service.ts | 24 +- .../platform/platform-build-service.ts | 67 +++- .../platform/platform-commands-service.ts | 9 +- .../platform/platform-watcher-service.ts | 107 +++--- lib/services/prepare-platform-js-service.ts | 3 +- .../prepare-platform-native-service.ts | 29 +- lib/services/project-changes-service.ts | 71 ++-- .../webpack/webpack-compiler-service.ts | 7 +- lib/services/webpack/webpack.d.ts | 307 ++++++++++++++++-- .../workflow/device-workflow-service.ts | 139 -------- .../workflow/platform-workflow-service.ts | 99 +----- .../workflow/workflow-data-service.ts | 100 ++++++ lib/services/workflow/workflow.d.ts | 23 +- test/project-changes-service.ts | 90 ++--- test/services/android-project-service.ts | 8 +- test/services/bundle-workflow-service.ts | 204 ++++++++++++ .../platform/platform-watcher-service.ts | 123 +++++++ test/stubs.ts | 13 +- 40 files changed, 1417 insertions(+), 1003 deletions(-) delete mode 100644 lib/factory/platform-workflow-data-factory.ts create mode 100644 lib/services/device/device-restart-application-service.ts delete mode 100644 lib/services/workflow/device-workflow-service.ts create mode 100644 lib/services/workflow/workflow-data-service.ts create mode 100644 test/services/bundle-workflow-service.ts create mode 100644 test/services/platform/platform-watcher-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 0c2e3e02fa..6c2da39e32 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -42,13 +42,13 @@ $injector.require("platformValidationService", "./services/platform/platform-val $injector.require("platformCommandsService", "./services/platform/platform-commands-service"); $injector.require("platformWatcherService", "./services/platform/platform-watcher-service"); +$injector.require("deviceInstallationService", "./services/device/device-installation-service"); +$injector.require("deviceRestartApplicationService", "./services/device/device-restart-application-service"); + $injector.require("platformWorkflowService", "./services/workflow/platform-workflow-service"); -$injector.require("deviceWorkflowService", "./services/workflow/device-workflow-service"); -$injector.require("runWorkflowService", "./services/workflow/run-workflow-service"); +$injector.require("workflowDataService", "./services/workflow/workflow-data-service"); $injector.require("bundleWorkflowService", "./services/bundle-workflow-service"); -$injector.require("platformWorkflowDataFactory", "./factory/platform-workflow-data-factory"); - $injector.require("buildArtefactsService", "./services/build-artefacts-service"); $injector.require("debugDataService", "./services/debug-data-service"); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 730522545f..1c63262c04 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -14,27 +14,28 @@ export class PublishIOS implements ICommand { private $prompter: IPrompter, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $platformValidationService: IPlatformValidationService, - private $platformBuildService: IPlatformBuildService, - private $xcodebuildService: IXcodebuildService) { + // private $platformBuildService: IPlatformBuildService, + // private $xcodebuildService: IXcodebuildService + ) { this.$projectData.initializeProjectData(); } - private get $platformsData(): IPlatformsData { - return this.$injector.resolve("platformsData"); - } + // private get $platformsData(): IPlatformsData { + // return this.$injector.resolve("platformsData"); + // } // This property was introduced due to the fact that the $platformService dependency // ultimately tries to resolve the current project's dir and fails if not executed from within a project - private get $platformService(): IPlatformService { - return this.$injector.resolve("platformService"); - } + // private get $platformService(): IPlatformService { + // return this.$injector.resolve("platformService"); + // } public async execute(args: string[]): Promise { let username = args[0]; let password = args[1]; const mobileProvisionIdentifier = args[2]; const codeSignIdentity = args[3]; - let ipaFilePath = this.$options.ipa ? path.resolve(this.$options.ipa) : null; + const ipaFilePath = this.$options.ipa ? path.resolve(this.$options.ipa) : null; if (!username) { username = await this.$prompter.getString("Apple ID", { allowEmpty: false }); @@ -55,40 +56,45 @@ export class PublishIOS implements ICommand { this.$options.release = true; if (!ipaFilePath) { - const platform = this.$devicePlatformsConstants.iOS; + // const platform = this.$devicePlatformsConstants.iOS; // No .ipa path provided, build .ipa on out own. - const preparePlatformData: IPreparePlatformData = { - release: this.$options.release, - useHotModuleReload: false, - env: this.$options.env, - }; - const buildConfig: IBuildConfig = { - projectDir: this.$options.path, - release: this.$options.release, - device: this.$options.device, - provision: this.$options.provision, - teamId: this.$options.teamId, - buildForDevice: true, - iCloudContainerEnvironment: this.$options.iCloudContainerEnvironment, - mobileProvisionIdentifier, - codeSignIdentity - }; - - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); - - if (mobileProvisionIdentifier || codeSignIdentity) { - this.$logger.info("Building .ipa with the selected mobile provision and/or certificate."); - // This is not very correct as if we build multiple targets we will try to sign all of them using the signing identity here. - await this.$platformService.preparePlatform(platformData, this.$projectData, preparePlatformData); - await this.$platformBuildService.buildPlatform(platformData, this.$projectData, buildConfig); - ipaFilePath = this.$platformService.lastOutputPath(platform, buildConfig, this.$projectData); - } else { - this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); - await this.$platformService.preparePlatform(platformData, this.$projectData, preparePlatformData); - - ipaFilePath = await this.$xcodebuildService.buildForAppStore(platformData, this.$projectData, buildConfig); - this.$logger.info(`Export at: ${ipaFilePath}`); - } + // const platformWorkflowData = { + // release: this.$options.release, + // useHotModuleReload: false, + // env: this.$options.env, + // platformParam: platform, + // signingOptions: { + // teamId: this.$options.teamId, + // provision: this.$options.provision + // } + // }; + // const buildConfig: IBuildConfig = { + // projectDir: this.$options.path, + // release: this.$options.release, + // device: this.$options.device, + // provision: this.$options.provision, + // teamId: this.$options.teamId, + // buildForDevice: true, + // iCloudContainerEnvironment: this.$options.iCloudContainerEnvironment, + // mobileProvisionIdentifier, + // codeSignIdentity + // }; + + // const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + + // if (mobileProvisionIdentifier || codeSignIdentity) { + // this.$logger.info("Building .ipa with the selected mobile provision and/or certificate."); + // // This is not very correct as if we build multiple targets we will try to sign all of them using the signing identity here. + // await this.$platformService.preparePlatform(platformData, this.$projectData, platformWorkflowData); + // await this.$platformBuildService.buildPlatform(platformData, this.$projectData, buildConfig); + // ipaFilePath = this.$platformService.lastOutputPath(platform, buildConfig, this.$projectData); + // } else { + // this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); + // await this.$platformService.preparePlatform(platformData, this.$projectData, platformWorkflowData); + + // ipaFilePath = await this.$xcodebuildService.buildForAppStore(platformData, this.$projectData, buildConfig); + // this.$logger.info(`Export at: ${ipaFilePath}`); + // } } await this.$itmsTransporterService.upload({ diff --git a/lib/commands/build.ts b/lib/commands/build.ts index eb40694acb..7f7dc6a4b2 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -7,8 +7,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { $projectData: IProjectData, $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - protected $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, - private $platformWorkflowService: IPlatformWorkflowService, + protected $platformWorkflowService: IPlatformWorkflowService, $platformValidationService: IPlatformValidationService, private $bundleValidatorHelper: IBundleValidatorHelper, protected $logger: ILogger) { @@ -18,26 +17,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { public async executeCore(args: string[]): Promise { const platform = args[0].toLowerCase(); - - const buildConfig: IBuildConfig = { - buildForDevice: this.$options.forDevice, - iCloudContainerEnvironment: this.$options.iCloudContainerEnvironment, - projectDir: this.$options.path, - clean: this.$options.clean, - teamId: this.$options.teamId, - device: this.$options.device, - provision: this.$options.provision, - release: this.$options.release, - keyStoreAlias: this.$options.keyStoreAlias, - keyStorePath: this.$options.keyStorePath, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword, - androidBundle: this.$options.aab - }; - - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); - const workflowData = this.$platformWorkflowDataFactory.createPlatformWorkflowData(platform, this.$options); - const outputPath = await this.$platformWorkflowService.buildPlatform(platformData, this.$projectData, workflowData, buildConfig); + const outputPath = await this.$platformWorkflowService.buildPlatform(platform, this.$projectData.projectDir, this.$options); return outputPath; } @@ -76,12 +56,11 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, $platformWorkflowService: IPlatformWorkflowService, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformWorkflowDataFactory, $platformWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); } public async execute(args: string[]): Promise { @@ -112,13 +91,12 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, $platformWorkflowService: IPlatformWorkflowService, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, protected $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformWorkflowDataFactory, $platformWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); } public async execute(args: string[]): Promise { diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 108af3b7d7..7963d015c3 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -4,7 +4,6 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm public allowedParameters = [this.$platformCommandParameter]; constructor($options: IOptions, - private $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, private $platformWorkflowService: IPlatformWorkflowService, $platformValidationService: IPlatformValidationService, $projectData: IProjectData, @@ -16,10 +15,8 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm public async execute(args: string[]): Promise { const platform = args[0]; - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); - const workflowData = this.$platformWorkflowDataFactory.createPlatformWorkflowData(platform, this.$options); - await this.$platformWorkflowService.preparePlatform(platformData, this.$projectData, workflowData); + await this.$platformWorkflowService.preparePlatform(platform, this.$projectData.projectDir, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 9f0a5bd788..d426813999 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -983,6 +983,7 @@ declare module Mobile { buildDevicePath(...args: string[]): string; correctDevicePath(filePath: string): string; isiOSTablet(deviceName: string): boolean; + getDeviceFileContent(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise; } interface IEmulatorHelper { diff --git a/lib/common/mobile/mobile-helper.ts b/lib/common/mobile/mobile-helper.ts index 18ab7b6341..3d2ff846cf 100644 --- a/lib/common/mobile/mobile-helper.ts +++ b/lib/common/mobile/mobile-helper.ts @@ -1,9 +1,12 @@ import * as helpers from "../helpers"; +import * as shell from "shelljs"; +import * as temp from "temp"; export class MobileHelper implements Mobile.IMobileHelper { private static DEVICE_PATH_SEPARATOR = "/"; constructor(private $errors: IErrors, + private $fs: IFileSystem, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) { } public get platformNames(): string[] { @@ -58,5 +61,24 @@ export class MobileHelper implements Mobile.IMobileHelper { public isiOSTablet(deviceName: string): boolean { return deviceName && deviceName.toLowerCase().indexOf("ipad") !== -1; } + + public async getDeviceFileContent(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise { + temp.track(); + const uniqueFilePath = temp.path({ suffix: ".tmp" }); + const platform = device.deviceInfo.platform.toLowerCase(); + try { + await device.fileSystem.getFile(deviceFilePath, projectData.projectIdentifiers[platform], uniqueFilePath); + } catch (e) { + return null; + } + + if (this.$fs.exists(uniqueFilePath)) { + const text = this.$fs.readText(uniqueFilePath); + shell.rm(uniqueFilePath); + return text; + } + + return null; + } } $injector.register("mobileHelper", MobileHelper); diff --git a/lib/constants.ts b/lib/constants.ts index ccd1ae5053..6a9638b8f5 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -139,6 +139,9 @@ export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; export const ANDROID_RELEASE_BUILD_ERROR_MESSAGE = "When producing a release build, you need to specify all --key-store-* options."; export const CACACHE_DIRECTORY_NAME = "_cacache"; +export const FILES_CHANGE_EVENT_NAME = "filesChangeEventData"; +export const INITIAL_SYNC_EVENT_NAME = "initialSyncEventData"; + export class DebugCommandErrors { public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them."; public static NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS = "Unable to find device or emulator for specified options."; diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e7985922ff..f14fbcaecc 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -1062,7 +1062,8 @@ interface IBuildArtefactsService { } interface IPlatformAddService { - addPlatform(addPlatformData: IAddPlatformData, projectData: IProjectData): Promise; + addPlatform(projectData: IProjectData, addPlatformData: any): Promise; + addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, addPlatformData: any): Promise; } interface IPlatformCommandsService { @@ -1079,10 +1080,4 @@ interface IAddPlatformData { platformParam: string; frameworkPath?: string; nativePrepare?: INativePrepare; -} - -interface IPlatformBuildService { - buildPlatform(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; - saveBuildInfoFile(platformData: IPlatformData, projectData: IProjectData, buildInfoFileDirname: string): void; - getBuildInfoFromFile(platformData: IPlatformData, buildConfig: IBuildConfig, buildOutputPath?: string): IBuildInfo; -} +} \ No newline at end of file diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 827098a53f..9c8cb05312 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -264,8 +264,10 @@ declare global { getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; } + // TODO: Rename this interface and change method's definition interface ILiveSyncService2 { - fullSync(device: Mobile.IDevice, deviceBuildInfoDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise; + syncInitialDataOnDevice(device: Mobile.IDevice, deviceBuildInfoDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise; + syncChangedDataOnDevice(device: Mobile.IDevice, filesToSync: string[], liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise; } /** @@ -359,7 +361,6 @@ declare global { filesToRemove: string[]; filesToSync: string[]; isReinstalled: boolean; - syncAllFiles: boolean; liveSyncDeviceInfo: ILiveSyncDeviceInfo; hmrData: IPlatformHmrData; force?: boolean; diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index bf0ee2e0a9..d275a1ae66 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -14,143 +14,6 @@ interface IBuildPlatformAction { buildPlatform(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): Promise; } -interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { - /** - * Ensures that the specified platform and its dependencies are installed. - * When there are changes to be prepared, it prepares the native project for the specified platform. - * When finishes, prepare saves the .nsprepareinfo file in platform folder. - * This file contains information about current project configuration and allows skipping unnecessary build, deploy and livesync steps. - * @param {IPreparePlatformInfo} platformInfo Options to control the preparation. - * @returns {boolean} true indicates that the platform was prepared. - */ - preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise; - - /** - * Determines whether a build is necessary. A build is necessary when one of the following is true: - * - there is no previous build. - * - the .nsbuildinfo file in product folder points to an old prepare. - * @param {string} platform The platform to build. - * @param {IProjectData} projectData DTO with information about the project. - * @param {IBuildConfig} @optional buildConfig Indicates whether the build is for device or emulator. - * @param {string} @optional outputPath Directory containing build information and artifacts. - * @returns {boolean} true indicates that the platform should be build. - */ - shouldBuild(platform: string, projectData: IProjectData, buildConfig?: IBuildConfig, outputPath?: string): Promise; - - /** - * Determines whether installation is necessary. It is necessary when one of the following is true: - * - the application is not installed. - * - the .nsbuildinfo file located in application root folder is different than the local .nsbuildinfo file - * @param {Mobile.IDevice} device The device where the application should be installed. - * @param {IProjectData} projectData DTO with information about the project. - * @param {string} @optional outputPath Directory containing build information and artifacts. - * @returns {Promise} true indicates that the application should be installed. - */ - shouldInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise; - - /** - * - * @param {Mobile.IDevice} device The device where the application should be installed. - * @param {IProjectData} projectData DTO with information about the project. - * @param {string} @optional outputPath Directory containing build information and artifacts. - */ - validateInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise; - - /** - * Installs the application on specified device. - * When finishes, saves .nsbuildinfo in application root folder to indicate the prepare that was used to build the app. - * * .nsbuildinfo is not persisted when building for release. - * @param {Mobile.IDevice} device The device where the application should be installed. - * @param {IBuildConfig} options The build configuration. - * @param {string} @optional pathToBuiltApp Path to build artifact. - * @param {string} @optional outputPath Directory containing build information and artifacts. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - installApplication(device: Mobile.IDevice, options: IBuildConfig, projectData: IProjectData, pathToBuiltApp?: string, outputPath?: string): Promise; - - /** - * Executes prepare, build and installOnPlatform when necessary to ensure that the latest version of the app is installed on specified platform. - * - When --clean option is specified it builds the app on every change. If not, build is executed only when there are native changes. - * @param {IDeployPlatformInfo} deployInfo Options required for project preparation and deployment. - * @returns {void} - */ - deployPlatform(deployInfo: IDeployPlatformInfo): Promise; - - /** - * Runs the application on specified platform. Assumes that the application is already build and installed. Fails if this is not true. - * @param {string} platform The platform where to start the application. - * @param {IRunPlatformOptions} runOptions Various options that help manage the run operation. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - startApplication(platform: string, runOptions: IRunPlatformOptions, appData: Mobile.IStartApplicationData): Promise; - - /** - * Returns information about the latest built application for device in the current project. - * @param {IPlatformData} platformData Data describing the current platform. - * @param {IBuildConfig} buildConfig Defines if the build is for release configuration. - * @param {string} @optional outputPath Directory that should contain the build artifact. - * @returns {IApplicationPackage} Information about latest built application. - */ - getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage; - - /** - * Returns information about the latest built application for simulator in the current project. - * @param {IPlatformData} platformData Data describing the current platform. - * @param {IBuildConfig} buildConfig Defines if the build is for release configuration. - * @param {string} @optional outputPath Directory that should contain the build artifact. - * @returns {IApplicationPackage} Information about latest built application. - */ - getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage; - - /** - * Copies latest build output to a specified location. - * @param {string} platform Mobile platform - Android, iOS. - * @param {string} targetPath Destination where the build artifact should be copied. - * @param {IBuildConfig} buildConfig Defines if the searched artifact should be for simulator and is it built for release. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - copyLastOutput(platform: string, targetPath: string, buildConfig: IBuildConfig, projectData: IProjectData): void; - - /** - * Gets the latest build output. - * @param {string} platform Mobile platform - Android, iOS. - * @param {IBuildConfig} buildConfig Defines if the searched artifact should be for simulator and is it built for release. - * @param {IProjectData} projectData DTO with information about the project. - * @param {string} @optional outputPath Directory that should contain the build artifact. - * @returns {string} The path to latest built artifact. - */ - lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData, outputPath?: string): string; - - /** - * Reads contents of a file on device. - * @param {Mobile.IDevice} device The device to read from. - * @param {string} deviceFilePath The file path. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {string} The contents of the file or null when there is no such file. - */ - readFile(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise; - - /** - * Saves build information in a proprietary file. - * @param {string} platform The build platform. - * @param {string} projectDir The project's directory. - * @param {string} buildInfoFileDirname The directory where the build file should be written to. - * @returns {void} - */ - saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void; - - /** - * Gives information for the current version of the runtime. - * @param {string} platform The platform to be checked. - * @param {IProjectData} projectData The data describing the project - * @returns {string} Runtime version - */ - getCurrentPlatformVersion(platform: string, projectData: IProjectData): string; -} - interface IPlatformOptions extends IPlatformSpecificData, ICreateProjectOptions { } /** @@ -176,7 +39,6 @@ interface IPlatformData { platformNameLowerCase: string; appDestinationDirectoryPath: string; getBuildOutputPath(options: IBuildOutputOptions): string; - bundleBuildOutputPath?: string; getValidBuildOutputData(buildOptions: IBuildOutputOptions): IValidBuildOutputData; frameworkFilesExtensions: string[]; frameworkDirectoriesExtensions?: string[]; diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index b3ad6137c3..8841e98b41 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -25,24 +25,6 @@ interface IProjectChangesInfo extends IAddedNativePlatform { readonly changesRequirePrepare: boolean; } -interface IProjectChangesOptions extends IRelease, IHasUseHotModuleReloadOption { - signingOptions: IiOSSigningOptions | IAndroidSigningOptions; - nativePlatformStatus?: "1" | "2" | "3"; -} - -interface ICheckForChangesOptions extends IPlatform, IProjectDataComposition { - projectChangesOptions: IProjectChangesOptions; -} - -interface IProjectChangesService { - checkForChanges(checkForChangesOpts: ICheckForChangesOptions): Promise; - getPrepareInfo(platform: string, projectData: IProjectData): IPrepareInfo; - savePrepareInfo(platform: string, projectData: IProjectData): void; - getPrepareInfoFilePath(platform: string, projectData: IProjectData): string; - setNativePlatformStatus(platform: string, projectData: IProjectData, nativePlatformStatus: IAddedNativePlatform): void; - currentChanges: IProjectChangesInfo; -} - /** * NativePlatformStatus.requiresPlatformAdd | NativePlatformStatus.requiresPrepare | NativePlatformStatus.alreadyPrepared */ diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 5d336a0060..117a77eaef 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -353,122 +353,6 @@ interface ILocalBuildService { interface ICleanNativeAppData extends IProjectDir, IPlatform { } -interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectServiceBase { - getPlatformData(projectData: IProjectData): IPlatformData; - validate(projectData: IProjectData, options: IOptions, notConfiguredEnvOptions?: INotConfiguredEnvOptions): Promise; - createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise; - interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; - interpolateConfigurationFile(projectData: IProjectData, signingOptions: IiOSSigningOptions | IAndroidSigningOptions): void; - - /** - * Executes additional actions after native project is created. - * @param {string} projectRoot Path to the real NativeScript project. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - afterCreateProject(projectRoot: string, projectData: IProjectData): void; - - /** - * Gets first chance to validate the options provided as command line arguments. - * @param {string} projectId Project identifier - for example org.nativescript.test. - * @param {any} provision UUID of the provisioning profile used in iOS option validation. - * @returns {void} - */ - validateOptions(projectId?: string, provision?: true | string, teamId?: true | string): Promise; - - buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise; - - /** - * Prepares images in Native project (for iOS). - * @param {IProjectData} projectData DTO with information about the project. - * @param {any} platformSpecificData Platform specific data required for project preparation. - * @returns {void} - */ - prepareProject(projectData: IProjectData, signingOptions: IiOSSigningOptions | IAndroidSigningOptions): Promise; - - /** - * Prepares App_Resources in the native project by clearing data from other platform and applying platform specific rules. - * @param {string} appResourcesDirectoryPath The place in the native project where the App_Resources are copied first. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void; - - /** - * Defines if current platform is prepared (i.e. if /platforms/ dir exists). - * @param {string} projectRoot The project directory (path where root's package.json is located). - * @param {IProjectData} projectData DTO with information about the project. - * @returns {boolean} True in case platform is prepare (i.e. if /platforms/ dir exists), false otherwise. - */ - isPlatformPrepared(projectRoot: string, projectData: IProjectData): boolean; - - /** - * Checks if current platform can be updated to a newer versions. - * @param {string} newInstalledModuleDir Path to the native project. - * @param {IProjectData} projectData DTO with information about the project. - * @return {boolean} True if platform can be updated. false otherwise. - */ - canUpdatePlatform(newInstalledModuleDir: string, projectData: IProjectData): boolean; - - preparePluginNativeCode(pluginData: IPluginData, options?: any): Promise; - - /** - * Removes native code of a plugin (CocoaPods, jars, libs, src). - * @param {IPluginData} Plugins data describing the plugin which should be cleaned. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise; - - beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise; - - handleNativeDependenciesChange(projectData: IProjectData, opts: IRelease): Promise; - - /** - * Gets the path wheren App_Resources should be copied. - * @returns {string} Path to native project, where App_Resources should be copied. - */ - getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string; - - cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise; - processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean }): Promise; - - /** - * Ensures there is configuration file (AndroidManifest.xml, Info.plist) in app/App_Resources. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - ensureConfigurationFileInAppResources(projectData: IProjectData): void; - - /** - * Stops all running processes that might hold a lock on the filesystem. - * Android: Gradle daemon processes are terminated. - * @param {IPlatformData} platformData The data for the specified platform. - * @returns {void} - */ - stopServices?(projectRoot: string): Promise; - - /** - * Removes build artifacts specific to the platform - * @param {string} projectRoot The root directory of the native project. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - cleanProject?(projectRoot: string, projectData: IProjectData): Promise - - /** - * Check the current state of the project, and validate against the options. - * If there are parts in the project that are inconsistent with the desired options, marks them in the changeset flags. - */ - checkForChanges(changeset: IProjectChangesInfo, signingOptions: IiOSSigningOptions, projectData: IProjectData): Promise; - - /** - * Get the deployment target's version - * Currently implemented only for iOS -> returns the value of IPHONEOS_DEPLOYMENT_TARGET property from xcconfig file - */ - getDeploymentTarget?(projectData: IProjectData): any; -} - interface IValidatePlatformOutput { checkEnvironmentRequirementsOutput: ICheckEnvironmentRequirementsOutput; } diff --git a/lib/factory/platform-workflow-data-factory.ts b/lib/factory/platform-workflow-data-factory.ts deleted file mode 100644 index eb0ca11352..0000000000 --- a/lib/factory/platform-workflow-data-factory.ts +++ /dev/null @@ -1,54 +0,0 @@ -export class PlatformWorkflowDataFactory implements IPlatformWorkflowDataFactory { - public createPlatformWorkflowData(platformParam: string, options: IOptions, nativePrepare?: INativePrepare): IPlatformWorkflowData { - if (platformParam.toLowerCase() === "ios") { - return (new IOSWorkflowData(platformParam, options, nativePrepare)).data; - } else if (platformParam.toLowerCase() === "android") { - return (new AndroidWorkflowData(platformParam, options, nativePrepare)).data; - } else { - throw new Error("Invalid workflowData!!!"); - } - } -} -$injector.register("platformWorkflowDataFactory", PlatformWorkflowDataFactory); - -abstract class WorkflowDataBase { - constructor(protected platformParam: string, protected $options: IOptions, protected nativePrepare?: INativePrepare) { } - - public abstract signingOptions: TSigningOptions; - - public get data() { - return { ...this.baseData, signingOptions: this.signingOptions }; - } - - private baseData = { - platformParam: this.platformParam, - release: this.$options.release, - useHotModuleReload: this.$options.hmr, - env: this.$options.env, - nativePrepare: this.nativePrepare - }; -} - -class AndroidWorkflowData extends WorkflowDataBase { - constructor(platformParam: string, $options: IOptions, nativePrepare?: INativePrepare) { - super(platformParam, $options, nativePrepare); - } - - public signingOptions: IAndroidSigningOptions = { - keyStoreAlias: this.$options.keyStoreAlias, - keyStorePath: this.$options.keyStorePath, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword, - }; -} - -class IOSWorkflowData extends WorkflowDataBase { - constructor(platformParam: string, $options: IOptions, nativePrepare?: INativePrepare) { - super(platformParam, $options, nativePrepare); - } - - public signingOptions: IiOSSigningOptions = { - teamId: this.$options.teamId, - provision: this.$options.provision, - }; -} diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 6f6b49502e..05b624fecf 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -78,8 +78,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { // Now let's take data for each device: const deviceDescriptors: ILiveSyncDeviceInfo[] = devices .map(d => { - let buildAction: IBuildAction; - const buildConfig: IBuildConfig = { buildForDevice: !d.isEmulator, iCloudContainerEnvironment: this.$options.iCloudContainerEnvironment, @@ -95,20 +93,22 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { keyStorePassword: this.$options.keyStorePassword }; - buildAction = additionalOptions && additionalOptions.buildPlatform ? + const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : this.$platformService.buildPlatform.bind(this.$platformService, d.deviceInfo.platform, buildConfig, this.$projectData); + const outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ + platform: d.deviceInfo.platform, + emulator: d.isEmulator, + projectDir: this.$projectData.projectDir + }); + const info: ILiveSyncDeviceInfo = { identifier: d.deviceInfo.identifier, buildAction, debugggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], debugOptions: this.$options, - outputPath: additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ - platform: d.deviceInfo.platform, - emulator: d.isEmulator, - projectDir: this.$projectData.projectDir - }), + outputPath, skipNativePrepare: additionalOptions && additionalOptions.skipNativePrepare, }; diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 427d5b6c61..978c859889 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -6,8 +6,9 @@ import * as projectServiceBaseLib from "./platform-project-service-base"; import { DeviceAndroidDebugBridge } from "../common/mobile/android/device-android-debug-bridge"; import { Configurations, LiveSyncPaths } from "../common/constants"; import { performanceLog } from ".././common/decorators"; +import { PreparePlatformData } from "./workflow/workflow-data-service"; -export class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { +export class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase { private static VALUES_DIRNAME = "values"; private static VALUES_VERSION_DIRNAME_PREFIX = AndroidProjectService.VALUES_DIRNAME + "-v"; private static ANDROID_PLATFORM_NAME = "android"; @@ -48,7 +49,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject normalizedPlatformName: "Android", platformNameLowerCase: "android", appDestinationDirectoryPath: path.join(...appDestinationDirectoryArr), - platformProjectService: this, + platformProjectService: this, projectRoot: projectRoot, getBuildOutputPath: (buildConfig: IBuildConfig) => { if (buildConfig.androidBundle) { @@ -57,7 +58,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return path.join(...deviceBuildOutputArr); }, - bundleBuildOutputPath: path.join(projectRoot, constants.APP_FOLDER_NAME, constants.BUILD_DIR, constants.OUTPUTS_DIR, constants.BUNDLE_DIR), getValidBuildOutputData: (buildOptions: IBuildOutputOptions): IValidBuildOutputData => { const buildMode = buildOptions.release ? Configurations.Release.toLowerCase() : Configurations.Debug.toLowerCase(); @@ -199,13 +199,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - public interpolateConfigurationFile(projectData: IProjectData, signingOptions: IAndroidSigningOptions): void { + public interpolateConfigurationFile(projectData: IProjectData, preparePlatformData: PreparePlatformData): void { const manifestPath = this.getPlatformData(projectData).configurationFilePath; shell.sed('-i', /__PACKAGE__/, projectData.projectIdentifiers.android, manifestPath); - if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { - const sdk = (signingOptions && signingOptions.sdk) || (this.$androidToolsInfo.getToolsInfo().compileSdkVersion || "").toString(); - shell.sed('-i', /__APILEVEL__/, sdk, manifestPath); - } } private getProjectNameFromId(projectData: IProjectData): string { @@ -242,7 +238,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject const platformData = this.getPlatformData(projectData); await this.$gradleBuildService.buildProject(platformData.projectRoot, buildConfig); - const outputPath = buildConfig.androidBundle ? platformData.bundleBuildOutputPath : platformData.getBuildOutputPath(buildConfig); + const outputPath = platformData.getBuildOutputPath(buildConfig); await this.$filesHashService.saveHashesForProject(this._platformData, outputPath); } diff --git a/lib/services/bundle-workflow-service.ts b/lib/services/bundle-workflow-service.ts index b56bce9008..f7a1130c3c 100644 --- a/lib/services/bundle-workflow-service.ts +++ b/lib/services/bundle-workflow-service.ts @@ -1,3 +1,6 @@ +import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME } from "../constants"; +import { WorkflowDataService } from "./workflow/workflow-data-service"; + // import * as path from "path"; // import * as constants from "../constants"; @@ -8,18 +11,21 @@ export class BundleWorkflowService implements IBundleWorkflowService { private liveSyncProcessesInfo: IDictionary = {}; constructor( + private $deviceInstallationService: IDeviceInstallationService, + private $deviceRestartApplicationService: IDeviceRestartApplicationService, private $devicesService: Mobile.IDevicesService, - private $deviceWorkflowService: IDeviceWorkflowService, private $errors: IErrors, - private $liveSyncService: ILiveSyncService2, + private $injector: IInjector, + private $mobileHelper: Mobile.IMobileHelper, // private $fs: IFileSystem, private $logger: ILogger, // private $platformAddService: IPlatformAddService, - private $platformsData: IPlatformsData, + private $platformAddService: IPlatformAddService, + private $platformBuildService: IPlatformBuildService, private $platformWatcherService: IPlatformWatcherService, - private $platformWorkflowService: IPlatformWorkflowService, private $pluginsService: IPluginsService, private $projectDataService: IProjectDataService, + private $workflowDataService: WorkflowDataService // private $projectChangesService: IProjectChangesService ) { } @@ -37,86 +43,87 @@ export class BundleWorkflowService implements IBundleWorkflowService { .uniq() .value(); - const workflowData: IPlatformWorkflowData = { - platformParam: null, - nativePrepare: liveSyncInfo.nativePrepare, - release: liveSyncInfo.release, - useHotModuleReload: liveSyncInfo.useHotModuleReload, - signingOptions: { - teamId: (liveSyncInfo).teamId, - provision: (liveSyncInfo).provision - } - }; - - // Ensure platform is added before starting JS(webpack) and native prepare for (const platform of platforms) { - const platformNameLowerCase = platform.toLowerCase(); - const platformData = this.$platformsData.getPlatformData(platformNameLowerCase, projectData); - workflowData.platformParam = platformNameLowerCase; - await this.$platformWorkflowService.addPlatformIfNeeded(platformData, projectData, workflowData); + const { nativePlatformData, addPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, { ...liveSyncInfo, platformParam: platform }); + await this.$platformAddService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); } this.setLiveSyncProcessInfo(projectDir, liveSyncInfo, deviceDescriptors); - const initalSyncDeviceAction = async (device: Mobile.IDevice): Promise => { - console.log("================== INITIAL SYNC DEVICE ACTION ================"); - const deviceBuildInfoDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - const platform = device.deviceInfo.platform; - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const buildConfig = { - buildForDevice: !device.isEmulator, - device: device.deviceInfo.identifier, - release: liveSyncInfo.release, - clean: liveSyncInfo.clean, - iCloudContainerEnvironment: "", - projectDir: projectData.projectDir, - teamId: null, - provision: null, - }; - const outputPath = deviceBuildInfoDescriptor.outputPath || platformData.getBuildOutputPath(buildConfig); - const packageFilePath = await this.$platformWorkflowService.buildPlatformIfNeeded(platformData, projectData, workflowData, buildConfig, outputPath); - - await this.$deviceWorkflowService.installOnDeviceIfNeeded(device, platformData, projectData, buildConfig, packageFilePath, outputPath); - - await this.$liveSyncService.fullSync(device, deviceBuildInfoDescriptor, projectData, liveSyncInfo); - }; - - // const filesChangeDeviceAction = async (device: Mobile.IDevice): Promise { - // // test - // }; - - this.$platformWatcherService.on("onInitialSync", async () => { // TODO: emit correct initialSyncData -> platform + hasNativeChange - await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(initalSyncDeviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); - }); - this.$platformWatcherService.on("fileChangeData", () => { - console.log("=================== RECEIVED FILES CHANGE ================ "); - // Emitted when webpack compilatation is done and native prepare is done - // console.log("--------- ========= ---------- ", data); - // AddActionToChain - }); - const shouldStartWatcher = !liveSyncInfo.skipWatcher && (liveSyncInfo.syncToPreviewApp || this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length); - if (shouldStartWatcher) { - // TODO: Extract the preparePlatformData to separate variable + this.$platformWatcherService.on(INITIAL_SYNC_EVENT_NAME, async (data: IInitialSyncEventData) => { + const executeAction = async (device: Mobile.IDevice) => { + const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + await this.syncInitialDataOnDevice(device, deviceDescriptor, projectData, liveSyncInfo); + }; + const canExecuteAction = (device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === data.platform.toLowerCase() && _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(executeAction, canExecuteAction)); + }); + this.$platformWatcherService.on(FILES_CHANGE_EVENT_NAME, async (data: IFilesChangeEventData) => { + const executeAction = async (device: Mobile.IDevice) => { + const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + await this.syncChangedDataOnDevice(device, deviceDescriptor, data, projectData, liveSyncInfo); + }; + const canExecuteAction = (device: Mobile.IDevice) => { + const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; + return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); + }; + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(executeAction, canExecuteAction)); + }); + for (const platform of platforms) { - const platformData = this.$platformsData.getPlatformData(platform.toLocaleLowerCase(), projectData); - await this.$platformWatcherService.startWatcher(platformData, projectData, { - webpackCompilerConfig: liveSyncInfo.webpackCompilerConfig, - preparePlatformData: { - release: liveSyncInfo.release, - useHotModuleReload: liveSyncInfo.useHotModuleReload, - nativePrepare: liveSyncInfo.nativePrepare, - signingOptions: { - teamId: (liveSyncInfo).teamId, - provision: (liveSyncInfo).provision - } - } - }); + const { nativePlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, liveSyncInfo); + await this.$platformWatcherService.startWatcher(nativePlatformData, projectData, preparePlatformData); } } } + private async syncInitialDataOnDevice(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { + const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); + const packageFilePath = await this.$platformBuildService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); + + await this.$deviceInstallationService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); + + // TODO: Consider to improve this + const platformLiveSyncService = this.getLiveSyncService(platformData.platformNameLowerCase); + const liveSyncResultInfo = await platformLiveSyncService.fullSync({ + projectData, + device, + useHotModuleReload: liveSyncInfo.useHotModuleReload, + watch: !liveSyncInfo.skipWatcher, + force: liveSyncInfo.force, + liveSyncDeviceInfo: deviceDescriptor + }); + + await this.$deviceRestartApplicationService.restartOnDevice(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService); + } + + private async syncChangedDataOnDevice(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { + console.log("syncChangedDataOnDevice================ ", data); + const { nativePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + + if (data.hasNativeChanges) { + // TODO: Consider to handle nativePluginsChange here (aar rebuilt) + await this.$platformBuildService.buildPlatform(nativePlatformData, projectData, buildPlatformData); + } + + const platformLiveSyncService = this.getLiveSyncService(device.deviceInfo.platform); + const liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, { + liveSyncDeviceInfo: deviceDescriptor, + projectData, + filesToRemove: [], + filesToSync: data.files, + isReinstalled: false, + hmrData: null, // platformHmrData, + useHotModuleReload: liveSyncInfo.useHotModuleReload, + force: liveSyncInfo.force, + connectTimeout: 1000 + }); + await this.$deviceRestartApplicationService.restartOnDevice(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService); + } + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; @@ -158,5 +165,15 @@ export class BundleWorkflowService implements IBundleWorkflowService { return result; } } + + private getLiveSyncService(platform: string): IPlatformLiveSyncService { + if (this.$mobileHelper.isiOSPlatform(platform)) { + return this.$injector.resolve("iOSLiveSyncService"); + } else if (this.$mobileHelper.isAndroidPlatform(platform)) { + return this.$injector.resolve("androidLiveSyncService"); + } + + this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); + } } $injector.register("bundleWorkflowService", BundleWorkflowService); diff --git a/lib/services/device/device-installation-service.ts b/lib/services/device/device-installation-service.ts index e69de29bb2..99786c17f7 100644 --- a/lib/services/device/device-installation-service.ts +++ b/lib/services/device/device-installation-service.ts @@ -0,0 +1,111 @@ +import { TrackActionNames, HASHES_FILE_NAME } from "../../constants"; +import { MobileHelper } from "../../common/mobile/mobile-helper"; +import * as helpers from "../../common/helpers"; +import * as path from "path"; + +const buildInfoFileName = ".nsbuildinfo"; + +export class DeviceInstallationService implements IDeviceInstallationService { + constructor( + private $analyticsService: IAnalyticsService, + private $buildArtefactsService: IBuildArtefactsService, + private $devicePathProvider: IDevicePathProvider, + private $fs: IFileSystem, + private $logger: ILogger, + private $mobileHelper: MobileHelper, + private $platformBuildService: IPlatformBuildService + ) { } + + public async installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise { + this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.Deploy, + device, + projectDir: projectData.projectDir + }); + + if (!packageFile) { + packageFile = await this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildConfig, outputFilePath); + } + + await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); + + const platform = device.deviceInfo.platform.toLowerCase(); + await device.applicationManager.reinstallApplication(projectData.projectIdentifiers[platform], packageFile); + + await this.updateHashesOnDevice({ + device, + appIdentifier: projectData.projectIdentifiers[platform], + outputFilePath, + platformData + }); + + if (!buildConfig.release) { + const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); + const options = buildConfig; + options.buildForDevice = !device.isEmulator; + const buildInfoFilePath = outputFilePath || platformData.getBuildOutputPath(buildConfig); + const appIdentifier = projectData.projectIdentifiers[platform]; + + await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); + } + + this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); + } + + public async installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise { + const shouldInstall = await this.shouldInstall(device, platformData, projectData, buildConfig); + if (shouldInstall) { + await this.installOnDevice(device, platformData, projectData, buildConfig, packageFile, outputFilePath); + } + } + + public async getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise { + const platform = device.deviceInfo.platform.toLowerCase(); + const deviceRootPath = await this.$devicePathProvider.getDeviceProjectRootPath(device, { + appIdentifier: projectData.projectIdentifiers[platform], + getDirname: true + }); + return helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, buildInfoFileName)); + } + + private async updateHashesOnDevice(data: { device: Mobile.IDevice, appIdentifier: string, outputFilePath: string, platformData: IPlatformData }): Promise { + const { device, appIdentifier, platformData, outputFilePath } = data; + + if (!this.$mobileHelper.isAndroidPlatform(platformData.normalizedPlatformName)) { + return; + } + + let hashes = {}; + const hashesFilePath = path.join(outputFilePath || platformData.getBuildOutputPath(null), HASHES_FILE_NAME); + if (this.$fs.exists(hashesFilePath)) { + hashes = this.$fs.readJson(hashesFilePath); + } + + await device.fileSystem.updateHashesOnDevice(hashes, appIdentifier); + } + + private async shouldInstall(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, release: IRelease, outputPath?: string): Promise { + const platform = device.deviceInfo.platform; + if (!(await device.applicationManager.isApplicationInstalled(projectData.projectIdentifiers[platform.toLowerCase()]))) { + return true; + } + + const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); + const localBuildInfo = this.$platformBuildService.getBuildInfoFromFile(platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); + + return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; + } + + private async getDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData): Promise { + const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); + try { + const deviceFileContent = await this.$mobileHelper.getDeviceFileContent(device, deviceFilePath, projectData); + return JSON.parse(deviceFileContent); + } catch (e) { + return null; + } + } +} +$injector.register("deviceInstallationService", DeviceInstallationService); diff --git a/lib/services/device/device-restart-application-service.ts b/lib/services/device/device-restart-application-service.ts new file mode 100644 index 0000000000..b4df1f9891 --- /dev/null +++ b/lib/services/device/device-restart-application-service.ts @@ -0,0 +1,82 @@ +import { performanceLog } from "../../common/decorators"; + +export class DeviceRestartApplicationService implements IDeviceRestartApplicationService { + + constructor(private $logger: ILogger) { } + + @performanceLog() + public async restartOnDevice(deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, platformLiveSyncService: IPlatformLiveSyncService): Promise { + return liveSyncResultInfo && deviceDescriptor.debugggingEnabled ? + this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, deviceDescriptor.debugOptions, platformLiveSyncService) : + this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor.debugOptions, platformLiveSyncService); + } + + @performanceLog() + private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, platformLiveSyncService: IPlatformLiveSyncService): Promise { + debugOptions = debugOptions || {}; + if (debugOptions.debugBrk) { + liveSyncResultInfo.waitForDebugger = true; + } + + const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, platformLiveSyncService, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); + + // 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: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + // debugOptions: debugOptions, + // }; + + // return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); + return null; + } + + private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts: IDebugOptions, platformLiveSyncService: IPlatformLiveSyncService, settings?: IRefreshApplicationSettings): Promise { + const result = { didRestart: false }; + const platform = liveSyncResultInfo.deviceAppData.platform; + const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; + try { + let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); + if (!shouldRestart) { + shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); + } + + if (shouldRestart) { + // const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; + // this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); + await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); + result.didRestart = true; + } + } catch (err) { + 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); + if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { + // this.emitLivesyncEvent(LiveSyncEvents.liveSyncNotification, { + // projectDir: projectData.projectDir, + // applicationIdentifier, + // deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + // notification: msg + // }); + } + + if (settings && settings.shouldCheckDeveloperDiscImage) { + // this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); + } + } + + // this.emitLivesyncEvent(LiveSyncEvents.liveSyncExecuted, { + // projectDir: projectData.projectDir, + // applicationIdentifier, + // syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + // deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + // isFullSync: liveSyncResultInfo.isFullSync + // }); + + return result; + } +} +$injector.register("deviceRestartApplicationService", DeviceRestartApplicationService); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 0490fc0ad0..a204f1fde0 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -12,6 +12,7 @@ import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants } from "../constants"; +import { IOSBuildData, IOSPrepareData } from "./workflow/workflow-data-service"; interface INativeSourceCodeGroup { name: string; @@ -25,7 +26,7 @@ const SimulatorPlatformSdkName = "iphonesimulator"; const getPlatformSdkName = (forDevice: boolean): string => forDevice ? DevicePlatformSdkName : SimulatorPlatformSdkName; const getConfigurationName = (release: boolean): string => release ? Configurations.Release : Configurations.Debug; -export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { +export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase { private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__"; private static IOS_PLATFORM_NAME = "ios"; @@ -67,7 +68,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ normalizedPlatformName: "iOS", platformNameLowerCase: "ios", appDestinationDirectoryPath: path.join(projectRoot, projectData.projectName), - platformProjectService: this, + platformProjectService: this, projectRoot: projectRoot, getBuildOutputPath: (options: IBuildOutputOptions): string => { const config = getConfigurationName(!options || options.release); @@ -192,23 +193,23 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ path.join(projectRoot, projectData.projectName)); } - public async buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + public async buildProject(projectRoot: string, projectData: IProjectData, buildPlatformData: IOSBuildData): Promise { const platformData = this.getPlatformData(projectData); const handler = (data: any) => { this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); }; - if (buildConfig.buildForDevice) { - await this.$iOSSigningService.setupSigningForDevice(projectRoot, projectData, buildConfig); + if (buildPlatformData.buildForDevice) { + await this.$iOSSigningService.setupSigningForDevice(projectRoot, projectData, buildPlatformData); await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, - this.$xcodebuildService.buildForDevice(platformData, projectData, buildConfig)); + this.$xcodebuildService.buildForDevice(platformData, projectData, buildPlatformData)); } else { await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, - this.$xcodebuildService.buildForSimulator(platformData, projectData, buildConfig)); + this.$xcodebuildService.buildForSimulator(platformData, projectData, buildPlatformData)); } this.validateApplicationIdentifier(projectData); @@ -276,13 +277,13 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return contentIsTheSame; } - public async prepareProject(projectData: IProjectData, signingOptions: IiOSSigningOptions): Promise { + public async prepareProject(projectData: IProjectData, preparePlatformData: IOSPrepareData): Promise { const projectRoot = path.join(projectData.platformsDir, "ios"); - const provision = signingOptions && signingOptions.provision; - const teamId = signingOptions && signingOptions.teamId; + const provision = preparePlatformData && preparePlatformData.provision; + const teamId = preparePlatformData && preparePlatformData.teamId; if (provision) { - await this.$iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision, signingOptions.mobileProvisionData); + await this.$iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision, preparePlatformData.mobileProvisionData); } if (teamId) { await this.$iOSSigningService.setupSigningFromTeam(projectRoot, projectData, teamId); @@ -507,7 +508,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return Promise.resolve(); } - public async checkForChanges(changesInfo: IProjectChangesInfo, signingOptions: IiOSSigningOptions, projectData: IProjectData): Promise { + public async checkForChanges(changesInfo: IProjectChangesInfo, signingOptions: IOSPrepareData, projectData: IProjectData): Promise { const { provision, teamId } = signingOptions; const hasProvision = provision !== undefined; const hasTeamId = teamId !== undefined; diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index d419883710..5e86ddd32d 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -9,8 +9,8 @@ export class LiveSyncService implements ILiveSyncService2 { private $mobileHelper: Mobile.IMobileHelper, ) { } - public async fullSync(device: Mobile.IDevice, liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { - const platformLiveSyncService = this.getLiveSyncService("ios"); + public async syncInitialDataOnDevice(device: Mobile.IDevice, liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { + const platformLiveSyncService = this.getLiveSyncService(device.deviceInfo.platform); const liveSyncResultInfo = await platformLiveSyncService.fullSync({ projectData, device, @@ -23,10 +23,47 @@ export class LiveSyncService implements ILiveSyncService2 { return liveSyncResultInfo; } + public async syncChangedDataOnDevice(device: Mobile.IDevice, filesToSync: string[], liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { + // const isInHMRMode = liveSyncInfo.useHotModuleReload; // && platformHmrData.hash; + // if (isInHMRMode) { + // this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); + // } + + const platformLiveSyncService = this.getLiveSyncService(device.deviceInfo.platform); + const liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, { + liveSyncDeviceInfo, + projectData, + filesToRemove: [], + filesToSync, + isReinstalled: false, + hmrData: null, // platformHmrData, + useHotModuleReload: liveSyncInfo.useHotModuleReload, + force: liveSyncInfo.force, + connectTimeout: 1000 + }); + + console.log("============ liveSyncResultInfo ============= ", liveSyncResultInfo); + + // await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + + // // If didRecover is true, this means we were in ErrorActivity and fallback files were already transferred and app will be restarted. + // if (!liveSyncResultInfo.didRecover && isInHMRMode) { + // const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); + // if (status === HmrConstants.HMR_ERROR_STATUS) { + // watchInfo.filesToSync = platformHmrData.fallbackFiles; + // liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); + // // We want to force a restart of the application. + // liveSyncResultInfo.isFullSync = true; + // await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + // } + // } + + // this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); + return; + } + @performanceLog() public async refreshApplication(liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { - // const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir); - return liveSyncDeviceInfo && liveSyncDeviceInfo.debugggingEnabled ? this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) : this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath); @@ -1076,4 +1113,3 @@ $injector.register("liveSyncService", LiveSyncService); // } // $injector.register("usbLiveSyncService", DeprecatedUsbLiveSyncService); - diff --git a/lib/services/local-build-service.ts b/lib/services/local-build-service.ts index ad09c8a3e7..f968f39a68 100644 --- a/lib/services/local-build-service.ts +++ b/lib/services/local-build-service.ts @@ -1,15 +1,15 @@ import { EventEmitter } from "events"; import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE } from "../constants"; +import { WorkflowDataService } from "./workflow/workflow-data-service"; export class LocalBuildService extends EventEmitter implements ILocalBuildService { constructor( private $errors: IErrors, private $mobileHelper: Mobile.IMobileHelper, private $platformsData: IPlatformsData, - private $platformWorkflowDataFactory: IPlatformWorkflowDataFactory, - private $platformWorkflowService: IPlatformWorkflowService, - private $projectData: IProjectData, - private $projectDataService: IProjectDataService + private $platformBuildService: IPlatformBuildService, + private $projectDataService: IProjectDataService, + private $workflowDataService: WorkflowDataService ) { super(); } public async build(platform: string, platformBuildOptions: IPlatformBuildData): Promise { @@ -17,15 +17,9 @@ export class LocalBuildService extends EventEmitter implements ILocalBuildServic this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } - this.$projectData.initializeProjectData(platformBuildOptions.projectDir); + const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, platformBuildOptions.projectDir, platformBuildOptions); - const projectData = this.$projectDataService.getProjectData(platformBuildOptions.projectDir); - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const workflowData = this.$platformWorkflowDataFactory.createPlatformWorkflowData(platform, platformBuildOptions, (platformBuildOptions).nativePrepare); - - platformBuildOptions.buildOutputStdio = "pipe"; - - const result = await this.$platformWorkflowService.buildPlatform(platformData, projectData, workflowData, platformBuildOptions); + const result = await this.$platformBuildService.buildPlatform(nativePlatformData, projectData, buildPlatformData); return result; } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 17290ac31f..f80c6526af 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -8,6 +8,7 @@ import { EventEmitter } from "events"; import { attachAwaitDetach } from "../common/helpers"; import * as temp from "temp"; import { performanceLog } from ".././common/decorators"; +import { PreparePlatformData } from "./workflow/workflow-data-service"; temp.track(); const buildInfoFileName = ".nsbuildinfo"; @@ -40,13 +41,13 @@ export class PlatformService extends EventEmitter implements IPlatformService { } @performanceLog() - public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { this.$logger.out("Preparing project..."); - await this.$webpackCompilerService.compile(platformData, projectData, { watch: false, env: preparePlatformData.env }); + await this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, { watch: false, env: preparePlatformData.env }); await this.$platformNativeService.preparePlatform(platformData, projectData, preparePlatformData); - this.$projectChangesService.savePrepareInfo(platformData.platformNameLowerCase, projectData); + this.$projectChangesService.savePrepareInfo(platformData); this.$logger.out(`Project successfully prepared (${platformData.platformNameLowerCase})`); @@ -74,7 +75,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return true; } - const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); const buildInfo = this.getBuildInfo(platform, platformData, buildConfig, outputPath); if (!prepareInfo || !buildInfo) { return true; @@ -116,7 +117,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.printInfoMessageOnSameLine(data.data.toString()); }; - await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildConfig)); + await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildConfig)); const buildInfoFilePath = this.getBuildOutputPath(platform, platformData, buildConfig); this.saveBuildInfoFile(platform, projectData.projectDir, buildInfoFilePath); @@ -130,7 +131,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const projectData = this.$projectDataService.getProjectData(projectDir); const platformData = this.$platformsData.getPlatformData(platform, projectData); - const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); const buildInfo: IBuildInfo = { prepareTime: prepareInfo.changesRequireBuildTime, buildTime: new Date().toString() @@ -263,10 +264,10 @@ export class PlatformService extends EventEmitter implements IPlatformService { clean: deployInfo.deployOptions.clean }; - let installPackageFile: string; + const installPackageFile: string = ""; const shouldBuild = await this.shouldBuild(deployInfo.platform, deployInfo.projectData, buildConfig, deployInfo.outputPath); if (shouldBuild) { - installPackageFile = await deployInfo.buildPlatform(deployInfo.platform, buildConfig, deployInfo.projectData); + // installPackageFile = await deployInfo.buildPlatform(deployInfo.platform, buildConfig, deployInfo.projectData); } else { this.$logger.out("Skipping package build. No changes detected on the native side. This will be fast!"); } @@ -306,9 +307,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private getBuildOutputPath(platform: string, platformData: IPlatformData, options: IBuildOutputOptions): string { - if (options.androidBundle) { - return platformData.bundleBuildOutputPath; - } + // if (options.androidBundle) { + // return platformData.bundleBuildOutputPath; + // } if (platform.toLowerCase() === this.$devicePlatformsConstants.iOS.toLowerCase()) { return platformData.getBuildOutputPath(options); diff --git a/lib/services/platform/platform-add-service.ts b/lib/services/platform/platform-add-service.ts index f51d8fa91a..97beb9131e 100644 --- a/lib/services/platform/platform-add-service.ts +++ b/lib/services/platform/platform-add-service.ts @@ -1,6 +1,7 @@ import * as path from "path"; import * as temp from "temp"; -import { PROJECT_FRAMEWORK_FOLDER_NAME } from "../../constants"; +import { PROJECT_FRAMEWORK_FOLDER_NAME, NativePlatformStatus } from "../../constants"; +import { AddPlatformData } from "../workflow/workflow-data-service"; export class PlatformAddService implements IPlatformAddService { constructor( @@ -12,11 +13,12 @@ export class PlatformAddService implements IPlatformAddService { private $platformsData: IPlatformsData, private $platformJSService: IPreparePlatformService, private $platformNativeService: IPreparePlatformService, + private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService, private $terminalSpinnerService: ITerminalSpinnerService ) { } - public async addPlatform(addPlatformData: IAddPlatformData, projectData: IProjectData): Promise { + public async addPlatform(projectData: IProjectData, addPlatformData: AddPlatformData): Promise { const { platformParam, frameworkPath, nativePrepare } = addPlatformData; const [ platform, version ] = platformParam.toLowerCase().split("@"); const platformData = this.$platformsData.getPlatformData(platform, projectData); @@ -36,6 +38,13 @@ export class PlatformAddService implements IPlatformAddService { this.$logger.out(`Platform ${platform} successfully added. v${installedPlatformVersion}`); } + public async addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, addPlatformData: AddPlatformData): Promise { + const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, addPlatformData.nativePrepare); + if (shouldAddPlatform) { + await this.addPlatform(projectData, addPlatformData); + } + } + private async getPackageToInstall(platformData: IPlatformData, projectData: IProjectData, frameworkPath?: string, version?: string): Promise { let result = null; if (frameworkPath) { @@ -98,5 +107,16 @@ export class PlatformAddService implements IPlatformAddService { return version; } + + private shouldAddPlatform(platformData: IPlatformData, projectData: IProjectData, nativePrepare: INativePrepare): boolean { + const platformName = platformData.platformNameLowerCase; + const hasPlatformDirectory = this.$fs.exists(path.join(projectData.platformsDir, platformName)); + const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); + const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === NativePlatformStatus.requiresPlatformAdd; + const result = !hasPlatformDirectory || (shouldAddNativePlatform && requiresNativePlatformAdd); + + return !!result; + } } $injector.register("platformAddService", PlatformAddService); diff --git a/lib/services/platform/platform-build-service.ts b/lib/services/platform/platform-build-service.ts index 7a90b4b69e..1c197ce201 100644 --- a/lib/services/platform/platform-build-service.ts +++ b/lib/services/platform/platform-build-service.ts @@ -3,6 +3,7 @@ import { Configurations } from "../../common/constants"; import { EventEmitter } from "events"; import { attachAwaitDetach } from "../../common/helpers"; import * as path from "path"; +import { BuildPlatformDataBase } from "../workflow/workflow-data-service"; const buildInfoFileName = ".nsbuildinfo"; @@ -16,23 +17,23 @@ export class PlatformBuildService extends EventEmitter implements IPlatformBuild private $projectChangesService: IProjectChangesService ) { super(); } - public async buildPlatform(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + public async buildPlatform(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: T): Promise { this.$logger.out("Building project..."); const platform = platformData.platformNameLowerCase; const action = constants.TrackActionNames.Build; - const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildConfig && buildConfig.buildForDevice; + const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildPlatformData && buildPlatformData.buildForDevice; await this.$analyticsService.trackEventActionInGoogleAnalytics({ action, isForDevice, platform, projectDir: projectData.projectDir, - additionalData: `${buildConfig.release ? Configurations.Release : Configurations.Debug}_${buildConfig.clean ? constants.BuildStates.Clean : constants.BuildStates.Incremental}` + additionalData: `${buildPlatformData.release ? Configurations.Release : Configurations.Debug}_${buildPlatformData.clean ? constants.BuildStates.Clean : constants.BuildStates.Incremental}` }); - if (buildConfig.clean) { + if (buildPlatformData.clean) { await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); } @@ -41,14 +42,14 @@ export class PlatformBuildService extends EventEmitter implements IPlatformBuild this.$logger.printInfoMessageOnSameLine(data.data.toString()); }; - await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildConfig)); + await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildPlatformData)); - const buildInfoFileDirname = platformData.getBuildOutputPath(buildConfig); + const buildInfoFileDirname = platformData.getBuildOutputPath(buildPlatformData); this.saveBuildInfoFile(platformData, projectData, buildInfoFileDirname); this.$logger.out("Project successfully built."); - const result = await this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildConfig); + const result = await this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildPlatformData); // if (this.$options.copyTo) { // this.$platformService.copyLastOutput(platform, this.$options.copyTo, buildConfig, this.$projectData); @@ -59,10 +60,22 @@ export class PlatformBuildService extends EventEmitter implements IPlatformBuild return result; } + public async buildPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: T, outputPath?: string): Promise { + let result = null; + + outputPath = outputPath || platformData.getBuildOutputPath(buildPlatformData); + const shouldBuildPlatform = await this.shouldBuildPlatform(platformData, projectData, buildPlatformData, outputPath); + if (shouldBuildPlatform) { + result = await this.buildPlatform(platformData, projectData, buildPlatformData); + } + + return result; + } + public saveBuildInfoFile(platformData: IPlatformData, projectData: IProjectData, buildInfoFileDirname: string): void { const buildInfoFile = path.join(buildInfoFileDirname, buildInfoFileName); - const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData.platformNameLowerCase, projectData); + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); const buildInfo: IBuildInfo = { prepareTime: prepareInfo.changesRequireBuildTime, buildTime: new Date().toString() @@ -71,7 +84,7 @@ export class PlatformBuildService extends EventEmitter implements IPlatformBuild this.$fs.writeJson(buildInfoFile, buildInfo); } - public getBuildInfoFromFile(platformData: IPlatformData, buildConfig: IBuildConfig, buildOutputPath?: string): IBuildInfo { + public getBuildInfoFromFile(platformData: IPlatformData, buildConfig: BuildPlatformDataBase, buildOutputPath?: string): IBuildInfo { buildOutputPath = buildOutputPath || platformData.getBuildOutputPath(buildConfig); const buildInfoFile = path.join(buildOutputPath, buildInfoFileName); if (this.$fs.exists(buildInfoFile)) { @@ -85,5 +98,41 @@ export class PlatformBuildService extends EventEmitter implements IPlatformBuild return null; } + + private async shouldBuildPlatform(platformData: IPlatformData, projectData: IProjectData, buildConfig: BuildPlatformDataBase, outputPath: string): Promise { + if (buildConfig.release && this.$projectChangesService.currentChanges.hasChanges) { + return true; + } + + if (this.$projectChangesService.currentChanges.changesRequireBuild) { + return true; + } + + if (!this.$fs.exists(outputPath)) { + return true; + } + + const validBuildOutputData = platformData.getValidBuildOutputData(buildConfig); + const packages = this.$buildArtefactsService.getAllBuiltApplicationPackages(outputPath, validBuildOutputData); + if (packages.length === 0) { + return true; + } + + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); + const buildInfo = this.getBuildInfoFromFile(platformData, buildConfig, outputPath); + if (!prepareInfo || !buildInfo) { + return true; + } + + if (buildConfig.clean) { + return true; + } + + if (prepareInfo.time === buildInfo.prepareTime) { + return false; + } + + return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; + } } $injector.register("platformBuildService", PlatformBuildService); diff --git a/lib/services/platform/platform-commands-service.ts b/lib/services/platform/platform-commands-service.ts index 1c1a2e058c..82d6b7ddb7 100644 --- a/lib/services/platform/platform-commands-service.ts +++ b/lib/services/platform/platform-commands-service.ts @@ -31,7 +31,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { } const addPlatformData = { platformParam: platform.toLowerCase(), frameworkPath }; - await this.$platformAddService.addPlatform(addPlatformData, projectData); + await this.$platformAddService.addPlatform(projectData, addPlatformData); } } @@ -83,7 +83,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { if (hasPlatformDirectory) { await this.updatePlatform(platform, version, projectData); } else { - await this.$platformAddService.addPlatform({ platformParam }, projectData); + await this.$platformAddService.addPlatform(projectData, { platformParam }); } } } @@ -113,7 +113,8 @@ export class PlatformCommandsService implements IPlatformCommandsService { return false; } - const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); if (!prepareInfo) { return true; } @@ -158,7 +159,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { await this.removePlatforms([packageName], projectData); packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; const addPlatformData = { platformParam: packageName }; - await this.$platformAddService.addPlatform(addPlatformData, projectData); + await this.$platformAddService.addPlatform(projectData, addPlatformData); this.$logger.out("Successfully updated to version ", updateOptions.newVersion); } diff --git a/lib/services/platform/platform-watcher-service.ts b/lib/services/platform/platform-watcher-service.ts index f9210b343d..4665ca6e24 100644 --- a/lib/services/platform/platform-watcher-service.ts +++ b/lib/services/platform/platform-watcher-service.ts @@ -1,21 +1,19 @@ -import { EventEmitter } from "events"; +import * as child_process from "child_process"; import * as choki from "chokidar"; +import { EventEmitter } from "events"; import * as path from "path"; +import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME } from "../../constants"; +import { PreparePlatformData } from "../workflow/workflow-data-service"; interface IPlatformWatcherData { - nativeWatcher: any; - webpackCompilerProcess: any; -} - -interface IFilesChangeData { - files: string[]; - hasNativeChange: boolean; + webpackCompilerProcess: child_process.ChildProcess; + nativeFilesWatcher: choki.FSWatcher; } export class PlatformWatcherService extends EventEmitter implements IPlatformWatcherService { private watchersData: IDictionary> = {}; private isInitialSyncEventEmitted = false; - private persistedFilesChangeData: IFilesChangeData[] = []; + private persistedFilesChangeEventData: IFilesChangeEventData[] = []; constructor( private $logger: ILogger, @@ -23,9 +21,7 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat private $webpackCompilerService: IWebpackCompilerService ) { super(); } - public async startWatcher(platformData: IPlatformData, projectData: IProjectData, startWatcherData: IStartWatcherData): Promise { - const { webpackCompilerConfig, preparePlatformData } = startWatcherData; - + public async startWatcher(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { this.$logger.out("Starting watchers..."); if (!this.watchersData[projectData.projectDir]) { @@ -34,70 +30,77 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase]) { this.watchersData[projectData.projectDir][platformData.platformNameLowerCase] = { - nativeWatcher: null, + nativeFilesWatcher: null, webpackCompilerProcess: null }; } - await this.startJsWatcher(platformData, projectData, webpackCompilerConfig); // -> start watcher + initial compilation - await this.startNativeWatcher(platformData, projectData, preparePlatformData); // -> start watcher + initial prepare + await this.prepareJSCodeWithWatch(platformData, projectData, { env: preparePlatformData.env }); // -> start watcher + initial compilation + const hasNativeChanges = await this.prepareNativeCodeWithWatch(platformData, projectData, preparePlatformData); // -> start watcher + initial prepare - this.emitInitialSyncEvent(); + this.emitInitialSyncEvent({ platform: platformData.platformNameLowerCase, hasNativeChanges }); } - private async startJsWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + private async prepareJSCodeWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { this.$webpackCompilerService.on("webpackEmittedFiles", files => { - console.log("RECEIVED webpackEmittedFiles ================="); - this.emitFilesChangeEvent({ files, hasNativeChange: false }); + this.emitFilesChangeEvent({ files, hasNativeChanges: false, platform: platformData.platformNameLowerCase }); }); - const childProcess = await this.$webpackCompilerService.startWatcher(platformData, projectData, config); + const childProcess = await this.$webpackCompilerService.compileWithWatch(platformData, projectData, config); this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess = childProcess; } } - private async startNativeWatcher(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { - if ((!preparePlatformData.nativePrepare || !preparePlatformData.nativePrepare.skipNativePrepare) && - !this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeWatcher) { - const patterns = [ - path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), - `node_modules/**/platforms/${platformData.platformNameLowerCase}/` - ]; - const watcherOptions: choki.WatchOptions = { - ignoreInitial: true, - cwd: projectData.projectDir, - awaitWriteFinish: { - pollInterval: 100, - stabilityThreshold: 500 - }, - ignored: ["**/.*", ".*"] // hidden files - }; - const watcher = choki.watch(patterns, watcherOptions) - .on("all", async (event: string, filePath: string) => { - filePath = path.join(projectData.projectDir, filePath); - this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); - this.emitFilesChangeEvent({ files: [], hasNativeChange: true }); - }); + private async prepareNativeCodeWithWatch(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + if ((preparePlatformData.nativePrepare && preparePlatformData.nativePrepare.skipNativePrepare) || this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher) { + return false; + } - this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeWatcher = watcher; + const patterns = [ + path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), + `node_modules/**/platforms/${platformData.platformNameLowerCase}/` + ]; + const watcherOptions: choki.WatchOptions = { + ignoreInitial: true, + cwd: projectData.projectDir, + awaitWriteFinish: { + pollInterval: 100, + stabilityThreshold: 500 + }, + ignored: ["**/.*", ".*"] // hidden files + }; + const watcher = choki.watch(patterns, watcherOptions) + .on("all", async (event: string, filePath: string) => { + filePath = path.join(projectData.projectDir, filePath); + this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); + this.emitFilesChangeEvent({ files: [], hasNativeChanges: true, platform: platformData.platformNameLowerCase }); + }); - await this.$platformNativeService.preparePlatform(platformData, projectData, preparePlatformData); - } + this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher; + + const hasNativeChanges = await this.$platformNativeService.preparePlatform(platformData, projectData, preparePlatformData); + + return hasNativeChanges; } - private emitFilesChangeEvent(filesChangeData: IFilesChangeData) { - console.log("================ emitFilesChangeEvent ================ ", this.isInitialSyncEventEmitted); + private emitFilesChangeEvent(filesChangeEventData: IFilesChangeEventData) { if (this.isInitialSyncEventEmitted) { - this.emit("fileChangeData", filesChangeData); + this.emit(FILES_CHANGE_EVENT_NAME, filesChangeEventData); } else { - this.persistedFilesChangeData.push(filesChangeData); + this.persistedFilesChangeEventData.push(filesChangeEventData); } } - private emitInitialSyncEvent() { - // TODO: Check the persisted data and add them in emitted event's data - this.emit("onInitialSync", ({})); + private emitInitialSyncEvent(initialSyncEventData: IInitialSyncEventData) { + const hasPersistedDataWithNativeChanges = this.persistedFilesChangeEventData.find(data => data.platform === initialSyncEventData.platform && data.hasNativeChanges); + if (hasPersistedDataWithNativeChanges) { + initialSyncEventData.hasNativeChanges = true; + } + + // TODO: Consider how to handle changed js files between initialSyncEvent and initial preperation of the project + + this.emit(INITIAL_SYNC_EVENT_NAME, initialSyncEventData); this.isInitialSyncEventEmitted = true; } } diff --git a/lib/services/prepare-platform-js-service.ts b/lib/services/prepare-platform-js-service.ts index fee21c364c..878fd2dc13 100644 --- a/lib/services/prepare-platform-js-service.ts +++ b/lib/services/prepare-platform-js-service.ts @@ -1,6 +1,7 @@ import { hook } from "../common/helpers"; import { performanceLog } from "./../common/decorators"; import { EventEmitter } from "events"; +import { PreparePlatformData } from "./workflow/workflow-data-service"; export class PlatformJSService extends EventEmitter implements IPreparePlatformService { @@ -16,7 +17,7 @@ export class PlatformJSService extends EventEmitter implements IPreparePlatformS @performanceLog() @hook('prepareJSApp') - public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { // intentionally left blank, keep the support for before-prepareJSApp and after-prepareJSApp hooks return true; } diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/prepare-platform-native-service.ts index 55b9552bf7..71bcab660a 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/prepare-platform-native-service.ts @@ -1,6 +1,7 @@ import * as path from "path"; import * as constants from "../constants"; import { performanceLog } from "../common/decorators"; +import { PreparePlatformData } from "./workflow/workflow-data-service"; export class PlatformNativeService implements IPreparePlatformService { constructor( @@ -21,28 +22,17 @@ export class PlatformNativeService implements IPreparePlatformService { platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); await platformData.platformProjectService.interpolateData(projectData, config); platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); - this.$projectChangesService.setNativePlatformStatus(platformData.normalizedPlatformName, projectData, - { nativePlatformStatus: constants.NativePlatformStatus.requiresPrepare }); + this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: constants.NativePlatformStatus.requiresPrepare }); } @performanceLog() - public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { - const { nativePrepare, release, useHotModuleReload, signingOptions } = preparePlatformData; + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + const { nativePrepare, release } = preparePlatformData; if (nativePrepare && nativePrepare.skipNativePrepare) { return false; } - const nativePlatformStatus = (nativePrepare && nativePrepare.skipNativePrepare) ? constants.NativePlatformStatus.requiresPlatformAdd : constants.NativePlatformStatus.requiresPrepare; - const changesInfo = await this.$projectChangesService.checkForChanges({ - platform: platformData.platformNameLowerCase, - projectData, - projectChangesOptions: { - signingOptions, - release, - nativePlatformStatus, - useHotModuleReload - } - }); + const changesInfo = await this.$projectChangesService.checkForChanges(platformData.platformNameLowerCase, projectData, preparePlatformData); const hasModulesChange = !changesInfo || changesInfo.modulesChanged; const hasConfigChange = !changesInfo || changesInfo.configChanged; @@ -60,7 +50,7 @@ export class PlatformNativeService implements IPreparePlatformService { this.prepareAppResources(platformData, projectData); if (hasChangesRequirePrepare) { - await platformData.platformProjectService.prepareProject(projectData, signingOptions); + await platformData.platformProjectService.prepareProject(projectData, preparePlatformData); } if (hasModulesChange) { @@ -72,9 +62,8 @@ export class PlatformNativeService implements IPreparePlatformService { await platformData.platformProjectService.handleNativeDependenciesChange(projectData, { release }); } - platformData.platformProjectService.interpolateConfigurationFile(projectData, signingOptions); - this.$projectChangesService.setNativePlatformStatus(platformData.platformNameLowerCase, projectData, - { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); + platformData.platformProjectService.interpolateConfigurationFile(projectData, preparePlatformData); + this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); return hasChanges; } @@ -123,7 +112,7 @@ export class PlatformNativeService implements IPreparePlatformService { return; } - const previousPrepareInfo = this.$projectChangesService.getPrepareInfo(platformData.platformNameLowerCase, projectData); + const previousPrepareInfo = this.$projectChangesService.getPrepareInfo(platformData); if (!previousPrepareInfo || previousPrepareInfo.nativePlatformStatus !== constants.NativePlatformStatus.alreadyPrepared) { return; } diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index f9a71266f9..07ad1e5223 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -1,6 +1,7 @@ import * as path from "path"; import { NODE_MODULES_FOLDER_NAME, NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME } from "../constants"; import { getHash, hook } from "../common/helpers"; +import { PreparePlatformData } from "./workflow/workflow-data-service"; const prepareInfoFileName = ".nsprepareinfo"; @@ -55,15 +56,14 @@ export class ProjectChangesService implements IProjectChangesService { } @hook("checkForChanges") - public async checkForChanges(checkForChangesOpts: ICheckForChangesOptions): Promise { - const { platform, projectData, projectChangesOptions } = checkForChangesOpts; + public async checkForChanges(platform: string, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { const platformData = this.$platformsData.getPlatformData(platform, projectData); this._changesInfo = new ProjectChangesInfo(); - const isNewPrepareInfo = await this.ensurePrepareInfo(platform, projectData, projectChangesOptions); + const isNewPrepareInfo = await this.ensurePrepareInfo(platform, projectData, preparePlatformData); if (!isNewPrepareInfo) { this._newFiles = 0; - this._changesInfo.packageChanged = this.isProjectFileChanged(projectData, platform); + this._changesInfo.packageChanged = this.isProjectFileChanged(projectData.projectDir, platformData); const platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName); this._changesInfo.appResourcesChanged = this.containsNewerFiles(platformResourcesDir, null, projectData); @@ -96,17 +96,16 @@ export class ProjectChangesService implements IProjectChangesService { this.$logger.trace(`Set value of configChanged to ${this._changesInfo.configChanged}`); } - if (checkForChangesOpts.projectChangesOptions.nativePlatformStatus !== NativePlatformStatus.requiresPlatformAdd) { - const projectService = platformData.platformProjectService; - await projectService.checkForChanges(this._changesInfo, projectChangesOptions.signingOptions, projectData); + if (!preparePlatformData.nativePrepare || !preparePlatformData.nativePrepare.skipNativePrepare) { + await platformData.platformProjectService.checkForChanges(this._changesInfo, preparePlatformData, projectData); } - if (projectChangesOptions.release !== this._prepareInfo.release) { - this.$logger.trace(`Setting all setting to true. Current options are: `, projectChangesOptions, " old prepare info is: ", this._prepareInfo); + if (preparePlatformData.release !== this._prepareInfo.release) { + this.$logger.trace(`Setting all setting to true. Current options are: `, preparePlatformData, " old prepare info is: ", this._prepareInfo); this._changesInfo.appResourcesChanged = true; this._changesInfo.modulesChanged = true; this._changesInfo.configChanged = true; - this._prepareInfo.release = projectChangesOptions.release; + this._prepareInfo.release = preparePlatformData.release; } if (this._changesInfo.packageChanged) { this.$logger.trace("Set modulesChanged to true as packageChanged is true"); @@ -123,7 +122,7 @@ export class ProjectChangesService implements IProjectChangesService { this._prepareInfo.changesRequireBuildTime = this._prepareInfo.time; } - this._prepareInfo.projectFileHash = this.getProjectFileStrippedHash(projectData, platform); + this._prepareInfo.projectFileHash = this.getProjectFileStrippedHash(projectData.projectDir, platformData); } this._changesInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus; @@ -132,14 +131,14 @@ export class ProjectChangesService implements IProjectChangesService { return this._changesInfo; } - public getPrepareInfoFilePath(platform: string, projectData: IProjectData): string { - const platformData = this.$platformsData.getPlatformData(platform, projectData); + public getPrepareInfoFilePath(platformData: IPlatformData): string { const prepareInfoFilePath = path.join(platformData.projectRoot, prepareInfoFileName); + return prepareInfoFilePath; } - public getPrepareInfo(platform: string, projectData: IProjectData): IPrepareInfo { - const prepareInfoFilePath = this.getPrepareInfoFilePath(platform, projectData); + public getPrepareInfo(platformData: IPlatformData): IPrepareInfo { + const prepareInfoFilePath = this.getPrepareInfoFilePath(platformData); let prepareInfo: IPrepareInfo = null; if (this.$fs.exists(prepareInfoFilePath)) { try { @@ -148,16 +147,17 @@ export class ProjectChangesService implements IProjectChangesService { prepareInfo = null; } } + return prepareInfo; } - public savePrepareInfo(platform: string, projectData: IProjectData): void { - const prepareInfoFilePath = this.getPrepareInfoFilePath(platform, projectData); + public savePrepareInfo(platformData: IPlatformData): void { + const prepareInfoFilePath = this.getPrepareInfoFilePath(platformData); this.$fs.writeJson(prepareInfoFilePath, this._prepareInfo); } - public setNativePlatformStatus(platform: string, projectData: IProjectData, addedPlatform: IAddedNativePlatform): void { - this._prepareInfo = this._prepareInfo || this.getPrepareInfo(platform, projectData); + public setNativePlatformStatus(platformData: IPlatformData, addedPlatform: IAddedNativePlatform): void { + this._prepareInfo = this._prepareInfo || this.getPrepareInfo(platformData); if (this._prepareInfo && addedPlatform.nativePlatformStatus === NativePlatformStatus.alreadyPrepared) { this._prepareInfo.nativePlatformStatus = addedPlatform.nativePlatformStatus; } else { @@ -166,29 +166,27 @@ export class ProjectChangesService implements IProjectChangesService { }; } - this.savePrepareInfo(platform, projectData); + this.savePrepareInfo(platformData); } - private async ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): Promise { - this._prepareInfo = this.getPrepareInfo(platform, projectData); + private async ensurePrepareInfo(platform: string, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + this._prepareInfo = this.getPrepareInfo(platformData); if (this._prepareInfo) { - this._prepareInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus && this._prepareInfo.nativePlatformStatus < projectChangesOptions.nativePlatformStatus ? - projectChangesOptions.nativePlatformStatus : - this._prepareInfo.nativePlatformStatus || projectChangesOptions.nativePlatformStatus; - - const platformData = this.$platformsData.getPlatformData(platform, projectData); const prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName); this._outputProjectMtime = this.$fs.getFsStats(prepareInfoFile).mtime.getTime(); this._outputProjectCTime = this.$fs.getFsStats(prepareInfoFile).ctime.getTime(); return false; } + const nativePlatformStatus = (!preparePlatformData.nativePrepare || !preparePlatformData.nativePrepare.skipNativePrepare) ? + NativePlatformStatus.requiresPrepare : NativePlatformStatus.requiresPlatformAdd; this._prepareInfo = { time: "", - nativePlatformStatus: projectChangesOptions.nativePlatformStatus, - release: projectChangesOptions.release, + nativePlatformStatus, + release: preparePlatformData.release, changesRequireBuild: true, - projectFileHash: this.getProjectFileStrippedHash(projectData, platform), + projectFileHash: this.getProjectFileStrippedHash(projectData.projectDir, platformData), changesRequireBuildTime: null }; @@ -201,14 +199,13 @@ export class ProjectChangesService implements IProjectChangesService { return true; } - private getProjectFileStrippedHash(projectData: IProjectData, platform: string): string { - platform = platform.toLowerCase(); - const projectFilePath = path.join(projectData.projectDir, PACKAGE_JSON_FILE_NAME); + private getProjectFileStrippedHash(projectDir: string, platformData: IPlatformData): string { + const projectFilePath = path.join(projectDir, PACKAGE_JSON_FILE_NAME); const projectFileContents = this.$fs.readJson(projectFilePath); _(this.$devicePlatformsConstants) .keys() .map(k => k.toLowerCase()) - .difference([platform]) + .difference([platformData.platformNameLowerCase]) .each(otherPlatform => { delete projectFileContents.nativescript[`tns-${otherPlatform}`]; }); @@ -216,9 +213,9 @@ export class ProjectChangesService implements IProjectChangesService { return getHash(JSON.stringify(projectFileContents)); } - private isProjectFileChanged(projectData: IProjectData, platform: string): boolean { - const projectFileStrippedContentsHash = this.getProjectFileStrippedHash(projectData, platform); - const prepareInfo = this.getPrepareInfo(platform, projectData); + private isProjectFileChanged(projectDir: string, platformData: IPlatformData): boolean { + const projectFileStrippedContentsHash = this.getProjectFileStrippedHash(projectDir, platformData); + const prepareInfo = this.getPrepareInfo(platformData); return projectFileStrippedContentsHash !== prepareInfo.projectFileHash; } diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 8f4554c0b9..cca9858113 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -9,8 +9,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp private $childProcess: IChildProcess ) { super(); } - // TODO: Rename this to compileWithWatch() - public async startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { return new Promise((resolve, reject) => { if (this.webpackProcesses[platformData.platformNameLowerCase]) { resolve(); @@ -35,6 +34,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const files = message.emittedFiles .filter((file: string) => file.indexOf("App_Resources") === -1) .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)); + console.log("===================== BEFORE EMIT webpack files ", files); this.emit("webpackEmittedFiles", files); } }); @@ -53,8 +53,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp }); } - // TODO: Rename this to compileWithoutWatch() - public async compile(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + public async compileWithoutWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { return new Promise((resolve, reject) => { if (this.webpackProcesses[platformData.platformNameLowerCase]) { resolve(); diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index 01b889611b..668e249783 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -1,9 +1,10 @@ import { EventEmitter } from "events"; +import { PreparePlatformData, BuildPlatformDataBase, WorkflowData } from "../workflow/workflow-data-service"; declare global { interface IWebpackCompilerService extends EventEmitter { - startWatcher(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; - compile(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; + compileWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; + compileWithoutWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; } interface IWebpackCompilerConfig { @@ -17,34 +18,300 @@ declare global { interface IPreparePlatformService { addPlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise; - preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise; + preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; } - interface IPreparePlatformData extends IRelease, IHasUseHotModuleReloadOption { - signingOptions?: IiOSSigningOptions | IAndroidSigningOptions; - nativePrepare?: INativePrepare; - env?: any; - frameworkPath?: string; + interface IPlatformBuildService { + buildPlatform(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: T): Promise + buildPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: T, outputPath?: string): Promise; + saveBuildInfoFile(platformData: IPlatformData, projectData: IProjectData, buildInfoFileDirname: string): void; + getBuildInfoFromFile(platformData: IPlatformData, buildConfig: BuildPlatformDataBase, buildOutputPath?: string): IBuildInfo; } - interface IiOSSigningOptions extends ITeamIdentifier, IProvision { - mobileProvisionData?: any; + interface IProjectChangesService { + checkForChanges(platform: string, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; + getPrepareInfoFilePath(platformData: IPlatformData): string; + getPrepareInfo(platformData: IPlatformData): IPrepareInfo; + savePrepareInfo(platformData: IPlatformData): void; + setNativePlatformStatus(platformData: IPlatformData, addedPlatform: IAddedNativePlatform): void; + currentChanges: IProjectChangesInfo; } - interface IAndroidSigningOptions { - keyStoreAlias: string; - keyStorePath: string; - keyStoreAliasPassword: string; - keyStorePassword: string; - sdk?: string; + interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { + /** + * Ensures that the specified platform and its dependencies are installed. + * When there are changes to be prepared, it prepares the native project for the specified platform. + * When finishes, prepare saves the .nsprepareinfo file in platform folder. + * This file contains information about current project configuration and allows skipping unnecessary build, deploy and livesync steps. + * @param {IPreparePlatformInfo} platformInfo Options to control the preparation. + * @returns {boolean} true indicates that the platform was prepared. + */ + preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; + + /** + * Determines whether a build is necessary. A build is necessary when one of the following is true: + * - there is no previous build. + * - the .nsbuildinfo file in product folder points to an old prepare. + * @param {string} platform The platform to build. + * @param {IProjectData} projectData DTO with information about the project. + * @param {IBuildConfig} @optional buildConfig Indicates whether the build is for device or emulator. + * @param {string} @optional outputPath Directory containing build information and artifacts. + * @returns {boolean} true indicates that the platform should be build. + */ + shouldBuild(platform: string, projectData: IProjectData, buildConfig?: IBuildConfig, outputPath?: string): Promise; + + /** + * Determines whether installation is necessary. It is necessary when one of the following is true: + * - the application is not installed. + * - the .nsbuildinfo file located in application root folder is different than the local .nsbuildinfo file + * @param {Mobile.IDevice} device The device where the application should be installed. + * @param {IProjectData} projectData DTO with information about the project. + * @param {string} @optional outputPath Directory containing build information and artifacts. + * @returns {Promise} true indicates that the application should be installed. + */ + shouldInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise; + + /** + * + * @param {Mobile.IDevice} device The device where the application should be installed. + * @param {IProjectData} projectData DTO with information about the project. + * @param {string} @optional outputPath Directory containing build information and artifacts. + */ + validateInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise; + + /** + * Installs the application on specified device. + * When finishes, saves .nsbuildinfo in application root folder to indicate the prepare that was used to build the app. + * * .nsbuildinfo is not persisted when building for release. + * @param {Mobile.IDevice} device The device where the application should be installed. + * @param {IBuildConfig} options The build configuration. + * @param {string} @optional pathToBuiltApp Path to build artifact. + * @param {string} @optional outputPath Directory containing build information and artifacts. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {void} + */ + installApplication(device: Mobile.IDevice, options: IBuildConfig, projectData: IProjectData, pathToBuiltApp?: string, outputPath?: string): Promise; + + /** + * Executes prepare, build and installOnPlatform when necessary to ensure that the latest version of the app is installed on specified platform. + * - When --clean option is specified it builds the app on every change. If not, build is executed only when there are native changes. + * @param {IDeployPlatformInfo} deployInfo Options required for project preparation and deployment. + * @returns {void} + */ + deployPlatform(deployInfo: IDeployPlatformInfo): Promise; + + /** + * Runs the application on specified platform. Assumes that the application is already build and installed. Fails if this is not true. + * @param {string} platform The platform where to start the application. + * @param {IRunPlatformOptions} runOptions Various options that help manage the run operation. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {void} + */ + startApplication(platform: string, runOptions: IRunPlatformOptions, appData: Mobile.IStartApplicationData): Promise; + + /** + * Returns information about the latest built application for device in the current project. + * @param {IPlatformData} platformData Data describing the current platform. + * @param {IBuildConfig} buildConfig Defines if the build is for release configuration. + * @param {string} @optional outputPath Directory that should contain the build artifact. + * @returns {IApplicationPackage} Information about latest built application. + */ + getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage; + + /** + * Returns information about the latest built application for simulator in the current project. + * @param {IPlatformData} platformData Data describing the current platform. + * @param {IBuildConfig} buildConfig Defines if the build is for release configuration. + * @param {string} @optional outputPath Directory that should contain the build artifact. + * @returns {IApplicationPackage} Information about latest built application. + */ + getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage; + + /** + * Copies latest build output to a specified location. + * @param {string} platform Mobile platform - Android, iOS. + * @param {string} targetPath Destination where the build artifact should be copied. + * @param {IBuildConfig} buildConfig Defines if the searched artifact should be for simulator and is it built for release. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {void} + */ + copyLastOutput(platform: string, targetPath: string, buildConfig: IBuildConfig, projectData: IProjectData): void; + + /** + * Gets the latest build output. + * @param {string} platform Mobile platform - Android, iOS. + * @param {IBuildConfig} buildConfig Defines if the searched artifact should be for simulator and is it built for release. + * @param {IProjectData} projectData DTO with information about the project. + * @param {string} @optional outputPath Directory that should contain the build artifact. + * @returns {string} The path to latest built artifact. + */ + lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData, outputPath?: string): string; + + /** + * Reads contents of a file on device. + * @param {Mobile.IDevice} device The device to read from. + * @param {string} deviceFilePath The file path. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {string} The contents of the file or null when there is no such file. + */ + readFile(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise; + + /** + * Saves build information in a proprietary file. + * @param {string} platform The build platform. + * @param {string} projectDir The project's directory. + * @param {string} buildInfoFileDirname The directory where the build file should be written to. + * @returns {void} + */ + saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void; + + /** + * Gives information for the current version of the runtime. + * @param {string} platform The platform to be checked. + * @param {IProjectData} projectData The data describing the project + * @returns {string} Runtime version + */ + getCurrentPlatformVersion(platform: string, projectData: IProjectData): string; } interface IPlatformWatcherService extends EventEmitter { - startWatcher(platformData: IPlatformData, projectData: IProjectData, startWatcherData: IStartWatcherData): Promise; + startWatcher(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; } - interface IStartWatcherData { - webpackCompilerConfig: IWebpackCompilerConfig; - preparePlatformData: IPreparePlatformData; + interface IFilesChangeEventData { + platform: string; + files: string[]; + hasNativeChanges: boolean; + } + + interface IInitialSyncEventData { + platform: string; + hasNativeChanges: boolean; + } + + interface IDeviceInstallationService { + installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise; + installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise; + getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise; + } + + 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; + createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise; + interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; + interpolateConfigurationFile(projectData: IProjectData, preparePlatformData: PreparePlatformData): void; + + /** + * Executes additional actions after native project is created. + * @param {string} projectRoot Path to the real NativeScript project. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {void} + */ + afterCreateProject(projectRoot: string, projectData: IProjectData): void; + + /** + * Gets first chance to validate the options provided as command line arguments. + * @param {string} projectId Project identifier - for example org.nativescript.test. + * @param {any} provision UUID of the provisioning profile used in iOS option validation. + * @returns {void} + */ + validateOptions(projectId?: string, provision?: true | string, teamId?: true | string): Promise; + + buildProject(projectRoot: string, projectData: IProjectData, buildConfig: T): Promise; + + /** + * Prepares images in Native project (for iOS). + * @param {IProjectData} projectData DTO with information about the project. + * @param {any} platformSpecificData Platform specific data required for project preparation. + * @returns {void} + */ + prepareProject(projectData: IProjectData, preparePlatformData: T): Promise; + + /** + * Prepares App_Resources in the native project by clearing data from other platform and applying platform specific rules. + * @param {string} appResourcesDirectoryPath The place in the native project where the App_Resources are copied first. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {void} + */ + prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void; + + /** + * Defines if current platform is prepared (i.e. if /platforms/ dir exists). + * @param {string} projectRoot The project directory (path where root's package.json is located). + * @param {IProjectData} projectData DTO with information about the project. + * @returns {boolean} True in case platform is prepare (i.e. if /platforms/ dir exists), false otherwise. + */ + isPlatformPrepared(projectRoot: string, projectData: IProjectData): boolean; + + /** + * Checks if current platform can be updated to a newer versions. + * @param {string} newInstalledModuleDir Path to the native project. + * @param {IProjectData} projectData DTO with information about the project. + * @return {boolean} True if platform can be updated. false otherwise. + */ + canUpdatePlatform(newInstalledModuleDir: string, projectData: IProjectData): boolean; + + preparePluginNativeCode(pluginData: IPluginData, options?: any): Promise; + + /** + * Removes native code of a plugin (CocoaPods, jars, libs, src). + * @param {IPluginData} Plugins data describing the plugin which should be cleaned. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {void} + */ + removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise; + + beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise; + + handleNativeDependenciesChange(projectData: IProjectData, opts: IRelease): Promise; + + /** + * Gets the path wheren App_Resources should be copied. + * @returns {string} Path to native project, where App_Resources should be copied. + */ + getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string; + + cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise; + processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean }): Promise; + + /** + * Ensures there is configuration file (AndroidManifest.xml, Info.plist) in app/App_Resources. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {void} + */ + ensureConfigurationFileInAppResources(projectData: IProjectData): void; + + /** + * Stops all running processes that might hold a lock on the filesystem. + * Android: Gradle daemon processes are terminated. + * @param {IPlatformData} platformData The data for the specified platform. + * @returns {void} + */ + stopServices?(projectRoot: string): Promise; + + /** + * Removes build artifacts specific to the platform + * @param {string} projectRoot The root directory of the native project. + * @param {IProjectData} projectData DTO with information about the project. + * @returns {void} + */ + cleanProject?(projectRoot: string, projectData: IProjectData): Promise + + /** + * Check the current state of the project, and validate against the options. + * If there are parts in the project that are inconsistent with the desired options, marks them in the changeset flags. + */ + checkForChanges(changeset: IProjectChangesInfo, preparePlatformData: T, projectData: IProjectData): Promise; + + /** + * Get the deployment target's version + * Currently implemented only for iOS -> returns the value of IPHONEOS_DEPLOYMENT_TARGET property from xcconfig file + */ + getDeploymentTarget?(projectData: IProjectData): any; } } \ No newline at end of file diff --git a/lib/services/workflow/device-workflow-service.ts b/lib/services/workflow/device-workflow-service.ts deleted file mode 100644 index 3f8ff48f06..0000000000 --- a/lib/services/workflow/device-workflow-service.ts +++ /dev/null @@ -1,139 +0,0 @@ -import * as constants from "../../constants"; -import * as helpers from "../../common/helpers"; -import * as path from "path"; -import * as shell from "shelljs"; -import * as temp from "temp"; - -// TODO: Extract it as common constant for this service and platform-build-service.ts -const buildInfoFileName = ".nsbuildinfo"; - -export class DeviceWorkflowService implements IDeviceWorkflowService { - constructor( - private $analyticsService: IAnalyticsService, - private $devicePathProvider: IDevicePathProvider, - private $fs: IFileSystem, - private $logger: ILogger, - private $mobileHelper: Mobile.IMobileHelper, - private $platformBuildService: IPlatformBuildService - ) { } - - // TODO: Extract this method to device-installation-service - public async installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise { - this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); - - await this.$analyticsService.trackEventActionInGoogleAnalytics({ - action: constants.TrackActionNames.Deploy, - device, - projectDir: projectData.projectDir - }); - - // TODO: Get latest built applicationPackage when no applicationPackage is provided - // const packageFile = applicationPackage.packageName; - // const outputFilePath = applicationPackage.packagePath; - - // if (!packageFile) { - // if (this.$devicesService.isiOSSimulator(device)) { - // packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig, outputFilePath).packageName; - // } else { - // packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig, outputFilePath).packageName; - // } - // } - - await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); - - const platform = device.deviceInfo.platform.toLowerCase(); - await device.applicationManager.reinstallApplication(projectData.projectIdentifiers[platform], packageFile); - - await this.updateHashesOnDevice({ - device, - appIdentifier: projectData.projectIdentifiers[platform], - outputFilePath, - platformData - }); - - if (!buildConfig.release) { - const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - const options = buildConfig; - options.buildForDevice = !device.isEmulator; - const buildInfoFilePath = outputFilePath || platformData.getBuildOutputPath(buildConfig); - const appIdentifier = projectData.projectIdentifiers[platform]; - - await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); - } - - this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); - } - - public async installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise { - const shouldInstall = await this.shouldInstall(device, platformData, projectData, buildConfig); - if (shouldInstall) { - await this.installOnDevice(device, platformData, projectData, buildConfig, packageFile, outputFilePath); - } - } - - private async updateHashesOnDevice(data: { device: Mobile.IDevice, appIdentifier: string, outputFilePath: string, platformData: IPlatformData }): Promise { - const { device, appIdentifier, platformData, outputFilePath } = data; - - if (!this.$mobileHelper.isAndroidPlatform(platformData.normalizedPlatformName)) { - return; - } - - let hashes = {}; - const hashesFilePath = path.join(outputFilePath || platformData.getBuildOutputPath(null), constants.HASHES_FILE_NAME); - if (this.$fs.exists(hashesFilePath)) { - hashes = this.$fs.readJson(hashesFilePath); - } - - await device.fileSystem.updateHashesOnDevice(hashes, appIdentifier); - } - - private async getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise { - const platform = device.deviceInfo.platform.toLowerCase(); - const deviceRootPath = await this.$devicePathProvider.getDeviceProjectRootPath(device, { - appIdentifier: projectData.projectIdentifiers[platform], - getDirname: true - }); - return helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, buildInfoFileName)); - } - - private async shouldInstall(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, release: IRelease, outputPath?: string): Promise { - const platform = device.deviceInfo.platform; - if (!(await device.applicationManager.isApplicationInstalled(projectData.projectIdentifiers[platform.toLowerCase()]))) { - return true; - } - - const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); - const localBuildInfo = this.$platformBuildService.getBuildInfoFromFile(platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); - - return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; - } - - private async getDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData): Promise { - const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - try { - return JSON.parse(await this.readFile(device, deviceFilePath, projectData)); - } catch (e) { - return null; - } - } - - public async readFile(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise { - temp.track(); - const uniqueFilePath = temp.path({ suffix: ".tmp" }); - const platform = device.deviceInfo.platform.toLowerCase(); - try { - await device.fileSystem.getFile(deviceFilePath, projectData.projectIdentifiers[platform], uniqueFilePath); - } catch (e) { - return null; - } - - if (this.$fs.exists(uniqueFilePath)) { - const text = this.$fs.readText(uniqueFilePath); - shell.rm(uniqueFilePath); - return text; - } - - return null; - } -} -$injector.register("deviceWorkflowService", DeviceWorkflowService); diff --git a/lib/services/workflow/platform-workflow-service.ts b/lib/services/workflow/platform-workflow-service.ts index d6cf2aa629..93fe33a858 100644 --- a/lib/services/workflow/platform-workflow-service.ts +++ b/lib/services/workflow/platform-workflow-service.ts @@ -1,97 +1,30 @@ -import * as path from "path"; -import { NativePlatformStatus } from "../../constants"; +import { WorkflowDataService } from "./workflow-data-service"; +import { PlatformAddService } from "../platform/platform-add-service"; +import { PlatformBuildService } from "../platform/platform-build-service"; +import { PlatformService } from "../platform-service"; export class PlatformWorkflowService implements IPlatformWorkflowService { constructor ( - private $buildArtefactsService: IBuildArtefactsService, - private $fs: IFileSystem, - private $platformAddService: IPlatformAddService, - private $platformBuildService: IPlatformBuildService, - private $platformService: IPreparePlatformService, - private $projectChangesService: IProjectChangesService + private $platformAddService: PlatformAddService, + private $platformBuildService: PlatformBuildService, + private $platformService: PlatformService, + private $workflowDataService: WorkflowDataService, ) { } - public async addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise { - const { platformParam, frameworkPath, nativePrepare } = workflowData; + public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise { + const { nativePlatformData, projectData, addPlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); - const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, nativePrepare); - if (shouldAddPlatform) { - await this.$platformAddService.addPlatform({ platformParam, frameworkPath, nativePrepare }, projectData); - } + await this.$platformAddService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); + await this.$platformService.preparePlatform(nativePlatformData, projectData, preparePlatformData); } - public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise { - await this.addPlatformIfNeeded(platformData, projectData, workflowData); - await this.$platformService.preparePlatform(platformData, projectData, workflowData); - } - - public async buildPlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData, buildConfig: IBuildConfig): Promise { - await this.preparePlatform(platformData, projectData, workflowData); - const result = await this.$platformBuildService.buildPlatform(platformData, projectData, buildConfig); - - return result; - } + public async buildPlatform(platform: string, projectDir: string, options: IOptions): Promise { + const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); - public async buildPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData, buildConfig: IBuildConfig, outputPath?: string): Promise { - await this.preparePlatform(platformData, projectData, workflowData); - - let result = null; - - outputPath = outputPath || platformData.getBuildOutputPath(buildConfig); - const shouldBuildPlatform = await this.shouldBuildPlatform(platformData, projectData, buildConfig, outputPath); - if (shouldBuildPlatform) { - result = await this.$platformBuildService.buildPlatform(platformData, projectData, buildConfig); - } + await this.preparePlatform(platform, projectDir, options); + const result = await this.$platformBuildService.buildPlatform(nativePlatformData, projectData, buildPlatformData); return result; } - - private shouldAddPlatform(platformData: IPlatformData, projectData: IProjectData, nativePrepare: INativePrepare): boolean { - const platformName = platformData.platformNameLowerCase; - const prepareInfo = this.$projectChangesService.getPrepareInfo(platformName, projectData); - const hasPlatformDirectory = this.$fs.exists(path.join(projectData.platformsDir, platformName)); - const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; - const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === NativePlatformStatus.requiresPlatformAdd; - const result = !hasPlatformDirectory || (shouldAddNativePlatform && requiresNativePlatformAdd); - - return !!result; - } - - private async shouldBuildPlatform(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, outputPath: string): Promise { - const platform = platformData.platformNameLowerCase; - if (buildConfig.release && this.$projectChangesService.currentChanges.hasChanges) { - return true; - } - - if (this.$projectChangesService.currentChanges.changesRequireBuild) { - return true; - } - - if (!this.$fs.exists(outputPath)) { - return true; - } - - const validBuildOutputData = platformData.getValidBuildOutputData(buildConfig); - const packages = this.$buildArtefactsService.getAllBuiltApplicationPackages(outputPath, validBuildOutputData); - if (packages.length === 0) { - return true; - } - - const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); - const buildInfo = this.$platformBuildService.getBuildInfoFromFile(platformData, buildConfig, outputPath); - if (!prepareInfo || !buildInfo) { - return true; - } - - if (buildConfig.clean) { - return true; - } - - if (prepareInfo.time === buildInfo.prepareTime) { - return false; - } - - return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; - } } $injector.register("platformWorkflowService", PlatformWorkflowService); diff --git a/lib/services/workflow/workflow-data-service.ts b/lib/services/workflow/workflow-data-service.ts new file mode 100644 index 0000000000..fa6571e9fb --- /dev/null +++ b/lib/services/workflow/workflow-data-service.ts @@ -0,0 +1,100 @@ +export class WorkflowDataService { + constructor( + private $platformsData: IPlatformsData, + private $projectDataService: IProjectDataService, + ) { } + + public createWorkflowData(platform: string, projectDir: string, options: IOptions | any): WorkflowData { + const projectData = this.$projectDataService.getProjectData(projectDir); + const nativePlatformData = this.$platformsData.getPlatformData(platform, projectData); + + const data: IDictionary = { + ios: { + projectData, + nativePlatformData, + addPlatformData: new AddPlatformData("ios", options), + preparePlatformData: new PreparePlatformData(options), + buildPlatformData: new IOSBuildData(options), + installOnDeviceData: {}, + liveSyncData: {}, + restartOnDeviceData: {} + }, + android: { + projectData, + nativePlatformData, + addPlatformData: new AddPlatformData("android", options), + preparePlatformData: new PreparePlatformData(options), + buildPlatformData: new AndroidBuildData(options), + installOnDeviceData: {}, + liveSyncData: {}, + restartOnDeviceData: {} + } + }; + + return data[platform.toLowerCase()]; + } +} +$injector.register("workflowDataService", WorkflowDataService); + +export class WorkflowData { + public projectData: IProjectData; + public nativePlatformData: IPlatformData; + public addPlatformData: AddPlatformData; + public preparePlatformData: PreparePlatformData; + public buildPlatformData: any; + public installOnDeviceData: any; + public liveSyncData: any; + public restartOnDeviceData: any; +} + +export class AddPlatformData { + constructor(private platform: string, private options: IOptions | any) { } + + public platformParam = this.options.platformParam || this.platform; + public frameworkPath = this.options.frameworkPath; + public nativePrepare = this.options.nativePrepare; +} + +export class PreparePlatformData { + constructor(protected options: IOptions | any) { } + + public env = this.options.env; + public release = this.options.release; + public nativePrepare = this.options.nativePrepare; +} + +export class IOSPrepareData extends PreparePlatformData { + constructor(options: IOptions | any) { super(options); } + + public teamId = this.options.teamId; + public provision = this.options.provision; + public mobileProvisionData = this.options.mobileProvisionData; +} + +export class BuildPlatformDataBase { + constructor(protected options: IOptions | any) { } + + public release = this.options.release; + public clean = this.options.clean; + public device = this.options.device; + public iCloudContainerEnvironment = this.options.iCloudContainerEnvironment; + public buildForDevice = this.options.forDevice; + public buildOutputStdio = this.options.buildOutputStdio || "inherit"; +} + +export class IOSBuildData extends BuildPlatformDataBase { + constructor(options: IOptions) { super(options); } + + public teamId = this.options.teamId; + public provision = this.options.provision; +} + +export class AndroidBuildData extends BuildPlatformDataBase { + constructor(options: IOptions) { super(options); } + + public keyStoreAlias = this.options.keyStoreAlias; + public keyStorePath = this.options.keyStorePath; + public keyStoreAliasPassword = this.options.keyStoreAliasPassword; + public keyStorePassword = this.options.keyStorePassword; + public androidBundle = this.options.aab; +} diff --git a/lib/services/workflow/workflow.d.ts b/lib/services/workflow/workflow.d.ts index dfbe8698ba..35d9e702ab 100644 --- a/lib/services/workflow/workflow.d.ts +++ b/lib/services/workflow/workflow.d.ts @@ -1,23 +1,12 @@ -interface IPlatformWorkflowData extends IRelease, IHasUseHotModuleReloadOption { - platformParam: string; - frameworkPath?: string; - nativePrepare?: INativePrepare; - env?: any; - signingOptions: IiOSSigningOptions | IAndroidSigningOptions; -} - interface IPlatformWorkflowService { - addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise; - preparePlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData): Promise; - buildPlatform(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData, buildConfig: IBuildConfig): Promise; - buildPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, workflowData: IPlatformWorkflowData, buildConfig: IBuildConfig, outputPath?: string): Promise; + preparePlatform(platform: string, projectDir: string, options: IOptions): Promise; + buildPlatform(platform: string, projectDir: string, options: IOptions): Promise; } -interface IPlatformWorkflowDataFactory { - createPlatformWorkflowData(platformParam: string, options: IOptions, nativePrepare?: INativePrepare): IPlatformWorkflowData; +interface IDeviceWorkflowService { } -interface IDeviceWorkflowService { - installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise; - installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise; +interface IAdditionalWorkflowOptions { + platformParam?: string; + nativePrepare?: INativePrepare; } \ No newline at end of file diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index 16ea3bd7cd..25d1a2ab72 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -57,6 +57,12 @@ class ProjectChangesServiceTest extends BaseServiceTest { get platformsData(): any { return this.injector.resolve("platformsData"); } + + getPlatformData(platform: string): IPlatformData { + return { + projectRoot: path.join(this.projectDir, "platforms", platform.toLowerCase()) + }; + } } describe("Project Changes Service Tests", () => { @@ -76,7 +82,7 @@ describe("Project Changes Service Tests", () => { projectRoot: path.join(platformsDir, platform), get platformProjectService(): any { return { - checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { + checkForChanges(changesInfo: IProjectChangesInfo): void { changesInfo.signingChanged = true; } }; @@ -87,7 +93,7 @@ describe("Project Changes Service Tests", () => { projectRoot: path.join(platformsDir, platform), get platformProjectService(): any { return { - checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): void { /* no android changes */ } + checkForChanges(changesInfo: IProjectChangesInfo): void { /* no android changes */ } }; } }; @@ -99,7 +105,7 @@ describe("Project Changes Service Tests", () => { it("Gets the correct Prepare Info path for ios/android", () => { for (const platform of ["ios", "android"]) { const actualPrepareInfoPath = serviceTest.projectChangesService - .getPrepareInfoFilePath(platform, this.projectData); + .getPrepareInfoFilePath(serviceTest.getPlatformData(platform)); const expectedPrepareInfoPath = path.join(serviceTest.projectDir, Constants.PLATFORMS_DIR_NAME, @@ -113,7 +119,7 @@ describe("Project Changes Service Tests", () => { describe("Get Prepare Info", () => { it("Returns empty if file path doesn't exists", () => { for (const platform of ["ios", "android"]) { - const projectInfo = serviceTest.projectChangesService.getPrepareInfo(platform, this.projectData); + const projectInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); assert.isNull(projectInfo); } @@ -139,7 +145,7 @@ describe("Project Changes Service Tests", () => { fs.writeJson(prepareInfoPath, expectedPrepareInfo); // act - const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, this.projectData); + const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); // assert assert.deepEqual(actualPrepareInfo, expectedPrepareInfo); @@ -149,42 +155,20 @@ describe("Project Changes Service Tests", () => { describe("Accumulates Changes From Project Services", () => { it("accumulates changes from the project service", async () => { - const iOSChanges = await serviceTest.projectChangesService.checkForChanges({ - platform: "ios", - projectData: serviceTest.projectData, - projectChangesOptions: { - release: false, - signingOptions: { - provision: undefined, - teamId: undefined, - }, - useHotModuleReload: false - } + const iOSChanges = await serviceTest.projectChangesService.checkForChanges("ios", serviceTest.projectData, { + provision: undefined, + teamId: undefined }); assert.isTrue(!!iOSChanges.signingChanged, "iOS signingChanged expected to be true"); - - const androidChanges = await serviceTest.projectChangesService.checkForChanges({ - platform: "android", - projectData: serviceTest.projectData, - projectChangesOptions: { - release: false, - signingOptions: { - provision: undefined, - teamId: undefined, - }, - useHotModuleReload: false - } - }); - assert.isFalse(!!androidChanges.signingChanged, "Android signingChanged expected to be false"); }); }); describe("setNativePlatformStatus", () => { it("creates prepare info and sets only the native platform status when there isn't an existing prepare info", () => { for (const platform of ["ios", "android"]) { - serviceTest.projectChangesService.setNativePlatformStatus(platform, serviceTest.projectData, { nativePlatformStatus: Constants.NativePlatformStatus.requiresPrepare }); + serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), { nativePlatformStatus: Constants.NativePlatformStatus.requiresPrepare }); - const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, serviceTest.projectData); + const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); assert.deepEqual(actualPrepareInfo, { nativePlatformStatus: Constants.NativePlatformStatus.requiresPrepare }); } @@ -192,24 +176,13 @@ describe("Project Changes Service Tests", () => { it(`shouldn't reset prepare info when native platform status is ${Constants.NativePlatformStatus.alreadyPrepared} and there is existing prepare info`, async () => { for (const platform of ["ios", "android"]) { - await serviceTest.projectChangesService.checkForChanges({ - platform, - projectData: serviceTest.projectData, - projectChangesOptions: { - release: false, - signingOptions: { - provision: undefined, - teamId: undefined, - }, - useHotModuleReload: false - } - }); - serviceTest.projectChangesService.savePrepareInfo(platform, serviceTest.projectData); - const prepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, serviceTest.projectData); - - serviceTest.projectChangesService.setNativePlatformStatus(platform, serviceTest.projectData, { nativePlatformStatus: Constants.NativePlatformStatus.alreadyPrepared }); - - const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, serviceTest.projectData); + await serviceTest.projectChangesService.checkForChanges(platform, serviceTest.projectData, {}); + serviceTest.projectChangesService.savePrepareInfo(serviceTest.getPlatformData(platform)); + const prepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); + + serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), { nativePlatformStatus: Constants.NativePlatformStatus.alreadyPrepared }); + + const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); prepareInfo.nativePlatformStatus = Constants.NativePlatformStatus.alreadyPrepared; assert.deepEqual(actualPrepareInfo, prepareInfo); } @@ -218,21 +191,10 @@ describe("Project Changes Service Tests", () => { _.each([Constants.NativePlatformStatus.requiresPlatformAdd, Constants.NativePlatformStatus.requiresPrepare], nativePlatformStatus => { it(`should reset prepare info when native platform status is ${nativePlatformStatus} and there is existing prepare info`, async () => { for (const platform of ["ios", "android"]) { - await serviceTest.projectChangesService.checkForChanges({ - platform, - projectData: serviceTest.projectData, - projectChangesOptions: { - release: false, - signingOptions: { - provision: undefined, - teamId: undefined, - }, - useHotModuleReload: false - } - }); - serviceTest.projectChangesService.setNativePlatformStatus(platform, serviceTest.projectData, { nativePlatformStatus: nativePlatformStatus }); + await serviceTest.projectChangesService.checkForChanges(platform, serviceTest.projectData, {}); + serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), { nativePlatformStatus: nativePlatformStatus }); - const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(platform, serviceTest.projectData); + const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); assert.deepEqual(actualPrepareInfo, { nativePlatformStatus: nativePlatformStatus }); } }); diff --git a/test/services/android-project-service.ts b/test/services/android-project-service.ts index 242a9be3be..96bdc0a128 100644 --- a/test/services/android-project-service.ts +++ b/test/services/android-project-service.ts @@ -94,7 +94,7 @@ describe("androidDeviceDebugService", () => { const buildConfig = getDefautlBuildConfig(); //act - await androidProjectService.buildProject("local/local", projectData, buildConfig); + await androidProjectService.buildProject("local/local", projectData, buildConfig); //assert assert.include(childProcess.lastCommandArgs, "assembleRelease"); @@ -106,7 +106,7 @@ describe("androidDeviceDebugService", () => { buildConfig.release = false; //act - await androidProjectService.buildProject("local/local", projectData, buildConfig); + await androidProjectService.buildProject("local/local", projectData, buildConfig); //assert assert.include(childProcess.lastCommandArgs, "assembleDebug"); @@ -118,7 +118,7 @@ describe("androidDeviceDebugService", () => { buildConfig.androidBundle = true; //act - await androidProjectService.buildProject("local/local", projectData, buildConfig); + await androidProjectService.buildProject("local/local", projectData, buildConfig); //assert assert.include(childProcess.lastCommandArgs, "bundleRelease"); @@ -131,7 +131,7 @@ describe("androidDeviceDebugService", () => { buildConfig.release = false; //act - await androidProjectService.buildProject("local/local", projectData, buildConfig); + await androidProjectService.buildProject("local/local", projectData, buildConfig); //assert assert.include(childProcess.lastCommandArgs, "bundleDebug"); diff --git a/test/services/bundle-workflow-service.ts b/test/services/bundle-workflow-service.ts new file mode 100644 index 0000000000..735a74d844 --- /dev/null +++ b/test/services/bundle-workflow-service.ts @@ -0,0 +1,204 @@ +import { Yok } from "../../lib/common/yok"; +import { BundleWorkflowService } from "../../lib/services/bundle-workflow-service"; +import { assert } from "chai"; +import { PlatformWorkflowService } from "../../lib/services/workflow/platform-workflow-service"; + +const deviceMap: IDictionary = { + myiOSDevice: { + deviceInfo: { + identifier: "myiOSDevice", + platform: "ios" + } + }, + myAndroidDevice: { + deviceInfo: { + identifier: "myAndroidDevice", + platform: "android" + } + } +}; + +function createTestInjector(): IInjector { + const injector = new Yok(); + + injector.register("devicesService", ({ + getDeviceByIdentifier: (identifier: string) => { return deviceMap[identifier]; } + })); + injector.register("deviceWorkflowService", ({})); + injector.register("errors", ({ + failWithoutHelp: () => ({}) + })); + injector.register("liveSyncService", ({})); + injector.register("logger", ({ + trace: () => ({}) + })); + injector.register("platformsData", ({ + getPlatformData: (platform: string) => ({ + platformNameLowerCase: platform.toLowerCase() + }) + })); + injector.register("platformWatcherService", ({ + on: () => ({}), + emit: () => ({}), + startWatcher: () => ({}) + })); + injector.register("platformWorkflowService", PlatformWorkflowService); + injector.register("pluginsService", ({})); + injector.register("projectDataService", ({ + getProjectData: () => ({ + projectDir + }) + })); + injector.register("buildArtefactsService", ({})); + injector.register("platformBuildService", ({})); + injector.register("platformAddService", ({})); + injector.register("platformService", ({})); + injector.register("projectChangesService", ({})); + injector.register("fs", ({})); + injector.register("bundleWorkflowService", BundleWorkflowService); + + return injector; +} + +const projectDir = "path/to/my/projectDir"; +const buildOutputPath = `${projectDir}/platform/ios/build/myproject.app`; + +const iOSDeviceDescriptor = { identifier: "myiOSDevice", buildAction: async () => buildOutputPath }; +const androidDeviceDescriptor = { identifier: "myAndroidDevice", buildAction: async () => buildOutputPath }; + +const liveSyncInfo = { + projectDir, + release: false, + useHotModuleReload: false, + webpackCompilerConfig: { + env: {}, + } +}; + +describe("BundleWorkflowService", () => { + describe("start", () => { + describe("when the run on device is called for second time for the same projectDir", () => { + it("should run only for new devies (for which the initial sync is still not executed)", async () => { + return; + }); + it("shouldn't run for old devices (for which initial sync is already executed)", async () => { + return; + }); + }); + describe("when platform is still not added", () => { + it("should add platform before start watchers", async () => { + const injector = createTestInjector(); + + let isAddPlatformIfNeededCalled = false; + const platformAddService: IPlatformAddService = injector.resolve("platformAddService"); + platformAddService.addPlatformIfNeeded = async () => { isAddPlatformIfNeededCalled = true; }; + + let isStartWatcherCalled = false; + const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); + (platformWatcherService).startWatcher = async () => { + assert.isTrue(isAddPlatformIfNeededCalled); + isStartWatcherCalled = true; + return true; + }; + + const bundleWorkflowService: IBundleWorkflowService = injector.resolve("bundleWorkflowService"); + await bundleWorkflowService.start(projectDir, [iOSDeviceDescriptor], liveSyncInfo); + + assert.isTrue(isStartWatcherCalled); + }); + + const testCases = [ + { + name: "should add only ios platform when only ios devices are connected", + connectedDevices: [iOSDeviceDescriptor], + expectedAddedPlatforms: ["ios"] + }, + { + name: "should add only android platform when only android devices are connected", + connectedDevices: [androidDeviceDescriptor], + expectedAddedPlatforms: ["android"] + }, + { + name: "should add both platforms when ios and android devices are connected", + connectedDevices: [iOSDeviceDescriptor, androidDeviceDescriptor], + expectedAddedPlatforms: ["ios", "android"] + } + ]; + + _.each(testCases, testCase => { + it(testCase.name, async () => { + const injector = createTestInjector(); + + const actualAddedPlatforms: IPlatformData[] = []; + const platformAddService: IPlatformAddService = injector.resolve("platformWorkflowService"); + platformAddService.addPlatformIfNeeded = async (platformData: IPlatformData) => { + actualAddedPlatforms.push(platformData); + }; + + const bundleWorkflowService: IBundleWorkflowService = injector.resolve("bundleWorkflowService"); + await bundleWorkflowService.start(projectDir, testCase.connectedDevices, liveSyncInfo); + + assert.deepEqual(actualAddedPlatforms.map(pData => pData.platformNameLowerCase), testCase.expectedAddedPlatforms); + }); + }); + }); + describe("on initialSyncEventData", () => { + let injector: IInjector; + let isBuildPlatformCalled = false; + beforeEach(() => { + injector = createTestInjector(); + + const platformAddService: IPlatformAddService = injector.resolve("platformWorkflowService"); + platformAddService.addPlatformIfNeeded = async () => { return; }; + + const platformBuildService: IPlatformBuildService = injector.resolve("platformBuildService"); + platformBuildService.buildPlatform = async () => { isBuildPlatformCalled = true; return buildOutputPath; }; + }); + + console.log("============== isBuildPlatformCalled ============= ", isBuildPlatformCalled); + + afterEach(() => { + isBuildPlatformCalled = false; + }); + + // _.each(["ios", "android"], platform => { + // it(`should build for ${platform} platform if there are native changes`, async () => { + // const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); + // platformWatcherService.emit(INITIAL_SYNC_EVENT_NAME, { platform, hasNativeChanges: true }); + + // const bundleWorkflowService: IBundleWorkflowService = injector.resolve("bundleWorkflowService"); + // await bundleWorkflowService.start(projectDir, [iOSDeviceDescriptor], liveSyncInfo); + + // assert.isTrue(isBuildPlatformCalled); + // }); + // }); + + it("shouldn't build for second android device", async () => { // shouldn't build for second iOS device or second iOS simulator + return; + }); + it("should build for iOS simulator if it is already built for iOS device", () => { + return; + }); + it("should build for iOS device if it is already built for iOS simulator", () => { + return; + }); + it("should install the built package when the project should be build", () => { + return; + }); + it("should install the latest built package when the project shouldn't be build", () => { + return; + }); + }); + describe("on filesChangeEventData", () => { + // TODO: add test cases heres + }); + describe("no watch", () => { + it("shouldn't start the watcher when skipWatcher flag is provided", () => { + return; + }); + it("shouldn't start the watcher when no devices to sync", () => { + return; + }); + }); + }); +}); diff --git a/test/services/platform/platform-watcher-service.ts b/test/services/platform/platform-watcher-service.ts new file mode 100644 index 0000000000..babc58ae8b --- /dev/null +++ b/test/services/platform/platform-watcher-service.ts @@ -0,0 +1,123 @@ +import { Yok } from "../../../lib/common/yok"; +import { PlatformWatcherService } from "../../../lib/services/platform/platform-watcher-service"; +import { assert } from "chai"; + +const projectData = { projectDir: "myProjectDir", getAppResourcesRelativeDirectoryPath: () => "/my/app_resources/dir/path" }; +const preparePlatformData = { }; + +let isCompileWithWatchCalled = false; +let isNativePrepareCalled = false; +let emittedEventNames: string[] = []; +let emittedEventData: any[] = []; + +function createTestInjector(data: { hasNativeChanges: boolean }): IInjector { + const injector = new Yok(); + injector.register("logger", ({ + out: () => ({}), + trace: () => ({}) + })); + injector.register("platformNativeService", ({ + preparePlatform: async () => { + isNativePrepareCalled = true; + return data.hasNativeChanges; + } + })); + injector.register("webpackCompilerService", ({ + on: () => ({}), + emit: () => ({}), + compileWithWatch: async () => { + isCompileWithWatchCalled = true; + } + })); + injector.register("platformWatcherService", PlatformWatcherService); + + const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); + platformWatcherService.emit = (eventName: string, eventData: any) => { + emittedEventNames.push(eventName); + emittedEventData.push(eventData); + assert.isTrue(isCompileWithWatchCalled); + assert.isTrue(isNativePrepareCalled); + return true; + }; + + return injector; +} + +describe("PlatformWatcherService", () => { + beforeEach(() => { + emittedEventNames = []; + emittedEventData = []; + }); + describe("startWatcher", () => { + describe("initialSyncEventData event", () => { + _.each(["iOS", "Android"], platform => { + _.each([true, false], hasNativeChanges => { + it(`should emit after native prepare and webpack's compilation are done for ${platform} platform and hasNativeChanges is ${hasNativeChanges}`, async () => { + const injector = createTestInjector({ hasNativeChanges }); + + const platformData = { platformNameLowerCase: platform.toLowerCase(), normalizedPlatformName: platform }; + const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); + await platformWatcherService.startWatcher(platformData, projectData, preparePlatformData); + + assert.lengthOf(emittedEventNames, 1); + assert.lengthOf(emittedEventData, 1); + assert.deepEqual(emittedEventNames[0], "initialSyncEventData"); + assert.deepEqual(emittedEventData[0], { platform: platform.toLowerCase(), hasNativeChanges }); + }); + }); + }); + + // TODO: Consider to write similar test for JS part if appropriate + _.each(["iOS", "Android"], platform => { + it(`should respect native changes that are made before the initial preparation of the project had been done for ${platform}`, async () => { + const injector = createTestInjector({ hasNativeChanges: false }); + + const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); + + const platformNativeService = injector.resolve("platformNativeService"); + platformNativeService.preparePlatform = async () => { + const nativeFilesWatcher = (platformWatcherService).watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher; + nativeFilesWatcher.emit("all", "change", "my/project/App_Resources/some/file"); + isNativePrepareCalled = true; + return false; + }; + + const platformData = { platformNameLowerCase: platform.toLowerCase(), normalizedPlatformName: platform }; + await platformWatcherService.startWatcher(platformData, projectData, preparePlatformData); + + assert.lengthOf(emittedEventNames, 1); + assert.lengthOf(emittedEventData, 1); + assert.deepEqual(emittedEventNames[0], "initialSyncEventData"); + assert.deepEqual(emittedEventData[0], { platform: platform.toLowerCase(), hasNativeChanges: true }); + }); + }); + }); + describe("filesChangeEventData event", () => { + _.each(["iOS", "Android"], platform => { + it(`shouldn't emit filesChangeEventData before initialSyncEventData if js code is changed before the initial preparation of project has been done for ${platform}`, async () => { + const injector = createTestInjector({ hasNativeChanges: false }); + const hasNativeChanges = false; + + const platformNativeService = injector.resolve("platformNativeService"); + const webpackCompilerService = injector.resolve("webpackCompilerService"); + platformNativeService.preparePlatform = async () => { + webpackCompilerService.emit("webpackEmittedFiles", ["/some/file/path"]); + isNativePrepareCalled = true; + return hasNativeChanges; + }; + + const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); + const platformData = { platformNameLowerCase: platform.toLowerCase(), normalizedPlatformName: platform }; + await platformWatcherService.startWatcher(platformData, projectData, preparePlatformData); + + assert.lengthOf(emittedEventNames, 1); + assert.lengthOf(emittedEventData, 1); + assert.deepEqual(emittedEventNames[0], "initialSyncEventData"); + assert.deepEqual(emittedEventData[0], { platform: platform.toLowerCase(), hasNativeChanges }); + + // TODO: assert /some/file/path is emitted + }); + }); + }); + }); +}); diff --git a/test/stubs.ts b/test/stubs.ts index 502d2f32c9..16b6abc30b 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -9,6 +9,7 @@ import * as prompt from "inquirer"; import { Yok } from "./../lib/common/yok"; import { HostInfo } from "./../lib/common/host-info"; import { DevicePlatformsConstants } from "./../lib/common/mobile/device-platforms-constants"; +import { PreparePlatformData } from "../lib/services/workflow/workflow-data-service"; export class LoggerStub implements ILogger { getLevel(): string { return undefined; } @@ -723,18 +724,18 @@ export class ChildProcessStub extends EventEmitter { } export class ProjectChangesService implements IProjectChangesService { - public async checkForChanges(checkForChangesOpts: ICheckForChangesOptions): Promise { + public async checkForChanges(platform: string, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { return {}; } - public getPrepareInfo(platform: string): IPrepareInfo { + public getPrepareInfo(platformData: IPlatformData): IPrepareInfo { return null; } - public savePrepareInfo(platform: string): void { + public savePrepareInfo(platformData: IPlatformData): void { } - public getPrepareInfoFilePath(platform: string): string { + public getPrepareInfoFilePath(platformData: IPlatformData): string { return ""; } @@ -742,7 +743,7 @@ export class ProjectChangesService implements IProjectChangesService { return {}; } - public setNativePlatformStatus(platform: string, projectData: IProjectData, nativePlatformStatus: IAddedNativePlatform): void { + public setNativePlatformStatus(platformData: IPlatformData, addedPlatform: IAddedNativePlatform): void { return; } } @@ -807,7 +808,7 @@ export class PlatformServiceStub extends EventEmitter implements IPlatformServic return Promise.resolve(); } - public preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: IPreparePlatformData): Promise { + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { return Promise.resolve(true); } From 68187de1f21cdd0116b459da6ba02cab0a8362d4 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 1 May 2019 18:54:38 +0300 Subject: [PATCH 05/30] chore: remove platform-workflow-service --- lib/bootstrap.ts | 1 - lib/commands/build.ts | 13 ++++--- lib/commands/prepare.ts | 5 ++- lib/declarations.d.ts | 4 +- lib/helpers/livesync-command-helper.ts | 2 +- lib/services/bundle-workflow-service.ts | 39 ++++++++++--------- .../workflow/platform-workflow-service.ts | 30 -------------- lib/services/workflow/workflow.d.ts | 12 ------ test/services/bundle-workflow-service.ts | 11 +++--- 9 files changed, 39 insertions(+), 78 deletions(-) delete mode 100644 lib/services/workflow/platform-workflow-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 6c2da39e32..7cfa1b83f1 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -45,7 +45,6 @@ $injector.require("platformWatcherService", "./services/platform/platform-watche $injector.require("deviceInstallationService", "./services/device/device-installation-service"); $injector.require("deviceRestartApplicationService", "./services/device/device-restart-application-service"); -$injector.require("platformWorkflowService", "./services/workflow/platform-workflow-service"); $injector.require("workflowDataService", "./services/workflow/workflow-data-service"); $injector.require("bundleWorkflowService", "./services/bundle-workflow-service"); diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 7f7dc6a4b2..ee44db5172 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -1,5 +1,6 @@ import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE, AndroidAppBundleMessages } from "../constants"; import { ValidatePlatformCommandBase } from "./command-base"; +import { BundleWorkflowService } from "../services/bundle-workflow-service"; export abstract class BuildCommandBase extends ValidatePlatformCommandBase { constructor($options: IOptions, @@ -7,7 +8,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { $projectData: IProjectData, $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - protected $platformWorkflowService: IPlatformWorkflowService, + protected $bundleWorkflowService: BundleWorkflowService, $platformValidationService: IPlatformValidationService, private $bundleValidatorHelper: IBundleValidatorHelper, protected $logger: ILogger) { @@ -17,7 +18,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { public async executeCore(args: string[]): Promise { const platform = args[0].toLowerCase(); - const outputPath = await this.$platformWorkflowService.buildPlatform(platform, this.$projectData.projectDir, this.$options); + const outputPath = await this.$bundleWorkflowService.buildPlatform(platform, this.$projectData.projectDir, this.$options); return outputPath; } @@ -56,11 +57,11 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $platformWorkflowService: IPlatformWorkflowService, + $bundleWorkflowService: BundleWorkflowService, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $bundleWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); } public async execute(args: string[]): Promise { @@ -91,12 +92,12 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $platformWorkflowService: IPlatformWorkflowService, + $bundleWorkflowService: BundleWorkflowService, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, protected $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $bundleWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); } public async execute(args: string[]): Promise { diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 7963d015c3..7ac59c88e2 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -1,10 +1,11 @@ import { ValidatePlatformCommandBase } from "./command-base"; +import { BundleWorkflowService } from "../services/bundle-workflow-service"; export class PrepareCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters = [this.$platformCommandParameter]; constructor($options: IOptions, - private $platformWorkflowService: IPlatformWorkflowService, + private $bundleWorkflowService: BundleWorkflowService, $platformValidationService: IPlatformValidationService, $projectData: IProjectData, private $platformCommandParameter: ICommandParameter, @@ -16,7 +17,7 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm public async execute(args: string[]): Promise { const platform = args[0]; - await this.$platformWorkflowService.preparePlatform(platform, this.$projectData.projectDir, this.$options); + await this.$bundleWorkflowService.preparePlatform(platform, this.$projectData.projectDir, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index f14fbcaecc..0299c191fe 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -1028,7 +1028,9 @@ interface INetworkConnectivityValidator { } interface IBundleWorkflowService { - start(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise; + preparePlatform(platform: string, projectDir: string, options: IOptions): Promise; + buildPlatform(platform: string, projectDir: string, options: IOptions): Promise; + runPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise; } interface IPlatformValidationService { diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 05b624fecf..2a347fe243 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -128,7 +128,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { force: this.$options.force }; - await this.$bundleWorkflowService.start(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); + await this.$bundleWorkflowService.runPlatform(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); // const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); // this.$liveSyncService.on(LiveSyncEvents.liveSyncStopped, (data: { projectDir: string, deviceIdentifier: string }) => { diff --git a/lib/services/bundle-workflow-service.ts b/lib/services/bundle-workflow-service.ts index f7a1130c3c..7594e1125c 100644 --- a/lib/services/bundle-workflow-service.ts +++ b/lib/services/bundle-workflow-service.ts @@ -1,12 +1,8 @@ import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME } from "../constants"; import { WorkflowDataService } from "./workflow/workflow-data-service"; -// import * as path from "path"; -// import * as constants from "../constants"; - const deviceDescriptorPrimaryKey = "identifier"; -// TODO: Rename this class to RunWorkflowService export class BundleWorkflowService implements IBundleWorkflowService { private liveSyncProcessesInfo: IDictionary = {}; @@ -17,23 +13,33 @@ export class BundleWorkflowService implements IBundleWorkflowService { private $errors: IErrors, private $injector: IInjector, private $mobileHelper: Mobile.IMobileHelper, - // private $fs: IFileSystem, private $logger: ILogger, - // private $platformAddService: IPlatformAddService, + private $platformService: IPlatformService, private $platformAddService: IPlatformAddService, private $platformBuildService: IPlatformBuildService, private $platformWatcherService: IPlatformWatcherService, private $pluginsService: IPluginsService, private $projectDataService: IProjectDataService, private $workflowDataService: WorkflowDataService - // private $projectChangesService: IProjectChangesService ) { } - // processInfo[projectDir] = { - // deviceDescriptors, nativeFilesWatcher, jsFilesWatcher - // } + public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise { + const { nativePlatformData, projectData, addPlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); + + await this.$platformAddService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); + await this.$platformService.preparePlatform(nativePlatformData, projectData, preparePlatformData); + } + + public async buildPlatform(platform: string, projectDir: string, options: IOptions): Promise { + const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); + + await this.preparePlatform(platform, projectDir, options); + const result = await this.$platformBuildService.buildPlatform(nativePlatformData, projectData, buildPlatformData); + + return result; + } - public async start(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + public async runPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { const projectData = this.$projectDataService.getProjectData(projectDir); await this.initializeSetup(projectData); @@ -81,6 +87,7 @@ export class BundleWorkflowService implements IBundleWorkflowService { private async syncInitialDataOnDevice(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); const packageFilePath = await this.$platformBuildService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); @@ -88,14 +95,8 @@ export class BundleWorkflowService implements IBundleWorkflowService { // TODO: Consider to improve this const platformLiveSyncService = this.getLiveSyncService(platformData.platformNameLowerCase); - const liveSyncResultInfo = await platformLiveSyncService.fullSync({ - projectData, - device, - useHotModuleReload: liveSyncInfo.useHotModuleReload, - watch: !liveSyncInfo.skipWatcher, - force: liveSyncInfo.force, - liveSyncDeviceInfo: deviceDescriptor - }); + const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; + const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor }); await this.$deviceRestartApplicationService.restartOnDevice(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService); } diff --git a/lib/services/workflow/platform-workflow-service.ts b/lib/services/workflow/platform-workflow-service.ts deleted file mode 100644 index 93fe33a858..0000000000 --- a/lib/services/workflow/platform-workflow-service.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { WorkflowDataService } from "./workflow-data-service"; -import { PlatformAddService } from "../platform/platform-add-service"; -import { PlatformBuildService } from "../platform/platform-build-service"; -import { PlatformService } from "../platform-service"; - -export class PlatformWorkflowService implements IPlatformWorkflowService { - constructor ( - private $platformAddService: PlatformAddService, - private $platformBuildService: PlatformBuildService, - private $platformService: PlatformService, - private $workflowDataService: WorkflowDataService, - ) { } - - public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise { - const { nativePlatformData, projectData, addPlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); - - await this.$platformAddService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); - await this.$platformService.preparePlatform(nativePlatformData, projectData, preparePlatformData); - } - - public async buildPlatform(platform: string, projectDir: string, options: IOptions): Promise { - const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); - - await this.preparePlatform(platform, projectDir, options); - const result = await this.$platformBuildService.buildPlatform(nativePlatformData, projectData, buildPlatformData); - - return result; - } -} -$injector.register("platformWorkflowService", PlatformWorkflowService); diff --git a/lib/services/workflow/workflow.d.ts b/lib/services/workflow/workflow.d.ts index 35d9e702ab..e69de29bb2 100644 --- a/lib/services/workflow/workflow.d.ts +++ b/lib/services/workflow/workflow.d.ts @@ -1,12 +0,0 @@ -interface IPlatformWorkflowService { - preparePlatform(platform: string, projectDir: string, options: IOptions): Promise; - buildPlatform(platform: string, projectDir: string, options: IOptions): Promise; -} - -interface IDeviceWorkflowService { -} - -interface IAdditionalWorkflowOptions { - platformParam?: string; - nativePrepare?: INativePrepare; -} \ No newline at end of file diff --git a/test/services/bundle-workflow-service.ts b/test/services/bundle-workflow-service.ts index 735a74d844..90ca15cb79 100644 --- a/test/services/bundle-workflow-service.ts +++ b/test/services/bundle-workflow-service.ts @@ -1,7 +1,6 @@ import { Yok } from "../../lib/common/yok"; import { BundleWorkflowService } from "../../lib/services/bundle-workflow-service"; import { assert } from "chai"; -import { PlatformWorkflowService } from "../../lib/services/workflow/platform-workflow-service"; const deviceMap: IDictionary = { myiOSDevice: { @@ -42,7 +41,7 @@ function createTestInjector(): IInjector { emit: () => ({}), startWatcher: () => ({}) })); - injector.register("platformWorkflowService", PlatformWorkflowService); + injector.register("bundleWorkflowService", BundleWorkflowService); injector.register("pluginsService", ({})); injector.register("projectDataService", ({ getProjectData: () => ({ @@ -102,7 +101,7 @@ describe("BundleWorkflowService", () => { }; const bundleWorkflowService: IBundleWorkflowService = injector.resolve("bundleWorkflowService"); - await bundleWorkflowService.start(projectDir, [iOSDeviceDescriptor], liveSyncInfo); + await bundleWorkflowService.runPlatform(projectDir, [iOSDeviceDescriptor], liveSyncInfo); assert.isTrue(isStartWatcherCalled); }); @@ -130,13 +129,13 @@ describe("BundleWorkflowService", () => { const injector = createTestInjector(); const actualAddedPlatforms: IPlatformData[] = []; - const platformAddService: IPlatformAddService = injector.resolve("platformWorkflowService"); + const platformAddService: IPlatformAddService = injector.resolve("bundleWorkflowService"); platformAddService.addPlatformIfNeeded = async (platformData: IPlatformData) => { actualAddedPlatforms.push(platformData); }; const bundleWorkflowService: IBundleWorkflowService = injector.resolve("bundleWorkflowService"); - await bundleWorkflowService.start(projectDir, testCase.connectedDevices, liveSyncInfo); + await bundleWorkflowService.runPlatform(projectDir, testCase.connectedDevices, liveSyncInfo); assert.deepEqual(actualAddedPlatforms.map(pData => pData.platformNameLowerCase), testCase.expectedAddedPlatforms); }); @@ -148,7 +147,7 @@ describe("BundleWorkflowService", () => { beforeEach(() => { injector = createTestInjector(); - const platformAddService: IPlatformAddService = injector.resolve("platformWorkflowService"); + const platformAddService: IPlatformAddService = injector.resolve("bundleWorkflowService"); platformAddService.addPlatformIfNeeded = async () => { return; }; const platformBuildService: IPlatformBuildService = injector.resolve("platformBuildService"); From eb52dd91ab953c33457fbef1e3bbb9e3f397765f Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 3 May 2019 12:04:15 +0300 Subject: [PATCH 06/30] chore: add preparePlatformService and remove platformJSService and platformNativeService --- lib/bootstrap.ts | 7 +- lib/commands/appstore-upload.ts | 2 +- lib/commands/deploy.ts | 9 +- lib/commands/platform-clean.ts | 7 +- lib/declarations.d.ts | 6 +- lib/definitions/livesync.d.ts | 1 + lib/helpers/deploy-command-helper.ts | 45 - lib/helpers/livesync-command-helper.ts | 67 +- lib/services/android-plugin-build-service.ts | 17 +- lib/services/bundle-workflow-service.ts | 58 +- .../device/device-installation-service.ts | 5 +- lib/services/local-build-service.ts | 5 +- .../platform-environment-requirements.ts | 36 +- lib/services/platform-service.ts | 504 ---- ...add-service.ts => add-platform-service.ts} | 30 +- ...d-service.ts => build-platform-service.ts} | 4 +- .../platform/platform-commands-service.ts | 27 +- .../platform/platform-install-service.ts | 0 .../platform/platform-watcher-service.ts | 14 +- .../prepare-platform-service.ts} | 43 +- lib/services/prepare-platform-js-service.ts | 26 - lib/services/test-execution-service.ts | 54 +- .../webpack/webpack-compiler-service.ts | 1 + lib/services/webpack/webpack.d.ts | 149 -- .../workflow/workflow-data-service.ts | 40 +- test/platform-commands.ts | 3 +- test/platform-service.ts | 2153 ++++++++--------- test/plugins-service.ts | 4 +- test/services/bundle-workflow-service.ts | 15 +- .../platform/platform-watcher-service.ts | 12 +- test/stubs.ts | 114 - 31 files changed, 1231 insertions(+), 2227 deletions(-) delete mode 100644 lib/helpers/deploy-command-helper.ts delete mode 100644 lib/services/platform-service.ts rename lib/services/platform/{platform-add-service.ts => add-platform-service.ts} (76%) rename lib/services/platform/{platform-build-service.ts => build-platform-service.ts} (97%) delete mode 100644 lib/services/platform/platform-install-service.ts rename lib/services/{prepare-platform-native-service.ts => platform/prepare-platform-service.ts} (76%) delete mode 100644 lib/services/prepare-platform-js-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 7cfa1b83f1..d0b4f0f708 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -34,10 +34,9 @@ $injector.require("tnsModulesService", "./services/tns-modules-service"); $injector.require("platformsData", "./platforms-data"); $injector.require("platformService", "./services/platform-service"); -$injector.require("platformJSService", "./services/prepare-platform-js-service"); -$injector.require("platformNativeService", "./services/prepare-platform-native-service"); -$injector.require("platformAddService", "./services/platform/platform-add-service"); -$injector.require("platformBuildService", "./services/platform/platform-build-service"); +$injector.require("addPlatformService", "./services/platform/add-platform-service"); +$injector.require("buildPlatformService", "./services/platform/build-platform-service"); +$injector.require("preparePlatformService", "./services/platform/prepare-platform-service"); $injector.require("platformValidationService", "./services/platform/platform-validation-service"); $injector.require("platformCommandsService", "./services/platform/platform-commands-service"); $injector.require("platformWatcherService", "./services/platform/platform-watcher-service"); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 1c63262c04..89cc1a1612 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -14,7 +14,7 @@ export class PublishIOS implements ICommand { private $prompter: IPrompter, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $platformValidationService: IPlatformValidationService, - // private $platformBuildService: IPlatformBuildService, + // private $buildPlatformService: BuildPlatformService, // private $xcodebuildService: IXcodebuildService ) { this.$projectData.initializeProjectData(); diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index cfc1485cc0..db14861ad8 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -5,24 +5,23 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement public allowedParameters: ICommandParameter[] = []; constructor($platformValidationService: IPlatformValidationService, - private $platformService: IPlatformService, private $platformCommandParameter: ICommandParameter, $options: IOptions, $projectData: IProjectData, - private $deployCommandHelper: IDeployCommandHelper, private $errors: IErrors, private $mobileHelper: Mobile.IMobileHelper, $platformsData: IPlatformsData, private $bundleValidatorHelper: IBundleValidatorHelper, + private $liveSyncCommandHelper: ILiveSyncCommandHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { super($options, $platformsData, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - const deployPlatformInfo = this.$deployCommandHelper.getDeployPlatformInfo(args[0]); - - return this.$platformService.deployPlatform(deployPlatformInfo); + const platform = args[0].toLowerCase(); + // TODO: Add a separate deployCommandHelper with base class for it and LiveSyncCommandHelper + await this.$liveSyncCommandHelper.executeCommandLiveSync(platform, { release: true }); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/platform-clean.ts b/lib/commands/platform-clean.ts index 056132c496..86c34c2bd9 100644 --- a/lib/commands/platform-clean.ts +++ b/lib/commands/platform-clean.ts @@ -1,12 +1,13 @@ +import { PlatformCommandsService } from "../services/platform/platform-commands-service"; + export class CleanCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor( private $errors: IErrors, private $options: IOptions, - private $platformCommandsService: IPlatformCommandsService, + private $platformCommandsService: PlatformCommandsService, private $platformValidationService: IPlatformValidationService, - private $platformService: IPlatformService, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $projectData: IProjectData ) { @@ -29,7 +30,7 @@ export class CleanCommand implements ICommand { for (const platform of args) { this.$platformValidationService.validatePlatformInstalled(platform, this.$projectData); - const currentRuntimeVersion = this.$platformService.getCurrentPlatformVersion(platform, this.$projectData); + const currentRuntimeVersion = this.$platformCommandsService.getCurrentPlatformVersion(platform, this.$projectData); await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ platform, projectDir: this.$projectData.projectDir, diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 0299c191fe..4415599529 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -1030,6 +1030,7 @@ interface INetworkConnectivityValidator { interface IBundleWorkflowService { preparePlatform(platform: string, projectDir: string, options: IOptions): Promise; buildPlatform(platform: string, projectDir: string, options: IOptions): Promise; + deployPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise; runPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise; } @@ -1063,11 +1064,6 @@ interface IBuildArtefactsService { getAllBuiltApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[]; } -interface IPlatformAddService { - addPlatform(projectData: IProjectData, addPlatformData: any): Promise; - addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, addPlatformData: any): Promise; -} - interface IPlatformCommandsService { addPlatforms(platforms: string[], projectData: IProjectData, frameworkPath: string): Promise; cleanPlatforms(platforms: string[], projectData: IProjectData, framworkPath: string): Promise; diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 9c8cb05312..5adaca731b 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -143,6 +143,7 @@ declare global { */ interface ILiveSyncInfo extends IProjectDir, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { webpackCompilerConfig: IWebpackCompilerConfig; + emulator?: boolean; /** * Forces a build before the initial livesync. diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts deleted file mode 100644 index 28b71f3a72..0000000000 --- a/lib/helpers/deploy-command-helper.ts +++ /dev/null @@ -1,45 +0,0 @@ -export class DeployCommandHelper implements IDeployCommandHelper { - - constructor(private $options: IOptions, - private $platformService: IPlatformService, - private $projectData: IProjectData) { - this.$projectData.initializeProjectData(); - } - - public getDeployPlatformInfo(platform: string): IDeployPlatformInfo { - const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { - bundle: !!this.$options.bundle, - release: this.$options.release, - useHotModuleReload: this.$options.hmr - }; - const deployOptions: IDeployPlatformOptions = { - clean: this.$options.clean, - device: this.$options.device, - projectDir: this.$projectData.projectDir, - emulator: this.$options.emulator, - release: this.$options.release, - forceInstall: true, - provision: this.$options.provision, - teamId: this.$options.teamId, - keyStoreAlias: this.$options.keyStoreAlias, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword, - keyStorePath: this.$options.keyStorePath - }; - - const deployPlatformInfo: IDeployPlatformInfo = { - platform, - appFilesUpdaterOptions, - deployOptions, - projectData: this.$projectData, - buildPlatform: this.$platformService.buildPlatform.bind(this.$platformService), - config: this.$options, - env: this.$options.env, - - }; - - return deployPlatformInfo; - } -} - -$injector.register("deployCommandHelper", DeployCommandHelper); diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 2a347fe243..61c9c6b0c1 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,24 +1,26 @@ +import { BuildPlatformService } from "../services/platform/build-platform-service"; + // import { LiveSyncEvents } from "../constants"; export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; - constructor(private $platformService: IPlatformService, + constructor( private $projectData: IProjectData, private $options: IOptions, private $bundleWorkflowService: IBundleWorkflowService, - // private $liveSyncService: ILiveSyncService, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $devicesService: Mobile.IDevicesService, private $platformsData: IPlatformsData, + private $buildPlatformService: BuildPlatformService, private $analyticsService: IAnalyticsService, private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, private $logger: ILogger, - private $cleanupService: ICleanupService) { - } + private $cleanupService: ICleanupService + ) { } public getPlatformsForOperation(platform: string): string[] { const availablePlatforms = platform ? [platform] : _.values(this.$platformsData.availablePlatforms); @@ -70,11 +72,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } } - if (this.$options.release) { - await this.runInReleaseMode(platform, additionalOptions); - return; - } - // Now let's take data for each device: const deviceDescriptors: ILiveSyncDeviceInfo[] = devices .map(d => { @@ -95,7 +92,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : - this.$platformService.buildPlatform.bind(this.$platformService, d.deviceInfo.platform, buildConfig, this.$projectData); + this.$buildPlatformService.buildPlatform.bind(this.$buildPlatformService, d.deviceInfo.platform, buildConfig, this.$projectData); const outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ platform: d.deviceInfo.platform, @@ -125,9 +122,16 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { }, timeout: this.$options.timeout, useHotModuleReload: this.$options.hmr, - force: this.$options.force + force: this.$options.force, + emulator: this.$options.emulator }; + // if (this.$options.release) { + // liveSyncInfo.skipWatcher = true; + // await this.$bundleWorkflowService.deployPlatform(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); + // return; + // } + await this.$bundleWorkflowService.runPlatform(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); // const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); @@ -158,47 +162,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return result; } - - private async runInReleaseMode(platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { - const runPlatformOptions: IRunPlatformOptions = { - device: this.$options.device, - emulator: this.$options.emulator, - justlaunch: this.$options.justlaunch - }; - - const deployOptions = _.merge(({ - projectDir: this.$projectData.projectDir, - clean: true - }), this.$options.argv); - - const availablePlatforms = this.getPlatformsForOperation(platform); - for (const currentPlatform of availablePlatforms) { - const deployPlatformInfo: IDeployPlatformInfo = { - platform: currentPlatform, - appFilesUpdaterOptions: { - bundle: !!this.$options.bundle, - release: this.$options.release, - useHotModuleReload: this.$options.hmr - }, - deployOptions, - buildPlatform: this.$platformService.buildPlatform.bind(this.$platformService), - projectData: this.$projectData, - config: this.$options, - env: this.$options.env - }; - - await this.$platformService.deployPlatform(deployPlatformInfo); - - await this.$platformService.startApplication( - currentPlatform, - runPlatformOptions, - { - appId: this.$projectData.projectIdentifiers[currentPlatform.toLowerCase()], - projectName: this.$projectData.projectName - } - ); - } - } } $injector.register("liveSyncCommandHelper", LiveSyncCommandHelper); diff --git a/lib/services/android-plugin-build-service.ts b/lib/services/android-plugin-build-service.ts index d2810a6db0..1f53d8c345 100644 --- a/lib/services/android-plugin-build-service.ts +++ b/lib/services/android-plugin-build-service.ts @@ -4,11 +4,11 @@ import { getShortPluginName, hook } from "../common/helpers"; import { Builder, parseString } from "xml2js"; export class AndroidPluginBuildService implements IAndroidPluginBuildService { - private get $platformService(): IPlatformService { - return this.$injector.resolve("platformService"); + private get $platformsData(): IPlatformsData { + return this.$injector.resolve("platformsData"); } - constructor(private $injector: IInjector, + constructor( private $fs: IFileSystem, private $childProcess: IChildProcess, private $hostInfo: IHostInfo, @@ -19,7 +19,9 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $errors: IErrors, private $filesHashService: IFilesHashService, - public $hooksService: IHooksService) { } + public $hooksService: IHooksService, + private $injector: IInjector + ) { } private static MANIFEST_ROOT = { $: { @@ -290,10 +292,9 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { private async getRuntimeGradleVersions(projectDir: string): Promise { let runtimeGradleVersions: IRuntimeGradleVersions = null; if (projectDir) { - const projectRuntimeVersion = this.$platformService.getCurrentPlatformVersion( - this.$devicePlatformsConstants.Android, - this.$projectDataService.getProjectData(projectDir)); - runtimeGradleVersions = await this.getGradleVersions(projectRuntimeVersion); + const projectData = this.$projectDataService.getProjectData(projectDir); + const platformData = this.$platformsData.getPlatformData(this.$devicePlatformsConstants.Android, projectData); + const projectRuntimeVersion = platformData.platformProjectService.getFrameworkVersion(projectData); this.$logger.trace(`Got gradle versions ${JSON.stringify(runtimeGradleVersions)} from runtime v${projectRuntimeVersion}`); } diff --git a/lib/services/bundle-workflow-service.ts b/lib/services/bundle-workflow-service.ts index 7594e1125c..cc21939274 100644 --- a/lib/services/bundle-workflow-service.ts +++ b/lib/services/bundle-workflow-service.ts @@ -1,5 +1,8 @@ import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME } from "../constants"; import { WorkflowDataService } from "./workflow/workflow-data-service"; +import { AddPlatformService } from "./platform/add-platform-service"; +import { BuildPlatformService } from "./platform/build-platform-service"; +import { PreparePlatformService } from "./platform/prepare-platform-service"; const deviceDescriptorPrimaryKey = "identifier"; @@ -14,9 +17,9 @@ export class BundleWorkflowService implements IBundleWorkflowService { private $injector: IInjector, private $mobileHelper: Mobile.IMobileHelper, private $logger: ILogger, - private $platformService: IPlatformService, - private $platformAddService: IPlatformAddService, - private $platformBuildService: IPlatformBuildService, + private $preparePlatformService: PreparePlatformService, + private $addPlatformService: AddPlatformService, + private $buildPlatformService: BuildPlatformService, private $platformWatcherService: IPlatformWatcherService, private $pluginsService: IPluginsService, private $projectDataService: IProjectDataService, @@ -26,32 +29,49 @@ export class BundleWorkflowService implements IBundleWorkflowService { public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise { const { nativePlatformData, projectData, addPlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); - await this.$platformAddService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); - await this.$platformService.preparePlatform(nativePlatformData, projectData, preparePlatformData); + await this.$addPlatformService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); + await this.$preparePlatformService.preparePlatform(nativePlatformData, projectData, preparePlatformData); } public async buildPlatform(platform: string, projectDir: string, options: IOptions): Promise { const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); await this.preparePlatform(platform, projectDir, options); - const result = await this.$platformBuildService.buildPlatform(nativePlatformData, projectData, buildPlatformData); + const result = await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); return result; } + public async deployPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + const platforms = this.getPlatformsFromDevices(deviceDescriptors); + + for (const platform of platforms) { + await this.preparePlatform(platform, projectDir, liveSyncInfo); + } + + const executeAction = async (device: Mobile.IDevice) => { + const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectDir, liveSyncInfo); + await this.$buildPlatformService.buildPlatformIfNeeded(nativePlatformData, projectData, buildPlatformData); + await this.$deviceInstallationService.installOnDeviceIfNeeded(device, nativePlatformData, projectData, buildPlatformData); + await device.applicationManager.startApplication({ + appId: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], + projectName: projectData.projectName + }); + this.$logger.out(`Successfully started on device with identifier '${device.deviceInfo.identifier}'.`); + }; + + await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => true); + } + public async runPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { const projectData = this.$projectDataService.getProjectData(projectDir); await this.initializeSetup(projectData); - const platforms = _(deviceDescriptors) - .map(device => this.$devicesService.getDeviceByIdentifier(device.identifier)) - .map(device => device.deviceInfo.platform) - .uniq() - .value(); + const platforms = this.getPlatformsFromDevices(deviceDescriptors); for (const platform of platforms) { const { nativePlatformData, addPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, { ...liveSyncInfo, platformParam: platform }); - await this.$platformAddService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); + await this.$addPlatformService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); } this.setLiveSyncProcessInfo(projectDir, liveSyncInfo, deviceDescriptors); @@ -89,7 +109,7 @@ export class BundleWorkflowService implements IBundleWorkflowService { const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); - const packageFilePath = await this.$platformBuildService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); + const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); await this.$deviceInstallationService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); @@ -107,7 +127,7 @@ export class BundleWorkflowService implements IBundleWorkflowService { if (data.hasNativeChanges) { // TODO: Consider to handle nativePluginsChange here (aar rebuilt) - await this.$platformBuildService.buildPlatform(nativePlatformData, projectData, buildPlatformData); + await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); } const platformLiveSyncService = this.getLiveSyncService(device.deviceInfo.platform); @@ -176,5 +196,15 @@ export class BundleWorkflowService implements IBundleWorkflowService { this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); } + + private getPlatformsFromDevices(deviceDescriptors: ILiveSyncDeviceInfo[]): string[] { + const platforms = _(deviceDescriptors) + .map(device => this.$devicesService.getDeviceByIdentifier(device.identifier)) + .map(device => device.deviceInfo.platform) + .uniq() + .value(); + + return platforms; + } } $injector.register("bundleWorkflowService", BundleWorkflowService); diff --git a/lib/services/device/device-installation-service.ts b/lib/services/device/device-installation-service.ts index 99786c17f7..fe28784db4 100644 --- a/lib/services/device/device-installation-service.ts +++ b/lib/services/device/device-installation-service.ts @@ -2,6 +2,7 @@ import { TrackActionNames, HASHES_FILE_NAME } from "../../constants"; import { MobileHelper } from "../../common/mobile/mobile-helper"; import * as helpers from "../../common/helpers"; import * as path from "path"; +import { BuildPlatformService } from "../platform/build-platform-service"; const buildInfoFileName = ".nsbuildinfo"; @@ -13,7 +14,7 @@ export class DeviceInstallationService implements IDeviceInstallationService { private $fs: IFileSystem, private $logger: ILogger, private $mobileHelper: MobileHelper, - private $platformBuildService: IPlatformBuildService + private $buildPlatformService: BuildPlatformService ) { } public async installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise { @@ -93,7 +94,7 @@ export class DeviceInstallationService implements IDeviceInstallationService { } const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); - const localBuildInfo = this.$platformBuildService.getBuildInfoFromFile(platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); + const localBuildInfo = this.$buildPlatformService.getBuildInfoFromFile(platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; } diff --git a/lib/services/local-build-service.ts b/lib/services/local-build-service.ts index f968f39a68..ae8618d1a0 100644 --- a/lib/services/local-build-service.ts +++ b/lib/services/local-build-service.ts @@ -1,13 +1,14 @@ import { EventEmitter } from "events"; import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE } from "../constants"; import { WorkflowDataService } from "./workflow/workflow-data-service"; +import { BuildPlatformService } from "./platform/build-platform-service"; export class LocalBuildService extends EventEmitter implements ILocalBuildService { constructor( private $errors: IErrors, private $mobileHelper: Mobile.IMobileHelper, private $platformsData: IPlatformsData, - private $platformBuildService: IPlatformBuildService, + private $buildPlatformService: BuildPlatformService, private $projectDataService: IProjectDataService, private $workflowDataService: WorkflowDataService ) { super(); } @@ -19,7 +20,7 @@ export class LocalBuildService extends EventEmitter implements ILocalBuildServic const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, platformBuildOptions.projectDir, platformBuildOptions); - const result = await this.$platformBuildService.buildPlatform(nativePlatformData, projectData, buildPlatformData); + const result = await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); return result; } diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index 6ef70bcfda..9f334615b0 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -1,7 +1,7 @@ import { NATIVESCRIPT_CLOUD_EXTENSION_NAME, TrackActionNames } from "../constants"; import { isInteractive } from "../common/helpers"; import { EOL } from "os"; -import { cache } from "../common/decorators"; +// import { cache } from "../common/decorators"; export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequirements { constructor(private $commandsService: ICommandsService, @@ -12,13 +12,13 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private $prompter: IPrompter, private $staticConfig: IStaticConfig, private $analyticsService: IAnalyticsService, - private $injector: IInjector, + // private $injector: IInjector, private $previewQrCodeService: IPreviewQrCodeService) { } - @cache() - private get $liveSyncService(): ILiveSyncService { - return this.$injector.resolve("liveSyncService"); - } + // @cache() + // private get $liveSyncService(): ILiveSyncService { + // return this.$injector.resolve("liveSyncService"); + // } public static CLOUD_SETUP_OPTION_NAME = "Configure for Cloud Builds"; public static LOCAL_SETUP_OPTION_NAME = "Configure for Local Builds"; @@ -181,18 +181,18 @@ 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.$liveSyncService.liveSync([], { - syncToPreviewApp: true, - projectDir, - skipWatcher: !options.watch, - clean: options.clean, - release: options.release, - webpackCompilerConfig: { - env: options.env, - }, - timeout: options.timeout, - useHotModuleReload: options.hmr - }); + // await this.$liveSyncService.liveSync([], { + // syncToPreviewApp: true, + // projectDir, + // skipWatcher: !options.watch, + // clean: options.clean, + // release: options.release, + // webpackCompilerConfig: { + // env: options.env, + // }, + // timeout: options.timeout, + // useHotModuleReload: options.hmr + // }); await this.$previewQrCodeService.printLiveSyncQrCode({ projectDir, useHotModuleReload: options.hmr, link: options.link }); } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts deleted file mode 100644 index f80c6526af..0000000000 --- a/lib/services/platform-service.ts +++ /dev/null @@ -1,504 +0,0 @@ -import * as path from "path"; -import * as shell from "shelljs"; -import * as constants from "../constants"; -import { Configurations } from "../common/constants"; -import * as helpers from "../common/helpers"; -import * as semver from "semver"; -import { EventEmitter } from "events"; -import { attachAwaitDetach } from "../common/helpers"; -import * as temp from "temp"; -import { performanceLog } from ".././common/decorators"; -import { PreparePlatformData } from "./workflow/workflow-data-service"; -temp.track(); - -const buildInfoFileName = ".nsbuildinfo"; - -export class PlatformService extends EventEmitter implements IPlatformService { - constructor(private $devicesService: Mobile.IDevicesService, - private $errors: IErrors, - private $fs: IFileSystem, - private $logger: ILogger, - private $platformsData: IPlatformsData, - private $projectDataService: IProjectDataService, - private $webpackCompilerService: IWebpackCompilerService, - // private $platformJSService: IPreparePlatformService, - private $platformNativeService: IPreparePlatformService, - private $mobileHelper: Mobile.IMobileHelper, - private $hostInfo: IHostInfo, - private $devicePathProvider: IDevicePathProvider, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $projectChangesService: IProjectChangesService, - private $analyticsService: IAnalyticsService, - public $hooksService: IHooksService, - ) { super(); } - - public getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); - const version = currentPlatformData && currentPlatformData.version; - - return version; - } - - @performanceLog() - public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - this.$logger.out("Preparing project..."); - - await this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, { watch: false, env: preparePlatformData.env }); - await this.$platformNativeService.preparePlatform(platformData, projectData, preparePlatformData); - - this.$projectChangesService.savePrepareInfo(platformData); - - this.$logger.out(`Project successfully prepared (${platformData.platformNameLowerCase})`); - - return true; - } - - public async shouldBuild(platform: string, projectData: IProjectData, buildConfig: IBuildConfig, outputPath?: string): Promise { - if (buildConfig.release && this.$projectChangesService.currentChanges.hasChanges) { - return true; - } - - if (this.$projectChangesService.currentChanges.changesRequireBuild) { - return true; - } - - const platformData = this.$platformsData.getPlatformData(platform, projectData); - outputPath = outputPath || platformData.getBuildOutputPath(buildConfig); - if (!this.$fs.exists(outputPath)) { - return true; - } - - const validBuildOutputData = platformData.getValidBuildOutputData(buildConfig); - const packages = this.getApplicationPackages(outputPath, validBuildOutputData); - if (packages.length === 0) { - return true; - } - - const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); - const buildInfo = this.getBuildInfo(platform, platformData, buildConfig, outputPath); - if (!prepareInfo || !buildInfo) { - return true; - } - - if (buildConfig.clean) { - return true; - } - - if (prepareInfo.time === buildInfo.prepareTime) { - return false; - } - - return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; - } - - @performanceLog() - public async buildPlatform(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): Promise { - this.$logger.out("Building project..."); - - const action = constants.TrackActionNames.Build; - const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildConfig && buildConfig.buildForDevice; - - await this.$analyticsService.trackEventActionInGoogleAnalytics({ - action, - isForDevice, - platform, - projectDir: projectData.projectDir, - additionalData: `${buildConfig.release ? Configurations.Release : Configurations.Debug}_${buildConfig.clean ? constants.BuildStates.Clean : constants.BuildStates.Incremental}` - }); - - const platformData = this.$platformsData.getPlatformData(platform, projectData); - if (buildConfig.clean) { - await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); - } - - const handler = (data: any) => { - this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); - this.$logger.printInfoMessageOnSameLine(data.data.toString()); - }; - - await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildConfig)); - - const buildInfoFilePath = this.getBuildOutputPath(platform, platformData, buildConfig); - this.saveBuildInfoFile(platform, projectData.projectDir, buildInfoFilePath); - - this.$logger.out("Project successfully built."); - return this.lastOutputPath(platform, buildConfig, projectData); - } - - public saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void { - const buildInfoFile = path.join(buildInfoFileDirname, buildInfoFileName); - const projectData = this.$projectDataService.getProjectData(projectDir); - const platformData = this.$platformsData.getPlatformData(platform, projectData); - - const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); - const buildInfo: IBuildInfo = { - prepareTime: prepareInfo.changesRequireBuildTime, - buildTime: new Date().toString() - }; - - const deploymentTarget = platformData.platformProjectService.getDeploymentTarget(projectData); - if (deploymentTarget) { - buildInfo.deploymentTarget = deploymentTarget.version; - } - - this.$fs.writeJson(buildInfoFile, buildInfo); - } - - public async shouldInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise { - const platform = device.deviceInfo.platform; - if (!(await device.applicationManager.isApplicationInstalled(projectData.projectIdentifiers[platform.toLowerCase()]))) { - return true; - } - - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); - const localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); - - return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; - } - - public async validateInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise { - const platform = device.deviceInfo.platform; - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const localBuildInfo = this.getBuildInfo(device.deviceInfo.platform, platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); - if (localBuildInfo.deploymentTarget) { - if (semver.lt(semver.coerce(device.deviceInfo.version), semver.coerce(localBuildInfo.deploymentTarget))) { - this.$errors.fail(`Unable to install on device with version ${device.deviceInfo.version} as deployment target is ${localBuildInfo.deploymentTarget}`); - } - } - } - - public async installApplication(device: Mobile.IDevice, buildConfig: IBuildConfig, projectData: IProjectData, packageFile?: string, outputFilePath?: string): Promise { - this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); - - await this.$analyticsService.trackEventActionInGoogleAnalytics({ - action: constants.TrackActionNames.Deploy, - device, - projectDir: projectData.projectDir - }); - - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); - if (!packageFile) { - if (this.$devicesService.isiOSSimulator(device)) { - packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig, outputFilePath).packageName; - } else { - packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig, outputFilePath).packageName; - } - } - - await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); - - const platform = device.deviceInfo.platform.toLowerCase(); - await device.applicationManager.reinstallApplication(projectData.projectIdentifiers[platform], packageFile); - - await this.updateHashesOnDevice({ - device, - appIdentifier: projectData.projectIdentifiers[platform], - outputFilePath, - platformData - }); - - if (!buildConfig.release) { - const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - const options = buildConfig; - options.buildForDevice = !device.isEmulator; - const buildInfoFilePath = outputFilePath || this.getBuildOutputPath(device.deviceInfo.platform, platformData, buildConfig); - const appIdentifier = projectData.projectIdentifiers[platform]; - - await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); - } - - this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); - } - - private async updateHashesOnDevice(data: { device: Mobile.IDevice, appIdentifier: string, outputFilePath: string, platformData: IPlatformData }): Promise { - const { device, appIdentifier, platformData, outputFilePath } = data; - - if (!this.$mobileHelper.isAndroidPlatform(platformData.normalizedPlatformName)) { - return; - } - - let hashes = {}; - const hashesFilePath = path.join(outputFilePath || platformData.getBuildOutputPath(null), constants.HASHES_FILE_NAME); - if (this.$fs.exists(hashesFilePath)) { - hashes = this.$fs.readJson(hashesFilePath); - } - - await device.fileSystem.updateHashesOnDevice(hashes, appIdentifier); - } - - public async deployPlatform(deployInfo: IDeployPlatformInfo): Promise { - // TODO: Refactor deploy platform command - // await this.preparePlatform({ - // platform: deployInfo.platform, - // appFilesUpdaterOptions: deployInfo.appFilesUpdaterOptions, - // projectData: deployInfo.projectData, - // config: deployInfo.config, - // nativePrepare: deployInfo.nativePrepare, - // env: deployInfo.env, - // webpackCompilerConfig: { - // watch: false, - // env: deployInfo.env - // } - // }); - const options: Mobile.IDevicesServicesInitializationOptions = { - platform: deployInfo.platform, - deviceId: deployInfo.deployOptions.device, - emulator: deployInfo.deployOptions.emulator - }; - await this.$devicesService.initialize(options); - const action = async (device: Mobile.IDevice): Promise => { - const buildConfig: IBuildConfig = { - buildForDevice: !this.$devicesService.isiOSSimulator(device), - iCloudContainerEnvironment: null, - projectDir: deployInfo.deployOptions.projectDir, - release: deployInfo.deployOptions.release, - device: deployInfo.deployOptions.device, - provision: deployInfo.deployOptions.provision, - teamId: deployInfo.deployOptions.teamId, - keyStoreAlias: deployInfo.deployOptions.keyStoreAlias, - keyStoreAliasPassword: deployInfo.deployOptions.keyStoreAliasPassword, - keyStorePassword: deployInfo.deployOptions.keyStorePassword, - keyStorePath: deployInfo.deployOptions.keyStorePath, - clean: deployInfo.deployOptions.clean - }; - - const installPackageFile: string = ""; - const shouldBuild = await this.shouldBuild(deployInfo.platform, deployInfo.projectData, buildConfig, deployInfo.outputPath); - if (shouldBuild) { - // installPackageFile = await deployInfo.buildPlatform(deployInfo.platform, buildConfig, deployInfo.projectData); - } else { - this.$logger.out("Skipping package build. No changes detected on the native side. This will be fast!"); - } - - if (deployInfo.deployOptions.forceInstall || shouldBuild || (await this.shouldInstall(device, deployInfo.projectData, buildConfig))) { - await this.installApplication(device, buildConfig, deployInfo.projectData, installPackageFile, deployInfo.outputPath); - } else { - this.$logger.out("Skipping install."); - } - - }; - - if (deployInfo.deployOptions.device) { - const device = await this.$devicesService.getDevice(deployInfo.deployOptions.device); - deployInfo.deployOptions.device = device.deviceInfo.identifier; - } - - await this.$devicesService.execute(action, this.getCanExecuteAction(deployInfo.platform, deployInfo.deployOptions)); - } - - public async startApplication(platform: string, runOptions: IRunPlatformOptions, appData: Mobile.IStartApplicationData): Promise { - this.$logger.out("Starting..."); - - const action = async (device: Mobile.IDevice) => { - await device.applicationManager.startApplication(appData); - this.$logger.out(`Successfully started on device with identifier '${device.deviceInfo.identifier}'.`); - }; - - await this.$devicesService.initialize({ platform: platform, deviceId: runOptions.device }); - - if (runOptions.device) { - const device = await this.$devicesService.getDevice(runOptions.device); - runOptions.device = device.deviceInfo.identifier; - } - - await this.$devicesService.execute(action, this.getCanExecuteAction(platform, runOptions)); - } - - private getBuildOutputPath(platform: string, platformData: IPlatformData, options: IBuildOutputOptions): string { - // if (options.androidBundle) { - // return platformData.bundleBuildOutputPath; - // } - - if (platform.toLowerCase() === this.$devicePlatformsConstants.iOS.toLowerCase()) { - return platformData.getBuildOutputPath(options); - } - - return platformData.getBuildOutputPath(options); - } - - private async getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise { - const platform = device.deviceInfo.platform.toLowerCase(); - const deviceRootPath = await this.$devicePathProvider.getDeviceProjectRootPath(device, { - appIdentifier: projectData.projectIdentifiers[platform], - getDirname: true - }); - return helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, buildInfoFileName)); - } - - private async getDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData): Promise { - const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - try { - return JSON.parse(await this.readFile(device, deviceFilePath, projectData)); - } catch (e) { - return null; - } - } - - private getBuildInfo(platform: string, platformData: IPlatformData, options: IBuildOutputOptions, buildOutputPath?: string): IBuildInfo { - buildOutputPath = buildOutputPath || this.getBuildOutputPath(platform, platformData, options); - const buildInfoFile = path.join(buildOutputPath, buildInfoFileName); - if (this.$fs.exists(buildInfoFile)) { - try { - const buildInfoTime = this.$fs.readJson(buildInfoFile); - return buildInfoTime; - } catch (e) { - return null; - } - } - - return null; - } - - public lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData, outputPath?: string): string { - let packageFile: string; - const platformData = this.$platformsData.getPlatformData(platform, projectData); - if (buildConfig.buildForDevice) { - packageFile = this.getLatestApplicationPackageForDevice(platformData, buildConfig, outputPath).packageName; - } else { - packageFile = this.getLatestApplicationPackageForEmulator(platformData, buildConfig, outputPath).packageName; - } - if (!packageFile || !this.$fs.exists(packageFile)) { - this.$errors.failWithoutHelp("Unable to find built application. Try 'tns build %s'.", platform); - } - return packageFile; - } - - public copyLastOutput(platform: string, targetPath: string, buildConfig: IBuildConfig, projectData: IProjectData): void { - platform = platform.toLowerCase(); - targetPath = path.resolve(targetPath); - - const packageFile = this.lastOutputPath(platform, buildConfig, projectData); - - this.$fs.ensureDirectoryExists(path.dirname(targetPath)); - - if (this.$fs.exists(targetPath) && this.$fs.getFsStats(targetPath).isDirectory()) { - const sourceFileName = path.basename(packageFile); - this.$logger.trace(`Specified target path: '${targetPath}' is directory. Same filename will be used: '${sourceFileName}'.`); - targetPath = path.join(targetPath, sourceFileName); - } - this.$fs.copyFile(packageFile, targetPath); - this.$logger.info(`Copied file '${packageFile}' to '${targetPath}'.`); - } - - private getCanExecuteAction(platform: string, options: IDeviceEmulator): any { - const canExecute = (currentDevice: Mobile.IDevice): boolean => { - if (options.device && currentDevice && currentDevice.deviceInfo) { - return currentDevice.deviceInfo.identifier === options.device; - } - - if (this.$mobileHelper.isiOSPlatform(platform) && this.$hostInfo.isDarwin) { - if (this.$devicesService.isOnlyiOSSimultorRunning() || options.emulator || this.$devicesService.isiOSSimulator(currentDevice)) { - return true; - } - - return this.$devicesService.isiOSDevice(currentDevice); - } - - return true; - }; - - return canExecute; - } - - public validatePlatform(platform: string, projectData: IProjectData): void { - if (!platform) { - this.$errors.fail("No platform specified."); - } - - platform = platform.split("@")[0].toLowerCase(); - - if (!this.$platformsData.getPlatformData(platform, projectData)) { - this.$errors.fail("Invalid platform %s. Valid platforms are %s.", platform, helpers.formatListOfNames(this.$platformsData.platformsNames)); - } - } - - private getApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[] { - // Get latest package` that is produced from build - let result = this.getApplicationPackagesCore(this.$fs.readDirectory(buildOutputPath).map(filename => path.join(buildOutputPath, filename)), validBuildOutputData.packageNames); - if (result) { - return result; - } - - const candidates = this.$fs.enumerateFilesInDirectorySync(buildOutputPath); - result = this.getApplicationPackagesCore(candidates, validBuildOutputData.packageNames); - if (result) { - return result; - } - - if (validBuildOutputData.regexes && validBuildOutputData.regexes.length) { - return this.createApplicationPackages(candidates.filter(filepath => _.some(validBuildOutputData.regexes, regex => regex.test(path.basename(filepath))))); - } - - return []; - } - - private getApplicationPackagesCore(candidates: string[], validPackageNames: string[]): IApplicationPackage[] { - const packages = candidates.filter(filePath => _.includes(validPackageNames, path.basename(filePath))); - if (packages.length > 0) { - return this.createApplicationPackages(packages); - } - - return null; - } - - private createApplicationPackages(packages: string[]): IApplicationPackage[] { - return packages.map(filepath => this.createApplicationPackage(filepath)); - } - - private createApplicationPackage(packageName: string): IApplicationPackage { - return { - packageName, - time: this.$fs.getFsStats(packageName).mtime - }; - } - - private getLatestApplicationPackage(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage { - let packages = this.getApplicationPackages(buildOutputPath, validBuildOutputData); - const packageExtName = path.extname(validBuildOutputData.packageNames[0]); - if (packages.length === 0) { - this.$errors.fail(`No ${packageExtName} found in ${buildOutputPath} directory.`); - } - - if (packages.length > 1) { - this.$logger.warn(`More than one ${packageExtName} found in ${buildOutputPath} directory. Using the last one produced from build.`); - } - - packages = _.sortBy(packages, pkg => pkg.time).reverse(); // We need to reverse because sortBy always sorts in ascending order - - return packages[0]; - } - - public getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage { - return this.getLatestApplicationPackage(outputPath || platformData.getBuildOutputPath(buildConfig), platformData.getValidBuildOutputData({ buildForDevice: true, release: buildConfig.release, androidBundle: buildConfig.androidBundle })); - } - - public getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage { - outputPath = outputPath || this.getBuildOutputPath(platformData.normalizedPlatformName.toLowerCase(), platformData, buildConfig); - const buildOutputOptions: IBuildOutputOptions = { buildForDevice: false, release: buildConfig.release, androidBundle: buildConfig.androidBundle }; - return this.getLatestApplicationPackage(outputPath || platformData.getBuildOutputPath(buildConfig), platformData.getValidBuildOutputData(buildOutputOptions)); - } - - // TODO: Remove this method from here. It has nothing to do with platform - public async readFile(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise { - temp.track(); - const uniqueFilePath = temp.path({ suffix: ".tmp" }); - const platform = device.deviceInfo.platform.toLowerCase(); - try { - await device.fileSystem.getFile(deviceFilePath, projectData.projectIdentifiers[platform], uniqueFilePath); - } catch (e) { - return null; - } - - if (this.$fs.exists(uniqueFilePath)) { - const text = this.$fs.readText(uniqueFilePath); - shell.rm(uniqueFilePath); - return text; - } - - return null; - } -} - -$injector.register("platformService", PlatformService); diff --git a/lib/services/platform/platform-add-service.ts b/lib/services/platform/add-platform-service.ts similarity index 76% rename from lib/services/platform/platform-add-service.ts rename to lib/services/platform/add-platform-service.ts index 97beb9131e..127fdad01f 100644 --- a/lib/services/platform/platform-add-service.ts +++ b/lib/services/platform/add-platform-service.ts @@ -2,8 +2,9 @@ import * as path from "path"; import * as temp from "temp"; import { PROJECT_FRAMEWORK_FOLDER_NAME, NativePlatformStatus } from "../../constants"; import { AddPlatformData } from "../workflow/workflow-data-service"; +import { performanceLog } from "../../common/decorators"; -export class PlatformAddService implements IPlatformAddService { +export class AddPlatformService { constructor( private $errors: IErrors, private $fs: IFileSystem, @@ -11,8 +12,6 @@ export class PlatformAddService implements IPlatformAddService { private $packageInstallationManager: IPackageInstallationManager, private $pacoteService: IPacoteService, private $platformsData: IPlatformsData, - private $platformJSService: IPreparePlatformService, - private $platformNativeService: IPreparePlatformService, private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService, private $terminalSpinnerService: ITerminalSpinnerService @@ -74,10 +73,10 @@ export class PlatformAddService implements IPlatformAddService { const frameworkPackageJsonContent = this.$fs.readJson(path.join(frameworkDirPath, "..", "package.json")); const frameworkVersion = frameworkPackageJsonContent.version; - await this.$platformJSService.addPlatform(platformData, projectData, frameworkDirPath, frameworkVersion); + await this.addJSPlatform(platformData, projectData, frameworkDirPath, frameworkVersion); if (!nativePrepare || !nativePrepare.skipNativePrepare) { - await this.$platformNativeService.addPlatform(platformData, projectData, frameworkDirPath, frameworkVersion); + await this.addNativePlatform(platformData, projectData, frameworkDirPath, frameworkVersion); } return frameworkVersion; @@ -118,5 +117,24 @@ export class PlatformAddService implements IPlatformAddService { return !!result; } + + private async addJSPlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise { + const frameworkPackageNameData = { version: frameworkVersion }; + this.$projectDataService.setNSValue(projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData); + } + + @performanceLog() + private async addNativePlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise { + const config = {}; + + const platformDir = path.join(projectData.platformsDir, platformData.normalizedPlatformName.toLowerCase()); + this.$fs.deleteDirectory(platformDir); + + await platformData.platformProjectService.createProject(path.resolve(frameworkDirPath), frameworkVersion, projectData, config); + platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); + await platformData.platformProjectService.interpolateData(projectData, config); + platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); + this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: NativePlatformStatus.requiresPrepare }); + } } -$injector.register("platformAddService", PlatformAddService); +$injector.register("addPlatformService", AddPlatformService); diff --git a/lib/services/platform/platform-build-service.ts b/lib/services/platform/build-platform-service.ts similarity index 97% rename from lib/services/platform/platform-build-service.ts rename to lib/services/platform/build-platform-service.ts index 1c197ce201..f85925d85d 100644 --- a/lib/services/platform/platform-build-service.ts +++ b/lib/services/platform/build-platform-service.ts @@ -7,7 +7,7 @@ import { BuildPlatformDataBase } from "../workflow/workflow-data-service"; const buildInfoFileName = ".nsbuildinfo"; -export class PlatformBuildService extends EventEmitter implements IPlatformBuildService { +export class BuildPlatformService extends EventEmitter { constructor( private $analyticsService: IAnalyticsService, private $buildArtefactsService: IBuildArtefactsService, @@ -135,4 +135,4 @@ export class PlatformBuildService extends EventEmitter implements IPlatformBuild return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; } } -$injector.register("platformBuildService", PlatformBuildService); +$injector.register("buildPlatformService", BuildPlatformService); diff --git a/lib/services/platform/platform-commands-service.ts b/lib/services/platform/platform-commands-service.ts index 82d6b7ddb7..d56b5299a1 100644 --- a/lib/services/platform/platform-commands-service.ts +++ b/lib/services/platform/platform-commands-service.ts @@ -2,6 +2,7 @@ import * as path from "path"; import * as semver from "semver"; import * as temp from "temp"; import * as constants from "../../constants"; +import { AddPlatformService } from "./add-platform-service"; export class PlatformCommandsService implements IPlatformCommandsService { constructor( @@ -10,7 +11,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { private $logger: ILogger, private $packageInstallationManager: IPackageInstallationManager, private $pacoteService: IPacoteService, - private $platformAddService: IPlatformAddService, + private $addPlatformService: AddPlatformService, private $platformsData: IPlatformsData, private $platformValidationService: IPlatformValidationService, private $projectChangesService: IProjectChangesService, @@ -31,7 +32,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { } const addPlatformData = { platformParam: platform.toLowerCase(), frameworkPath }; - await this.$platformAddService.addPlatform(projectData, addPlatformData); + await this.$addPlatformService.addPlatform(projectData, addPlatformData); } } @@ -83,7 +84,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { if (hasPlatformDirectory) { await this.updatePlatform(platform, version, projectData); } else { - await this.$platformAddService.addPlatform(projectData, { platformParam }); + await this.$addPlatformService.addPlatform(projectData, { platformParam }); } } } @@ -108,6 +109,14 @@ export class PlatformCommandsService implements IPlatformCommandsService { return _.filter(this.$platformsData.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); } + public getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); + const version = currentPlatformData && currentPlatformData.version; + + return version; + } + private isPlatformAdded(platform: string, platformPath: string, projectData: IProjectData): boolean { if (!this.$fs.exists(platformPath)) { return false; @@ -158,19 +167,11 @@ export class PlatformCommandsService implements IPlatformCommandsService { let packageName = platformData.normalizedPlatformName.toLowerCase(); await this.removePlatforms([packageName], projectData); packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; - const addPlatformData = { platformParam: packageName }; - await this.$platformAddService.addPlatform(projectData, addPlatformData); + const addPlatformData = { platformParam: packageName, frameworkPath: null, nativePrepare: null}; + await this.$addPlatformService.addPlatform(projectData, addPlatformData); this.$logger.out("Successfully updated to version ", updateOptions.newVersion); } - private getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); - const version = currentPlatformData && currentPlatformData.version; - - return version; - } - private isPlatformPrepared(platform: string, projectData: IProjectData): boolean { const platformData = this.$platformsData.getPlatformData(platform, projectData); return platformData.platformProjectService.isPlatformPrepared(platformData.projectRoot, projectData); diff --git a/lib/services/platform/platform-install-service.ts b/lib/services/platform/platform-install-service.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/services/platform/platform-watcher-service.ts b/lib/services/platform/platform-watcher-service.ts index 4665ca6e24..b28ab63cfc 100644 --- a/lib/services/platform/platform-watcher-service.ts +++ b/lib/services/platform/platform-watcher-service.ts @@ -4,6 +4,7 @@ import { EventEmitter } from "events"; import * as path from "path"; import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME } from "../../constants"; import { PreparePlatformData } from "../workflow/workflow-data-service"; +import { PreparePlatformService } from "./prepare-platform-service"; interface IPlatformWatcherData { webpackCompilerProcess: child_process.ChildProcess; @@ -17,7 +18,7 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat constructor( private $logger: ILogger, - private $platformNativeService: IPreparePlatformService, + private $preparePlatformService: PreparePlatformService, private $webpackCompilerService: IWebpackCompilerService ) { super(); } @@ -35,15 +36,16 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat }; } - await this.prepareJSCodeWithWatch(platformData, projectData, { env: preparePlatformData.env }); // -> start watcher + initial compilation - const hasNativeChanges = await this.prepareNativeCodeWithWatch(platformData, projectData, preparePlatformData); // -> start watcher + initial prepare + await this.startJSWatcherWithPrepare(platformData, projectData, { env: preparePlatformData.env }); // -> start watcher + initial compilation + const hasNativeChanges = await this.startNativeWatcherWithPrepare(platformData, projectData, preparePlatformData); // -> start watcher + initial prepare this.emitInitialSyncEvent({ platform: platformData.platformNameLowerCase, hasNativeChanges }); } - private async prepareJSCodeWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { this.$webpackCompilerService.on("webpackEmittedFiles", files => { + console.log("=============== WEBPACK EMITTED FILES ============"); this.emitFilesChangeEvent({ files, hasNativeChanges: false, platform: platformData.platformNameLowerCase }); }); @@ -52,7 +54,7 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat } } - private async prepareNativeCodeWithWatch(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + private async startNativeWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { if ((preparePlatformData.nativePrepare && preparePlatformData.nativePrepare.skipNativePrepare) || this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher) { return false; } @@ -79,7 +81,7 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher; - const hasNativeChanges = await this.$platformNativeService.preparePlatform(platformData, projectData, preparePlatformData); + const hasNativeChanges = await this.$preparePlatformService.prepareNativePlatform(platformData, projectData, preparePlatformData); return hasNativeChanges; } diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/platform/prepare-platform-service.ts similarity index 76% rename from lib/services/prepare-platform-native-service.ts rename to lib/services/platform/prepare-platform-service.ts index 71bcab660a..cd9a463df7 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/platform/prepare-platform-service.ts @@ -1,32 +1,34 @@ +import { performanceLog } from "../../common/decorators"; +import { PreparePlatformData } from "../workflow/workflow-data-service"; import * as path from "path"; -import * as constants from "../constants"; -import { performanceLog } from "../common/decorators"; -import { PreparePlatformData } from "./workflow/workflow-data-service"; +import { NativePlatformStatus, APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME } from "../../constants"; -export class PlatformNativeService implements IPreparePlatformService { +export class PreparePlatformService { constructor( + private $androidResourcesMigrationService: IAndroidResourcesMigrationService, private $fs: IFileSystem, + private $logger: ILogger, private $nodeModulesBuilder: INodeModulesBuilder, private $projectChangesService: IProjectChangesService, - private $androidResourcesMigrationService: IAndroidResourcesMigrationService + private $webpackCompilerService: IWebpackCompilerService, ) { } @performanceLog() - public async addPlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise { - const config = {}; + public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + this.$logger.out("Preparing project..."); + + await this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, { watch: false, env: preparePlatformData.env }); + await this.prepareNativePlatform(platformData, projectData, preparePlatformData); + + this.$projectChangesService.savePrepareInfo(platformData); - const platformDir = path.join(projectData.platformsDir, platformData.normalizedPlatformName.toLowerCase()); - this.$fs.deleteDirectory(platformDir); + this.$logger.out(`Project successfully prepared (${platformData.platformNameLowerCase})`); - await platformData.platformProjectService.createProject(path.resolve(frameworkDirPath), frameworkVersion, projectData, config); - platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); - await platformData.platformProjectService.interpolateData(projectData, config); - platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); - this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: constants.NativePlatformStatus.requiresPrepare }); + return true; } @performanceLog() - public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + public async prepareNativePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { const { nativePrepare, release } = preparePlatformData; if (nativePrepare && nativePrepare.skipNativePrepare) { return false; @@ -63,14 +65,14 @@ export class PlatformNativeService implements IPreparePlatformService { } platformData.platformProjectService.interpolateConfigurationFile(projectData, preparePlatformData); - this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); + this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: NativePlatformStatus.alreadyPrepared }); return hasChanges; } private prepareAppResources(platformData: IPlatformData, projectData: IProjectData): void { - const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - const appResourcesDestinationDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME); + const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); + const appResourcesDestinationDirectoryPath = path.join(appDestinationDirectoryPath, APP_RESOURCES_FOLDER_NAME); if (this.$fs.exists(appResourcesDestinationDirectoryPath)) { platformData.platformProjectService.prepareAppResources(appResourcesDestinationDirectoryPath, projectData); @@ -113,7 +115,7 @@ export class PlatformNativeService implements IPreparePlatformService { } const previousPrepareInfo = this.$projectChangesService.getPrepareInfo(platformData); - if (!previousPrepareInfo || previousPrepareInfo.nativePlatformStatus !== constants.NativePlatformStatus.alreadyPrepared) { + if (!previousPrepareInfo || previousPrepareInfo.nativePlatformStatus !== NativePlatformStatus.alreadyPrepared) { return; } @@ -124,5 +126,4 @@ export class PlatformNativeService implements IPreparePlatformService { } } } - -$injector.register("platformNativeService", PlatformNativeService); +$injector.register("preparePlatformService", PreparePlatformService); diff --git a/lib/services/prepare-platform-js-service.ts b/lib/services/prepare-platform-js-service.ts deleted file mode 100644 index 878fd2dc13..0000000000 --- a/lib/services/prepare-platform-js-service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { hook } from "../common/helpers"; -import { performanceLog } from "./../common/decorators"; -import { EventEmitter } from "events"; -import { PreparePlatformData } from "./workflow/workflow-data-service"; - -export class PlatformJSService extends EventEmitter implements IPreparePlatformService { - - constructor( - private $projectDataService: IProjectDataService, - // private $webpackCompilerService: IWebpackCompilerService - ) { super(); } - - public async addPlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise { - const frameworkPackageNameData = { version: frameworkVersion }; - this.$projectDataService.setNSValue(projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData); - } - - @performanceLog() - @hook('prepareJSApp') - public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - // intentionally left blank, keep the support for before-prepareJSApp and after-prepareJSApp hooks - return true; - } -} - -$injector.register("platformJSService", PlatformJSService); diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 71412b4979..7ce9faa8ae 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -11,8 +11,8 @@ export class TestExecutionService implements ITestExecutionService { private static CONFIG_FILE_NAME = `node_modules/${constants.TEST_RUNNER_NAME}/config.js`; private static SOCKETIO_JS_FILE_NAME = `node_modules/${constants.TEST_RUNNER_NAME}/socket.io.js`; - constructor(private $platformService: IPlatformService, - private $liveSyncService: ILiveSyncService, + constructor( + private $liveSyncCommandHelper: ILiveSyncCommandHelper, private $httpClient: Server.IHttpClient, private $config: IConfiguration, private $logger: ILogger, @@ -75,54 +75,10 @@ export class TestExecutionService implements ITestExecutionService { devices = this.$devicesService.getDeviceInstances(); } - // Now let's take data for each device: - const platformLowerCase = this.platform && this.platform.toLowerCase(); - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices.filter(d => !platformLowerCase || d.deviceInfo.platform.toLowerCase() === platformLowerCase) - .map(d => { - const info: ILiveSyncDeviceInfo = { - identifier: d.deviceInfo.identifier, - buildAction: async (): Promise => { - const buildConfig: IBuildConfig = { - buildForDevice: !d.isEmulator, - iCloudContainerEnvironment: this.$options.iCloudContainerEnvironment, - projectDir: this.$options.path, - clean: this.$options.clean, - teamId: this.$options.teamId, - device: this.$options.device, - provision: this.$options.provision, - release: this.$options.release, - keyStoreAlias: this.$options.keyStoreAlias, - keyStorePath: this.$options.keyStorePath, - keyStoreAliasPassword: this.$options.keyStoreAliasPassword, - keyStorePassword: this.$options.keyStorePassword - }; - - await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, projectData); - const pathToBuildResult = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, projectData); - return pathToBuildResult; - }, - debugOptions: this.$options, - debugggingEnabled: this.$options.debugBrk - }; - - return info; - }); + if (!this.$options.env) { this.$options.env = { }; } + this.$options.env.unitTesting = true; - const env = this.$options.env || {}; - env.unitTesting = !!this.$options.bundle; - - const liveSyncInfo: ILiveSyncInfo = { - projectDir: projectData.projectDir, - skipWatcher: !this.$options.watch || this.$options.justlaunch, - release: this.$options.release, - webpackCompilerConfig: { - env, - }, - timeout: this.$options.timeout, - useHotModuleReload: this.$options.hmr - }; - - await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + await this.$liveSyncCommandHelper.executeLiveSyncOperation(devices, this.platform, {}); }; karmaRunner.on("message", (karmaData: any) => { diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index cca9858113..e6fdd96410 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -81,6 +81,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp "--preserve-symlinks", `--config=${path.join(projectData.projectDir, "webpack.config.js")}`, `--env.${platformData.normalizedPlatformName.toLowerCase()}` + // `--env.unitTesting` ]; if (config.watch) { diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index 668e249783..4bce2bff9e 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -16,18 +16,6 @@ declare global { } - interface IPreparePlatformService { - addPlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise; - preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; - } - - interface IPlatformBuildService { - buildPlatform(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: T): Promise - buildPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: T, outputPath?: string): Promise; - saveBuildInfoFile(platformData: IPlatformData, projectData: IProjectData, buildInfoFileDirname: string): void; - getBuildInfoFromFile(platformData: IPlatformData, buildConfig: BuildPlatformDataBase, buildOutputPath?: string): IBuildInfo; - } - interface IProjectChangesService { checkForChanges(platform: string, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; getPrepareInfoFilePath(platformData: IPlatformData): string; @@ -37,143 +25,6 @@ declare global { currentChanges: IProjectChangesInfo; } - interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { - /** - * Ensures that the specified platform and its dependencies are installed. - * When there are changes to be prepared, it prepares the native project for the specified platform. - * When finishes, prepare saves the .nsprepareinfo file in platform folder. - * This file contains information about current project configuration and allows skipping unnecessary build, deploy and livesync steps. - * @param {IPreparePlatformInfo} platformInfo Options to control the preparation. - * @returns {boolean} true indicates that the platform was prepared. - */ - preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; - - /** - * Determines whether a build is necessary. A build is necessary when one of the following is true: - * - there is no previous build. - * - the .nsbuildinfo file in product folder points to an old prepare. - * @param {string} platform The platform to build. - * @param {IProjectData} projectData DTO with information about the project. - * @param {IBuildConfig} @optional buildConfig Indicates whether the build is for device or emulator. - * @param {string} @optional outputPath Directory containing build information and artifacts. - * @returns {boolean} true indicates that the platform should be build. - */ - shouldBuild(platform: string, projectData: IProjectData, buildConfig?: IBuildConfig, outputPath?: string): Promise; - - /** - * Determines whether installation is necessary. It is necessary when one of the following is true: - * - the application is not installed. - * - the .nsbuildinfo file located in application root folder is different than the local .nsbuildinfo file - * @param {Mobile.IDevice} device The device where the application should be installed. - * @param {IProjectData} projectData DTO with information about the project. - * @param {string} @optional outputPath Directory containing build information and artifacts. - * @returns {Promise} true indicates that the application should be installed. - */ - shouldInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise; - - /** - * - * @param {Mobile.IDevice} device The device where the application should be installed. - * @param {IProjectData} projectData DTO with information about the project. - * @param {string} @optional outputPath Directory containing build information and artifacts. - */ - validateInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise; - - /** - * Installs the application on specified device. - * When finishes, saves .nsbuildinfo in application root folder to indicate the prepare that was used to build the app. - * * .nsbuildinfo is not persisted when building for release. - * @param {Mobile.IDevice} device The device where the application should be installed. - * @param {IBuildConfig} options The build configuration. - * @param {string} @optional pathToBuiltApp Path to build artifact. - * @param {string} @optional outputPath Directory containing build information and artifacts. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - installApplication(device: Mobile.IDevice, options: IBuildConfig, projectData: IProjectData, pathToBuiltApp?: string, outputPath?: string): Promise; - - /** - * Executes prepare, build and installOnPlatform when necessary to ensure that the latest version of the app is installed on specified platform. - * - When --clean option is specified it builds the app on every change. If not, build is executed only when there are native changes. - * @param {IDeployPlatformInfo} deployInfo Options required for project preparation and deployment. - * @returns {void} - */ - deployPlatform(deployInfo: IDeployPlatformInfo): Promise; - - /** - * Runs the application on specified platform. Assumes that the application is already build and installed. Fails if this is not true. - * @param {string} platform The platform where to start the application. - * @param {IRunPlatformOptions} runOptions Various options that help manage the run operation. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - startApplication(platform: string, runOptions: IRunPlatformOptions, appData: Mobile.IStartApplicationData): Promise; - - /** - * Returns information about the latest built application for device in the current project. - * @param {IPlatformData} platformData Data describing the current platform. - * @param {IBuildConfig} buildConfig Defines if the build is for release configuration. - * @param {string} @optional outputPath Directory that should contain the build artifact. - * @returns {IApplicationPackage} Information about latest built application. - */ - getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage; - - /** - * Returns information about the latest built application for simulator in the current project. - * @param {IPlatformData} platformData Data describing the current platform. - * @param {IBuildConfig} buildConfig Defines if the build is for release configuration. - * @param {string} @optional outputPath Directory that should contain the build artifact. - * @returns {IApplicationPackage} Information about latest built application. - */ - getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage; - - /** - * Copies latest build output to a specified location. - * @param {string} platform Mobile platform - Android, iOS. - * @param {string} targetPath Destination where the build artifact should be copied. - * @param {IBuildConfig} buildConfig Defines if the searched artifact should be for simulator and is it built for release. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {void} - */ - copyLastOutput(platform: string, targetPath: string, buildConfig: IBuildConfig, projectData: IProjectData): void; - - /** - * Gets the latest build output. - * @param {string} platform Mobile platform - Android, iOS. - * @param {IBuildConfig} buildConfig Defines if the searched artifact should be for simulator and is it built for release. - * @param {IProjectData} projectData DTO with information about the project. - * @param {string} @optional outputPath Directory that should contain the build artifact. - * @returns {string} The path to latest built artifact. - */ - lastOutputPath(platform: string, buildConfig: IBuildConfig, projectData: IProjectData, outputPath?: string): string; - - /** - * Reads contents of a file on device. - * @param {Mobile.IDevice} device The device to read from. - * @param {string} deviceFilePath The file path. - * @param {IProjectData} projectData DTO with information about the project. - * @returns {string} The contents of the file or null when there is no such file. - */ - readFile(device: Mobile.IDevice, deviceFilePath: string, projectData: IProjectData): Promise; - - /** - * Saves build information in a proprietary file. - * @param {string} platform The build platform. - * @param {string} projectDir The project's directory. - * @param {string} buildInfoFileDirname The directory where the build file should be written to. - * @returns {void} - */ - saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void; - - /** - * Gives information for the current version of the runtime. - * @param {string} platform The platform to be checked. - * @param {IProjectData} projectData The data describing the project - * @returns {string} Runtime version - */ - getCurrentPlatformVersion(platform: string, projectData: IProjectData): string; - } - interface IPlatformWatcherService extends EventEmitter { startWatcher(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; } diff --git a/lib/services/workflow/workflow-data-service.ts b/lib/services/workflow/workflow-data-service.ts index fa6571e9fb..369593bd83 100644 --- a/lib/services/workflow/workflow-data-service.ts +++ b/lib/services/workflow/workflow-data-service.ts @@ -1,3 +1,5 @@ +export type AddPlatformData = Pick & Partial> & Partial>; + export class WorkflowDataService { constructor( private $platformsData: IPlatformsData, @@ -12,20 +14,20 @@ export class WorkflowDataService { ios: { projectData, nativePlatformData, - addPlatformData: new AddPlatformData("ios", options), + addPlatformData: this.getAddPlatformData("ios", options), preparePlatformData: new PreparePlatformData(options), buildPlatformData: new IOSBuildData(options), - installOnDeviceData: {}, + deployPlatformData: new DeployPlatformData(options), liveSyncData: {}, restartOnDeviceData: {} }, android: { projectData, nativePlatformData, - addPlatformData: new AddPlatformData("android", options), + addPlatformData: this.getAddPlatformData("android", options), preparePlatformData: new PreparePlatformData(options), buildPlatformData: new AndroidBuildData(options), - installOnDeviceData: {}, + deployPlatformData: new DeployPlatformData(options), liveSyncData: {}, restartOnDeviceData: {} } @@ -33,6 +35,14 @@ export class WorkflowDataService { return data[platform.toLowerCase()]; } + + private getAddPlatformData(platform: string, options: IOptions | any) { + return { + frameworkPath: options.frameworkPath, + nativePrepare: options.nativePrepare, + platformParam: options.platformParam || platform, + }; + } } $injector.register("workflowDataService", WorkflowDataService); @@ -42,18 +52,18 @@ export class WorkflowData { public addPlatformData: AddPlatformData; public preparePlatformData: PreparePlatformData; public buildPlatformData: any; - public installOnDeviceData: any; + public deployPlatformData: DeployPlatformData; public liveSyncData: any; public restartOnDeviceData: any; } -export class AddPlatformData { - constructor(private platform: string, private options: IOptions | any) { } +// export class AddPlatformData { +// constructor(private platform: string, private options: IOptions | any) { } - public platformParam = this.options.platformParam || this.platform; - public frameworkPath = this.options.frameworkPath; - public nativePrepare = this.options.nativePrepare; -} +// public platformParam = this.options.platformParam || this.platform; +// public frameworkPath = this.options.frameworkPath; +// public nativePrepare = this.options.nativePrepare; +// } export class PreparePlatformData { constructor(protected options: IOptions | any) { } @@ -98,3 +108,11 @@ export class AndroidBuildData extends BuildPlatformDataBase { public keyStorePassword = this.options.keyStorePassword; public androidBundle = this.options.aab; } + +export class DeployPlatformData { + constructor(private options: IOptions) { } + + public clean = this.options.clean; + public release = this.options.release; + public forceInstall = true; +} diff --git a/test/platform-commands.ts b/test/platform-commands.ts index f8b416e68d..46fde5a5f5 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -140,8 +140,7 @@ function createTestInjector() { testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("xmlValidator", XmlValidator); testInjector.register("npm", {}); - testInjector.register("preparePlatformNativeService", {}); - testInjector.register("preparePlatformJSService", {}); + testInjector.register("preparePlatformService", {}); testInjector.register("childProcess", ChildProcessLib.ChildProcess); testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); testInjector.register("analyticsService", { diff --git a/test/platform-service.ts b/test/platform-service.ts index 2e2ae670fb..42ac196641 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -1,1150 +1,1003 @@ -import * as yok from "../lib/common/yok"; -import * as stubs from "./stubs"; -import * as PlatformServiceLib from "../lib/services/platform-service"; -import { VERSION_STRING, PACKAGE_JSON_FILE_NAME, AddPlaformErrors } from "../lib/constants"; -import * as fsLib from "../lib/common/file-system"; -import * as optionsLib from "../lib/options"; -import * as hostInfoLib from "../lib/common/host-info"; -import * as ProjectFilesManagerLib from "../lib/common/services/project-files-manager"; -import * as path from "path"; -import { format } from "util"; -import { assert } from "chai"; -import { LocalToDevicePathDataFactory } from "../lib/common/mobile/local-to-device-path-data-factory"; -import { MobileHelper } from "../lib/common/mobile/mobile-helper"; -import { ProjectFilesProvider } from "../lib/providers/project-files-provider"; -import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; -import { XmlValidator } from "../lib/xml-validator"; -import { PlatformJSService } from "../lib/services/prepare-platform-js-service"; -import * as ChildProcessLib from "../lib/common/child-process"; -import ProjectChangesLib = require("../lib/services/project-changes-service"); -import { Messages } from "../lib/common/messages/messages"; -import { SettingsService } from "../lib/common/test/unit-tests/stubs"; -import { mkdir } from "shelljs"; -import * as constants from "../lib/constants"; -import { PlatformCommandsService } from "../lib/services/platform/platform-commands-service"; -import { StaticConfig } from "../lib/config"; -import { PlatformNativeService } from "../lib/services/prepare-platform-native-service"; - -require("should"); -const temp = require("temp"); -temp.track(); - -function createTestInjector() { - const testInjector = new yok.Yok(); - - testInjector.register('platformService', PlatformServiceLib.PlatformService); - testInjector.register("platformCommandsService", PlatformCommandsService); - testInjector.register('errors', stubs.ErrorsStub); - testInjector.register('logger', stubs.LoggerStub); - testInjector.register("nodeModulesDependenciesBuilder", {}); - testInjector.register('packageInstallationManager', stubs.PackageInstallationManagerStub); - // TODO: Remove the projectData - it shouldn't be required in the service itself. - testInjector.register('projectData', stubs.ProjectDataStub); - testInjector.register('platformsData', stubs.PlatformsDataStub); - testInjector.register('devicesService', {}); - testInjector.register('androidEmulatorServices', {}); - testInjector.register('projectDataService', stubs.ProjectDataService); - testInjector.register('prompter', {}); - testInjector.register('sysInfo', {}); - testInjector.register("commandsService", { - tryExecuteCommand: () => { /* intentionally left blank */ } - }); - testInjector.register("options", optionsLib.Options); - testInjector.register("hostInfo", hostInfoLib.HostInfo); - testInjector.register("staticConfig", StaticConfig); - testInjector.register("nodeModulesBuilder", { - prepareNodeModules: () => { - return Promise.resolve(); - }, - prepareJSNodeModules: () => { - return Promise.resolve(); - } - }); - testInjector.register("pluginsService", { - getAllInstalledPlugins: () => { - return []; - }, - ensureAllDependenciesAreInstalled: () => { - return Promise.resolve(); - }, - validate: (platformData: IPlatformData, projectData: IProjectData) => { - return Promise.resolve(); - } - }); - testInjector.register("projectFilesManager", ProjectFilesManagerLib.ProjectFilesManager); - testInjector.register("hooksService", stubs.HooksServiceStub); - testInjector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); - testInjector.register("mobileHelper", MobileHelper); - testInjector.register("projectFilesProvider", ProjectFilesProvider); - testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); - testInjector.register("xmlValidator", XmlValidator); - testInjector.register("platformNativeService", PlatformNativeService); - testInjector.register("platformJSService", PlatformJSService); - testInjector.register("packageManager", { - uninstall: async () => { - return true; - } - }); - testInjector.register("childProcess", ChildProcessLib.ChildProcess); - testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); - testInjector.register("analyticsService", { - track: async (): Promise => undefined, - trackEventActionInGoogleAnalytics: () => Promise.resolve() - }); - testInjector.register("messages", Messages); - testInjector.register("devicePathProvider", {}); - testInjector.register("helpService", { - showCommandLineHelp: async (): Promise => (undefined) - }); - testInjector.register("settingsService", SettingsService); - testInjector.register("terminalSpinnerService", { - createSpinner: (msg: string) => ({ - start: (): void => undefined, - stop: (): void => undefined, - message: (): void => undefined - }) - }); - testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub); - testInjector.register("filesHashService", { - generateHashes: () => Promise.resolve(), - getChanges: () => Promise.resolve({ test: "testHash" }) - }); - testInjector.register("pacoteService", { - extractPackage: async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => { - mkdir(path.join(destinationDirectory, "framework")); - (new fsLib.FileSystem(testInjector)).writeFile(path.join(destinationDirectory, PACKAGE_JSON_FILE_NAME), JSON.stringify({ - name: "package-name", - version: "1.0.0" - })); - } - }); - testInjector.register("usbLiveSyncService", () => ({})); - testInjector.register("doctorService", { - checkForDeprecatedShortImportsInAppDir: (projectDir: string): void => undefined - }); - testInjector.register("cleanupService", { - setShouldDispose: (shouldDispose: boolean): void => undefined - }); - - return testInjector; -} - -describe('Platform Service Tests', () => { - let platformCommandsService: IPlatformCommandsService, platformService: IPlatformService, testInjector: IInjector; - - beforeEach(() => { - testInjector = createTestInjector(); - testInjector.register("fs", stubs.FileSystemStub); - testInjector.resolve("projectData").initializeProjectData(); - platformCommandsService = testInjector.resolve("platformCommandsService"); - platformService = testInjector.resolve("platformService"); - }); - - describe("add platform unit tests", () => { - describe("#add platform()", () => { - it("should not fail if platform is not normalized", async () => { - const fs = testInjector.resolve("fs"); - fs.exists = () => false; - const projectData: IProjectData = testInjector.resolve("projectData"); - await platformCommandsService.addPlatforms(["Android"], projectData, ""); - await platformCommandsService.addPlatforms(["ANDROID"], projectData, ""); - await platformCommandsService.addPlatforms(["AnDrOiD"], projectData, ""); - await platformCommandsService.addPlatforms(["androiD"], projectData, ""); - - await platformCommandsService.addPlatforms(["iOS"], projectData, ""); - await platformCommandsService.addPlatforms(["IOS"], projectData, ""); - await platformCommandsService.addPlatforms(["IoS"], projectData, ""); - await platformCommandsService.addPlatforms(["iOs"], projectData, ""); - }); - - it("should fail if platform is already installed", async () => { - const projectData: IProjectData = testInjector.resolve("projectData"); - // By default fs.exists returns true, so the platforms directory should exists - await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); - await assert.isRejected(platformCommandsService.addPlatforms(["ios"], projectData, ""), "Platform ios already added"); - }); - - it("should fail if unable to extract runtime package", async () => { - const fs = testInjector.resolve("fs"); - fs.exists = () => false; - - const pacoteService = testInjector.resolve("pacoteService"); - const errorMessage = "Pacote service unable to extract package"; - pacoteService.extractPackage = async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => { - throw new Error(errorMessage); - }; - - const projectData: IProjectData = testInjector.resolve("projectData"); - await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), errorMessage); - }); - - it("fails when path passed to frameworkPath does not exist", async () => { - const fs = testInjector.resolve("fs"); - fs.exists = () => false; - - const projectData: IProjectData = testInjector.resolve("projectData"); - const frameworkPath = "invalidPath"; - const errorMessage = format(AddPlaformErrors.InvalidFrameworkPathStringFormat, frameworkPath); - await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, frameworkPath), errorMessage); - }); - - const assertCorrectDataIsPassedToPacoteService = async (versionString: string): Promise => { - const fs = testInjector.resolve("fs"); - fs.exists = () => false; - - const pacoteService = testInjector.resolve("pacoteService"); - let packageNamePassedToPacoteService = ""; - pacoteService.extractPackage = async (name: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => { - packageNamePassedToPacoteService = name; - }; - - const platformsData = testInjector.resolve("platformsData"); - const packageName = "packageName"; - platformsData.getPlatformData = (platform: string, pData: IProjectData): IPlatformData => { - return { - frameworkPackageName: packageName, - platformNameLowerCase: "", - platformProjectService: new stubs.PlatformProjectServiceStub(), - projectRoot: "", - normalizedPlatformName: "", - appDestinationDirectoryPath: "", - getBuildOutputPath: () => "", - getValidBuildOutputData: (buildOptions: IBuildOutputOptions) => ({ packageNames: [] }), - frameworkFilesExtensions: [], - relativeToFrameworkConfigurationFilePath: "", - fastLivesyncFileExtensions: [] - }; - }; - const projectData: IProjectData = testInjector.resolve("projectData"); - - await platformCommandsService.addPlatforms(["android"], projectData, ""); - assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); - await platformCommandsService.addPlatforms(["ios"], projectData, ""); - assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); - }; - it("should respect platform version in package.json's nativescript key", async () => { - const versionString = "2.5.0"; - const nsValueObject: any = { - [VERSION_STRING]: versionString - }; - const projectDataService = testInjector.resolve("projectDataService"); - projectDataService.getNSValue = () => nsValueObject; - - await assertCorrectDataIsPassedToPacoteService(versionString); - }); - - it("should install latest platform if no information found in package.json's nativescript key", async () => { - - const projectDataService = testInjector.resolve("projectDataService"); - projectDataService.getNSValue = (): any => null; - - const latestCompatibleVersion = "1.0.0"; - const packageInstallationManager = testInjector.resolve("packageInstallationManager"); - packageInstallationManager.getLatestCompatibleVersion = async (packageName: string, referenceVersion?: string): Promise => { - return latestCompatibleVersion; - }; - - await assertCorrectDataIsPassedToPacoteService(latestCompatibleVersion); - }); - - // Workflow: tns preview; tns platform add - it(`should add platform when only .js part of the platform has already been added (nativePlatformStatus is ${constants.NativePlatformStatus.requiresPlatformAdd})`, async () => { - const fs = testInjector.resolve("fs"); - fs.exists = () => true; - const projectChangesService = testInjector.resolve("projectChangesService"); - projectChangesService.getPrepareInfo = () => ({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - const projectData = testInjector.resolve("projectData"); - let isJsPlatformAdded = false; - const preparePlatformJSService = testInjector.resolve("preparePlatformJSService"); - preparePlatformJSService.addPlatform = async () => isJsPlatformAdded = true; - let isNativePlatformAdded = false; - const preparePlatformNativeService = testInjector.resolve("preparePlatformNativeService"); - preparePlatformNativeService.addPlatform = async () => isNativePlatformAdded = true; - - await platformCommandsService.addPlatforms(["android"], projectData, ""); - - assert.isTrue(isJsPlatformAdded); - assert.isTrue(isNativePlatformAdded); - }); - - // Workflow: tns platform add; tns platform add - it("shouldn't add platform when platforms folder exist and no .nsprepare file", async () => { - const fs = testInjector.resolve("fs"); - fs.exists = () => true; - const projectChangesService = testInjector.resolve("projectChangesService"); - projectChangesService.getPrepareInfo = () => null; - const projectData = testInjector.resolve("projectData"); - - await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); - }); - - // Workflow: tns run; tns platform add - it(`shouldn't add platform when both native and .js parts of the platform have already been added (nativePlatformStatus is ${constants.NativePlatformStatus.alreadyPrepared})`, async () => { - const fs = testInjector.resolve("fs"); - fs.exists = () => true; - const projectChangesService = testInjector.resolve("projectChangesService"); - projectChangesService.getPrepareInfo = () => ({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - const projectData = testInjector.resolve("projectData"); - - await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); - }); - }); - }); - - describe("remove platform unit tests", () => { - it("should fail when platforms are not added", async () => { - const ExpectedErrorsCaught = 2; - let errorsCaught = 0; - const projectData: IProjectData = testInjector.resolve("projectData"); - testInjector.resolve("fs").exists = () => false; - - try { - await platformCommandsService.removePlatforms(["android"], projectData); - } catch (e) { - errorsCaught++; - } - - try { - await platformCommandsService.removePlatforms(["ios"], projectData); - } catch (e) { - errorsCaught++; - } - - assert.isTrue(errorsCaught === ExpectedErrorsCaught); - }); - it("shouldn't fail when platforms are added", async () => { - const projectData: IProjectData = testInjector.resolve("projectData"); - testInjector.resolve("fs").exists = () => false; - await platformCommandsService.addPlatforms(["android"], projectData, ""); - - testInjector.resolve("fs").exists = () => true; - await platformCommandsService.removePlatforms(["android"], projectData); - }); - }); - - describe("clean platform unit tests", () => { - it("should preserve the specified in the project nativescript version", async () => { - const versionString = "2.4.1"; - const fs = testInjector.resolve("fs"); - fs.exists = () => false; - - const nsValueObject: any = {}; - nsValueObject[VERSION_STRING] = versionString; - const projectDataService = testInjector.resolve("projectDataService"); - projectDataService.getNSValue = () => nsValueObject; - - const packageInstallationManager = testInjector.resolve("packageInstallationManager"); - packageInstallationManager.install = (packageName: string, packageDir: string, options: INpmInstallOptions) => { - assert.deepEqual(options.version, versionString); - return ""; - }; - - const projectData: IProjectData = testInjector.resolve("projectData"); - platformCommandsService.removePlatforms = (platforms: string[], prjctData: IProjectData): Promise => { - nsValueObject[VERSION_STRING] = undefined; - return Promise.resolve(); - }; - - await platformCommandsService.cleanPlatforms(["android"], projectData, ""); - - nsValueObject[VERSION_STRING] = versionString; - await platformCommandsService.cleanPlatforms(["ios"], projectData, ""); - }); - }); - - // TODO: Commented as it doesn't seem correct. Check what's the case and why it's been expected to fail. - // describe("list platform unit tests", () => { - // it("fails when platforms are not added", () => { - // assert.throws(async () => await platformService.getAvailablePlatforms()); - // }); - // }); - - describe("update Platform", () => { - describe("#updatePlatform(platform)", () => { - it("should fail when the versions are the same", async () => { - const packageInstallationManager: IPackageInstallationManager = testInjector.resolve("packageInstallationManager"); - packageInstallationManager.getLatestVersion = async () => "0.2.0"; - const projectData: IProjectData = testInjector.resolve("projectData"); - - await assert.isRejected(platformCommandsService.updatePlatforms(["android"], projectData)); - }); - }); - }); - - // TODO: check this tests with QAs - // describe("prepare platform unit tests", () => { - // let fs: IFileSystem; - - // beforeEach(() => { - // testInjector = createTestInjector(); - // testInjector.register("fs", fsLib.FileSystem); - // fs = testInjector.resolve("fs"); - // testInjector.resolve("projectData").initializeProjectData(); - // }); - - // function prepareDirStructure() { - // const tempFolder = temp.mkdirSync("prepare_platform"); - - // const appFolderPath = path.join(tempFolder, "app"); - // fs.createDirectory(appFolderPath); - - // const nodeModulesPath = path.join(tempFolder, "node_modules"); - // fs.createDirectory(nodeModulesPath); - - // const testsFolderPath = path.join(appFolderPath, "tests"); - // fs.createDirectory(testsFolderPath); - - // const app1FolderPath = path.join(tempFolder, "app1"); - // fs.createDirectory(app1FolderPath); - - // const appDestFolderPath = path.join(tempFolder, "appDest"); - // const appResourcesFolderPath = path.join(appDestFolderPath, "App_Resources"); - // const appResourcesPath = path.join(appFolderPath, "App_Resources/Android"); - // fs.createDirectory(appResourcesPath); - // fs.writeFile(path.join(appResourcesPath, "test.txt"), "test"); - // fs.writeJson(path.join(tempFolder, "package.json"), { - // name: "testname", - // nativescript: { - // id: "org.nativescript.testname" - // } - // }); - - // return { tempFolder, appFolderPath, app1FolderPath, appDestFolderPath, appResourcesFolderPath }; - // } - - // async function execPreparePlatform(platformToTest: string, testDirData: any, - // release?: boolean) { - // const platformsData = testInjector.resolve("platformsData"); - // platformsData.platformsNames = ["ios", "android"]; - // platformsData.getPlatformData = (platform: string) => { - // return { - // appDestinationDirectoryPath: testDirData.appDestFolderPath, - // appResourcesDestinationDirectoryPath: testDirData.appResourcesFolderPath, - // normalizedPlatformName: platformToTest, - // configurationFileName: platformToTest === "ios" ? INFO_PLIST_FILE_NAME : MANIFEST_FILE_NAME, - // projectRoot: testDirData.tempFolder, - // platformProjectService: { - // prepareProject: (): any => null, - // validate: () => Promise.resolve(), - // createProject: (projectRoot: string, frameworkDir: string) => Promise.resolve(), - // interpolateData: (projectRoot: string) => Promise.resolve(), - // afterCreateProject: (projectRoot: string): any => null, - // getAppResourcesDestinationDirectoryPath: (pData: IProjectData, frameworkVersion?: string): string => { - // if (platform.toLowerCase() === "ios") { - // const dirPath = path.join(testDirData.appDestFolderPath, "Resources"); - // fs.ensureDirectoryExists(dirPath); - // return dirPath; - // } else { - // const dirPath = path.join(testDirData.appDestFolderPath, "src", "main", "res"); - // fs.ensureDirectoryExists(dirPath); - // return dirPath; - // } - // }, - // processConfigurationFilesFromAppResources: () => Promise.resolve(), - // handleNativeDependenciesChange: () => Promise.resolve(), - // ensureConfigurationFileInAppResources: (): any => null, - // interpolateConfigurationFile: (): void => undefined, - // isPlatformPrepared: (projectRoot: string) => false, - // prepareAppResources: (appResourcesDirectoryPath: string, pData: IProjectData): void => undefined, - // checkForChanges: () => { /* */ } - // } - // }; - // }; - - // const projectData = testInjector.resolve("projectData"); - // projectData.projectDir = testDirData.tempFolder; - // projectData.projectName = "app"; - // projectData.appDirectoryPath = testDirData.appFolderPath; - // projectData.appResourcesDirectoryPath = path.join(testDirData.appFolderPath, "App_Resources"); - - // platformService = testInjector.resolve("platformService"); - // const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: release, useHotModuleReload: false }; - // await platformService.preparePlatform({ - // platform: platformToTest, - // appFilesUpdaterOptions, - // platformTemplate: "", - // projectData, - // "": { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, - // env: {} - // }); - // } - - // async function testPreparePlatform(platformToTest: string, release?: boolean): Promise { - // const testDirData = prepareDirStructure(); - // const created: CreatedTestData = new CreatedTestData(); - // created.testDirData = testDirData; - - // // Add platform specific files to app and app1 folders - // const platformSpecificFiles = [ - // "test1.ios.js", "test1-ios-js", "test2.android.js", "test2-android-js", - // "main.js" - // ]; - - // const destinationDirectories = [testDirData.appFolderPath, testDirData.app1FolderPath]; - - // _.each(destinationDirectories, directoryPath => { - // _.each(platformSpecificFiles, filePath => { - // const fileFullPath = path.join(directoryPath, filePath); - // fs.writeFile(fileFullPath, "testData"); - - // created.files.push(fileFullPath); - // }); - // }); - - // // Add App_Resources file to app and app1 folders - // _.each(destinationDirectories, directoryPath => { - // const iosIconFullPath = path.join(directoryPath, "App_Resources/iOS/icon.png"); - // fs.writeFile(iosIconFullPath, "test-image"); - // created.resources.ios.push(iosIconFullPath); - - // const androidFullPath = path.join(directoryPath, "App_Resources/Android/icon.png"); - // fs.writeFile(androidFullPath, "test-image"); - // created.resources.android.push(androidFullPath); - // }); - - // await execPreparePlatform(platformToTest, testDirData, release); - - // const test1FileName = platformToTest.toLowerCase() === "ios" ? "test1.js" : "test2.js"; - // const test2FileName = platformToTest.toLowerCase() === "ios" ? "test2.js" : "test1.js"; - - // // Asserts that the files in app folder are process as platform specific - // assert.isTrue(fs.exists(path.join(testDirData.appDestFolderPath, "app", test1FileName))); - // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app", "test1-js"))); - - // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app", test2FileName))); - // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app", "test2-js"))); - - // // Asserts that the files in app1 folder aren't process as platform specific - // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app1")), "Asserts that the files in app1 folder aren't process as platform specific"); - - // if (release) { - // // Asserts that the files in tests folder aren't copied - // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "tests")), "Asserts that the files in tests folder aren't copied"); - // } - - // return created; - // } - - // function updateFile(files: string[], fileName: string, content: string) { - // const fileToUpdate = _.find(files, (f) => f.indexOf(fileName) !== -1); - // fs.writeFile(fileToUpdate, content); - // } - - // it("should process only files in app folder when preparing for iOS platform", async () => { - // await testPreparePlatform("iOS"); - // }); - - // it("should process only files in app folder when preparing for Android platform", async () => { - // await testPreparePlatform("Android"); - // }); - - // it("should process only files in app folder when preparing for iOS platform", async () => { - // await testPreparePlatform("iOS", true); - // }); - - // it("should process only files in app folder when preparing for Android platform", async () => { - // await testPreparePlatform("Android", true); - // }); - - // function getDefaultFolderVerificationData(platform: string, appDestFolderPath: string) { - // const data: any = {}; - // if (platform.toLowerCase() === "ios") { - // data[path.join(appDestFolderPath, "app")] = { - // missingFiles: ["test1.ios.js", "test2.android.js", "test2.js"], - // presentFiles: ["test1.js", "test2-android-js", "test1-ios-js", "main.js"] - // }; - - // data[appDestFolderPath] = { - // filesWithContent: [ - // { - // name: "Resources/icon.png", - // content: "test-image" - // } - // ] - // }; - // } else { - // data[path.join(appDestFolderPath, "app")] = { - // missingFiles: ["test1.android.js", "test2.ios.js", "test1.js"], - // presentFiles: ["test2.js", "test2-android-js", "test1-ios-js"] - // }; - - // data[appDestFolderPath] = { - // filesWithContent: [ - // { - // name: "src/main/res/icon.png", - // content: "test-image" - // } - // ] - // }; - // } - - // return data; - // } - - // function mergeModifications(def: any, mod: any) { - // // custom merge to reflect changes - // const merged: any = _.cloneDeep(def); - // _.forOwn(mod, (modFolder, folderRoot) => { - // // whole folder not present in Default - // if (!def.hasOwnProperty(folderRoot)) { - // merged[folderRoot] = _.cloneDeep(modFolder[folderRoot]); - // } else { - // const defFolder = def[folderRoot]; - // merged[folderRoot].filesWithContent = _.merge(defFolder.filesWithContent || [], modFolder.filesWithContent || []); - // merged[folderRoot].missingFiles = (defFolder.missingFiles || []).concat(modFolder.missingFiles || []); - // merged[folderRoot].presentFiles = (defFolder.presentFiles || []).concat(modFolder.presentFiles || []); - - // // remove the missingFiles from the presentFiles if they were initially there - // if (modFolder.missingFiles) { - // merged[folderRoot].presentFiles = _.difference(defFolder.presentFiles, modFolder.missingFiles); - // } - - // // remove the presentFiles from the missingFiles if they were initially there. - // if (modFolder.presentFiles) { - // merged[folderRoot].missingFiles = _.difference(defFolder.presentFiles, modFolder.presentFiles); - // } - // } - // }); - - // return merged; - // } - - // // Executes a changes test case: - // // 1. Executes Prepare Platform for the Platform - // // 2. Applies some changes to the App. Persists the expected Modifications - // // 3. Executes again Prepare Platform for the Platform - // // 4. Gets the Default Destination App Structure and merges it with the Modifications - // // 5. Asserts the Destination App matches our expectations - // async function testChangesApplied(platform: string, applyChangesFn: (createdTestData: CreatedTestData) => any) { - // const createdTestData = await testPreparePlatform(platform); - - // const modifications = applyChangesFn(createdTestData); - - // await execPreparePlatform(platform, createdTestData.testDirData); - - // const defaultStructure = getDefaultFolderVerificationData(platform, createdTestData.testDirData.appDestFolderPath); - - // const merged = mergeModifications(defaultStructure, modifications); - - // DestinationFolderVerifier.verify(merged, fs); - // } - - // it("should sync only changed files, without special folders (iOS)", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "updated-content-ios"; - // updateFile(createdTestData.files, "test1.ios.js", expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { - // filesWithContent: [ - // { - // name: "test1.js", - // content: expectedFileContent - // } - // ] - // }; - // return modifications; - // }; - // await testChangesApplied("iOS", applyChangesFn); - // }); - - // it("should sync only changed files, without special folders (Android) #2697", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "updated-content-android"; - // updateFile(createdTestData.files, "test2.android.js", expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { - // filesWithContent: [ - // { - // name: "test2.js", - // content: expectedFileContent - // } - // ] - // }; - // return modifications; - // }; - // await testChangesApplied("Android", applyChangesFn); - // }); - - // it("Ensure App_Resources get reloaded after change in the app folder (iOS) #2560", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "updated-icon-content"; - // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/icon.png"); - // fs.writeFile(iconPngPath, expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[createdTestData.testDirData.appDestFolderPath] = { - // filesWithContent: [ - // { - // name: "Resources/icon.png", - // content: expectedFileContent - // } - // ] - // }; - - // return modifications; - // }; - // await testChangesApplied("iOS", applyChangesFn); - // }); - - // it("Ensure App_Resources get reloaded after change in the app folder (Android) #2560", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "updated-icon-content"; - // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/icon.png"); - // fs.writeFile(iconPngPath, expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[createdTestData.testDirData.appDestFolderPath] = { - // filesWithContent: [ - // { - // name: "src/main/res/icon.png", - // content: expectedFileContent - // } - // ] - // }; - - // return modifications; - // }; - // await testChangesApplied("Android", applyChangesFn); - // }); - - // it("Ensure App_Resources get reloaded after a new file appears in the app folder (iOS) #2560", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "new-file-content"; - // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/new-file.png"); - // fs.writeFile(iconPngPath, expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[createdTestData.testDirData.appDestFolderPath] = { - // filesWithContent: [ - // { - // name: "Resources/new-file.png", - // content: expectedFileContent - // } - // ] - // }; - - // return modifications; - // }; - // await testChangesApplied("iOS", applyChangesFn); - // }); - - // it("Ensure App_Resources get reloaded after a new file appears in the app folder (Android) #2560", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "new-file-content"; - // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/new-file.png"); - // fs.writeFile(iconPngPath, expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[createdTestData.testDirData.appDestFolderPath] = { - // filesWithContent: [ - // { - // name: "src/main/res/new-file.png", - // content: expectedFileContent - // } - // ] - // }; - - // return modifications; - // }; - // await testChangesApplied("Android", applyChangesFn); - // }); - - // it("should sync new platform specific files (iOS)", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "new-content-ios"; - // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.ios.js"), expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { - // filesWithContent: [ - // { - // name: "test3.js", - // content: expectedFileContent - // } - // ] - // }; - - // return modifications; - // }; - // await testChangesApplied("iOS", applyChangesFn); - // }); - - // it("should sync new platform specific files (Android)", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "new-content-android"; - // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.android.js"), expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { - // filesWithContent: [ - // { - // name: "test3.js", - // content: expectedFileContent - // } - // ] - // }; - - // return modifications; - // }; - // await testChangesApplied("Android", applyChangesFn); - // }); - - // it("should sync new common files (iOS)", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "new-content-ios"; - // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { - // filesWithContent: [ - // { - // name: "test3.js", - // content: expectedFileContent - // } - // ] - // }; - - // return modifications; - // }; - // await testChangesApplied("iOS", applyChangesFn); - // }); - - // it("should sync new common file (Android)", async () => { - // const applyChangesFn = (createdTestData: CreatedTestData) => { - // // apply changes - // const expectedFileContent = "new-content-android"; - // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); - - // // construct the folder modifications data - // const modifications: any = {}; - // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { - // filesWithContent: [ - // { - // name: "test3.js", - // content: expectedFileContent - // } - // ] - // }; - - // return modifications; - // }; - // await testChangesApplied("Android", applyChangesFn); - // }); - - // it("invalid xml is caught", async () => { - // require("colors"); - // const testDirData = prepareDirStructure(); - - // // generate invalid xml - // const fileFullPath = path.join(testDirData.appFolderPath, "file.xml"); - // fs.writeFile(fileFullPath, ""); - - // const platformsData = testInjector.resolve("platformsData"); - // platformsData.platformsNames = ["android"]; - // platformsData.getPlatformData = (platform: string) => { - // return { - // appDestinationDirectoryPath: testDirData.appDestFolderPath, - // appResourcesDestinationDirectoryPath: testDirData.appResourcesFolderPath, - // normalizedPlatformName: "Android", - // projectRoot: testDirData.tempFolder, - // configurationFileName: "configFileName", - // platformProjectService: { - // prepareProject: (): any => null, - // prepareAppResources: (): any => null, - // validate: () => Promise.resolve(), - // createProject: (projectRoot: string, frameworkDir: string) => Promise.resolve(), - // interpolateData: (projectRoot: string) => Promise.resolve(), - // afterCreateProject: (projectRoot: string): any => null, - // getAppResourcesDestinationDirectoryPath: () => testDirData.appResourcesFolderPath, - // processConfigurationFilesFromAppResources: () => Promise.resolve(), - // handleNativeDependenciesChange: () => Promise.resolve(), - // ensureConfigurationFileInAppResources: (): any => null, - // interpolateConfigurationFile: (): void => undefined, - // isPlatformPrepared: (projectRoot: string) => false, - // checkForChanges: () => { /* */ } - // }, - // frameworkPackageName: "tns-ios" - // }; - // }; - - // const projectData = testInjector.resolve("projectData"); - // projectData.projectDir = testDirData.tempFolder; - // projectData.appDirectoryPath = projectData.getAppDirectoryPath(); - // projectData.appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); - - // platformService = testInjector.resolve("platformService"); - // const oldLoggerWarner = testInjector.resolve("$logger").warn; - // let warnings: string = ""; - // try { - // testInjector.resolve("$logger").warn = (text: string) => warnings += text; - // const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: false, useHotModuleReload: false }; - // await platformService.preparePlatform({ - // platform: "android", - // appFilesUpdaterOptions, - // platformTemplate: "", - // projectData, - // "": { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, - // env: {} - // }); - // } finally { - // testInjector.resolve("$logger").warn = oldLoggerWarner; - // } - - // // Asserts that prepare has caught invalid xml - // assert.isFalse(warnings.indexOf("has errors") !== -1); - // }); - // }); - - describe("build", () => { - function mockData(buildOutput: string[], projectName: string): void { - mockPlatformsData(projectName); - mockFileSystem(buildOutput); - platformService.saveBuildInfoFile = () => undefined; - } - - function mockPlatformsData(projectName: string): void { - const platformsData = testInjector.resolve("platformsData"); - platformsData.getPlatformData = (platform: string) => { - return { - deviceBuildOutputPath: "", - normalizedPlatformName: "", - getBuildOutputPath: () => "", - platformProjectService: { - buildProject: () => Promise.resolve(), - on: () => ({}), - removeListener: () => ({}) - }, - getValidBuildOutputData: () => ({ - packageNames: ["app-debug.apk", "app-release.apk", `${projectName}-debug.apk`, `${projectName}-release.apk`], - regexes: [/app-.*-(debug|release).apk/, new RegExp(`${projectName}-.*-(debug|release).apk`)] - }) - }; - }; - } - - function mockFileSystem(enumeratedFiles: string[]): void { - const fs = testInjector.resolve("fs"); - fs.enumerateFilesInDirectorySync = () => enumeratedFiles; - fs.readDirectory = () => []; - fs.getFsStats = () => (({ mtime: new Date() })); - } - - describe("android platform", () => { - function getTestCases(configuration: string, apkName: string) { - return [{ - name: "no additional options are specified in .gradle file", - buildOutput: [`/my/path/${configuration}/${apkName}-${configuration}.apk`], - expectedResult: `/my/path/${configuration}/${apkName}-${configuration}.apk` - }, { - name: "productFlavors are specified in .gradle file", - buildOutput: [`/my/path/arm64Demo/${configuration}/${apkName}-arm64-demo-${configuration}.apk`, - `/my/path/arm64Full/${configuration}/${apkName}-arm64-full-${configuration}.apk`, - `/my/path/armDemo/${configuration}/${apkName}-arm-demo-${configuration}.apk`, - `/my/path/armFull/${configuration}/${apkName}-arm-full-${configuration}.apk`, - `/my/path/x86Demo/${configuration}/${apkName}-x86-demo-${configuration}.apk`, - `/my/path/x86Full/${configuration}/${apkName}-x86-full-${configuration}.apk`], - expectedResult: `/my/path/x86Full/${configuration}/${apkName}-x86-full-${configuration}.apk` - }, { - name: "split options are specified in .gradle file", - buildOutput: [`/my/path/${configuration}/${apkName}-arm64-v8a-${configuration}.apk`, - `/my/path/${configuration}/${apkName}-armeabi-v7a-${configuration}.apk`, - `/my/path/${configuration}/${apkName}-universal-${configuration}.apk`, - `/my/path/${configuration}/${apkName}-x86-${configuration}.apk`], - expectedResult: `/my/path/${configuration}/${apkName}-x86-${configuration}.apk` - }, { - name: "android-runtime has version < 4.0.0", - buildOutput: [`/my/path/apk/${apkName}-${configuration}.apk`], - expectedResult: `/my/path/apk/${apkName}-${configuration}.apk` - }]; - } - - const platform = "Android"; - const buildConfigs = [{ buildForDevice: false }, { buildForDevice: true }]; - const apkNames = ["app", "testProj"]; - const configurations = ["debug", "release"]; - - _.each(apkNames, apkName => { - _.each(buildConfigs, buildConfig => { - _.each(configurations, configuration => { - _.each(getTestCases(configuration, apkName), testCase => { - it(`should find correct ${configuration} ${apkName}.apk when ${testCase.name} and buildConfig is ${JSON.stringify(buildConfig)}`, async () => { - mockData(testCase.buildOutput, apkName); - const actualResult = await platformService.buildPlatform(platform, buildConfig, { projectName: "" }); - assert.deepEqual(actualResult, testCase.expectedResult); - }); - }); - }); - }); - }); - }); - }); - - describe("ensurePlatformInstalled", () => { - const platform = "android"; - const appFilesUpdaterOptions = { bundle: true }; - let areWebpackFilesPersisted = false; - - let projectData: IProjectData = null; - let usbLiveSyncService: any = null; - let projectChangesService: IProjectChangesService = null; - - beforeEach(() => { - reset(); - - (platformCommandsService).addPlatform = () => { /** */ }; - (platformCommandsService).persistWebpackFiles = () => areWebpackFilesPersisted = true; - - projectData = testInjector.resolve("projectData"); - usbLiveSyncService = testInjector.resolve("usbLiveSyncService"); - projectChangesService = testInjector.resolve("projectChangesService"); - - usbLiveSyncService.isInitialized = true; - }); - - function reset() { - areWebpackFilesPersisted = false; - } - - function mockPrepareInfo(prepareInfo: any) { - projectChangesService.getPrepareInfo = () => prepareInfo; - } - - const testCases = [ - { - name: "should persist webpack files when prepareInfo is null (first execution of `tns run --bundle`)", - areWebpackFilesPersisted: true - }, - { - name: "should persist webpack files when prepareInfo is null and skipNativePrepare is true (first execution of `tns preview --bundle`)", - nativePrepare: { skipNativePrepare: true }, - areWebpackFilesPersisted: true - }, - { - name: "should not persist webpack files when requires platform add", - prepareInfo: { nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }, - areWebpackFilesPersisted: true - }, - { - name: "should persist webpack files when requires platform add and skipNativePrepare is true", - prepareInfo: { nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }, - nativePrepare: { skipNativePrepare: true }, - areWebpackFilesPersisted: false - }, - { - name: "should persist webpack files when platform is already prepared", - prepareInfo: { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }, - areWebpackFilesPersisted: false - }, - { - name: "should not persist webpack files when platform is already prepared and skipNativePrepare is true", - prepareInfo: { nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }, - areWebpackFilesPersisted: false - }, - { - name: "should not persist webpack files when no webpack watcher is started (first execution of `tns build --bundle`)", - isWebpackWatcherStarted: false, - areWebpackFilesPersisted: false - }, - { - name: "should not persist webpack files when no webpack watcher is started and skipNativePrepare is true (local JS prepare from cloud command)", - isWebpackWatcherStarted: false, - nativePrepare: { skipNativePrepare: true }, - areWebpackFilesPersisted: false - } - ]; - - _.each(testCases, (testCase: any) => { - it(`${testCase.name}`, async () => { - usbLiveSyncService.isInitialized = testCase.isWebpackWatcherStarted === undefined ? true : testCase.isWebpackWatcherStarted; - mockPrepareInfo(testCase.prepareInfo); - - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, testCase.nativePrepare); - assert.deepEqual(areWebpackFilesPersisted, testCase.areWebpackFilesPersisted); - }); - }); - - it("should not persist webpack files after the second execution of `tns preview --bundle` or `tns cloud run --bundle`", async () => { - // First execution of `tns preview --bundle` - mockPrepareInfo(null); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); - assert.isTrue(areWebpackFilesPersisted); - - // Second execution of `tns preview --bundle` - reset(); - mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); - assert.isFalse(areWebpackFilesPersisted); - }); - - it("should not persist webpack files after the second execution of `tns run --bundle`", async () => { - // First execution of `tns run --bundle` - mockPrepareInfo(null); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); - assert.isTrue(areWebpackFilesPersisted); - - // Second execution of `tns run --bundle` - reset(); - mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); - assert.isFalse(areWebpackFilesPersisted); - }); - - it("should handle correctly the following sequence of commands: `tns preview --bundle`, `tns run --bundle` and `tns preview --bundle`", async () => { - // First execution of `tns preview --bundle` - mockPrepareInfo(null); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); - assert.isTrue(areWebpackFilesPersisted); - - // Execution of `tns run --bundle` - reset(); - mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); - assert.isTrue(areWebpackFilesPersisted); - - // Execution of `tns preview --bundle` - reset(); - mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); - assert.isFalse(areWebpackFilesPersisted); - }); - - it("should handle correctly the following sequence of commands: `tns preview --bundle`, `tns run --bundle` and `tns build --bundle`", async () => { - // Execution of `tns preview --bundle` - mockPrepareInfo(null); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions, { skipNativePrepare: true }); - assert.isTrue(areWebpackFilesPersisted); - - // Execution of `tns run --bundle` - reset(); - mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); - assert.isTrue(areWebpackFilesPersisted); - - // Execution of `tns build --bundle` - reset(); - mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - await (platformCommandsService).ensurePlatformInstalled(platform, projectData, "", appFilesUpdaterOptions); - assert.isFalse(areWebpackFilesPersisted); - }); - }); -}); +// import * as yok from "../lib/common/yok"; +// import * as stubs from "./stubs"; +// import * as PlatformServiceLib from "../lib/services/platform-service"; +// import { VERSION_STRING, PACKAGE_JSON_FILE_NAME, AddPlaformErrors } from "../lib/constants"; +// import * as fsLib from "../lib/common/file-system"; +// import * as optionsLib from "../lib/options"; +// import * as hostInfoLib from "../lib/common/host-info"; +// import * as ProjectFilesManagerLib from "../lib/common/services/project-files-manager"; +// import * as path from "path"; +// import { format } from "util"; +// import { assert } from "chai"; +// import { LocalToDevicePathDataFactory } from "../lib/common/mobile/local-to-device-path-data-factory"; +// import { MobileHelper } from "../lib/common/mobile/mobile-helper"; +// import { ProjectFilesProvider } from "../lib/providers/project-files-provider"; +// import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; +// import { XmlValidator } from "../lib/xml-validator"; +// import { PlatformJSService } from "../lib/services/prepare-platform-js-service"; +// import * as ChildProcessLib from "../lib/common/child-process"; +// import ProjectChangesLib = require("../lib/services/project-changes-service"); +// import { Messages } from "../lib/common/messages/messages"; +// import { SettingsService } from "../lib/common/test/unit-tests/stubs"; +// import { mkdir } from "shelljs"; +// import * as constants from "../lib/constants"; +// import { PlatformCommandsService } from "../lib/services/platform/platform-commands-service"; +// import { StaticConfig } from "../lib/config"; +// import { PlatformNativeService } from "../lib/services/prepare-platform-native-service"; + +// require("should"); +// const temp = require("temp"); +// temp.track(); + +// function createTestInjector() { +// const testInjector = new yok.Yok(); + +// testInjector.register('platformService', PlatformServiceLib.PlatformService); +// testInjector.register("platformCommandsService", PlatformCommandsService); +// testInjector.register('errors', stubs.ErrorsStub); +// testInjector.register('logger', stubs.LoggerStub); +// testInjector.register("nodeModulesDependenciesBuilder", {}); +// testInjector.register('packageInstallationManager', stubs.PackageInstallationManagerStub); +// // TODO: Remove the projectData - it shouldn't be required in the service itself. +// testInjector.register('projectData', stubs.ProjectDataStub); +// testInjector.register('platformsData', stubs.PlatformsDataStub); +// testInjector.register('devicesService', {}); +// testInjector.register('androidEmulatorServices', {}); +// testInjector.register('projectDataService', stubs.ProjectDataService); +// testInjector.register('prompter', {}); +// testInjector.register('sysInfo', {}); +// testInjector.register("commandsService", { +// tryExecuteCommand: () => { /* intentionally left blank */ } +// }); +// testInjector.register("options", optionsLib.Options); +// testInjector.register("hostInfo", hostInfoLib.HostInfo); +// testInjector.register("staticConfig", StaticConfig); +// testInjector.register("nodeModulesBuilder", { +// prepareNodeModules: () => { +// return Promise.resolve(); +// }, +// prepareJSNodeModules: () => { +// return Promise.resolve(); +// } +// }); +// testInjector.register("pluginsService", { +// getAllInstalledPlugins: () => { +// return []; +// }, +// ensureAllDependenciesAreInstalled: () => { +// return Promise.resolve(); +// }, +// validate: (platformData: IPlatformData, projectData: IProjectData) => { +// return Promise.resolve(); +// } +// }); +// testInjector.register("projectFilesManager", ProjectFilesManagerLib.ProjectFilesManager); +// testInjector.register("hooksService", stubs.HooksServiceStub); +// testInjector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); +// testInjector.register("mobileHelper", MobileHelper); +// testInjector.register("projectFilesProvider", ProjectFilesProvider); +// testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); +// testInjector.register("xmlValidator", XmlValidator); +// testInjector.register("preparePlatformService", PlatformNativeService); +// testInjector.register("platformJSService", PlatformJSService); +// testInjector.register("packageManager", { +// uninstall: async () => { +// return true; +// } +// }); +// testInjector.register("childProcess", ChildProcessLib.ChildProcess); +// testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); +// testInjector.register("analyticsService", { +// track: async (): Promise => undefined, +// trackEventActionInGoogleAnalytics: () => Promise.resolve() +// }); +// testInjector.register("messages", Messages); +// testInjector.register("devicePathProvider", {}); +// testInjector.register("helpService", { +// showCommandLineHelp: async (): Promise => (undefined) +// }); +// testInjector.register("settingsService", SettingsService); +// testInjector.register("terminalSpinnerService", { +// createSpinner: (msg: string) => ({ +// start: (): void => undefined, +// stop: (): void => undefined, +// message: (): void => undefined +// }) +// }); +// testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub); +// testInjector.register("filesHashService", { +// generateHashes: () => Promise.resolve(), +// getChanges: () => Promise.resolve({ test: "testHash" }) +// }); +// testInjector.register("pacoteService", { +// extractPackage: async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => { +// mkdir(path.join(destinationDirectory, "framework")); +// (new fsLib.FileSystem(testInjector)).writeFile(path.join(destinationDirectory, PACKAGE_JSON_FILE_NAME), JSON.stringify({ +// name: "package-name", +// version: "1.0.0" +// })); +// } +// }); +// testInjector.register("usbLiveSyncService", () => ({})); +// testInjector.register("doctorService", { +// checkForDeprecatedShortImportsInAppDir: (projectDir: string): void => undefined +// }); +// testInjector.register("cleanupService", { +// setShouldDispose: (shouldDispose: boolean): void => undefined +// }); + +// return testInjector; +// } + +// describe('Platform Service Tests', () => { +// let platformCommandsService: IPlatformCommandsService, testInjector: IInjector; +// let platformService: IPlatformService; + +// beforeEach(() => { +// testInjector = createTestInjector(); +// testInjector.register("fs", stubs.FileSystemStub); +// testInjector.resolve("projectData").initializeProjectData(); +// platformCommandsService = testInjector.resolve("platformCommandsService"); +// platformService = testInjector.resolve("platformService"); +// console.log("============ PLATFORM SERVICE ========== ", platformService); +// }); + +// describe("add platform unit tests", () => { +// describe("#add platform()", () => { +// it("should not fail if platform is not normalized", async () => { +// const fs = testInjector.resolve("fs"); +// fs.exists = () => false; +// const projectData: IProjectData = testInjector.resolve("projectData"); +// await platformCommandsService.addPlatforms(["Android"], projectData, ""); +// await platformCommandsService.addPlatforms(["ANDROID"], projectData, ""); +// await platformCommandsService.addPlatforms(["AnDrOiD"], projectData, ""); +// await platformCommandsService.addPlatforms(["androiD"], projectData, ""); + +// await platformCommandsService.addPlatforms(["iOS"], projectData, ""); +// await platformCommandsService.addPlatforms(["IOS"], projectData, ""); +// await platformCommandsService.addPlatforms(["IoS"], projectData, ""); +// await platformCommandsService.addPlatforms(["iOs"], projectData, ""); +// }); + +// it("should fail if platform is already installed", async () => { +// const projectData: IProjectData = testInjector.resolve("projectData"); +// // By default fs.exists returns true, so the platforms directory should exists +// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); +// await assert.isRejected(platformCommandsService.addPlatforms(["ios"], projectData, ""), "Platform ios already added"); +// }); + +// it("should fail if unable to extract runtime package", async () => { +// const fs = testInjector.resolve("fs"); +// fs.exists = () => false; + +// const pacoteService = testInjector.resolve("pacoteService"); +// const errorMessage = "Pacote service unable to extract package"; +// pacoteService.extractPackage = async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => { +// throw new Error(errorMessage); +// }; + +// const projectData: IProjectData = testInjector.resolve("projectData"); +// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), errorMessage); +// }); + +// it("fails when path passed to frameworkPath does not exist", async () => { +// const fs = testInjector.resolve("fs"); +// fs.exists = () => false; + +// const projectData: IProjectData = testInjector.resolve("projectData"); +// const frameworkPath = "invalidPath"; +// const errorMessage = format(AddPlaformErrors.InvalidFrameworkPathStringFormat, frameworkPath); +// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, frameworkPath), errorMessage); +// }); + +// const assertCorrectDataIsPassedToPacoteService = async (versionString: string): Promise => { +// const fs = testInjector.resolve("fs"); +// fs.exists = () => false; + +// const pacoteService = testInjector.resolve("pacoteService"); +// let packageNamePassedToPacoteService = ""; +// pacoteService.extractPackage = async (name: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => { +// packageNamePassedToPacoteService = name; +// }; + +// const platformsData = testInjector.resolve("platformsData"); +// const packageName = "packageName"; +// platformsData.getPlatformData = (platform: string, pData: IProjectData): IPlatformData => { +// return { +// frameworkPackageName: packageName, +// platformNameLowerCase: "", +// platformProjectService: new stubs.PlatformProjectServiceStub(), +// projectRoot: "", +// normalizedPlatformName: "", +// appDestinationDirectoryPath: "", +// getBuildOutputPath: () => "", +// getValidBuildOutputData: (buildOptions: IBuildOutputOptions) => ({ packageNames: [] }), +// frameworkFilesExtensions: [], +// relativeToFrameworkConfigurationFilePath: "", +// fastLivesyncFileExtensions: [] +// }; +// }; +// const projectData: IProjectData = testInjector.resolve("projectData"); + +// await platformCommandsService.addPlatforms(["android"], projectData, ""); +// assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); +// await platformCommandsService.addPlatforms(["ios"], projectData, ""); +// assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); +// }; +// it("should respect platform version in package.json's nativescript key", async () => { +// const versionString = "2.5.0"; +// const nsValueObject: any = { +// [VERSION_STRING]: versionString +// }; +// const projectDataService = testInjector.resolve("projectDataService"); +// projectDataService.getNSValue = () => nsValueObject; + +// await assertCorrectDataIsPassedToPacoteService(versionString); +// }); + +// it("should install latest platform if no information found in package.json's nativescript key", async () => { + +// const projectDataService = testInjector.resolve("projectDataService"); +// projectDataService.getNSValue = (): any => null; + +// const latestCompatibleVersion = "1.0.0"; +// const packageInstallationManager = testInjector.resolve("packageInstallationManager"); +// packageInstallationManager.getLatestCompatibleVersion = async (packageName: string, referenceVersion?: string): Promise => { +// return latestCompatibleVersion; +// }; + +// await assertCorrectDataIsPassedToPacoteService(latestCompatibleVersion); +// }); + +// // Workflow: tns preview; tns platform add +// it(`should add platform when only .js part of the platform has already been added (nativePlatformStatus is ${constants.NativePlatformStatus.requiresPlatformAdd})`, async () => { +// const fs = testInjector.resolve("fs"); +// fs.exists = () => true; +// const projectChangesService = testInjector.resolve("projectChangesService"); +// projectChangesService.getPrepareInfo = () => ({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); +// const projectData = testInjector.resolve("projectData"); +// let isJsPlatformAdded = false; +// const preparePlatformJSService = testInjector.resolve("preparePlatformJSService"); +// preparePlatformJSService.addPlatform = async () => isJsPlatformAdded = true; +// let isNativePlatformAdded = false; +// const preparePlatformService = testInjector.resolve("preparePlatformService"); +// preparePlatformService.addNativePlatform = async () => isNativePlatformAdded = true; + +// await platformCommandsService.addPlatforms(["android"], projectData, ""); + +// assert.isTrue(isJsPlatformAdded); +// assert.isTrue(isNativePlatformAdded); +// }); + +// // Workflow: tns platform add; tns platform add +// it("shouldn't add platform when platforms folder exist and no .nsprepare file", async () => { +// const fs = testInjector.resolve("fs"); +// fs.exists = () => true; +// const projectChangesService = testInjector.resolve("projectChangesService"); +// projectChangesService.getPrepareInfo = () => null; +// const projectData = testInjector.resolve("projectData"); + +// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); +// }); + +// // Workflow: tns run; tns platform add +// it(`shouldn't add platform when both native and .js parts of the platform have already been added (nativePlatformStatus is ${constants.NativePlatformStatus.alreadyPrepared})`, async () => { +// const fs = testInjector.resolve("fs"); +// fs.exists = () => true; +// const projectChangesService = testInjector.resolve("projectChangesService"); +// projectChangesService.getPrepareInfo = () => ({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); +// const projectData = testInjector.resolve("projectData"); + +// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); +// }); +// }); +// }); + +// describe("remove platform unit tests", () => { +// it("should fail when platforms are not added", async () => { +// const ExpectedErrorsCaught = 2; +// let errorsCaught = 0; +// const projectData: IProjectData = testInjector.resolve("projectData"); +// testInjector.resolve("fs").exists = () => false; + +// try { +// await platformCommandsService.removePlatforms(["android"], projectData); +// } catch (e) { +// errorsCaught++; +// } + +// try { +// await platformCommandsService.removePlatforms(["ios"], projectData); +// } catch (e) { +// errorsCaught++; +// } + +// assert.isTrue(errorsCaught === ExpectedErrorsCaught); +// }); +// it("shouldn't fail when platforms are added", async () => { +// const projectData: IProjectData = testInjector.resolve("projectData"); +// testInjector.resolve("fs").exists = () => false; +// await platformCommandsService.addPlatforms(["android"], projectData, ""); + +// testInjector.resolve("fs").exists = () => true; +// await platformCommandsService.removePlatforms(["android"], projectData); +// }); +// }); + +// describe("clean platform unit tests", () => { +// it("should preserve the specified in the project nativescript version", async () => { +// const versionString = "2.4.1"; +// const fs = testInjector.resolve("fs"); +// fs.exists = () => false; + +// const nsValueObject: any = {}; +// nsValueObject[VERSION_STRING] = versionString; +// const projectDataService = testInjector.resolve("projectDataService"); +// projectDataService.getNSValue = () => nsValueObject; + +// const packageInstallationManager = testInjector.resolve("packageInstallationManager"); +// packageInstallationManager.install = (packageName: string, packageDir: string, options: INpmInstallOptions) => { +// assert.deepEqual(options.version, versionString); +// return ""; +// }; + +// const projectData: IProjectData = testInjector.resolve("projectData"); +// platformCommandsService.removePlatforms = (platforms: string[], prjctData: IProjectData): Promise => { +// nsValueObject[VERSION_STRING] = undefined; +// return Promise.resolve(); +// }; + +// await platformCommandsService.cleanPlatforms(["android"], projectData, ""); + +// nsValueObject[VERSION_STRING] = versionString; +// await platformCommandsService.cleanPlatforms(["ios"], projectData, ""); +// }); +// }); + +// // TODO: Commented as it doesn't seem correct. Check what's the case and why it's been expected to fail. +// // describe("list platform unit tests", () => { +// // it("fails when platforms are not added", () => { +// // assert.throws(async () => await platformService.getAvailablePlatforms()); +// // }); +// // }); + +// describe("update Platform", () => { +// describe("#updatePlatform(platform)", () => { +// it("should fail when the versions are the same", async () => { +// const packageInstallationManager: IPackageInstallationManager = testInjector.resolve("packageInstallationManager"); +// packageInstallationManager.getLatestVersion = async () => "0.2.0"; +// const projectData: IProjectData = testInjector.resolve("projectData"); + +// await assert.isRejected(platformCommandsService.updatePlatforms(["android"], projectData)); +// }); +// }); +// }); + +// // TODO: check this tests with QAs +// // describe("prepare platform unit tests", () => { +// // let fs: IFileSystem; + +// // beforeEach(() => { +// // testInjector = createTestInjector(); +// // testInjector.register("fs", fsLib.FileSystem); +// // fs = testInjector.resolve("fs"); +// // testInjector.resolve("projectData").initializeProjectData(); +// // }); + +// // function prepareDirStructure() { +// // const tempFolder = temp.mkdirSync("prepare_platform"); + +// // const appFolderPath = path.join(tempFolder, "app"); +// // fs.createDirectory(appFolderPath); + +// // const nodeModulesPath = path.join(tempFolder, "node_modules"); +// // fs.createDirectory(nodeModulesPath); + +// // const testsFolderPath = path.join(appFolderPath, "tests"); +// // fs.createDirectory(testsFolderPath); + +// // const app1FolderPath = path.join(tempFolder, "app1"); +// // fs.createDirectory(app1FolderPath); + +// // const appDestFolderPath = path.join(tempFolder, "appDest"); +// // const appResourcesFolderPath = path.join(appDestFolderPath, "App_Resources"); +// // const appResourcesPath = path.join(appFolderPath, "App_Resources/Android"); +// // fs.createDirectory(appResourcesPath); +// // fs.writeFile(path.join(appResourcesPath, "test.txt"), "test"); +// // fs.writeJson(path.join(tempFolder, "package.json"), { +// // name: "testname", +// // nativescript: { +// // id: "org.nativescript.testname" +// // } +// // }); + +// // return { tempFolder, appFolderPath, app1FolderPath, appDestFolderPath, appResourcesFolderPath }; +// // } + +// // async function execPreparePlatform(platformToTest: string, testDirData: any, +// // release?: boolean) { +// // const platformsData = testInjector.resolve("platformsData"); +// // platformsData.platformsNames = ["ios", "android"]; +// // platformsData.getPlatformData = (platform: string) => { +// // return { +// // appDestinationDirectoryPath: testDirData.appDestFolderPath, +// // appResourcesDestinationDirectoryPath: testDirData.appResourcesFolderPath, +// // normalizedPlatformName: platformToTest, +// // configurationFileName: platformToTest === "ios" ? INFO_PLIST_FILE_NAME : MANIFEST_FILE_NAME, +// // projectRoot: testDirData.tempFolder, +// // platformProjectService: { +// // prepareProject: (): any => null, +// // validate: () => Promise.resolve(), +// // createProject: (projectRoot: string, frameworkDir: string) => Promise.resolve(), +// // interpolateData: (projectRoot: string) => Promise.resolve(), +// // afterCreateProject: (projectRoot: string): any => null, +// // getAppResourcesDestinationDirectoryPath: (pData: IProjectData, frameworkVersion?: string): string => { +// // if (platform.toLowerCase() === "ios") { +// // const dirPath = path.join(testDirData.appDestFolderPath, "Resources"); +// // fs.ensureDirectoryExists(dirPath); +// // return dirPath; +// // } else { +// // const dirPath = path.join(testDirData.appDestFolderPath, "src", "main", "res"); +// // fs.ensureDirectoryExists(dirPath); +// // return dirPath; +// // } +// // }, +// // processConfigurationFilesFromAppResources: () => Promise.resolve(), +// // handleNativeDependenciesChange: () => Promise.resolve(), +// // ensureConfigurationFileInAppResources: (): any => null, +// // interpolateConfigurationFile: (): void => undefined, +// // isPlatformPrepared: (projectRoot: string) => false, +// // prepareAppResources: (appResourcesDirectoryPath: string, pData: IProjectData): void => undefined, +// // checkForChanges: () => { /* */ } +// // } +// // }; +// // }; + +// // const projectData = testInjector.resolve("projectData"); +// // projectData.projectDir = testDirData.tempFolder; +// // projectData.projectName = "app"; +// // projectData.appDirectoryPath = testDirData.appFolderPath; +// // projectData.appResourcesDirectoryPath = path.join(testDirData.appFolderPath, "App_Resources"); + +// // platformService = testInjector.resolve("platformService"); +// // const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: release, useHotModuleReload: false }; +// // await platformService.preparePlatform({ +// // platform: platformToTest, +// // appFilesUpdaterOptions, +// // platformTemplate: "", +// // projectData, +// // "": { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, +// // env: {} +// // }); +// // } + +// // async function testPreparePlatform(platformToTest: string, release?: boolean): Promise { +// // const testDirData = prepareDirStructure(); +// // const created: CreatedTestData = new CreatedTestData(); +// // created.testDirData = testDirData; + +// // // Add platform specific files to app and app1 folders +// // const platformSpecificFiles = [ +// // "test1.ios.js", "test1-ios-js", "test2.android.js", "test2-android-js", +// // "main.js" +// // ]; + +// // const destinationDirectories = [testDirData.appFolderPath, testDirData.app1FolderPath]; + +// // _.each(destinationDirectories, directoryPath => { +// // _.each(platformSpecificFiles, filePath => { +// // const fileFullPath = path.join(directoryPath, filePath); +// // fs.writeFile(fileFullPath, "testData"); + +// // created.files.push(fileFullPath); +// // }); +// // }); + +// // // Add App_Resources file to app and app1 folders +// // _.each(destinationDirectories, directoryPath => { +// // const iosIconFullPath = path.join(directoryPath, "App_Resources/iOS/icon.png"); +// // fs.writeFile(iosIconFullPath, "test-image"); +// // created.resources.ios.push(iosIconFullPath); + +// // const androidFullPath = path.join(directoryPath, "App_Resources/Android/icon.png"); +// // fs.writeFile(androidFullPath, "test-image"); +// // created.resources.android.push(androidFullPath); +// // }); + +// // await execPreparePlatform(platformToTest, testDirData, release); + +// // const test1FileName = platformToTest.toLowerCase() === "ios" ? "test1.js" : "test2.js"; +// // const test2FileName = platformToTest.toLowerCase() === "ios" ? "test2.js" : "test1.js"; + +// // // Asserts that the files in app folder are process as platform specific +// // assert.isTrue(fs.exists(path.join(testDirData.appDestFolderPath, "app", test1FileName))); +// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app", "test1-js"))); + +// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app", test2FileName))); +// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app", "test2-js"))); + +// // // Asserts that the files in app1 folder aren't process as platform specific +// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app1")), "Asserts that the files in app1 folder aren't process as platform specific"); + +// // if (release) { +// // // Asserts that the files in tests folder aren't copied +// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "tests")), "Asserts that the files in tests folder aren't copied"); +// // } + +// // return created; +// // } + +// // function updateFile(files: string[], fileName: string, content: string) { +// // const fileToUpdate = _.find(files, (f) => f.indexOf(fileName) !== -1); +// // fs.writeFile(fileToUpdate, content); +// // } + +// // it("should process only files in app folder when preparing for iOS platform", async () => { +// // await testPreparePlatform("iOS"); +// // }); + +// // it("should process only files in app folder when preparing for Android platform", async () => { +// // await testPreparePlatform("Android"); +// // }); + +// // it("should process only files in app folder when preparing for iOS platform", async () => { +// // await testPreparePlatform("iOS", true); +// // }); + +// // it("should process only files in app folder when preparing for Android platform", async () => { +// // await testPreparePlatform("Android", true); +// // }); + +// // function getDefaultFolderVerificationData(platform: string, appDestFolderPath: string) { +// // const data: any = {}; +// // if (platform.toLowerCase() === "ios") { +// // data[path.join(appDestFolderPath, "app")] = { +// // missingFiles: ["test1.ios.js", "test2.android.js", "test2.js"], +// // presentFiles: ["test1.js", "test2-android-js", "test1-ios-js", "main.js"] +// // }; + +// // data[appDestFolderPath] = { +// // filesWithContent: [ +// // { +// // name: "Resources/icon.png", +// // content: "test-image" +// // } +// // ] +// // }; +// // } else { +// // data[path.join(appDestFolderPath, "app")] = { +// // missingFiles: ["test1.android.js", "test2.ios.js", "test1.js"], +// // presentFiles: ["test2.js", "test2-android-js", "test1-ios-js"] +// // }; + +// // data[appDestFolderPath] = { +// // filesWithContent: [ +// // { +// // name: "src/main/res/icon.png", +// // content: "test-image" +// // } +// // ] +// // }; +// // } + +// // return data; +// // } + +// // function mergeModifications(def: any, mod: any) { +// // // custom merge to reflect changes +// // const merged: any = _.cloneDeep(def); +// // _.forOwn(mod, (modFolder, folderRoot) => { +// // // whole folder not present in Default +// // if (!def.hasOwnProperty(folderRoot)) { +// // merged[folderRoot] = _.cloneDeep(modFolder[folderRoot]); +// // } else { +// // const defFolder = def[folderRoot]; +// // merged[folderRoot].filesWithContent = _.merge(defFolder.filesWithContent || [], modFolder.filesWithContent || []); +// // merged[folderRoot].missingFiles = (defFolder.missingFiles || []).concat(modFolder.missingFiles || []); +// // merged[folderRoot].presentFiles = (defFolder.presentFiles || []).concat(modFolder.presentFiles || []); + +// // // remove the missingFiles from the presentFiles if they were initially there +// // if (modFolder.missingFiles) { +// // merged[folderRoot].presentFiles = _.difference(defFolder.presentFiles, modFolder.missingFiles); +// // } + +// // // remove the presentFiles from the missingFiles if they were initially there. +// // if (modFolder.presentFiles) { +// // merged[folderRoot].missingFiles = _.difference(defFolder.presentFiles, modFolder.presentFiles); +// // } +// // } +// // }); + +// // return merged; +// // } + +// // // Executes a changes test case: +// // // 1. Executes Prepare Platform for the Platform +// // // 2. Applies some changes to the App. Persists the expected Modifications +// // // 3. Executes again Prepare Platform for the Platform +// // // 4. Gets the Default Destination App Structure and merges it with the Modifications +// // // 5. Asserts the Destination App matches our expectations +// // async function testChangesApplied(platform: string, applyChangesFn: (createdTestData: CreatedTestData) => any) { +// // const createdTestData = await testPreparePlatform(platform); + +// // const modifications = applyChangesFn(createdTestData); + +// // await execPreparePlatform(platform, createdTestData.testDirData); + +// // const defaultStructure = getDefaultFolderVerificationData(platform, createdTestData.testDirData.appDestFolderPath); + +// // const merged = mergeModifications(defaultStructure, modifications); + +// // DestinationFolderVerifier.verify(merged, fs); +// // } + +// // it("should sync only changed files, without special folders (iOS)", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "updated-content-ios"; +// // updateFile(createdTestData.files, "test1.ios.js", expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { +// // filesWithContent: [ +// // { +// // name: "test1.js", +// // content: expectedFileContent +// // } +// // ] +// // }; +// // return modifications; +// // }; +// // await testChangesApplied("iOS", applyChangesFn); +// // }); + +// // it("should sync only changed files, without special folders (Android) #2697", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "updated-content-android"; +// // updateFile(createdTestData.files, "test2.android.js", expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { +// // filesWithContent: [ +// // { +// // name: "test2.js", +// // content: expectedFileContent +// // } +// // ] +// // }; +// // return modifications; +// // }; +// // await testChangesApplied("Android", applyChangesFn); +// // }); + +// // it("Ensure App_Resources get reloaded after change in the app folder (iOS) #2560", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "updated-icon-content"; +// // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/icon.png"); +// // fs.writeFile(iconPngPath, expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[createdTestData.testDirData.appDestFolderPath] = { +// // filesWithContent: [ +// // { +// // name: "Resources/icon.png", +// // content: expectedFileContent +// // } +// // ] +// // }; + +// // return modifications; +// // }; +// // await testChangesApplied("iOS", applyChangesFn); +// // }); + +// // it("Ensure App_Resources get reloaded after change in the app folder (Android) #2560", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "updated-icon-content"; +// // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/icon.png"); +// // fs.writeFile(iconPngPath, expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[createdTestData.testDirData.appDestFolderPath] = { +// // filesWithContent: [ +// // { +// // name: "src/main/res/icon.png", +// // content: expectedFileContent +// // } +// // ] +// // }; + +// // return modifications; +// // }; +// // await testChangesApplied("Android", applyChangesFn); +// // }); + +// // it("Ensure App_Resources get reloaded after a new file appears in the app folder (iOS) #2560", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "new-file-content"; +// // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/new-file.png"); +// // fs.writeFile(iconPngPath, expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[createdTestData.testDirData.appDestFolderPath] = { +// // filesWithContent: [ +// // { +// // name: "Resources/new-file.png", +// // content: expectedFileContent +// // } +// // ] +// // }; + +// // return modifications; +// // }; +// // await testChangesApplied("iOS", applyChangesFn); +// // }); + +// // it("Ensure App_Resources get reloaded after a new file appears in the app folder (Android) #2560", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "new-file-content"; +// // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/new-file.png"); +// // fs.writeFile(iconPngPath, expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[createdTestData.testDirData.appDestFolderPath] = { +// // filesWithContent: [ +// // { +// // name: "src/main/res/new-file.png", +// // content: expectedFileContent +// // } +// // ] +// // }; + +// // return modifications; +// // }; +// // await testChangesApplied("Android", applyChangesFn); +// // }); + +// // it("should sync new platform specific files (iOS)", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "new-content-ios"; +// // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.ios.js"), expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { +// // filesWithContent: [ +// // { +// // name: "test3.js", +// // content: expectedFileContent +// // } +// // ] +// // }; + +// // return modifications; +// // }; +// // await testChangesApplied("iOS", applyChangesFn); +// // }); + +// // it("should sync new platform specific files (Android)", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "new-content-android"; +// // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.android.js"), expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { +// // filesWithContent: [ +// // { +// // name: "test3.js", +// // content: expectedFileContent +// // } +// // ] +// // }; + +// // return modifications; +// // }; +// // await testChangesApplied("Android", applyChangesFn); +// // }); + +// // it("should sync new common files (iOS)", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "new-content-ios"; +// // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { +// // filesWithContent: [ +// // { +// // name: "test3.js", +// // content: expectedFileContent +// // } +// // ] +// // }; + +// // return modifications; +// // }; +// // await testChangesApplied("iOS", applyChangesFn); +// // }); + +// // it("should sync new common file (Android)", async () => { +// // const applyChangesFn = (createdTestData: CreatedTestData) => { +// // // apply changes +// // const expectedFileContent = "new-content-android"; +// // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); + +// // // construct the folder modifications data +// // const modifications: any = {}; +// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { +// // filesWithContent: [ +// // { +// // name: "test3.js", +// // content: expectedFileContent +// // } +// // ] +// // }; + +// // return modifications; +// // }; +// // await testChangesApplied("Android", applyChangesFn); +// // }); + +// // it("invalid xml is caught", async () => { +// // require("colors"); +// // const testDirData = prepareDirStructure(); + +// // // generate invalid xml +// // const fileFullPath = path.join(testDirData.appFolderPath, "file.xml"); +// // fs.writeFile(fileFullPath, ""); + +// // const platformsData = testInjector.resolve("platformsData"); +// // platformsData.platformsNames = ["android"]; +// // platformsData.getPlatformData = (platform: string) => { +// // return { +// // appDestinationDirectoryPath: testDirData.appDestFolderPath, +// // appResourcesDestinationDirectoryPath: testDirData.appResourcesFolderPath, +// // normalizedPlatformName: "Android", +// // projectRoot: testDirData.tempFolder, +// // configurationFileName: "configFileName", +// // platformProjectService: { +// // prepareProject: (): any => null, +// // prepareAppResources: (): any => null, +// // validate: () => Promise.resolve(), +// // createProject: (projectRoot: string, frameworkDir: string) => Promise.resolve(), +// // interpolateData: (projectRoot: string) => Promise.resolve(), +// // afterCreateProject: (projectRoot: string): any => null, +// // getAppResourcesDestinationDirectoryPath: () => testDirData.appResourcesFolderPath, +// // processConfigurationFilesFromAppResources: () => Promise.resolve(), +// // handleNativeDependenciesChange: () => Promise.resolve(), +// // ensureConfigurationFileInAppResources: (): any => null, +// // interpolateConfigurationFile: (): void => undefined, +// // isPlatformPrepared: (projectRoot: string) => false, +// // checkForChanges: () => { /* */ } +// // }, +// // frameworkPackageName: "tns-ios" +// // }; +// // }; + +// // const projectData = testInjector.resolve("projectData"); +// // projectData.projectDir = testDirData.tempFolder; +// // projectData.appDirectoryPath = projectData.getAppDirectoryPath(); +// // projectData.appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); + +// // platformService = testInjector.resolve("platformService"); +// // const oldLoggerWarner = testInjector.resolve("$logger").warn; +// // let warnings: string = ""; +// // try { +// // testInjector.resolve("$logger").warn = (text: string) => warnings += text; +// // const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: false, useHotModuleReload: false }; +// // await platformService.preparePlatform({ +// // platform: "android", +// // appFilesUpdaterOptions, +// // platformTemplate: "", +// // projectData, +// // "": { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, +// // env: {} +// // }); +// // } finally { +// // testInjector.resolve("$logger").warn = oldLoggerWarner; +// // } + +// // // Asserts that prepare has caught invalid xml +// // assert.isFalse(warnings.indexOf("has errors") !== -1); +// // }); +// // }); + +// // describe("build", () => { +// // function mockData(buildOutput: string[], projectName: string): void { +// // mockPlatformsData(projectName); +// // mockFileSystem(buildOutput); +// // platformService.saveBuildInfoFile = () => undefined; +// // } + +// // function mockPlatformsData(projectName: string): void { +// // const platformsData = testInjector.resolve("platformsData"); +// // platformsData.getPlatformData = (platform: string) => { +// // return { +// // deviceBuildOutputPath: "", +// // normalizedPlatformName: "", +// // getBuildOutputPath: () => "", +// // platformProjectService: { +// // buildProject: () => Promise.resolve(), +// // on: () => ({}), +// // removeListener: () => ({}) +// // }, +// // getValidBuildOutputData: () => ({ +// // packageNames: ["app-debug.apk", "app-release.apk", `${projectName}-debug.apk`, `${projectName}-release.apk`], +// // regexes: [/app-.*-(debug|release).apk/, new RegExp(`${projectName}-.*-(debug|release).apk`)] +// // }) +// // }; +// // }; +// // } + +// // function mockFileSystem(enumeratedFiles: string[]): void { +// // const fs = testInjector.resolve("fs"); +// // fs.enumerateFilesInDirectorySync = () => enumeratedFiles; +// // fs.readDirectory = () => []; +// // fs.getFsStats = () => (({ mtime: new Date() })); +// // } + +// // describe("android platform", () => { +// // function getTestCases(configuration: string, apkName: string) { +// // return [{ +// // name: "no additional options are specified in .gradle file", +// // buildOutput: [`/my/path/${configuration}/${apkName}-${configuration}.apk`], +// // expectedResult: `/my/path/${configuration}/${apkName}-${configuration}.apk` +// // }, { +// // name: "productFlavors are specified in .gradle file", +// // buildOutput: [`/my/path/arm64Demo/${configuration}/${apkName}-arm64-demo-${configuration}.apk`, +// // `/my/path/arm64Full/${configuration}/${apkName}-arm64-full-${configuration}.apk`, +// // `/my/path/armDemo/${configuration}/${apkName}-arm-demo-${configuration}.apk`, +// // `/my/path/armFull/${configuration}/${apkName}-arm-full-${configuration}.apk`, +// // `/my/path/x86Demo/${configuration}/${apkName}-x86-demo-${configuration}.apk`, +// // `/my/path/x86Full/${configuration}/${apkName}-x86-full-${configuration}.apk`], +// // expectedResult: `/my/path/x86Full/${configuration}/${apkName}-x86-full-${configuration}.apk` +// // }, { +// // name: "split options are specified in .gradle file", +// // buildOutput: [`/my/path/${configuration}/${apkName}-arm64-v8a-${configuration}.apk`, +// // `/my/path/${configuration}/${apkName}-armeabi-v7a-${configuration}.apk`, +// // `/my/path/${configuration}/${apkName}-universal-${configuration}.apk`, +// // `/my/path/${configuration}/${apkName}-x86-${configuration}.apk`], +// // expectedResult: `/my/path/${configuration}/${apkName}-x86-${configuration}.apk` +// // }, { +// // name: "android-runtime has version < 4.0.0", +// // buildOutput: [`/my/path/apk/${apkName}-${configuration}.apk`], +// // expectedResult: `/my/path/apk/${apkName}-${configuration}.apk` +// // }]; +// // } + +// // const platform = "Android"; +// // const buildConfigs = [{ buildForDevice: false }, { buildForDevice: true }]; +// // const apkNames = ["app", "testProj"]; +// // const configurations = ["debug", "release"]; + +// // _.each(apkNames, apkName => { +// // _.each(buildConfigs, buildConfig => { +// // _.each(configurations, configuration => { +// // _.each(getTestCases(configuration, apkName), testCase => { +// // it(`should find correct ${configuration} ${apkName}.apk when ${testCase.name} and buildConfig is ${JSON.stringify(buildConfig)}`, async () => { +// // mockData(testCase.buildOutput, apkName); +// // const actualResult = await platformService.buildPlatform(platform, buildConfig, { projectName: "" }); +// // assert.deepEqual(actualResult, testCase.expectedResult); +// // }); +// // }); +// // }); +// // }); +// // }); +// // }); +// // }); +// }); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index f0ef9708d5..13c65167a0 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -7,7 +7,6 @@ import { YarnPackageManager } from "../lib/yarn-package-manager"; import { FileSystem } from "../lib/common/file-system"; import { ProjectData } from "../lib/project-data"; import { ChildProcess } from "../lib/common/child-process"; -import { PlatformService } from '../lib/services/platform-service'; import { Options } from "../lib/options"; import { CommandsService } from "../lib/common/services/commands-service"; import { StaticConfig } from "../lib/config"; @@ -38,6 +37,7 @@ import { PLUGINS_BUILD_DATA_FILENAME } from '../lib/constants'; import { GradleCommandService } from '../lib/services/android/gradle-command-service'; import { GradleBuildService } from '../lib/services/android/gradle-build-service'; import { GradleBuildArgsService } from '../lib/services/android/gradle-build-args-service'; +import { PreparePlatformService } from '../lib/services/platform/prepare-platform-service'; temp.track(); let isErrorThrown = false; @@ -57,7 +57,7 @@ function createTestInjector() { testInjector.register("projectData", ProjectData); testInjector.register("platforsmData", stubs.PlatformsDataStub); testInjector.register("childProcess", ChildProcess); - testInjector.register("platformService", PlatformService); + testInjector.register("platformService", PreparePlatformService); testInjector.register("platformsData", PlatformsData); testInjector.register("androidEmulatorServices", {}); testInjector.register("androidToolsInfo", AndroidToolsInfo); diff --git a/test/services/bundle-workflow-service.ts b/test/services/bundle-workflow-service.ts index 90ca15cb79..5bf89ac538 100644 --- a/test/services/bundle-workflow-service.ts +++ b/test/services/bundle-workflow-service.ts @@ -1,6 +1,7 @@ import { Yok } from "../../lib/common/yok"; import { BundleWorkflowService } from "../../lib/services/bundle-workflow-service"; import { assert } from "chai"; +import { AddPlatformService } from "../../lib/services/platform/add-platform-service"; const deviceMap: IDictionary = { myiOSDevice: { @@ -49,7 +50,7 @@ function createTestInjector(): IInjector { }) })); injector.register("buildArtefactsService", ({})); - injector.register("platformBuildService", ({})); + injector.register("buildPlatformService", ({})); injector.register("platformAddService", ({})); injector.register("platformService", ({})); injector.register("projectChangesService", ({})); @@ -89,7 +90,7 @@ describe("BundleWorkflowService", () => { const injector = createTestInjector(); let isAddPlatformIfNeededCalled = false; - const platformAddService: IPlatformAddService = injector.resolve("platformAddService"); + const platformAddService: AddPlatformService = injector.resolve("platformAddService"); platformAddService.addPlatformIfNeeded = async () => { isAddPlatformIfNeededCalled = true; }; let isStartWatcherCalled = false; @@ -129,7 +130,7 @@ describe("BundleWorkflowService", () => { const injector = createTestInjector(); const actualAddedPlatforms: IPlatformData[] = []; - const platformAddService: IPlatformAddService = injector.resolve("bundleWorkflowService"); + const platformAddService: AddPlatformService = injector.resolve("bundleWorkflowService"); platformAddService.addPlatformIfNeeded = async (platformData: IPlatformData) => { actualAddedPlatforms.push(platformData); }; @@ -147,11 +148,11 @@ describe("BundleWorkflowService", () => { beforeEach(() => { injector = createTestInjector(); - const platformAddService: IPlatformAddService = injector.resolve("bundleWorkflowService"); - platformAddService.addPlatformIfNeeded = async () => { return; }; + const addPlatformService = injector.resolve("addPlatformService"); + addPlatformService.addPlatformIfNeeded = async () => { return; }; - const platformBuildService: IPlatformBuildService = injector.resolve("platformBuildService"); - platformBuildService.buildPlatform = async () => { isBuildPlatformCalled = true; return buildOutputPath; }; + const buildPlatformService = injector.resolve("buildPlatformService"); + buildPlatformService.buildPlatform = async () => { isBuildPlatformCalled = true; return buildOutputPath; }; }); console.log("============== isBuildPlatformCalled ============= ", isBuildPlatformCalled); diff --git a/test/services/platform/platform-watcher-service.ts b/test/services/platform/platform-watcher-service.ts index babc58ae8b..ec7b51e876 100644 --- a/test/services/platform/platform-watcher-service.ts +++ b/test/services/platform/platform-watcher-service.ts @@ -16,8 +16,8 @@ function createTestInjector(data: { hasNativeChanges: boolean }): IInjector { out: () => ({}), trace: () => ({}) })); - injector.register("platformNativeService", ({ - preparePlatform: async () => { + injector.register("preparePlatformService", ({ + prepareNativePlatform: async () => { isNativePrepareCalled = true; return data.hasNativeChanges; } @@ -74,8 +74,8 @@ describe("PlatformWatcherService", () => { const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); - const platformNativeService = injector.resolve("platformNativeService"); - platformNativeService.preparePlatform = async () => { + const preparePlatformService = injector.resolve("preparePlatformService"); + preparePlatformService.prepareNativePlatform = async () => { const nativeFilesWatcher = (platformWatcherService).watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher; nativeFilesWatcher.emit("all", "change", "my/project/App_Resources/some/file"); isNativePrepareCalled = true; @@ -98,9 +98,9 @@ describe("PlatformWatcherService", () => { const injector = createTestInjector({ hasNativeChanges: false }); const hasNativeChanges = false; - const platformNativeService = injector.resolve("platformNativeService"); + const preparePlatformService = injector.resolve("preparePlatformService"); const webpackCompilerService = injector.resolve("webpackCompilerService"); - platformNativeService.preparePlatform = async () => { + preparePlatformService.prepareNativePlatform = async () => { webpackCompilerService.emit("webpackEmittedFiles", ["/some/file/path"]); isNativePrepareCalled = true; return hasNativeChanges; diff --git a/test/stubs.ts b/test/stubs.ts index 16b6abc30b..7b8f54e77e 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -767,119 +767,6 @@ export class CommandsService implements ICommandsService { } } -export class PlatformServiceStub extends EventEmitter implements IPlatformService { - public shouldPrepare(): Promise { - return Promise.resolve(true); - } - - public validateOptions(): Promise { - return Promise.resolve(true); - } - - public cleanPlatforms(platforms: string[]): Promise { - return Promise.resolve(); - } - - public addPlatforms(platforms: string[]): Promise { - return Promise.resolve(); - } - - public getInstalledPlatforms(): string[] { - return []; - } - - public getAvailablePlatforms(): string[] { - return []; - } - - public getPreparedPlatforms(): string[] { - return []; - } - - public saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void { - return; - } - - public async removePlatforms(platforms: string[]): Promise { - - } - - public updatePlatforms(platforms: string[]): Promise { - return Promise.resolve(); - } - - public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - return Promise.resolve(true); - } - - public shouldBuild(platform: string, projectData: IProjectData, buildConfig?: IBuildConfig): Promise { - return Promise.resolve(true); - } - - public buildPlatform(platform: string, buildConfig?: IBuildConfig): Promise { - return Promise.resolve(""); - } - - public async shouldInstall(device: Mobile.IDevice): Promise { - return true; - } - - public async validateInstall(device: Mobile.IDevice): Promise { - return; - } - - public installApplication(device: Mobile.IDevice, options: IRelease): Promise { - return Promise.resolve(); - } - - public deployPlatform(config: IDeployPlatformInfo): Promise { - return Promise.resolve(); - } - - public startApplication(platform: string, runOptions: IRunPlatformOptions): Promise { - return Promise.resolve(); - } - - public cleanDestinationApp(platformInfo: IPreparePlatformInfo): Promise { - return Promise.resolve(); - } - - public validatePlatformInstalled(platform: string): void { - - } - - public validatePlatform(platform: string): void { - - } - - isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean { - return true; - } - - public getLatestApplicationPackageForDevice(platformData: IPlatformData): IApplicationPackage { - return null; - } - - public getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig): IApplicationPackage { - return null; - } - - public copyLastOutput(platform: string, targetPath: string, buildConfig: IBuildConfig): void { - } - - public lastOutputPath(platform: string, buildConfig: IBuildConfig): string { - return ""; - } - - public readFile(device: Mobile.IDevice, deviceFilePath: string): Promise { - return Promise.resolve(""); - } - - public getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { - return null; - } -} - export class AndroidResourcesMigrationServiceStub implements IAndroidResourcesMigrationService { canMigrate(platformString: string): boolean { return true; @@ -922,7 +809,6 @@ export class InjectorStub extends Yok implements IInjector { this.register('projectDataService', ProjectDataService); this.register('devicePlatformsConstants', DevicePlatformsConstants); this.register("androidResourcesMigrationService", AndroidResourcesMigrationServiceStub); - this.register("platformService", PlatformServiceStub); this.register("commandsService", CommandsService); this.register("projectChangesService", ProjectChangesService); this.register('childProcess', ChildProcessStub); From faaf3372f81888c56765309a461c5d3274d69251 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 3 May 2019 15:38:48 +0300 Subject: [PATCH 07/30] refactor: refactor refreshApplication service --- lib/bootstrap.ts | 2 +- lib/services/bundle-workflow-service.ts | 117 +++++++++--- .../device-refresh-application-service.ts | 180 ++++++++++++++++++ .../device-restart-application-service.ts | 82 -------- .../platform-livesync-service-base.ts | 8 +- 5 files changed, 282 insertions(+), 107 deletions(-) create mode 100644 lib/services/device/device-refresh-application-service.ts delete mode 100644 lib/services/device/device-restart-application-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index d0b4f0f708..c9c79317f2 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -42,7 +42,7 @@ $injector.require("platformCommandsService", "./services/platform/platform-comma $injector.require("platformWatcherService", "./services/platform/platform-watcher-service"); $injector.require("deviceInstallationService", "./services/device/device-installation-service"); -$injector.require("deviceRestartApplicationService", "./services/device/device-restart-application-service"); +$injector.require("deviceRefreshApplicationService", "./services/device/device-refresh-application-service"); $injector.require("workflowDataService", "./services/workflow/workflow-data-service"); $injector.require("bundleWorkflowService", "./services/bundle-workflow-service"); diff --git a/lib/services/bundle-workflow-service.ts b/lib/services/bundle-workflow-service.ts index cc21939274..4811ed443a 100644 --- a/lib/services/bundle-workflow-service.ts +++ b/lib/services/bundle-workflow-service.ts @@ -1,30 +1,33 @@ -import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME } from "../constants"; +import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME, LiveSyncEvents } from "../constants"; import { WorkflowDataService } from "./workflow/workflow-data-service"; import { AddPlatformService } from "./platform/add-platform-service"; import { BuildPlatformService } from "./platform/build-platform-service"; import { PreparePlatformService } from "./platform/prepare-platform-service"; +import { EventEmitter } from "events"; +import { DeviceRefreshApplicationService } from "./device/device-refresh-application-service"; const deviceDescriptorPrimaryKey = "identifier"; -export class BundleWorkflowService implements IBundleWorkflowService { - private liveSyncProcessesInfo: IDictionary = {}; +export class BundleWorkflowService extends EventEmitter implements IBundleWorkflowService { + private liveSyncProcessesInfo: IDictionary = {}; constructor( + private $addPlatformService: AddPlatformService, + private $buildPlatformService: BuildPlatformService, private $deviceInstallationService: IDeviceInstallationService, - private $deviceRestartApplicationService: IDeviceRestartApplicationService, + private $deviceRefreshApplicationService: DeviceRefreshApplicationService, private $devicesService: Mobile.IDevicesService, private $errors: IErrors, + private $hooksService: IHooksService, private $injector: IInjector, - private $mobileHelper: Mobile.IMobileHelper, private $logger: ILogger, - private $preparePlatformService: PreparePlatformService, - private $addPlatformService: AddPlatformService, - private $buildPlatformService: BuildPlatformService, + private $mobileHelper: Mobile.IMobileHelper, private $platformWatcherService: IPlatformWatcherService, private $pluginsService: IPluginsService, + private $preparePlatformService: PreparePlatformService, private $projectDataService: IProjectDataService, private $workflowDataService: WorkflowDataService - ) { } + ) { super(); } public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise { const { nativePlatformData, projectData, addPlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); @@ -53,11 +56,6 @@ export class BundleWorkflowService implements IBundleWorkflowService { const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectDir, liveSyncInfo); await this.$buildPlatformService.buildPlatformIfNeeded(nativePlatformData, projectData, buildPlatformData); await this.$deviceInstallationService.installOnDeviceIfNeeded(device, nativePlatformData, projectData, buildPlatformData); - await device.applicationManager.startApplication({ - appId: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], - projectName: projectData.projectName - }); - this.$logger.out(`Successfully started on device with identifier '${device.deviceInfo.identifier}'.`); }; await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => true); @@ -108,17 +106,89 @@ export class BundleWorkflowService implements IBundleWorkflowService { private async syncInitialDataOnDevice(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); - const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); - const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); + try { + const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); + const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); - await this.$deviceInstallationService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); + await this.$deviceInstallationService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); - // TODO: Consider to improve this - const platformLiveSyncService = this.getLiveSyncService(platformData.platformNameLowerCase); - const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; - const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor }); + // TODO: Consider to improve this + const platformLiveSyncService = this.getLiveSyncService(platformData.platformNameLowerCase); + const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; + const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor }); - await this.$deviceRestartApplicationService.restartOnDevice(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService); + await this.$deviceRefreshApplicationService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); + } catch (err) { + this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); + + this.emitLivesyncEvent(LiveSyncEvents.liveSyncError, { + error: err, + deviceIdentifier: device.deviceInfo.identifier, + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectIdentifiers[platformData.platformNameLowerCase] + }); + + await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier], { shouldAwaitAllActions: false }); + } + } + + public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { + const liveSyncProcessInfo = this.liveSyncProcessesInfo[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. + const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; + + const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); + + const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) + .map(descriptor => descriptor.identifier); + + // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. + if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { + if (liveSyncProcessInfo.timer) { + clearTimeout(liveSyncProcessInfo.timer); + } + + if (liveSyncProcessInfo.watcherInfo && liveSyncProcessInfo.watcherInfo.watcher) { + liveSyncProcessInfo.watcherInfo.watcher.close(); + } + + liveSyncProcessInfo.watcherInfo = null; + liveSyncProcessInfo.isStopped = true; + + if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) { + await liveSyncProcessInfo.actionsChain; + } + + liveSyncProcessInfo.deviceDescriptors = []; + + if (liveSyncProcessInfo.syncToPreviewApp) { + // await this.$previewAppLiveSyncService.stopLiveSync(); + // this.$previewAppLiveSyncService.removeAllListeners(); + } + + // Kill typescript watcher + const projectData = this.$projectDataService.getProjectData(projectDir); + await this.$hooksService.executeAfterHooks('watch', { + hookArgs: { + projectData + } + }); + } else if (liveSyncProcessInfo.currentSyncAction && shouldAwaitPendingOperation) { + await liveSyncProcessInfo.currentSyncAction; + } + + // Emit LiveSync stopped when we've really stopped. + _.each(removedDeviceIdentifiers, deviceIdentifier => { + this.emitLivesyncEvent(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier }); + }); + } + } + + public emitLivesyncEvent(event: string, livesyncData: ILiveSyncEventData): boolean { + this.$logger.trace(`Will emit event ${event} with data`, livesyncData); + return this.emit(event, livesyncData); } private async syncChangedDataOnDevice(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { @@ -142,7 +212,8 @@ export class BundleWorkflowService implements IBundleWorkflowService { force: liveSyncInfo.force, connectTimeout: 1000 }); - await this.$deviceRestartApplicationService.restartOnDevice(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService); + + await this.$deviceRefreshApplicationService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); } public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { diff --git a/lib/services/device/device-refresh-application-service.ts b/lib/services/device/device-refresh-application-service.ts new file mode 100644 index 0000000000..4a91b64d4e --- /dev/null +++ b/lib/services/device/device-refresh-application-service.ts @@ -0,0 +1,180 @@ +import { performanceLog } from "../../common/decorators"; +import { EventEmitter } from "events"; +import { DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, LiveSyncEvents, DEBUGGER_ATTACHED_EVENT_NAME } from "../../constants"; +import { EOL } from "os"; + +export class DeviceRefreshApplicationService { + + constructor( + // private $buildArtefactsService: IBuildArtefactsService, + private $debugDataService: IDebugDataService, + private $debugService: IDebugService, + private $devicesService: Mobile.IDevicesService, + private $errors: IErrors, + private $logger: ILogger, + // private $platformsData: IPlatformsData, + private $projectDataService: IProjectDataService + ) { } + + @performanceLog() + public async refreshApplication(deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, platformLiveSyncService: IPlatformLiveSyncService, eventEmitter: EventEmitter): Promise { + return liveSyncResultInfo && deviceDescriptor.debugggingEnabled ? + this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, deviceDescriptor, platformLiveSyncService, eventEmitter) : + this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor, platformLiveSyncService, eventEmitter); + } + + @performanceLog() + public async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, platformLiveSyncService: IPlatformLiveSyncService, eventEmitter: EventEmitter): Promise { + const { debugOptions } = deviceDescriptor; + if (debugOptions.debugBrk) { + liveSyncResultInfo.waitForDebugger = true; + } + + const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor, platformLiveSyncService, eventEmitter, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); + + // 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: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + debugOptions: debugOptions, + }; + + return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, deviceDescriptor, { projectDir: projectData.projectDir }); + } + + public async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, platformLiveSyncService: IPlatformLiveSyncService, eventEmitter: EventEmitter, settings?: IRefreshApplicationSettings): Promise { + const result = { didRestart: false }; + const platform = liveSyncResultInfo.deviceAppData.platform; + const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; + try { + let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); + if (!shouldRestart) { + shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); + } + + if (shouldRestart) { + const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; + eventEmitter.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); + await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); + result.didRestart = true; + } + } catch (err) { + 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); + if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { + eventEmitter.emit(LiveSyncEvents.liveSyncNotification, { + projectDir: projectData.projectDir, + applicationIdentifier, + deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + notification: msg + }); + } + + if (settings && settings.shouldCheckDeveloperDiscImage) { + this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, deviceDescriptor, eventEmitter); + } + } + + eventEmitter.emit(LiveSyncEvents.liveSyncExecuted, { + projectDir: projectData.projectDir, + applicationIdentifier, + syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + isFullSync: liveSyncResultInfo.isFullSync + }); + + return result; + } + + // TODO: This should be into separate class + public async attachDebugger(settings: IAttachDebuggerOptions): Promise { + // Default values + if (settings.debugOptions) { + settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; + settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; + } else { + settings.debugOptions = { + chrome: true, + start: true + }; + } + + const projectData = this.$projectDataService.getProjectData(settings.projectDir); + const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); + // const platformData = this.$platformsData.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. + // debugData.pathToAppPackage = this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildConfig, settings.outputPath); + const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); + const result = this.printDebugInformation(debugInfo, null, settings.debugOptions.forceDebuggerAttachedEvent); + return result; + } + + public printDebugInformation(debugInformation: IDebugInformation, eventEmitter: EventEmitter, fireDebuggerAttachedEvent: boolean = true): IDebugInformation { + if (!!debugInformation.url) { + if (fireDebuggerAttachedEvent) { + eventEmitter.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; + } + + @performanceLog() + private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, deviceDescriptor: ILiveSyncDeviceInfo, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + if (!deviceDescriptor) { + this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); + } + + deviceDescriptor.debugggingEnabled = true; + deviceDescriptor.debugOptions = deviceOption.debugOptions; + const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); + const attachDebuggerOptions: IAttachDebuggerOptions = { + deviceIdentifier: deviceOption.deviceIdentifier, + isEmulator: currentDeviceInstance.isEmulator, + outputPath: deviceDescriptor.outputPath, + platform: currentDeviceInstance.deviceInfo.platform, + projectDir: debuggingAdditionalOptions.projectDir, + debugOptions: deviceOption.debugOptions + }; + + let debugInformation: IDebugInformation; + try { + debugInformation = await this.attachDebugger(attachDebuggerOptions); + } catch (err) { + this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); + attachDebuggerOptions.debugOptions.start = false; + try { + debugInformation = await this.attachDebugger(attachDebuggerOptions); + } catch (innerErr) { + this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); + throw err; + } + } + + return debugInformation; + } + + private handleDeveloperDiskImageError(err: any, liveSyncResultInfo: ILiveSyncResultInfo, projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, eventEmitter: EventEmitter) { + if ((err.message || err) === "Could not find developer disk image") { + const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; + const attachDebuggerOptions: IAttachDebuggerOptions = { + platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, + isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, + projectDir: projectData.projectDir, + deviceIdentifier, + debugOptions: deviceDescriptor.debugOptions, + outputPath: deviceDescriptor.outputPath + }; + eventEmitter.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); + } + } +} +$injector.register("deviceRefreshApplicationService", DeviceRefreshApplicationService); diff --git a/lib/services/device/device-restart-application-service.ts b/lib/services/device/device-restart-application-service.ts deleted file mode 100644 index b4df1f9891..0000000000 --- a/lib/services/device/device-restart-application-service.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { performanceLog } from "../../common/decorators"; - -export class DeviceRestartApplicationService implements IDeviceRestartApplicationService { - - constructor(private $logger: ILogger) { } - - @performanceLog() - public async restartOnDevice(deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, platformLiveSyncService: IPlatformLiveSyncService): Promise { - return liveSyncResultInfo && deviceDescriptor.debugggingEnabled ? - this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, deviceDescriptor.debugOptions, platformLiveSyncService) : - this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor.debugOptions, platformLiveSyncService); - } - - @performanceLog() - private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, platformLiveSyncService: IPlatformLiveSyncService): Promise { - debugOptions = debugOptions || {}; - if (debugOptions.debugBrk) { - liveSyncResultInfo.waitForDebugger = true; - } - - const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, platformLiveSyncService, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); - - // 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: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - // debugOptions: debugOptions, - // }; - - // return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); - return null; - } - - private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts: IDebugOptions, platformLiveSyncService: IPlatformLiveSyncService, settings?: IRefreshApplicationSettings): Promise { - const result = { didRestart: false }; - const platform = liveSyncResultInfo.deviceAppData.platform; - const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; - try { - let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); - if (!shouldRestart) { - shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); - } - - if (shouldRestart) { - // const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; - // this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); - await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); - result.didRestart = true; - } - } catch (err) { - 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); - if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { - // this.emitLivesyncEvent(LiveSyncEvents.liveSyncNotification, { - // projectDir: projectData.projectDir, - // applicationIdentifier, - // deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - // notification: msg - // }); - } - - if (settings && settings.shouldCheckDeveloperDiscImage) { - // this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); - } - } - - // this.emitLivesyncEvent(LiveSyncEvents.liveSyncExecuted, { - // projectDir: projectData.projectDir, - // applicationIdentifier, - // syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), - // deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - // isFullSync: liveSyncResultInfo.isFullSync - // }); - - return result; - } -} -$injector.register("deviceRestartApplicationService", DeviceRestartApplicationService); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index d0fb91d933..ee5edd1506 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -64,11 +64,17 @@ export abstract class PlatformLiveSyncServiceBase { 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 }); + let waitForDebugger = null; + if (syncInfo.liveSyncDeviceInfo && syncInfo.liveSyncDeviceInfo.debugggingEnabled) { + waitForDebugger = syncInfo.liveSyncDeviceInfo && syncInfo.liveSyncDeviceInfo.debugOptions && syncInfo.liveSyncDeviceInfo.debugOptions.debugBrk; + } + return { modifiedFilesData, isFullSync: true, deviceAppData, - useHotModuleReload: syncInfo.useHotModuleReload + useHotModuleReload: syncInfo.useHotModuleReload, + waitForDebugger }; } From 0e4109bab976e68b760eb636f32dbbd4cce38489 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 3 May 2019 16:43:47 +0300 Subject: [PATCH 08/30] refactor: refactor bundleWorkflowService to mainController --- lib/bootstrap.ts | 4 ++- lib/commands/build.ts | 14 +++++----- lib/commands/prepare.ts | 6 ++-- lib/common/definitions/mobile.d.ts | 4 ++- .../mobile/mobile-core/devices-service.ts | 10 +++++++ .../main-controller.ts} | 28 ++++++------------- lib/controllers/run-on-device-controller.ts | 4 +++ lib/declarations.d.ts | 7 ----- lib/helpers/livesync-command-helper.ts | 5 ++-- test/services/bundle-workflow-service.ts | 13 ++++----- 10 files changed, 48 insertions(+), 47 deletions(-) rename lib/{services/bundle-workflow-service.ts => controllers/main-controller.ts} (93%) create mode 100644 lib/controllers/run-on-device-controller.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index c9c79317f2..26feb62b59 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -45,7 +45,9 @@ $injector.require("deviceInstallationService", "./services/device/device-install $injector.require("deviceRefreshApplicationService", "./services/device/device-refresh-application-service"); $injector.require("workflowDataService", "./services/workflow/workflow-data-service"); -$injector.require("bundleWorkflowService", "./services/bundle-workflow-service"); + +$injector.require("mainController", "./controllers/main-controller"); +$injector.require("runOnDeviceController", "./controllers/run-on-device"); $injector.require("buildArtefactsService", "./services/build-artefacts-service"); diff --git a/lib/commands/build.ts b/lib/commands/build.ts index ee44db5172..8c93b29e3c 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -1,6 +1,6 @@ import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE, AndroidAppBundleMessages } from "../constants"; import { ValidatePlatformCommandBase } from "./command-base"; -import { BundleWorkflowService } from "../services/bundle-workflow-service"; +import { MainController } from "../controllers/main-controller"; export abstract class BuildCommandBase extends ValidatePlatformCommandBase { constructor($options: IOptions, @@ -8,7 +8,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { $projectData: IProjectData, $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - protected $bundleWorkflowService: BundleWorkflowService, + protected $mainController: MainController, $platformValidationService: IPlatformValidationService, private $bundleValidatorHelper: IBundleValidatorHelper, protected $logger: ILogger) { @@ -18,7 +18,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { public async executeCore(args: string[]): Promise { const platform = args[0].toLowerCase(); - const outputPath = await this.$bundleWorkflowService.buildPlatform(platform, this.$projectData.projectDir, this.$options); + const outputPath = await this.$mainController.buildPlatform(platform, this.$projectData.projectDir, this.$options); return outputPath; } @@ -57,11 +57,11 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $bundleWorkflowService: BundleWorkflowService, + $mainController: MainController, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $bundleWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $mainController, $platformValidationService, $bundleValidatorHelper, $logger); } public async execute(args: string[]): Promise { @@ -92,12 +92,12 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $bundleWorkflowService: BundleWorkflowService, + $mainController: MainController, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, protected $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $bundleWorkflowService, $platformValidationService, $bundleValidatorHelper, $logger); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $mainController, $platformValidationService, $bundleValidatorHelper, $logger); } public async execute(args: string[]): Promise { diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 7ac59c88e2..b4f6adcef8 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -1,11 +1,11 @@ import { ValidatePlatformCommandBase } from "./command-base"; -import { BundleWorkflowService } from "../services/bundle-workflow-service"; +import { MainController } from "../controllers/main-controller"; export class PrepareCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters = [this.$platformCommandParameter]; constructor($options: IOptions, - private $bundleWorkflowService: BundleWorkflowService, + private $mainController: MainController, $platformValidationService: IPlatformValidationService, $projectData: IProjectData, private $platformCommandParameter: ICommandParameter, @@ -17,7 +17,7 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm public async execute(args: string[]): Promise { const platform = args[0]; - await this.$bundleWorkflowService.preparePlatform(platform, this.$projectData.projectDir, this.$options); + await this.$mainController.preparePlatform(platform, this.$projectData.projectDir, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index d426813999..6997689d80 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -495,7 +495,9 @@ declare module Mobile { * Returns a single device based on the specified options. If more than one devices are matching, * prompts the user for a manual choice or returns the first one for non interactive terminals. */ - pickSingleDevice(options: IPickSingleDeviceOptions): Promise + pickSingleDevice(options: IPickSingleDeviceOptions): Promise; + + getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceInfo[]): string[]; } interface IPickSingleDeviceOptions { diff --git a/lib/common/mobile/mobile-core/devices-service.ts b/lib/common/mobile/mobile-core/devices-service.ts index 72ebf1b517..f89768ca67 100644 --- a/lib/common/mobile/mobile-core/devices-service.ts +++ b/lib/common/mobile/mobile-core/devices-service.ts @@ -601,6 +601,16 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi } } + public getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceInfo[]): string[] { + const platforms = _(deviceDescriptors) + .map(device => this.getDeviceByIdentifier(device.identifier)) + .map(device => device.deviceInfo.platform) + .uniq() + .value(); + + return platforms; + } + private async initializeCore(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise { if (this._isInitialized) { return; diff --git a/lib/services/bundle-workflow-service.ts b/lib/controllers/main-controller.ts similarity index 93% rename from lib/services/bundle-workflow-service.ts rename to lib/controllers/main-controller.ts index 4811ed443a..81866feee0 100644 --- a/lib/services/bundle-workflow-service.ts +++ b/lib/controllers/main-controller.ts @@ -1,14 +1,14 @@ import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME, LiveSyncEvents } from "../constants"; -import { WorkflowDataService } from "./workflow/workflow-data-service"; -import { AddPlatformService } from "./platform/add-platform-service"; -import { BuildPlatformService } from "./platform/build-platform-service"; -import { PreparePlatformService } from "./platform/prepare-platform-service"; +import { WorkflowDataService } from "../services/workflow/workflow-data-service"; +import { AddPlatformService } from "../services/platform/add-platform-service"; +import { BuildPlatformService } from "../services/platform/build-platform-service"; +import { PreparePlatformService } from "../services/platform/prepare-platform-service"; import { EventEmitter } from "events"; -import { DeviceRefreshApplicationService } from "./device/device-refresh-application-service"; +import { DeviceRefreshApplicationService } from "../services/device/device-refresh-application-service"; const deviceDescriptorPrimaryKey = "identifier"; -export class BundleWorkflowService extends EventEmitter implements IBundleWorkflowService { +export class MainController extends EventEmitter { private liveSyncProcessesInfo: IDictionary = {}; constructor( @@ -46,7 +46,7 @@ export class BundleWorkflowService extends EventEmitter implements IBundleWorkfl } public async deployPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { - const platforms = this.getPlatformsFromDevices(deviceDescriptors); + const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); for (const platform of platforms) { await this.preparePlatform(platform, projectDir, liveSyncInfo); @@ -65,7 +65,7 @@ export class BundleWorkflowService extends EventEmitter implements IBundleWorkfl const projectData = this.$projectDataService.getProjectData(projectDir); await this.initializeSetup(projectData); - const platforms = this.getPlatformsFromDevices(deviceDescriptors); + const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); for (const platform of platforms) { const { nativePlatformData, addPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, { ...liveSyncInfo, platformParam: platform }); @@ -267,15 +267,5 @@ export class BundleWorkflowService extends EventEmitter implements IBundleWorkfl this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); } - - private getPlatformsFromDevices(deviceDescriptors: ILiveSyncDeviceInfo[]): string[] { - const platforms = _(deviceDescriptors) - .map(device => this.$devicesService.getDeviceByIdentifier(device.identifier)) - .map(device => device.deviceInfo.platform) - .uniq() - .value(); - - return platforms; - } } -$injector.register("bundleWorkflowService", BundleWorkflowService); +$injector.register("mainController", MainController); diff --git a/lib/controllers/run-on-device-controller.ts b/lib/controllers/run-on-device-controller.ts new file mode 100644 index 0000000000..24c3937fa3 --- /dev/null +++ b/lib/controllers/run-on-device-controller.ts @@ -0,0 +1,4 @@ +export class RunOnDeviceController { + +} +$injector.register("runOnDeviceController", RunOnDeviceController); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 4415599529..f94320b730 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -1027,13 +1027,6 @@ interface INetworkConnectivityValidator { validate(): Promise; } -interface IBundleWorkflowService { - preparePlatform(platform: string, projectDir: string, options: IOptions): Promise; - buildPlatform(platform: string, projectDir: string, options: IOptions): Promise; - deployPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise; - runPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise; -} - interface IPlatformValidationService { /** * Ensures the passed platform is a valid one (from the supported ones) diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 61c9c6b0c1..a98056e64e 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,4 +1,5 @@ import { BuildPlatformService } from "../services/platform/build-platform-service"; +import { MainController } from "../controllers/main-controller"; // import { LiveSyncEvents } from "../constants"; @@ -8,7 +9,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { constructor( private $projectData: IProjectData, private $options: IOptions, - private $bundleWorkflowService: IBundleWorkflowService, + private $mainController: MainController, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $devicesService: Mobile.IDevicesService, @@ -132,7 +133,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { // return; // } - await this.$bundleWorkflowService.runPlatform(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); + await this.$mainController.runPlatform(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); // const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); // this.$liveSyncService.on(LiveSyncEvents.liveSyncStopped, (data: { projectDir: string, deviceIdentifier: string }) => { diff --git a/test/services/bundle-workflow-service.ts b/test/services/bundle-workflow-service.ts index 5bf89ac538..4f157264b0 100644 --- a/test/services/bundle-workflow-service.ts +++ b/test/services/bundle-workflow-service.ts @@ -1,7 +1,7 @@ import { Yok } from "../../lib/common/yok"; -import { BundleWorkflowService } from "../../lib/services/bundle-workflow-service"; import { assert } from "chai"; import { AddPlatformService } from "../../lib/services/platform/add-platform-service"; +import { MainController } from "../../lib/controllers/main-controller"; const deviceMap: IDictionary = { myiOSDevice: { @@ -42,7 +42,7 @@ function createTestInjector(): IInjector { emit: () => ({}), startWatcher: () => ({}) })); - injector.register("bundleWorkflowService", BundleWorkflowService); + injector.register("mainController", MainController); injector.register("pluginsService", ({})); injector.register("projectDataService", ({ getProjectData: () => ({ @@ -55,7 +55,6 @@ function createTestInjector(): IInjector { injector.register("platformService", ({})); injector.register("projectChangesService", ({})); injector.register("fs", ({})); - injector.register("bundleWorkflowService", BundleWorkflowService); return injector; } @@ -75,7 +74,7 @@ const liveSyncInfo = { } }; -describe("BundleWorkflowService", () => { +describe("MainController", () => { describe("start", () => { describe("when the run on device is called for second time for the same projectDir", () => { it("should run only for new devies (for which the initial sync is still not executed)", async () => { @@ -101,8 +100,8 @@ describe("BundleWorkflowService", () => { return true; }; - const bundleWorkflowService: IBundleWorkflowService = injector.resolve("bundleWorkflowService"); - await bundleWorkflowService.runPlatform(projectDir, [iOSDeviceDescriptor], liveSyncInfo); + const mainController: MainController = injector.resolve("mainController"); + await mainController.runPlatform(projectDir, [iOSDeviceDescriptor], liveSyncInfo); assert.isTrue(isStartWatcherCalled); }); @@ -130,7 +129,7 @@ describe("BundleWorkflowService", () => { const injector = createTestInjector(); const actualAddedPlatforms: IPlatformData[] = []; - const platformAddService: AddPlatformService = injector.resolve("bundleWorkflowService"); + const platformAddService: AddPlatformService = injector.resolve("platformAddService"); platformAddService.addPlatformIfNeeded = async (platformData: IPlatformData) => { actualAddedPlatforms.push(platformData); }; From 73ee30649e41545eb130fac88ad3a3730c2166c6 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 3 May 2019 23:53:17 +0300 Subject: [PATCH 09/30] chore: rename deviceInstallAppService and deviceRefreshAppService so the same convention is used for both classes --- lib/bootstrap.ts | 4 ++-- lib/controllers/main-controller.ts | 21 ++++++++++--------- ...rvice.ts => device-install-app-service.ts} | 4 ++-- ...rvice.ts => device-refresh-app-service.ts} | 4 ++-- lib/services/webpack/webpack.d.ts | 6 ------ .../main-controller.ts} | 8 +++---- 6 files changed, 21 insertions(+), 26 deletions(-) rename lib/services/device/{device-installation-service.ts => device-install-app-service.ts} (96%) rename lib/services/device/{device-refresh-application-service.ts => device-refresh-app-service.ts} (98%) rename test/{services/bundle-workflow-service.ts => controllers/main-controller.ts} (94%) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 26feb62b59..5731c35e0a 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -41,8 +41,8 @@ $injector.require("platformValidationService", "./services/platform/platform-val $injector.require("platformCommandsService", "./services/platform/platform-commands-service"); $injector.require("platformWatcherService", "./services/platform/platform-watcher-service"); -$injector.require("deviceInstallationService", "./services/device/device-installation-service"); -$injector.require("deviceRefreshApplicationService", "./services/device/device-refresh-application-service"); +$injector.require("deviceInstallAppService", "./services/device/device-install-app-service"); +$injector.require("deviceRefreshAppService", "./services/device/device-refresh-app-service"); $injector.require("workflowDataService", "./services/workflow/workflow-data-service"); diff --git a/lib/controllers/main-controller.ts b/lib/controllers/main-controller.ts index 81866feee0..17315b9f3b 100644 --- a/lib/controllers/main-controller.ts +++ b/lib/controllers/main-controller.ts @@ -1,10 +1,11 @@ -import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME, LiveSyncEvents } from "../constants"; -import { WorkflowDataService } from "../services/workflow/workflow-data-service"; import { AddPlatformService } from "../services/platform/add-platform-service"; import { BuildPlatformService } from "../services/platform/build-platform-service"; -import { PreparePlatformService } from "../services/platform/prepare-platform-service"; +import { DeviceInstallAppService } from "../services/device/device-install-app-service"; +import { DeviceRefreshAppService } from "../services/device/device-refresh-app-service"; import { EventEmitter } from "events"; -import { DeviceRefreshApplicationService } from "../services/device/device-refresh-application-service"; +import { FILES_CHANGE_EVENT_NAME, INITIAL_SYNC_EVENT_NAME, LiveSyncEvents } from "../constants"; +import { PreparePlatformService } from "../services/platform/prepare-platform-service"; +import { WorkflowDataService } from "../services/workflow/workflow-data-service"; const deviceDescriptorPrimaryKey = "identifier"; @@ -14,8 +15,8 @@ export class MainController extends EventEmitter { constructor( private $addPlatformService: AddPlatformService, private $buildPlatformService: BuildPlatformService, - private $deviceInstallationService: IDeviceInstallationService, - private $deviceRefreshApplicationService: DeviceRefreshApplicationService, + private $deviceInstallAppService: DeviceInstallAppService, + private $deviceRefreshAppService: DeviceRefreshAppService, private $devicesService: Mobile.IDevicesService, private $errors: IErrors, private $hooksService: IHooksService, @@ -55,7 +56,7 @@ export class MainController extends EventEmitter { const executeAction = async (device: Mobile.IDevice) => { const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectDir, liveSyncInfo); await this.$buildPlatformService.buildPlatformIfNeeded(nativePlatformData, projectData, buildPlatformData); - await this.$deviceInstallationService.installOnDeviceIfNeeded(device, nativePlatformData, projectData, buildPlatformData); + await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, nativePlatformData, projectData, buildPlatformData); }; await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => true); @@ -110,14 +111,14 @@ export class MainController extends EventEmitter { const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); - await this.$deviceInstallationService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); + await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); // TODO: Consider to improve this const platformLiveSyncService = this.getLiveSyncService(platformData.platformNameLowerCase); const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor }); - await this.$deviceRefreshApplicationService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); + await this.$deviceRefreshAppService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); } catch (err) { this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); @@ -213,7 +214,7 @@ export class MainController extends EventEmitter { connectTimeout: 1000 }); - await this.$deviceRefreshApplicationService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); + await this.$deviceRefreshAppService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); } public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { diff --git a/lib/services/device/device-installation-service.ts b/lib/services/device/device-install-app-service.ts similarity index 96% rename from lib/services/device/device-installation-service.ts rename to lib/services/device/device-install-app-service.ts index fe28784db4..23221f58d9 100644 --- a/lib/services/device/device-installation-service.ts +++ b/lib/services/device/device-install-app-service.ts @@ -6,7 +6,7 @@ import { BuildPlatformService } from "../platform/build-platform-service"; const buildInfoFileName = ".nsbuildinfo"; -export class DeviceInstallationService implements IDeviceInstallationService { +export class DeviceInstallAppService { constructor( private $analyticsService: IAnalyticsService, private $buildArtefactsService: IBuildArtefactsService, @@ -109,4 +109,4 @@ export class DeviceInstallationService implements IDeviceInstallationService { } } } -$injector.register("deviceInstallationService", DeviceInstallationService); +$injector.register("deviceInstallAppService", DeviceInstallAppService); diff --git a/lib/services/device/device-refresh-application-service.ts b/lib/services/device/device-refresh-app-service.ts similarity index 98% rename from lib/services/device/device-refresh-application-service.ts rename to lib/services/device/device-refresh-app-service.ts index 4a91b64d4e..10af4f1ab7 100644 --- a/lib/services/device/device-refresh-application-service.ts +++ b/lib/services/device/device-refresh-app-service.ts @@ -3,7 +3,7 @@ import { EventEmitter } from "events"; import { DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, LiveSyncEvents, DEBUGGER_ATTACHED_EVENT_NAME } from "../../constants"; import { EOL } from "os"; -export class DeviceRefreshApplicationService { +export class DeviceRefreshAppService { constructor( // private $buildArtefactsService: IBuildArtefactsService, @@ -177,4 +177,4 @@ export class DeviceRefreshApplicationService { } } } -$injector.register("deviceRefreshApplicationService", DeviceRefreshApplicationService); +$injector.register("deviceRefreshAppService", DeviceRefreshAppService); diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index 4bce2bff9e..78b01920bf 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -40,12 +40,6 @@ declare global { hasNativeChanges: boolean; } - interface IDeviceInstallationService { - installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise; - installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise; - getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise; - } - interface IDeviceRestartApplicationService { restartOnDevice(deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, platformLiveSyncService: IPlatformLiveSyncService): Promise; } diff --git a/test/services/bundle-workflow-service.ts b/test/controllers/main-controller.ts similarity index 94% rename from test/services/bundle-workflow-service.ts rename to test/controllers/main-controller.ts index 4f157264b0..da2902347a 100644 --- a/test/services/bundle-workflow-service.ts +++ b/test/controllers/main-controller.ts @@ -134,8 +134,8 @@ describe("MainController", () => { actualAddedPlatforms.push(platformData); }; - const bundleWorkflowService: IBundleWorkflowService = injector.resolve("bundleWorkflowService"); - await bundleWorkflowService.runPlatform(projectDir, testCase.connectedDevices, liveSyncInfo); + const mainController = injector.resolve("mainController"); + await mainController.runPlatform(projectDir, testCase.connectedDevices, liveSyncInfo); assert.deepEqual(actualAddedPlatforms.map(pData => pData.platformNameLowerCase), testCase.expectedAddedPlatforms); }); @@ -165,8 +165,8 @@ describe("MainController", () => { // const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); // platformWatcherService.emit(INITIAL_SYNC_EVENT_NAME, { platform, hasNativeChanges: true }); - // const bundleWorkflowService: IBundleWorkflowService = injector.resolve("bundleWorkflowService"); - // await bundleWorkflowService.start(projectDir, [iOSDeviceDescriptor], liveSyncInfo); + // const mainController: MainController = injector.resolve("mainController"); + // await mainController.start(projectDir, [iOSDeviceDescriptor], liveSyncInfo); // assert.isTrue(isBuildPlatformCalled); // }); From 22f594e7569ce38da58e2f226fb818ba9e1f156c Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 4 May 2019 12:59:38 +0300 Subject: [PATCH 10/30] refactor: introduce separate run-on-devices-controller --- PublicAPI.md | 1 + lib/bootstrap.ts | 7 +- lib/constants.ts | 12 +- lib/controllers/main-controller.ts | 177 ++++++------------ lib/controllers/run-on-device-controller.ts | 4 - lib/controllers/run-on-devices-controller.ts | 139 ++++++++++++++ lib/helpers/livesync-command-helper.ts | 2 +- lib/resolvers/livesync-service-resolver.ts | 18 ++ .../device/device-refresh-app-service.ts | 6 +- lib/services/run-on-devices-data-service.ts | 29 +++ test/controllers/main-controller.ts | 2 +- 11 files changed, 258 insertions(+), 139 deletions(-) delete mode 100644 lib/controllers/run-on-device-controller.ts create mode 100644 lib/controllers/run-on-devices-controller.ts create mode 100644 lib/resolvers/livesync-service-resolver.ts create mode 100644 lib/services/run-on-devices-data-service.ts diff --git a/PublicAPI.md b/PublicAPI.md index 066640eb0d..db09729335 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -1,3 +1,4 @@ + Public API == diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 5731c35e0a..8e2afddc02 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -41,15 +41,18 @@ $injector.require("platformValidationService", "./services/platform/platform-val $injector.require("platformCommandsService", "./services/platform/platform-commands-service"); $injector.require("platformWatcherService", "./services/platform/platform-watcher-service"); +$injector.require("buildArtefactsService", "./services/build-artefacts-service"); + +$injector.require("runOnDevicesDataService", "./services/run-on-devices-data-service"); $injector.require("deviceInstallAppService", "./services/device/device-install-app-service"); $injector.require("deviceRefreshAppService", "./services/device/device-refresh-app-service"); $injector.require("workflowDataService", "./services/workflow/workflow-data-service"); $injector.require("mainController", "./controllers/main-controller"); -$injector.require("runOnDeviceController", "./controllers/run-on-device"); +$injector.require("runOnDevicesController", "./controllers/run-on-devices-controller"); -$injector.require("buildArtefactsService", "./services/build-artefacts-service"); +$injector.require("liveSyncServiceResolver", "./resolvers/livesync-service-resolver"); $injector.require("debugDataService", "./services/debug-data-service"); $injector.requirePublicClass("debugService", "./services/debug-service"); diff --git a/lib/constants.ts b/lib/constants.ts index 6a9638b8f5..a8b179a400 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -277,12 +277,12 @@ export class AndroidAppBundleMessages { public static ANDROID_APP_BUNDLE_PUBLISH_DOCS_MESSAGE = "How to use Android App Bundle for publishing: https://docs.nativescript.org/tooling/publishing/publishing-android-apps#android-app-bundle"; } -export const LiveSyncEvents = { - liveSyncStopped: "liveSyncStopped", +export const RunOnDeviceEvents = { + runOnDeviceStopped: "runOnDeviceStopped", // In case we name it error, EventEmitter expects instance of Error to be raised and will also raise uncaught exception in case there's no handler - liveSyncError: "liveSyncError", + runOnDeviceError: "runOnDeviceError", previewAppLiveSyncError: PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, - liveSyncExecuted: "liveSyncExecuted", - liveSyncStarted: "liveSyncStarted", - liveSyncNotification: "notify" + runOnDeviceExecuted: "runOnDeviceExecuted", + runOnDeviceStarted: "runOnDeviceStarted", + runOnDeviceNotification: "notify" }; diff --git a/lib/controllers/main-controller.ts b/lib/controllers/main-controller.ts index 17315b9f3b..4563ba6588 100644 --- a/lib/controllers/main-controller.ts +++ b/lib/controllers/main-controller.ts @@ -1,32 +1,30 @@ import { AddPlatformService } from "../services/platform/add-platform-service"; import { BuildPlatformService } from "../services/platform/build-platform-service"; import { DeviceInstallAppService } from "../services/device/device-install-app-service"; -import { DeviceRefreshAppService } from "../services/device/device-refresh-app-service"; import { EventEmitter } from "events"; -import { FILES_CHANGE_EVENT_NAME, INITIAL_SYNC_EVENT_NAME, LiveSyncEvents } from "../constants"; +import { FILES_CHANGE_EVENT_NAME, INITIAL_SYNC_EVENT_NAME, RunOnDeviceEvents } from "../constants"; import { PreparePlatformService } from "../services/platform/prepare-platform-service"; import { WorkflowDataService } from "../services/workflow/workflow-data-service"; - -const deviceDescriptorPrimaryKey = "identifier"; +import { RunOnDevicesController } from "./run-on-devices-controller"; +import { RunOnDevicesDataService } from "../services/run-on-devices-data-service"; +import { cache } from "../common/decorators"; +import { DeviceDiscoveryEventNames } from "../common/constants"; export class MainController extends EventEmitter { - private liveSyncProcessesInfo: IDictionary = {}; - constructor( private $addPlatformService: AddPlatformService, private $buildPlatformService: BuildPlatformService, private $deviceInstallAppService: DeviceInstallAppService, - private $deviceRefreshAppService: DeviceRefreshAppService, private $devicesService: Mobile.IDevicesService, private $errors: IErrors, private $hooksService: IHooksService, - private $injector: IInjector, private $logger: ILogger, - private $mobileHelper: Mobile.IMobileHelper, private $platformWatcherService: IPlatformWatcherService, private $pluginsService: IPluginsService, private $preparePlatformService: PreparePlatformService, private $projectDataService: IProjectDataService, + private $runOnDevicesController: RunOnDevicesController, + private $runOnDevicesDataService: RunOnDevicesDataService, private $workflowDataService: WorkflowDataService ) { super(); } @@ -46,7 +44,7 @@ export class MainController extends EventEmitter { return result; } - public async deployPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + public async deployOnDevices(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); for (const platform of platforms) { @@ -62,7 +60,7 @@ export class MainController extends EventEmitter { await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => true); } - public async runPlatform(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + public async runOnDevices(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { const projectData = this.$projectDataService.getProjectData(projectDir); await this.initializeSetup(projectData); @@ -73,28 +71,19 @@ export class MainController extends EventEmitter { await this.$addPlatformService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); } - this.setLiveSyncProcessInfo(projectDir, liveSyncInfo, deviceDescriptors); + // TODO: Consider to handle correctly the descriptors when livesync is executed for second time for the same projectDir - const shouldStartWatcher = !liveSyncInfo.skipWatcher && (liveSyncInfo.syncToPreviewApp || this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length); + this.$runOnDevicesDataService.persistData(projectDir, liveSyncInfo, deviceDescriptors); + + const shouldStartWatcher = !liveSyncInfo.skipWatcher && (liveSyncInfo.syncToPreviewApp || this.$runOnDevicesDataService.hasDeviceDescriptors(projectDir)); if (shouldStartWatcher) { + this.handleRunOnDeviceEvents(projectDir); + this.$platformWatcherService.on(INITIAL_SYNC_EVENT_NAME, async (data: IInitialSyncEventData) => { - const executeAction = async (device: Mobile.IDevice) => { - const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.syncInitialDataOnDevice(device, deviceDescriptor, projectData, liveSyncInfo); - }; - const canExecuteAction = (device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === data.platform.toLowerCase() && _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); - await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(executeAction, canExecuteAction)); + await this.$runOnDevicesController.syncInitialDataOnDevice(data, projectData, liveSyncInfo, deviceDescriptors); }); this.$platformWatcherService.on(FILES_CHANGE_EVENT_NAME, async (data: IFilesChangeEventData) => { - const executeAction = async (device: Mobile.IDevice) => { - const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.syncChangedDataOnDevice(device, deviceDescriptor, data, projectData, liveSyncInfo); - }; - const canExecuteAction = (device: Mobile.IDevice) => { - const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; - return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); - }; - await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(executeAction, canExecuteAction)); + await this.$runOnDevicesController.syncChangedDataOnDevice(data, projectData, liveSyncInfo, deviceDescriptors); }); for (const platform of platforms) { @@ -102,39 +91,14 @@ export class MainController extends EventEmitter { await this.$platformWatcherService.startWatcher(nativePlatformData, projectData, preparePlatformData); } } - } - - private async syncInitialDataOnDevice(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { - const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); - - try { - const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); - const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); - await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); + // TODO: Consider how to handle --justlaunch - // TODO: Consider to improve this - const platformLiveSyncService = this.getLiveSyncService(platformData.platformNameLowerCase); - const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; - const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor }); - - await this.$deviceRefreshAppService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); - } catch (err) { - this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); - - this.emitLivesyncEvent(LiveSyncEvents.liveSyncError, { - error: err, - deviceIdentifier: device.deviceInfo.identifier, - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectIdentifiers[platformData.platformNameLowerCase] - }); - - await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier], { shouldAwaitAllActions: false }); - } + this.attachDeviceLostHandler(); } - public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { - const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectDir]; + public async stopRunOnDevices(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { + const liveSyncProcessInfo = this.$runOnDevicesDataService.getData(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. @@ -180,59 +144,37 @@ export class MainController extends EventEmitter { await liveSyncProcessInfo.currentSyncAction; } - // Emit LiveSync stopped when we've really stopped. + // Emit RunOnDevice stopped when we've really stopped. _.each(removedDeviceIdentifiers, deviceIdentifier => { - this.emitLivesyncEvent(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier }); + this.emit(RunOnDeviceEvents.runOnDeviceStopped, { projectDir, deviceIdentifier }); }); } } - public emitLivesyncEvent(event: string, livesyncData: ILiveSyncEventData): boolean { - this.$logger.trace(`Will emit event ${event} with data`, livesyncData); - return this.emit(event, livesyncData); + public getRunOnDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + return this.$runOnDevicesDataService.getDeviceDescriptors(projectDir); } - private async syncChangedDataOnDevice(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { - console.log("syncChangedDataOnDevice================ ", data); - const { nativePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); - - if (data.hasNativeChanges) { - // TODO: Consider to handle nativePluginsChange here (aar rebuilt) - await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); - } - - const platformLiveSyncService = this.getLiveSyncService(device.deviceInfo.platform); - const liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, { - liveSyncDeviceInfo: deviceDescriptor, - projectData, - filesToRemove: [], - filesToSync: data.files, - isReinstalled: false, - hmrData: null, // platformHmrData, - useHotModuleReload: liveSyncInfo.useHotModuleReload, - force: liveSyncInfo.force, - connectTimeout: 1000 + private handleRunOnDeviceEvents(projectDir: string): void { + this.$runOnDevicesController.on(RunOnDeviceEvents.runOnDeviceError, async data => { + this.emit(RunOnDeviceEvents.runOnDeviceError, data); + await this.stopRunOnDevices(projectDir, [data.deviceIdentifier], { shouldAwaitAllActions: false }); }); - await this.$deviceRefreshAppService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); - } - - public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { - const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; - const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; - return currentDescriptors || []; + this.$runOnDevicesController.on(RunOnDeviceEvents.runOnDeviceStarted, data => { + this.emit(RunOnDeviceEvents.runOnDeviceStarted, data); + }); } - private setLiveSyncProcessInfo(projectDir: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): void { - this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); - this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); - this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; - this.liveSyncProcessesInfo[projectDir].isStopped = false; - this.liveSyncProcessesInfo[projectDir].syncToPreviewApp = liveSyncInfo.syncToPreviewApp; + // TODO: expose previewOnDevice() method { } + // TODO: enableDebugging -> mainController + // TODO: disableDebugging -> mainController + // TODO: attachDebugger -> mainController + // mainController.runOnDevices(), runOnDevicesController.on("event", () => {}) - const currentDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); - this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); - } + // debugOnDevicesController.enableDebugging() + // debugOnDevicesController.disableDebugging() + // debugOnDevicesController.attachDebugger private async initializeSetup(projectData: IProjectData): Promise { try { @@ -243,30 +185,21 @@ export class MainController extends EventEmitter { } } - private async addActionToChain(projectDir: string, action: () => Promise): Promise { - const liveSyncInfo = this.liveSyncProcessesInfo[projectDir]; - if (liveSyncInfo) { - liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { - if (!liveSyncInfo.isStopped) { - liveSyncInfo.currentSyncAction = action(); - const res = await liveSyncInfo.currentSyncAction; - return res; - } - }); - - const result = await liveSyncInfo.actionsChain; - return result; - } - } - - private getLiveSyncService(platform: string): IPlatformLiveSyncService { - if (this.$mobileHelper.isiOSPlatform(platform)) { - return this.$injector.resolve("iOSLiveSyncService"); - } else if (this.$mobileHelper.isAndroidPlatform(platform)) { - return this.$injector.resolve("androidLiveSyncService"); - } - - this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); + @cache() + private attachDeviceLostHandler(): void { + 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.liveSyncProcessesInfo) { + // try { + // if (_.find(this.liveSyncProcessesInfo[projectDir].deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { + // await this.stopLiveSync(projectDir, [device.deviceInfo.identifier]); + // } + // } catch (err) { + // this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); + // } + // } + }); } } $injector.register("mainController", MainController); diff --git a/lib/controllers/run-on-device-controller.ts b/lib/controllers/run-on-device-controller.ts deleted file mode 100644 index 24c3937fa3..0000000000 --- a/lib/controllers/run-on-device-controller.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class RunOnDeviceController { - -} -$injector.register("runOnDeviceController", RunOnDeviceController); diff --git a/lib/controllers/run-on-devices-controller.ts b/lib/controllers/run-on-devices-controller.ts new file mode 100644 index 0000000000..44cd4ba668 --- /dev/null +++ b/lib/controllers/run-on-devices-controller.ts @@ -0,0 +1,139 @@ +import { WorkflowDataService } from "../services/workflow/workflow-data-service"; +import { BuildPlatformService } from "../services/platform/build-platform-service"; +import { DeviceInstallAppService } from "../services/device/device-install-app-service"; +import { DeviceRefreshAppService } from "../services/device/device-refresh-app-service"; +import { LiveSyncServiceResolver } from "../resolvers/livesync-service-resolver"; +import { EventEmitter } from "events"; +import { RunOnDevicesDataService } from "../services/run-on-devices-data-service"; +import { RunOnDeviceEvents } from "../constants"; + +export class RunOnDevicesController extends EventEmitter { + constructor( + private $buildPlatformService: BuildPlatformService, + private $deviceInstallAppService: DeviceInstallAppService, + private $deviceRefreshAppService: DeviceRefreshAppService, + private $devicesService: Mobile.IDevicesService, + public $hooksService: IHooksService, + private $liveSyncServiceResolver: LiveSyncServiceResolver, + private $logger: ILogger, + private $runOnDevicesDataService: RunOnDevicesDataService, + private $workflowDataService: WorkflowDataService + ) { super(); } + + public async syncInitialDataOnDevice(data: IInitialSyncEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + const executeAction = async (device: Mobile.IDevice) => { + const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + await this.syncInitialDataOnDeviceSafe(device, deviceDescriptor, projectData, liveSyncInfo); + }; + const canExecuteAction = (device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === data.platform.toLowerCase() && _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(executeAction, canExecuteAction)); + } + + public async syncChangedDataOnDevice(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + const executeAction = async (device: Mobile.IDevice) => { + const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + await this.syncChangedDataOnDeviceSafe(device, deviceDescriptor, data, projectData, liveSyncInfo); + }; + const canExecuteAction = (device: Mobile.IDevice) => { + const liveSyncProcessInfo = this.$runOnDevicesDataService.getData(projectData.projectDir); + return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); + }; + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(executeAction, canExecuteAction)); + } + + private async syncInitialDataOnDeviceSafe(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { + const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + + try { + const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); + const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); + + await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); + + 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 }); + + await this.$deviceRefreshAppService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); + + this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); + + this.emitRunOnDeviceEvent(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.emitRunOnDeviceEvent(RunOnDeviceEvents.runOnDeviceError, { + error: err, + deviceIdentifier: device.deviceInfo.identifier, + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectIdentifiers[platformData.platformNameLowerCase] + }); + } + } + + private async syncChangedDataOnDeviceSafe(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { + const { nativePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + + try { + if (data.hasNativeChanges) { + // TODO: Consider to handle nativePluginsChange here (aar rebuilt) + await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); + } + + const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(device.deviceInfo.platform); + const liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, { + liveSyncDeviceInfo: deviceDescriptor, + projectData, + filesToRemove: [], + filesToSync: data.files, + isReinstalled: false, + hmrData: null, // platformHmrData, + useHotModuleReload: liveSyncInfo.useHotModuleReload, + force: liveSyncInfo.force, + connectTimeout: 1000 + }); + + await this.$deviceRefreshAppService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); + } catch (err) { + const allErrors = (err).allErrors; + + 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.emitRunOnDeviceEvent(RunOnDeviceEvents.runOnDeviceError, { + error: deviceError, + deviceIdentifier: deviceError.deviceIdentifier, + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] + }); + } + } + } + } + + private async addActionToChain(projectDir: string, action: () => Promise): Promise { + const liveSyncInfo = this.$runOnDevicesDataService.getData(projectDir); + if (liveSyncInfo) { + liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { + if (!liveSyncInfo.isStopped) { + liveSyncInfo.currentSyncAction = action(); + const res = await liveSyncInfo.currentSyncAction; + return res; + } + }); + + const result = await liveSyncInfo.actionsChain; + return result; + } + } + + private emitRunOnDeviceEvent(event: string, runOnDeviceData: ILiveSyncEventData): boolean { + this.$logger.trace(`Will emit event ${event} with data`, runOnDeviceData); + return this.emit(event, runOnDeviceData); + } +} +$injector.register("runOnDevicesController", RunOnDevicesController); diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index a98056e64e..3486fd6007 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -133,7 +133,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { // return; // } - await this.$mainController.runPlatform(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); + await this.$mainController.runOnDevices(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); // const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); // this.$liveSyncService.on(LiveSyncEvents.liveSyncStopped, (data: { projectDir: string, deviceIdentifier: string }) => { diff --git a/lib/resolvers/livesync-service-resolver.ts b/lib/resolvers/livesync-service-resolver.ts new file mode 100644 index 0000000000..5d97b1d2c9 --- /dev/null +++ b/lib/resolvers/livesync-service-resolver.ts @@ -0,0 +1,18 @@ +export class LiveSyncServiceResolver { + constructor( + private $errors: IErrors, + private $injector: IInjector, + private $mobileHelper: Mobile.IMobileHelper + ) { } + + public resolveLiveSyncService(platform: string): IPlatformLiveSyncService { + if (this.$mobileHelper.isiOSPlatform(platform)) { + return this.$injector.resolve("iOSLiveSyncService"); + } else if (this.$mobileHelper.isAndroidPlatform(platform)) { + return this.$injector.resolve("androidLiveSyncService"); + } + + this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); + } +} +$injector.register("liveSyncServiceResolver", LiveSyncServiceResolver); diff --git a/lib/services/device/device-refresh-app-service.ts b/lib/services/device/device-refresh-app-service.ts index 10af4f1ab7..9e121bffa8 100644 --- a/lib/services/device/device-refresh-app-service.ts +++ b/lib/services/device/device-refresh-app-service.ts @@ -1,6 +1,6 @@ import { performanceLog } from "../../common/decorators"; import { EventEmitter } from "events"; -import { DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, LiveSyncEvents, DEBUGGER_ATTACHED_EVENT_NAME } from "../../constants"; +import { DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, RunOnDeviceEvents, DEBUGGER_ATTACHED_EVENT_NAME } from "../../constants"; import { EOL } from "os"; export class DeviceRefreshAppService { @@ -66,7 +66,7 @@ export class DeviceRefreshAppService { const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; this.$logger.warn(msg); if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { - eventEmitter.emit(LiveSyncEvents.liveSyncNotification, { + eventEmitter.emit(RunOnDeviceEvents.runOnDeviceNotification, { projectDir: projectData.projectDir, applicationIdentifier, deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, @@ -79,7 +79,7 @@ export class DeviceRefreshAppService { } } - eventEmitter.emit(LiveSyncEvents.liveSyncExecuted, { + eventEmitter.emit(RunOnDeviceEvents.runOnDeviceExecuted, { projectDir: projectData.projectDir, applicationIdentifier, syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), diff --git a/lib/services/run-on-devices-data-service.ts b/lib/services/run-on-devices-data-service.ts new file mode 100644 index 0000000000..7c1c1bf219 --- /dev/null +++ b/lib/services/run-on-devices-data-service.ts @@ -0,0 +1,29 @@ +export class RunOnDevicesDataService { + private liveSyncProcessesInfo: IDictionary = {}; + + public getData(projectDir: string): ILiveSyncProcessInfo { + return this.liveSyncProcessesInfo[projectDir]; + } + + public getDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; + const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; + return currentDescriptors || []; + } + + public hasDeviceDescriptors(projectDir: string) { + return this.liveSyncProcessesInfo[projectDir].deviceDescriptors.length; + } + + public persistData(projectDir: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): void { + this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); + this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); + this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; + this.liveSyncProcessesInfo[projectDir].isStopped = false; + this.liveSyncProcessesInfo[projectDir].syncToPreviewApp = liveSyncInfo.syncToPreviewApp; + + const currentDeviceDescriptors = this.getDeviceDescriptors(projectDir); + this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); + } +} +$injector.register("runOnDevicesDataService", RunOnDevicesDataService); diff --git a/test/controllers/main-controller.ts b/test/controllers/main-controller.ts index da2902347a..9286669c2a 100644 --- a/test/controllers/main-controller.ts +++ b/test/controllers/main-controller.ts @@ -101,7 +101,7 @@ describe("MainController", () => { }; const mainController: MainController = injector.resolve("mainController"); - await mainController.runPlatform(projectDir, [iOSDeviceDescriptor], liveSyncInfo); + await mainController.runOnDevices(projectDir, [iOSDeviceDescriptor], liveSyncInfo); assert.isTrue(isStartWatcherCalled); }); From 64427e9f9474670582292582d8ceb93643edc24f Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 4 May 2019 18:23:59 +0300 Subject: [PATCH 11/30] refactor: extract debug specific logic to separate service --- lib/bootstrap.ts | 3 + .../debug-on-devices-controller.ts | 0 lib/controllers/run-on-devices-controller.ts | 60 ++++--- lib/run-on-devices-emitter.ts | 72 ++++++++ .../device/device-debug-app-service.ts | 103 ++++++++++++ .../device/device-refresh-app-service.ts | 155 ++---------------- .../platform-livesync-service-base.ts | 6 - 7 files changed, 224 insertions(+), 175 deletions(-) create mode 100644 lib/controllers/debug-on-devices-controller.ts create mode 100644 lib/run-on-devices-emitter.ts create mode 100644 lib/services/device/device-debug-app-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 8e2afddc02..323dc518e3 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -44,8 +44,11 @@ $injector.require("platformWatcherService", "./services/platform/platform-watche $injector.require("buildArtefactsService", "./services/build-artefacts-service"); $injector.require("runOnDevicesDataService", "./services/run-on-devices-data-service"); +$injector.require("runOnDevicesEmitter", "./run-on-devices-emitter"); + $injector.require("deviceInstallAppService", "./services/device/device-install-app-service"); $injector.require("deviceRefreshAppService", "./services/device/device-refresh-app-service"); +$injector.require("deviceDebugAppService", "./services/device/device-debug-app-service"); $injector.require("workflowDataService", "./services/workflow/workflow-data-service"); diff --git a/lib/controllers/debug-on-devices-controller.ts b/lib/controllers/debug-on-devices-controller.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/controllers/run-on-devices-controller.ts b/lib/controllers/run-on-devices-controller.ts index 44cd4ba668..dbd69a05a6 100644 --- a/lib/controllers/run-on-devices-controller.ts +++ b/lib/controllers/run-on-devices-controller.ts @@ -1,15 +1,17 @@ -import { WorkflowDataService } from "../services/workflow/workflow-data-service"; import { BuildPlatformService } from "../services/platform/build-platform-service"; +import { DeviceDebugAppService } from "../services/device/device-debug-app-service"; import { DeviceInstallAppService } from "../services/device/device-install-app-service"; import { DeviceRefreshAppService } from "../services/device/device-refresh-app-service"; -import { LiveSyncServiceResolver } from "../resolvers/livesync-service-resolver"; import { EventEmitter } from "events"; +import { LiveSyncServiceResolver } from "../resolvers/livesync-service-resolver"; import { RunOnDevicesDataService } from "../services/run-on-devices-data-service"; -import { RunOnDeviceEvents } from "../constants"; +import { RunOnDevicesEmitter } from "../run-on-devices-emitter"; +import { WorkflowDataService } from "../services/workflow/workflow-data-service"; export class RunOnDevicesController extends EventEmitter { constructor( private $buildPlatformService: BuildPlatformService, + private $deviceDebugAppService: DeviceDebugAppService, private $deviceInstallAppService: DeviceInstallAppService, private $deviceRefreshAppService: DeviceRefreshAppService, private $devicesService: Mobile.IDevicesService, @@ -17,6 +19,7 @@ export class RunOnDevicesController extends EventEmitter { private $liveSyncServiceResolver: LiveSyncServiceResolver, private $logger: ILogger, private $runOnDevicesDataService: RunOnDevicesDataService, + private $runOnDevicesEmitter: RunOnDevicesEmitter, private $workflowDataService: WorkflowDataService ) { super(); } @@ -54,24 +57,26 @@ export class RunOnDevicesController extends EventEmitter { const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor }); - await this.$deviceRefreshAppService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); + const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor); + + this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + isFullSync: liveSyncResultInfo.isFullSync + }); + + if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) { + await this.$deviceDebugAppService.refreshApplicationWithDebug(projectData, deviceDescriptor, refreshInfo); + } this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - this.emitRunOnDeviceEvent(RunOnDeviceEvents.runOnDeviceStarted, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] - }); + this.$runOnDevicesEmitter.emitRunOnDeviceStartedEvent(projectData, device); } catch (err) { this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); - this.emitRunOnDeviceEvent(RunOnDeviceEvents.runOnDeviceError, { - error: err, - deviceIdentifier: device.deviceInfo.identifier, - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectIdentifiers[platformData.platformNameLowerCase] - }); + this.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, err); + + // TODO: Consider to call here directly stopRunOnDevices } } @@ -97,19 +102,25 @@ export class RunOnDevicesController extends EventEmitter { connectTimeout: 1000 }); - await this.$deviceRefreshAppService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this); + const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor); + + this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + isFullSync: liveSyncResultInfo.isFullSync + }); + + if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) { + await this.$deviceDebugAppService.refreshApplicationWithDebug(projectData, deviceDescriptor, refreshInfo); + } + + this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); } catch (err) { const allErrors = (err).allErrors; 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.emitRunOnDeviceEvent(RunOnDeviceEvents.runOnDeviceError, { - error: deviceError, - deviceIdentifier: deviceError.deviceIdentifier, - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] - }); + this.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, deviceError); } } } @@ -130,10 +141,5 @@ export class RunOnDevicesController extends EventEmitter { return result; } } - - private emitRunOnDeviceEvent(event: string, runOnDeviceData: ILiveSyncEventData): boolean { - this.$logger.trace(`Will emit event ${event} with data`, runOnDeviceData); - return this.emit(event, runOnDeviceData); - } } $injector.register("runOnDevicesController", RunOnDevicesController); diff --git a/lib/run-on-devices-emitter.ts b/lib/run-on-devices-emitter.ts new file mode 100644 index 0000000000..9507e7b575 --- /dev/null +++ b/lib/run-on-devices-emitter.ts @@ -0,0 +1,72 @@ +import { EventEmitter } from "events"; +import { RunOnDeviceEvents, DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "./constants"; + +export class RunOnDevicesEmitter extends EventEmitter { + constructor( + private $logger: ILogger + ) { super(); } + + public emitRunOnDeviceStartedEvent(projectData: IProjectData, device: Mobile.IDevice) { + this.emitCore(RunOnDeviceEvents.runOnDeviceStarted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] + }); + } + + public emitRunOnDeviceNotificationEvent(projectData: IProjectData, device: Mobile.IDevice, notification: string) { + this.emitCore(RunOnDeviceEvents.runOnDeviceNotification, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], + notification + }); + } + + public emitRunOnDeviceErrorEvent(projectData: IProjectData, device: Mobile.IDevice, error: Error) { + this.emitCore(RunOnDeviceEvents.runOnDeviceError, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], + error, + }); + } + + public emitRunOnDeviceExecutedEvent(projectData: IProjectData, device: Mobile.IDevice, options: { syncedFiles: string[], isFullSync: boolean }) { + 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 emitDebuggerAttachedEvent(debugInformation: IDebugInformation) { + this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); + } + + public emitDebuggerDetachedEvent(device: Mobile.IDevice) { + const deviceIdentifier = device.deviceInfo.identifier; + this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); + } + + public emitUserInteractionNeededEvent(projectData: IProjectData, device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo) { + 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("runOnDevicesEmitter", RunOnDevicesEmitter); diff --git a/lib/services/device/device-debug-app-service.ts b/lib/services/device/device-debug-app-service.ts new file mode 100644 index 0000000000..3abce5a5cc --- /dev/null +++ b/lib/services/device/device-debug-app-service.ts @@ -0,0 +1,103 @@ +import { performanceLog } from "../../common/decorators"; +import { RunOnDevicesEmitter } from "../../run-on-devices-emitter"; +import { EOL } from "os"; + +export class DeviceDebugAppService { + constructor( + private $debugDataService: IDebugDataService, + private $debugService: IDebugService, + private $devicesService: Mobile.IDevicesService, + private $errors: IErrors, + private $logger: ILogger, + private $projectDataService: IProjectDataService, + private $runOnDevicesEmitter: RunOnDevicesEmitter + ) { } + + @performanceLog() + public async refreshApplicationWithDebug(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, + }; + + return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, deviceDescriptor, { projectDir: projectData.projectDir }); + } + + public async attachDebugger(settings: IAttachDebuggerOptions): Promise { + // Default values + if (settings.debugOptions) { + settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; + settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; + } else { + settings.debugOptions = { + chrome: true, + start: true + }; + } + + const projectData = this.$projectDataService.getProjectData(settings.projectDir); + const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); + // const platformData = this.$platformsData.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. + // debugData.pathToAppPackage = this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildConfig, settings.outputPath); + const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); + const result = this.printDebugInformation(debugInfo, settings.debugOptions.forceDebuggerAttachedEvent); + return result; + } + + public printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean = true): IDebugInformation { + if (!!debugInformation.url) { + if (fireDebuggerAttachedEvent) { + this.$runOnDevicesEmitter.emitDebuggerAttachedEvent(debugInformation); + } + + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan); + } + + return debugInformation; + } + + @performanceLog() + private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, deviceDescriptor: ILiveSyncDeviceInfo, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + if (!deviceDescriptor) { + this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); + } + + deviceDescriptor.debugggingEnabled = true; + deviceDescriptor.debugOptions = deviceOption.debugOptions; + const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); + const attachDebuggerOptions: IAttachDebuggerOptions = { + deviceIdentifier: deviceOption.deviceIdentifier, + isEmulator: currentDeviceInstance.isEmulator, + outputPath: deviceDescriptor.outputPath, + platform: currentDeviceInstance.deviceInfo.platform, + projectDir: debuggingAdditionalOptions.projectDir, + debugOptions: deviceOption.debugOptions + }; + + let debugInformation: IDebugInformation; + try { + debugInformation = await this.attachDebugger(attachDebuggerOptions); + } catch (err) { + this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); + attachDebuggerOptions.debugOptions.start = false; + try { + debugInformation = await this.attachDebugger(attachDebuggerOptions); + } catch (innerErr) { + this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); + throw err; + } + } + + return debugInformation; + } +} +$injector.register("deviceDebugAppService", DeviceDebugAppService); diff --git a/lib/services/device/device-refresh-app-service.ts b/lib/services/device/device-refresh-app-service.ts index 9e121bffa8..7786118110 100644 --- a/lib/services/device/device-refresh-app-service.ts +++ b/lib/services/device/device-refresh-app-service.ts @@ -1,54 +1,26 @@ import { performanceLog } from "../../common/decorators"; -import { EventEmitter } from "events"; -import { DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, RunOnDeviceEvents, DEBUGGER_ATTACHED_EVENT_NAME } from "../../constants"; -import { EOL } from "os"; +import { RunOnDevicesEmitter } from "../../run-on-devices-emitter"; +import { LiveSyncServiceResolver } from "../../resolvers/livesync-service-resolver"; export class DeviceRefreshAppService { constructor( - // private $buildArtefactsService: IBuildArtefactsService, - private $debugDataService: IDebugDataService, - private $debugService: IDebugService, - private $devicesService: Mobile.IDevicesService, - private $errors: IErrors, + private $liveSyncServiceResolver: LiveSyncServiceResolver, private $logger: ILogger, - // private $platformsData: IPlatformsData, - private $projectDataService: IProjectDataService + private $runOnDevicesEmitter: RunOnDevicesEmitter ) { } @performanceLog() - public async refreshApplication(deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, platformLiveSyncService: IPlatformLiveSyncService, eventEmitter: EventEmitter): Promise { - return liveSyncResultInfo && deviceDescriptor.debugggingEnabled ? - this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, deviceDescriptor, platformLiveSyncService, eventEmitter) : - this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor, platformLiveSyncService, eventEmitter); - } - - @performanceLog() - public async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, platformLiveSyncService: IPlatformLiveSyncService, eventEmitter: EventEmitter): Promise { - const { debugOptions } = deviceDescriptor; - if (debugOptions.debugBrk) { - liveSyncResultInfo.waitForDebugger = true; + public async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, settings?: IRefreshApplicationSettings): Promise { + if (deviceDescriptor && deviceDescriptor.debugggingEnabled) { + liveSyncResultInfo.waitForDebugger = deviceDescriptor.debugOptions && deviceDescriptor.debugOptions.debugBrk; } - const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor, platformLiveSyncService, eventEmitter, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); - - // 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: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - debugOptions: debugOptions, - }; - - return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, deviceDescriptor, { projectDir: projectData.projectDir }); - } - - public async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, platformLiveSyncService: IPlatformLiveSyncService, eventEmitter: EventEmitter, 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); if (!shouldRestart) { @@ -56,8 +28,7 @@ export class DeviceRefreshAppService { } if (shouldRestart) { - const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; - eventEmitter.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); + this.$runOnDevicesEmitter.emitDebuggerDetachedEvent(liveSyncResultInfo.deviceAppData.device); await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); result.didRestart = true; } @@ -66,115 +37,15 @@ export class DeviceRefreshAppService { const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; this.$logger.warn(msg); if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { - eventEmitter.emit(RunOnDeviceEvents.runOnDeviceNotification, { - projectDir: projectData.projectDir, - applicationIdentifier, - deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - notification: msg - }); + this.$runOnDevicesEmitter.emitRunOnDeviceNotificationEvent(projectData, liveSyncResultInfo.deviceAppData.device, msg); } - if (settings && settings.shouldCheckDeveloperDiscImage) { - this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, deviceDescriptor, eventEmitter); + if (settings && settings.shouldCheckDeveloperDiscImage && (err.message || err) === "Could not find developer disk image") { + this.$runOnDevicesEmitter.emitUserInteractionNeededEvent(projectData, liveSyncResultInfo.deviceAppData.device, deviceDescriptor); } } - eventEmitter.emit(RunOnDeviceEvents.runOnDeviceExecuted, { - projectDir: projectData.projectDir, - applicationIdentifier, - syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), - deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - isFullSync: liveSyncResultInfo.isFullSync - }); - return result; } - - // TODO: This should be into separate class - public async attachDebugger(settings: IAttachDebuggerOptions): Promise { - // Default values - if (settings.debugOptions) { - settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; - settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; - } else { - settings.debugOptions = { - chrome: true, - start: true - }; - } - - const projectData = this.$projectDataService.getProjectData(settings.projectDir); - const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); - // const platformData = this.$platformsData.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. - // debugData.pathToAppPackage = this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildConfig, settings.outputPath); - const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); - const result = this.printDebugInformation(debugInfo, null, settings.debugOptions.forceDebuggerAttachedEvent); - return result; - } - - public printDebugInformation(debugInformation: IDebugInformation, eventEmitter: EventEmitter, fireDebuggerAttachedEvent: boolean = true): IDebugInformation { - if (!!debugInformation.url) { - if (fireDebuggerAttachedEvent) { - eventEmitter.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; - } - - @performanceLog() - private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, deviceDescriptor: ILiveSyncDeviceInfo, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { - if (!deviceDescriptor) { - this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); - } - - deviceDescriptor.debugggingEnabled = true; - deviceDescriptor.debugOptions = deviceOption.debugOptions; - const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); - const attachDebuggerOptions: IAttachDebuggerOptions = { - deviceIdentifier: deviceOption.deviceIdentifier, - isEmulator: currentDeviceInstance.isEmulator, - outputPath: deviceDescriptor.outputPath, - platform: currentDeviceInstance.deviceInfo.platform, - projectDir: debuggingAdditionalOptions.projectDir, - debugOptions: deviceOption.debugOptions - }; - - let debugInformation: IDebugInformation; - try { - debugInformation = await this.attachDebugger(attachDebuggerOptions); - } catch (err) { - this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); - attachDebuggerOptions.debugOptions.start = false; - try { - debugInformation = await this.attachDebugger(attachDebuggerOptions); - } catch (innerErr) { - this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); - throw err; - } - } - - return debugInformation; - } - - private handleDeveloperDiskImageError(err: any, liveSyncResultInfo: ILiveSyncResultInfo, projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, eventEmitter: EventEmitter) { - if ((err.message || err) === "Could not find developer disk image") { - const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; - const attachDebuggerOptions: IAttachDebuggerOptions = { - platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, - isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, - projectDir: projectData.projectDir, - deviceIdentifier, - debugOptions: deviceDescriptor.debugOptions, - outputPath: deviceDescriptor.outputPath - }; - eventEmitter.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); - } - } } $injector.register("deviceRefreshAppService", DeviceRefreshAppService); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index ee5edd1506..c32e9beed7 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -64,17 +64,11 @@ export abstract class PlatformLiveSyncServiceBase { 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 }); - let waitForDebugger = null; - if (syncInfo.liveSyncDeviceInfo && syncInfo.liveSyncDeviceInfo.debugggingEnabled) { - waitForDebugger = syncInfo.liveSyncDeviceInfo && syncInfo.liveSyncDeviceInfo.debugOptions && syncInfo.liveSyncDeviceInfo.debugOptions.debugBrk; - } - return { modifiedFilesData, isFullSync: true, deviceAppData, useHotModuleReload: syncInfo.useHotModuleReload, - waitForDebugger }; } From f2caa9d52acdd030fd2b338ccec76cf9800679f5 Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 4 May 2019 21:48:59 +0300 Subject: [PATCH 12/30] chore: emit runOnDeviceStopperEvent --- lib/bootstrap.ts | 7 +++--- lib/controllers/main-controller.ts | 28 ++++++++++----------- lib/run-on-devices-emitter.ts | 7 ++++++ lib/services/run-on-devices-data-service.ts | 4 +++ 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 323dc518e3..f7fc07c85e 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -43,14 +43,13 @@ $injector.require("platformWatcherService", "./services/platform/platform-watche $injector.require("buildArtefactsService", "./services/build-artefacts-service"); -$injector.require("runOnDevicesDataService", "./services/run-on-devices-data-service"); -$injector.require("runOnDevicesEmitter", "./run-on-devices-emitter"); - +$injector.require("deviceDebugAppService", "./services/device/device-debug-app-service"); $injector.require("deviceInstallAppService", "./services/device/device-install-app-service"); $injector.require("deviceRefreshAppService", "./services/device/device-refresh-app-service"); -$injector.require("deviceDebugAppService", "./services/device/device-debug-app-service"); $injector.require("workflowDataService", "./services/workflow/workflow-data-service"); +$injector.require("runOnDevicesDataService", "./services/run-on-devices-data-service"); +$injector.require("runOnDevicesEmitter", "./run-on-devices-emitter"); $injector.require("mainController", "./controllers/main-controller"); $injector.require("runOnDevicesController", "./controllers/run-on-devices-controller"); diff --git a/lib/controllers/main-controller.ts b/lib/controllers/main-controller.ts index 4563ba6588..b06c118700 100644 --- a/lib/controllers/main-controller.ts +++ b/lib/controllers/main-controller.ts @@ -9,6 +9,7 @@ import { RunOnDevicesController } from "./run-on-devices-controller"; import { RunOnDevicesDataService } from "../services/run-on-devices-data-service"; import { cache } from "../common/decorators"; import { DeviceDiscoveryEventNames } from "../common/constants"; +import { RunOnDevicesEmitter } from "../run-on-devices-emitter"; export class MainController extends EventEmitter { constructor( @@ -25,6 +26,7 @@ export class MainController extends EventEmitter { private $projectDataService: IProjectDataService, private $runOnDevicesController: RunOnDevicesController, private $runOnDevicesDataService: RunOnDevicesDataService, + private $runOnDevicesEmitter: RunOnDevicesEmitter, private $workflowDataService: WorkflowDataService ) { super(); } @@ -146,7 +148,7 @@ export class MainController extends EventEmitter { // Emit RunOnDevice stopped when we've really stopped. _.each(removedDeviceIdentifiers, deviceIdentifier => { - this.emit(RunOnDeviceEvents.runOnDeviceStopped, { projectDir, deviceIdentifier }); + this.$runOnDevicesEmitter.emitRunOnDeviceStoppedEvent(projectDir, deviceIdentifier); }); } } @@ -157,13 +159,8 @@ export class MainController extends EventEmitter { private handleRunOnDeviceEvents(projectDir: string): void { this.$runOnDevicesController.on(RunOnDeviceEvents.runOnDeviceError, async data => { - this.emit(RunOnDeviceEvents.runOnDeviceError, data); await this.stopRunOnDevices(projectDir, [data.deviceIdentifier], { shouldAwaitAllActions: false }); }); - - this.$runOnDevicesController.on(RunOnDeviceEvents.runOnDeviceStarted, data => { - this.emit(RunOnDeviceEvents.runOnDeviceStarted, data); - }); } // TODO: expose previewOnDevice() method { } @@ -190,15 +187,16 @@ export class MainController extends EventEmitter { 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.liveSyncProcessesInfo) { - // try { - // if (_.find(this.liveSyncProcessesInfo[projectDir].deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { - // await this.stopLiveSync(projectDir, [device.deviceInfo.identifier]); - // } - // } catch (err) { - // this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); - // } - // } + for (const projectDir in this.$runOnDevicesDataService.getAllData()) { + try { + const deviceDescriptors = this.$runOnDevicesDataService.getDeviceDescriptors(projectDir); + if (_.find(deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { + await this.stopRunOnDevices(projectDir, [device.deviceInfo.identifier]); + } + } catch (err) { + this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); + } + } }); } } diff --git a/lib/run-on-devices-emitter.ts b/lib/run-on-devices-emitter.ts index 9507e7b575..55ed8e3ed1 100644 --- a/lib/run-on-devices-emitter.ts +++ b/lib/run-on-devices-emitter.ts @@ -42,6 +42,13 @@ export class RunOnDevicesEmitter extends EventEmitter { }); } + public emitRunOnDeviceStoppedEvent(projectDir: string, deviceIdentifier: string) { + this.emitCore(RunOnDeviceEvents.runOnDeviceStopped, { + projectDir, + deviceIdentifier + }); + } + public emitDebuggerAttachedEvent(debugInformation: IDebugInformation) { this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); } diff --git a/lib/services/run-on-devices-data-service.ts b/lib/services/run-on-devices-data-service.ts index 7c1c1bf219..6e100b4ec4 100644 --- a/lib/services/run-on-devices-data-service.ts +++ b/lib/services/run-on-devices-data-service.ts @@ -5,6 +5,10 @@ export class RunOnDevicesDataService { return this.liveSyncProcessesInfo[projectDir]; } + public getAllData(): IDictionary { + return this.liveSyncProcessesInfo; + } + public getDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; From 2b8d62ccc97e1865a9100a8407841bc875cd3e1c Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 4 May 2019 22:27:12 +0300 Subject: [PATCH 13/30] feat: add env support to webpackCompilerService --- .vscode/launch.json | 2 +- lib/definitions/livesync.d.ts | 3 +- lib/definitions/platform.d.ts | 8 --- lib/helpers/livesync-command-helper.ts | 4 +- .../platform/platform-watcher-service.ts | 1 - .../webpack/webpack-compiler-service.ts | 58 +++++++++++++++++-- .../workflow/workflow-data-service.ts | 47 ++++++++++----- test/controllers/main-controller.ts | 4 +- test/tns-appstore-upload.ts | 2 +- 9 files changed, 90 insertions(+), 39 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index cfe5787073..8a06a76d19 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "program": "${workspaceRoot}/lib/nativescript-cli.js", // example commands - "args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle"] + "args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle", "--env.sourceMap"] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] // "args": [ "platform", "remove", "android", "--path", "cliapp"] diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 5adaca731b..44e618491e 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -141,8 +141,7 @@ declare global { /** * Describes a LiveSync operation. */ - interface ILiveSyncInfo extends IProjectDir, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { - webpackCompilerConfig: IWebpackCompilerConfig; + interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { emulator?: boolean; /** diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index d275a1ae66..f8f69d9d31 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -106,10 +106,6 @@ interface IPreparePlatformJSInfo extends IPreparePlatformCoreInfo, ICopyAppFiles interface IPlatformOptions extends IRelease, IHasUseHotModuleReloadOption { } -interface IShouldPrepareInfo extends IOptionalProjectChangesInfoComposition { - platformInfo: IPreparePlatformInfo; -} - interface IOptionalProjectChangesInfoComposition { changesInfo?: IProjectChangesInfo; } @@ -118,10 +114,6 @@ interface IPreparePlatformCoreInfo extends IPreparePlatformInfoBase, IOptionalPr platformSpecificData: IPlatformSpecificData; } -interface IPreparePlatformInfo extends IPreparePlatformInfoBase, IPlatformConfig { - webpackCompilerConfig: IWebpackCompilerConfig; -} - interface IPlatformConfig { config: IPlatformOptions; } diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 3486fd6007..70a8568a4a 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -118,9 +118,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { skipWatcher: !this.$options.watch, clean: this.$options.clean, release: this.$options.release, - webpackCompilerConfig: { - env: this.$options.env - }, + env: this.$options.env, timeout: this.$options.timeout, useHotModuleReload: this.$options.hmr, force: this.$options.force, diff --git a/lib/services/platform/platform-watcher-service.ts b/lib/services/platform/platform-watcher-service.ts index b28ab63cfc..6d4e449ac4 100644 --- a/lib/services/platform/platform-watcher-service.ts +++ b/lib/services/platform/platform-watcher-service.ts @@ -45,7 +45,6 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { this.$webpackCompilerService.on("webpackEmittedFiles", files => { - console.log("=============== WEBPACK EMITTED FILES ============"); this.emitFilesChangeEvent({ files, hasNativeChanges: false, platform: platformData.platformNameLowerCase }); }); diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index e6fdd96410..23f72c4c0c 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -6,7 +6,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp private webpackProcesses: IDictionary = {}; constructor( - private $childProcess: IChildProcess + private $childProcess: IChildProcess, + private $projectData: IProjectData ) { super(); } public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { @@ -34,7 +35,6 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const files = message.emittedFiles .filter((file: string) => file.indexOf("App_Resources") === -1) .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)); - console.log("===================== BEFORE EMIT webpack files ", files); this.emit("webpackEmittedFiles", files); } }); @@ -76,20 +76,20 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp } private startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): child_process.ChildProcess { + const envData = this.buildEnvData(platformData.platformNameLowerCase, config.env); + const envParams = this.buildEnvCommandLineParams(envData); + const args = [ path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), "--preserve-symlinks", `--config=${path.join(projectData.projectDir, "webpack.config.js")}`, - `--env.${platformData.normalizedPlatformName.toLowerCase()}` - // `--env.unitTesting` + ...envParams ]; if (config.watch) { args.push("--watch"); } - // TODO: provide env variables - const stdio = config.watch ? ["inherit", "inherit", "inherit", "ipc"] : "inherit"; const childProcess = this.$childProcess.spawn("node", args, { cwd: projectData.projectDir, stdio }); @@ -97,5 +97,51 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return childProcess; } + + private buildEnvData(platform: string, env: any) { + const envData = Object.assign({}, + env, + { [platform.toLowerCase()]: true } + ); + + const appPath = this.$projectData.getAppDirectoryRelativePath(); + const appResourcesPath = this.$projectData.getAppResourcesRelativeDirectoryPath(); + Object.assign(envData, + appPath && { appPath }, + appResourcesPath && { appResourcesPath } + ); + + return envData; + } + + private buildEnvCommandLineParams(envData: any) { + const envFlagNames = Object.keys(envData); + // const snapshotEnvIndex = envFlagNames.indexOf("snapshot"); + // if (snapshotEnvIndex > -1 && !utils.shouldSnapshot(config)) { + // logSnapshotWarningMessage($logger); + // envFlagNames.splice(snapshotEnvIndex, 1); + // } + + const args: any[] = []; + envFlagNames.map(item => { + let envValue = envData[item]; + if (typeof envValue === "undefined") { + return; + } + if (typeof envValue === "boolean") { + if (envValue) { + args.push(`--env.${item}`); + } + } else { + if (!Array.isArray(envValue)) { + envValue = [envValue]; + } + + envValue.map((value: any) => args.push(`--env.${item}=${value}`)) + } + }); + + return args; + } } $injector.register("webpackCompilerService", WebpackCompilerService); diff --git a/lib/services/workflow/workflow-data-service.ts b/lib/services/workflow/workflow-data-service.ts index 369593bd83..269e010921 100644 --- a/lib/services/workflow/workflow-data-service.ts +++ b/lib/services/workflow/workflow-data-service.ts @@ -1,4 +1,6 @@ export type AddPlatformData = Pick & Partial> & Partial>; +export type PreparePlatformData = Pick & Pick; +export type IOSPrepareData = PreparePlatformData & Pick & Pick; export class WorkflowDataService { constructor( @@ -15,7 +17,7 @@ export class WorkflowDataService { projectData, nativePlatformData, addPlatformData: this.getAddPlatformData("ios", options), - preparePlatformData: new PreparePlatformData(options), + preparePlatformData: this.getIOSPrepareData(options), buildPlatformData: new IOSBuildData(options), deployPlatformData: new DeployPlatformData(options), liveSyncData: {}, @@ -25,7 +27,7 @@ export class WorkflowDataService { projectData, nativePlatformData, addPlatformData: this.getAddPlatformData("android", options), - preparePlatformData: new PreparePlatformData(options), + preparePlatformData: this.getPreparePlatformData(options), buildPlatformData: new AndroidBuildData(options), deployPlatformData: new DeployPlatformData(options), liveSyncData: {}, @@ -43,6 +45,23 @@ export class WorkflowDataService { platformParam: options.platformParam || platform, }; } + + private getPreparePlatformData(options: IOptions | any) { + return { + env: options.env, + release: options.release, + nativePrepare: options.nativePrepare + }; + } + + private getIOSPrepareData(options: IOptions | any) { + return { + ...this.getPreparePlatformData(options), + teamId: options.teamId, + provision: options.provision, + mobileProvisionData: options.mobileProvisionData + }; + } } $injector.register("workflowDataService", WorkflowDataService); @@ -65,21 +84,21 @@ export class WorkflowData { // public nativePrepare = this.options.nativePrepare; // } -export class PreparePlatformData { - constructor(protected options: IOptions | any) { } +// export class PreparePlatformData { +// constructor(protected options: IOptions | any) { } - public env = this.options.env; - public release = this.options.release; - public nativePrepare = this.options.nativePrepare; -} +// public env = this.options.env; +// public release = this.options.release; +// public nativePrepare = this.options.nativePrepare; +// } -export class IOSPrepareData extends PreparePlatformData { - constructor(options: IOptions | any) { super(options); } +// export class IOSPrepareData extends PreparePlatformData { +// constructor(options: IOptions | any) { super(options); } - public teamId = this.options.teamId; - public provision = this.options.provision; - public mobileProvisionData = this.options.mobileProvisionData; -} +// public teamId = this.options.teamId; +// public provision = this.options.provision; +// public mobileProvisionData = this.options.mobileProvisionData; +// } export class BuildPlatformDataBase { constructor(protected options: IOptions | any) { } diff --git a/test/controllers/main-controller.ts b/test/controllers/main-controller.ts index 9286669c2a..096ee10665 100644 --- a/test/controllers/main-controller.ts +++ b/test/controllers/main-controller.ts @@ -69,9 +69,7 @@ const liveSyncInfo = { projectDir, release: false, useHotModuleReload: false, - webpackCompilerConfig: { - env: {}, - } + env: {} }; describe("MainController", () => { diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index f6225139f9..9a0d69e0f4 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -104,7 +104,7 @@ class AppStore { expectPreparePlatform() { this.expectedPreparePlatformCalls = 1; - this.platformService.preparePlatform = (platformInfo: IPreparePlatformInfo) => { + this.platformService.preparePlatform = (platformInfo: any) => { chai.assert.equal(platformInfo.platform, "iOS"); this.preparePlatformCalls++; return Promise.resolve(true); From 042a04382c975abcea64a7e09be24a5918b26e56 Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 5 May 2019 06:54:29 +0300 Subject: [PATCH 14/30] feat: add support for fallbackFiles + hmr mode --- .vscode/launch.json | 2 +- lib/controllers/run-on-devices-controller.ts | 53 +++++++++++++------ .../device/device-debug-app-service.ts | 2 +- .../platform/platform-watcher-service.ts | 6 +-- .../webpack/webpack-compiler-service.ts | 42 ++++++++++++++- lib/services/webpack/webpack.d.ts | 1 + .../workflow/workflow-data-service.ts | 14 +++-- 7 files changed, 95 insertions(+), 25 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8a06a76d19..45ce643d97 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "program": "${workspaceRoot}/lib/nativescript-cli.js", // example commands - "args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle", "--env.sourceMap"] + "args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--hmr"] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] // "args": [ "platform", "remove", "android", "--path", "cliapp"] diff --git a/lib/controllers/run-on-devices-controller.ts b/lib/controllers/run-on-devices-controller.ts index dbd69a05a6..61a913895d 100644 --- a/lib/controllers/run-on-devices-controller.ts +++ b/lib/controllers/run-on-devices-controller.ts @@ -7,6 +7,7 @@ import { LiveSyncServiceResolver } from "../resolvers/livesync-service-resolver" import { RunOnDevicesDataService } from "../services/run-on-devices-data-service"; import { RunOnDevicesEmitter } from "../run-on-devices-emitter"; import { WorkflowDataService } from "../services/workflow/workflow-data-service"; +import { HmrConstants } from "../common/constants"; export class RunOnDevicesController extends EventEmitter { constructor( @@ -15,6 +16,7 @@ export class RunOnDevicesController extends EventEmitter { private $deviceInstallAppService: DeviceInstallAppService, private $deviceRefreshAppService: DeviceRefreshAppService, private $devicesService: Mobile.IDevicesService, + private $hmrStatusService: IHmrStatusService, public $hooksService: IHooksService, private $liveSyncServiceResolver: LiveSyncServiceResolver, private $logger: ILogger, @@ -65,7 +67,7 @@ export class RunOnDevicesController extends EventEmitter { }); if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) { - await this.$deviceDebugAppService.refreshApplicationWithDebug(projectData, deviceDescriptor, refreshInfo); + await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo); } this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); @@ -89,28 +91,36 @@ export class RunOnDevicesController extends EventEmitter { await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); } + const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; + if (isInHMRMode) { + this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); + } + const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(device.deviceInfo.platform); - const liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, { + const watchInfo = { liveSyncDeviceInfo: deviceDescriptor, projectData, - filesToRemove: [], + filesToRemove: [], filesToSync: data.files, isReinstalled: false, - hmrData: null, // platformHmrData, + hmrData: data.hmrData, useHotModuleReload: liveSyncInfo.useHotModuleReload, force: liveSyncInfo.force, connectTimeout: 1000 - }); - - const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor); - - this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { - syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), - isFullSync: liveSyncResultInfo.isFullSync - }); - - if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) { - await this.$deviceDebugAppService.refreshApplicationWithDebug(projectData, deviceDescriptor, refreshInfo); + }; + let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); + + await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + + if (!liveSyncResultInfo.didRecover && isInHMRMode) { + const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); + if (status === HmrConstants.HMR_ERROR_STATUS) { + watchInfo.filesToSync = data.hmrData.fallbackFiles; + liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); + // We want to force a restart of the application. + liveSyncResultInfo.isFullSync = true; + await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + } } this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); @@ -126,6 +136,19 @@ export class RunOnDevicesController extends EventEmitter { } } + private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo) { + const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor); + + this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + isFullSync: liveSyncResultInfo.isFullSync + }); + + if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) { + await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo); + } + } + private async addActionToChain(projectDir: string, action: () => Promise): Promise { const liveSyncInfo = this.$runOnDevicesDataService.getData(projectDir); if (liveSyncInfo) { diff --git a/lib/services/device/device-debug-app-service.ts b/lib/services/device/device-debug-app-service.ts index 3abce5a5cc..643f01c210 100644 --- a/lib/services/device/device-debug-app-service.ts +++ b/lib/services/device/device-debug-app-service.ts @@ -14,7 +14,7 @@ export class DeviceDebugAppService { ) { } @performanceLog() - public async refreshApplicationWithDebug(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise { + 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 diff --git a/lib/services/platform/platform-watcher-service.ts b/lib/services/platform/platform-watcher-service.ts index 6d4e449ac4..d6731593fe 100644 --- a/lib/services/platform/platform-watcher-service.ts +++ b/lib/services/platform/platform-watcher-service.ts @@ -44,8 +44,8 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { - this.$webpackCompilerService.on("webpackEmittedFiles", files => { - this.emitFilesChangeEvent({ files, hasNativeChanges: false, platform: platformData.platformNameLowerCase }); + this.$webpackCompilerService.on("webpackEmittedFiles", data => { + this.emitFilesChangeEvent({ ...data, hasNativeChanges: false, platform: platformData.platformNameLowerCase }); }); const childProcess = await this.$webpackCompilerService.compileWithWatch(platformData, projectData, config); @@ -75,7 +75,7 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat .on("all", async (event: string, filePath: string) => { filePath = path.join(projectData.projectDir, filePath); this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); - this.emitFilesChangeEvent({ files: [], hasNativeChanges: true, platform: platformData.platformNameLowerCase }); + this.emitFilesChangeEvent({ files: [], hmrData: null, hasNativeChanges: true, platform: platformData.platformNameLowerCase }); }); this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher; diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 23f72c4c0c..4ef7350fad 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -35,7 +35,18 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const files = message.emittedFiles .filter((file: string) => file.indexOf("App_Resources") === -1) .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)); - this.emit("webpackEmittedFiles", files); + + const result = this.getUpdatedEmittedFiles(message.emittedFiles); + + const data = { + files, + hmrData: { + hash: result.hash, + fallbackFiles: result.fallbackFiles + } + }; + + this.emit("webpackEmittedFiles", data); } }); @@ -143,5 +154,34 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return args; } + + private getUpdatedEmittedFiles(emittedFiles: string[]) { + let fallbackFiles: string[] = []; + let hotHash; + if (emittedFiles.some(x => x.endsWith('.hot-update.json'))) { + let result = emittedFiles.slice(); + const hotUpdateScripts = emittedFiles.filter(x => x.endsWith('.hot-update.js')); + hotUpdateScripts.forEach(hotUpdateScript => { + const { name, hash } = this.parseHotUpdateChunkName(hotUpdateScript); + hotHash = hash; + // remove bundle/vendor.js files if there's a bundle.XXX.hot-update.js or vendor.XXX.hot-update.js + result = result.filter(file => file !== `${name}.js`); + }); + //if applying of hot update fails, we must fallback to the full files + fallbackFiles = emittedFiles.filter(file => result.indexOf(file) === -1); + return { emittedFiles: result, fallbackFiles, hash: hotHash }; + } + + return { emittedFiles, fallbackFiles }; + } + + private parseHotUpdateChunkName(name: string) { + const matcher = /^(.+)\.(.+)\.hot-update/gm; + const matches = matcher.exec(name); + return { + name: matches[1] || "", + hash: matches[2] || "", + }; + } } $injector.register("webpackCompilerService", WebpackCompilerService); diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index 78b01920bf..0512d6290b 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -32,6 +32,7 @@ declare global { interface IFilesChangeEventData { platform: string; files: string[]; + hmrData: IPlatformHmrData; hasNativeChanges: boolean; } diff --git a/lib/services/workflow/workflow-data-service.ts b/lib/services/workflow/workflow-data-service.ts index 269e010921..b8fc2f2df7 100644 --- a/lib/services/workflow/workflow-data-service.ts +++ b/lib/services/workflow/workflow-data-service.ts @@ -39,28 +39,34 @@ export class WorkflowDataService { } private getAddPlatformData(platform: string, options: IOptions | any) { - return { + const result = { frameworkPath: options.frameworkPath, nativePrepare: options.nativePrepare, platformParam: options.platformParam || platform, }; + + return result; } private getPreparePlatformData(options: IOptions | any) { - return { - env: options.env, + const result = { + env: { ...options.env, hmr: options.hmr || options.useHotModuleReload }, release: options.release, nativePrepare: options.nativePrepare }; + + return result; } private getIOSPrepareData(options: IOptions | any) { - return { + const result = { ...this.getPreparePlatformData(options), teamId: options.teamId, provision: options.provision, mobileProvisionData: options.mobileProvisionData }; + + return result; } } $injector.register("workflowDataService", WorkflowDataService); From e85daf7c010a3e58c49def3598371a2f29fd7a19 Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 5 May 2019 08:18:01 +0300 Subject: [PATCH 15/30] fix: fix deploy command --- lib/commands/deploy.ts | 6 +- lib/controllers/run-on-devices-controller.ts | 164 +++++++++---------- lib/helpers/deploy-command-helper.ts | 80 +++++++++ 3 files changed, 161 insertions(+), 89 deletions(-) create mode 100644 lib/helpers/deploy-command-helper.ts diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index db14861ad8..535554e0c1 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -1,5 +1,6 @@ import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE } from "../constants"; import { ValidatePlatformCommandBase } from "./command-base"; +import { DeployCommandHelper } from "../helpers/deploy-command-helper"; export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; @@ -12,7 +13,7 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement private $mobileHelper: Mobile.IMobileHelper, $platformsData: IPlatformsData, private $bundleValidatorHelper: IBundleValidatorHelper, - private $liveSyncCommandHelper: ILiveSyncCommandHelper, + private $deployCommandHelper: DeployCommandHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { super($options, $platformsData, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); @@ -20,8 +21,7 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement public async execute(args: string[]): Promise { const platform = args[0].toLowerCase(); - // TODO: Add a separate deployCommandHelper with base class for it and LiveSyncCommandHelper - await this.$liveSyncCommandHelper.executeCommandLiveSync(platform, { release: true }); + await this.$deployCommandHelper.deploy(platform, { release: true }); } public async canExecute(args: string[]): Promise { diff --git a/lib/controllers/run-on-devices-controller.ts b/lib/controllers/run-on-devices-controller.ts index 61a913895d..4664958d30 100644 --- a/lib/controllers/run-on-devices-controller.ts +++ b/lib/controllers/run-on-devices-controller.ts @@ -26,114 +26,106 @@ export class RunOnDevicesController extends EventEmitter { ) { super(); } public async syncInitialDataOnDevice(data: IInitialSyncEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { - const executeAction = async (device: Mobile.IDevice) => { + const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.syncInitialDataOnDeviceSafe(device, deviceDescriptor, projectData, liveSyncInfo); - }; - const canExecuteAction = (device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === data.platform.toLowerCase() && _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); - await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(executeAction, canExecuteAction)); - } - - public async syncChangedDataOnDevice(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { - const executeAction = async (device: Mobile.IDevice) => { - const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - await this.syncChangedDataOnDeviceSafe(device, deviceDescriptor, data, projectData, liveSyncInfo); - }; - const canExecuteAction = (device: Mobile.IDevice) => { - const liveSyncProcessInfo = this.$runOnDevicesDataService.getData(projectData.projectDir); - return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); - }; - await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(executeAction, canExecuteAction)); - } + const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); - private async syncInitialDataOnDeviceSafe(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { - const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + try { + const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); + const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); - try { - const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); - const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); + await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); - await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); + 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 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 refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor); - const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor); + this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + isFullSync: liveSyncResultInfo.isFullSync + }); - this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { - syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), - isFullSync: liveSyncResultInfo.isFullSync - }); + if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) { + await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo); + } - if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) { - await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo); - } + this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); + this.$runOnDevicesEmitter.emitRunOnDeviceStartedEvent(projectData, device); + } catch (err) { + this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); - this.$runOnDevicesEmitter.emitRunOnDeviceStartedEvent(projectData, device); - } catch (err) { - this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); + this.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, err); - this.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, err); + // TODO: Consider to call here directly stopRunOnDevices + } + }; - // TODO: Consider to call here directly stopRunOnDevices - } + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === data.platform.toLowerCase() && _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } - private async syncChangedDataOnDeviceSafe(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { - const { nativePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + public async syncChangedDataOnDevice(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + const deviceAction = async (device: Mobile.IDevice) => { + const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + const { nativePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); - try { - if (data.hasNativeChanges) { - // TODO: Consider to handle nativePluginsChange here (aar rebuilt) - await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); - } + try { + if (data.hasNativeChanges) { + // TODO: Consider to handle nativePluginsChange here (aar rebuilt) + await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); + } - const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; - if (isInHMRMode) { - this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); - } + const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; + if (isInHMRMode) { + this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); + } - const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(device.deviceInfo.platform); - const watchInfo = { - liveSyncDeviceInfo: deviceDescriptor, - projectData, - filesToRemove: [], - filesToSync: data.files, - isReinstalled: false, - hmrData: data.hmrData, - useHotModuleReload: liveSyncInfo.useHotModuleReload, - force: liveSyncInfo.force, - connectTimeout: 1000 - }; - let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); - - await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); - - if (!liveSyncResultInfo.didRecover && isInHMRMode) { - const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); - if (status === HmrConstants.HMR_ERROR_STATUS) { - watchInfo.filesToSync = data.hmrData.fallbackFiles; - liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); - // We want to force a restart of the application. - liveSyncResultInfo.isFullSync = true; - await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(device.deviceInfo.platform); + const watchInfo = { + liveSyncDeviceInfo: deviceDescriptor, + projectData, + filesToRemove: [], + filesToSync: data.files, + isReinstalled: false, + hmrData: data.hmrData, + useHotModuleReload: liveSyncInfo.useHotModuleReload, + force: liveSyncInfo.force, + connectTimeout: 1000 + }; + let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); + + await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + + if (!liveSyncResultInfo.didRecover && isInHMRMode) { + const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); + if (status === HmrConstants.HMR_ERROR_STATUS) { + watchInfo.filesToSync = data.hmrData.fallbackFiles; + liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); + // We want to force a restart of the application. + liveSyncResultInfo.isFullSync = true; + await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + } } - } - this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - } catch (err) { - const allErrors = (err).allErrors; + this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); + } catch (err) { + const allErrors = (err).allErrors; - 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.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, deviceError); + 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.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, deviceError); + } } } - } + }; + + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => { + const liveSyncProcessInfo = this.$runOnDevicesDataService.getData(projectData.projectDir); + return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); + })); } private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo) { diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts new file mode 100644 index 0000000000..2b3452449f --- /dev/null +++ b/lib/helpers/deploy-command-helper.ts @@ -0,0 +1,80 @@ +import { BuildPlatformService } from "../services/platform/build-platform-service"; +import { MainController } from "../controllers/main-controller"; + +export class DeployCommandHelper { + constructor( + private $buildPlatformService: BuildPlatformService, + private $devicesService: Mobile.IDevicesService, + private $mainController: MainController, + private $options: IOptions, + private $projectData: IProjectData + ) { } + + public async deploy(platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions) { + const emulator = this.$options.emulator; + await this.$devicesService.initialize({ + deviceId: this.$options.device, + platform, + emulator, + skipInferPlatform: !platform, + sdk: this.$options.sdk + }); + + const devices = this.$devicesService.getDeviceInstances() + .filter(d => !platform || d.deviceInfo.platform.toLowerCase() === platform.toLowerCase()); + + const deviceDescriptors: ILiveSyncDeviceInfo[] = devices + .map(d => { + const buildConfig: IBuildConfig = { + buildForDevice: !d.isEmulator, + iCloudContainerEnvironment: this.$options.iCloudContainerEnvironment, + projectDir: this.$options.path, + clean: this.$options.clean, + teamId: this.$options.teamId, + device: this.$options.device, + provision: this.$options.provision, + release: this.$options.release, + keyStoreAlias: this.$options.keyStoreAlias, + keyStorePath: this.$options.keyStorePath, + keyStoreAliasPassword: this.$options.keyStoreAliasPassword, + keyStorePassword: this.$options.keyStorePassword + }; + + const buildAction = additionalOptions && additionalOptions.buildPlatform ? + additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : + this.$buildPlatformService.buildPlatform.bind(this.$buildPlatformService, d.deviceInfo.platform, buildConfig, this.$projectData); + + const outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ + platform: d.deviceInfo.platform, + emulator: d.isEmulator, + projectDir: this.$projectData.projectDir + }); + + const info: ILiveSyncDeviceInfo = { + identifier: d.deviceInfo.identifier, + buildAction, + debugggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], + debugOptions: this.$options, + outputPath, + skipNativePrepare: additionalOptions && additionalOptions.skipNativePrepare, + }; + + return info; + }); + + const liveSyncInfo: ILiveSyncInfo = { + projectDir: this.$projectData.projectDir, + skipWatcher: !this.$options.watch, + 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 + }; + + await this.$mainController.deployOnDevices(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); + } +} +$injector.register("deployCommandHelper", DeployCommandHelper); From 046d6e9d272dce0a7ef885d97aca086b671b3315 Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 5 May 2019 08:55:17 +0300 Subject: [PATCH 16/30] fix: remove unneeded interfaces --- lib/declarations.d.ts | 12 ---- lib/definitions/livesync.d.ts | 106 +++------------------------------- lib/definitions/platform.d.ts | 59 ------------------- 3 files changed, 7 insertions(+), 170 deletions(-) diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index f94320b730..5b66b6f0bc 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -912,18 +912,6 @@ interface IXcconfigService { mergeFiles(sourceFile: string, destinationFile: string): Promise; } -/** - * Describes helper used during execution of deploy commands. - */ -interface IDeployCommandHelper { - /** - * Retrieves data needed to execute deploy command. - * @param {string} platform platform to which to deploy - could be android or ios. - * @return {IDeployPlatformInfo} data needed to execute deploy command. - */ - getDeployPlatformInfo(platform: string): IDeployPlatformInfo; -} - /** * Describes helper for validating bundling. */ diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 44e618491e..2ccce6badf 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -1,70 +1,8 @@ import { EventEmitter } from "events"; declare global { - // This interface is a mashup of NodeJS' along with Chokidar's event watchers - interface IFSWatcher extends NodeJS.EventEmitter { - // from fs.FSWatcher - close(): void; - - /** - * events.EventEmitter - * 1. change - * 2. error - */ - addListener(event: string, listener: Function): this; - addListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - addListener(event: "error", listener: (code: number, signal: string) => void): this; - - on(event: string, listener: Function): this; - on(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - on(event: "error", listener: (code: number, signal: string) => void): this; - - once(event: string, listener: Function): this; - once(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - once(event: "error", listener: (code: number, signal: string) => void): this; - - prependListener(event: string, listener: Function): this; - prependListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - prependListener(event: "error", listener: (code: number, signal: string) => void): this; - - prependOnceListener(event: string, listener: Function): this; - prependOnceListener(event: "change", listener: (eventType: string, filename: string | Buffer) => void): this; - prependOnceListener(event: "error", listener: (code: number, signal: string) => void): this; - - // From chokidar FSWatcher - - /** - * Add files, directories, or glob patterns for tracking. Takes an array of strings or just one - * string. - */ - add(paths: string | string[]): void; - - /** - * Stop watching files, directories, or glob patterns. Takes an array of strings or just one - * string. - */ - unwatch(paths: string | string[]): void; - - /** - * Returns an object representing all the paths on the file system being watched by this - * `FSWatcher` instance. The object's keys are all the directories (using absolute paths unless - * the `cwd` option was used), and the values are arrays of the names of the items contained in - * each directory. - */ - getWatched(): IDictionary; - - /** - * Removes all listeners from watched files. - */ - close(): void; - } - interface ILiveSyncProcessInfo { timer: NodeJS.Timer; - watcherInfo: { - watcher: IFSWatcher, - patterns: string[] - }; actionsChain: Promise; isStopped: boolean; deviceDescriptors: ILiveSyncDeviceInfo[]; @@ -131,18 +69,16 @@ declare global { debugggingEnabled?: boolean; } - interface IOptionalSkipWatcher { - /** - * Defines if the watcher should be skipped. If not passed, fs.Watcher will be started. - */ - skipWatcher?: boolean; - } - /** * Describes a LiveSync operation. */ - interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { + interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IRelease, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { emulator?: boolean; + + /** + * Defines if the watcher should be skipped. If not passed, fs.Watcher will be started. + */ + skipWatcher?: boolean; /** * Forces a build before the initial livesync. @@ -189,43 +125,14 @@ declare global { isFullSync?: boolean } - interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } - interface IIsEmulator { isEmulator: boolean; } - interface ILiveSyncBuildInfo extends IIsEmulator, IPlatform { - pathToBuildItem: string; - } - interface IProjectDataComposition { projectData: IProjectData; } - /** - * Desribes object that can be passed to ensureLatestAppPackageIsInstalledOnDevice method. - */ - interface IEnsureLatestAppPackageIsInstalledOnDeviceOptions extends IProjectDataComposition, IEnvOptions, IBundle, IRelease, IOptionalFilesToRemove, IOptionalFilesToSync { - device: Mobile.IDevice; - preparedPlatforms: string[]; - rebuiltInformation: ILiveSyncBuildInfo[]; - deviceBuildInfoDescriptor: ILiveSyncDeviceInfo; - settings: ILatestAppPackageInstalledSettings; - liveSyncData?: ILiveSyncInfo; - modifiedFiles?: string[]; - } - - /** - * Describes the action that has been executed during ensureLatestAppPackageIsInstalledOnDevice execution. - */ - interface IAppInstalledOnDeviceResult { - /** - * Defines if the app has been installed on device from the ensureLatestAppPackageIsInstalledOnDevice method. - */ - appInstalled: boolean; - } - /** * Describes LiveSync operations. */ @@ -401,6 +308,7 @@ declare global { shouldRestart(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService; } + interface IRestartApplicationInfo { didRestart: boolean; } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index f8f69d9d31..f02893d430 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -14,8 +14,6 @@ interface IBuildPlatformAction { buildPlatform(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): Promise; } -interface IPlatformOptions extends IPlatformSpecificData, ICreateProjectOptions { } - /** * Platform specific data required for project preparation. */ @@ -63,16 +61,6 @@ interface IPlatformsData { getPlatformData(platform: string, projectData: IProjectData): IPlatformData; } -interface IAppFilesUpdaterOptionsComposition { - appFilesUpdaterOptions: IAppFilesUpdaterOptions; -} - -interface INodeModulesData extends IPlatform, IProjectDataComposition, IAppFilesUpdaterOptionsComposition { - absoluteOutputPath: string; - lastModifiedTime: Date; - projectFilesConfig: IProjectFilesConfig; -} - interface INodeModulesBuilder { prepareNodeModules(platformData: IPlatformData, projectData: IProjectData): Promise; } @@ -93,53 +81,6 @@ interface IBuildInfo { deploymentTarget?: string; } -interface IPlatformDataComposition { - platformData: IPlatformData; -} - -interface ICopyAppFilesData extends IProjectDataComposition, IAppFilesUpdaterOptionsComposition, IPlatformDataComposition, IOptionalFilesToSync, IOptionalFilesToRemove { } - -interface IPreparePlatformJSInfo extends IPreparePlatformCoreInfo, ICopyAppFilesData { - projectFilesConfig?: IProjectFilesConfig; -} - -interface IPlatformOptions extends IRelease, IHasUseHotModuleReloadOption { -} - -interface IOptionalProjectChangesInfoComposition { - changesInfo?: IProjectChangesInfo; -} - -interface IPreparePlatformCoreInfo extends IPreparePlatformInfoBase, IOptionalProjectChangesInfoComposition { - platformSpecificData: IPlatformSpecificData; -} - -interface IPlatformConfig { - config: IPlatformOptions; -} - -interface IOptionalFilesToSync { - filesToSync?: string[]; -} - -interface IOptionalFilesToRemove { - filesToRemove?: string[]; -} - -interface IPreparePlatformInfoBase extends IPlatform, IAppFilesUpdaterOptionsComposition, IProjectDataComposition, IEnvOptions, IOptionalFilesToSync, IOptionalFilesToRemove, IOptionalNativePrepareComposition { } - -interface IOptionalNativePrepareComposition { - nativePrepare?: INativePrepare; -} - -interface IDeployPlatformInfo extends IPlatform, IAppFilesUpdaterOptionsComposition, IProjectDataComposition, IPlatformConfig, IEnvOptions, IOptionalNativePrepareComposition, IOptionalOutputPath, IBuildPlatformAction { - deployOptions: IDeployPlatformOptions -} - -interface IUpdateAppOptions extends IOptionalFilesToSync, IOptionalFilesToRemove { - beforeCopyAction: (sourceFiles: string[]) => void; -} - interface IPlatformEnvironmentRequirements { checkEnvironmentRequirements(input: ICheckEnvironmentRequirementsInput): Promise; } From f5f08360cdd9e19bdfdd8346f5b02a99cd3562b0 Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 5 May 2019 08:57:46 +0300 Subject: [PATCH 17/30] feat: stop webpack and native watchers on stopRunOnDevices() --- lib/controllers/main-controller.ts | 13 ++++++------ .../platform/platform-watcher-service.ts | 21 ++++++++++++++++--- .../webpack/webpack-compiler-service.ts | 18 ++++++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/lib/controllers/main-controller.ts b/lib/controllers/main-controller.ts index b06c118700..9108d3905a 100644 --- a/lib/controllers/main-controller.ts +++ b/lib/controllers/main-controller.ts @@ -10,6 +10,7 @@ import { RunOnDevicesDataService } from "../services/run-on-devices-data-service import { cache } from "../common/decorators"; import { DeviceDiscoveryEventNames } from "../common/constants"; import { RunOnDevicesEmitter } from "../run-on-devices-emitter"; +import { PlatformWatcherService } from "../services/platform/platform-watcher-service"; export class MainController extends EventEmitter { constructor( @@ -20,7 +21,7 @@ export class MainController extends EventEmitter { private $errors: IErrors, private $hooksService: IHooksService, private $logger: ILogger, - private $platformWatcherService: IPlatformWatcherService, + private $platformWatcherService: PlatformWatcherService, private $pluginsService: IPluginsService, private $preparePlatformService: PreparePlatformService, private $projectDataService: IProjectDataService, @@ -90,7 +91,7 @@ export class MainController extends EventEmitter { for (const platform of platforms) { const { nativePlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, liveSyncInfo); - await this.$platformWatcherService.startWatcher(nativePlatformData, projectData, preparePlatformData); + await this.$platformWatcherService.startWatchers(nativePlatformData, projectData, preparePlatformData); } } @@ -117,11 +118,11 @@ export class MainController extends EventEmitter { clearTimeout(liveSyncProcessInfo.timer); } - if (liveSyncProcessInfo.watcherInfo && liveSyncProcessInfo.watcherInfo.watcher) { - liveSyncProcessInfo.watcherInfo.watcher.close(); - } + _.each(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => { + const device = this.$devicesService.getDeviceByIdentifier(deviceDescriptor.identifier); + this.$platformWatcherService.stopWatchers(projectDir, device.deviceInfo.platform); + }); - liveSyncProcessInfo.watcherInfo = null; liveSyncProcessInfo.isStopped = true; if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) { diff --git a/lib/services/platform/platform-watcher-service.ts b/lib/services/platform/platform-watcher-service.ts index d6731593fe..945998ff01 100644 --- a/lib/services/platform/platform-watcher-service.ts +++ b/lib/services/platform/platform-watcher-service.ts @@ -5,13 +5,14 @@ import * as path from "path"; import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME } from "../../constants"; import { PreparePlatformData } from "../workflow/workflow-data-service"; import { PreparePlatformService } from "./prepare-platform-service"; +import { WebpackCompilerService } from "../webpack/webpack-compiler-service"; interface IPlatformWatcherData { webpackCompilerProcess: child_process.ChildProcess; nativeFilesWatcher: choki.FSWatcher; } -export class PlatformWatcherService extends EventEmitter implements IPlatformWatcherService { +export class PlatformWatcherService extends EventEmitter { private watchersData: IDictionary> = {}; private isInitialSyncEventEmitted = false; private persistedFilesChangeEventData: IFilesChangeEventData[] = []; @@ -19,10 +20,10 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat constructor( private $logger: ILogger, private $preparePlatformService: PreparePlatformService, - private $webpackCompilerService: IWebpackCompilerService + private $webpackCompilerService: WebpackCompilerService ) { super(); } - public async startWatcher(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + public async startWatchers(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { this.$logger.out("Starting watchers..."); if (!this.watchersData[projectData.projectDir]) { @@ -42,6 +43,20 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat this.emitInitialSyncEvent({ platform: platformData.platformNameLowerCase, hasNativeChanges }); } + public async stopWatchers(projectDir: string, platform: string) { + const platformLowerCase = platform.toLowerCase(); + + if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) { + this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher.close(); + this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher = null; + } + + if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess) { + this.$webpackCompilerService.stopWebpackCompile(platform); + this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess = null; + } + } + private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { this.$webpackCompilerService.on("webpackEmittedFiles", data => { diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 4ef7350fad..2707192244 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -7,6 +7,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp constructor( private $childProcess: IChildProcess, + private $logger: ILogger, private $projectData: IProjectData ) { super(); } @@ -86,6 +87,14 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp }); } + public stopWebpackCompile(platform: string) { + if (platform) { + this.stopWebpackForPlatform(platform); + } else { + Object.keys(this.webpackProcesses).forEach(pl => this.stopWebpackForPlatform(pl)); + } + } + private startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): child_process.ChildProcess { const envData = this.buildEnvData(platformData.platformNameLowerCase, config.env); const envParams = this.buildEnvCommandLineParams(envData); @@ -183,5 +192,14 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp hash: matches[2] || "", }; } + + private stopWebpackForPlatform(platform: string) { + this.$logger.trace(`Stopping webpack watch for platform ${platform}.`); + const webpackProcess = this.webpackProcesses[platform]; + if (webpackProcess) { + webpackProcess.kill("SIGINT"); + delete this.webpackProcesses[platform]; + } + } } $injector.register("webpackCompilerService", WebpackCompilerService); From ed8410adb312c5daa6a4a8992d82b4abc5e58589 Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 5 May 2019 19:16:31 +0300 Subject: [PATCH 18/30] feat: remove preview-sync hook and integrate preview command to work with new changes --- lib/commands/preview.ts | 6 +- lib/declarations.d.ts | 2 - .../preview-app-livesync-service.ts | 69 +++++++------------ .../playground/preview-qr-code-service.ts | 2 + 4 files changed, 28 insertions(+), 51 deletions(-) diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index bb2aab8c35..67092e6bf8 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -7,8 +7,8 @@ export class PreviewCommand implements ICommand { constructor(private $analyticsService: IAnalyticsService, private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, - private $liveSyncService: ILiveSyncService, private $logger: ILogger, + private $previewAppLiveSyncService: IPreviewAppLiveSyncService, private $networkConnectivityValidator: INetworkConnectivityValidator, private $projectData: IProjectData, private $options: IOptions, @@ -24,10 +24,10 @@ export class PreviewCommand implements ICommand { this.$logger.info(message); }); - await this.$liveSyncService.liveSyncToPreviewApp({ + await this.$previewAppLiveSyncService.initialize({ + projectDir: this.$projectData.projectDir, bundle: !!this.$options.bundle, useHotModuleReload: this.$options.hmr, - projectDir: this.$projectData.projectDir, env: this.$options.env }); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 5b66b6f0bc..9dbe021fee 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -578,8 +578,6 @@ interface IHasAndroidBundle { androidBundle?: boolean; } -interface IAppFilesUpdaterOptions { } - interface IPlatformBuildData extends IRelease, IHasUseHotModuleReloadOption, IBuildConfig, IEnvOptions { } interface IDeviceEmulator extends IHasEmulatorOption, IDeviceIdentifier { } diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index ad59828b55..8289015a6e 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -1,30 +1,33 @@ import * as path from "path"; import { Device, FilesPayload } from "nativescript-preview-sdk"; -import { APP_RESOURCES_FOLDER_NAME, APP_FOLDER_NAME, TrackActionNames } from "../../../constants"; +import { APP_RESOURCES_FOLDER_NAME, APP_FOLDER_NAME, TrackActionNames, FILES_CHANGE_EVENT_NAME } from "../../../constants"; import { PreviewAppLiveSyncEvents } from "./preview-app-constants"; import { HmrConstants } from "../../../common/constants"; import { stringify } from "../../../common/helpers"; import { EventEmitter } from "events"; import { performanceLog } from "../../../common/decorators"; +import { WorkflowDataService } from "../../workflow/workflow-data-service"; +import { PlatformWatcherService } from "../../platform/platform-watcher-service"; export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewAppLiveSyncService { private deviceInitializationPromise: IDictionary> = {}; + private promise = Promise.resolve(); constructor( private $analyticsService: IAnalyticsService, private $errors: IErrors, - private $hooksService: IHooksService, + private $hmrStatusService: IHmrStatusService, private $logger: ILogger, private $platformsData: IPlatformsData, + private $platformWatcherService: PlatformWatcherService, private $projectDataService: IProjectDataService, private $previewSdkService: IPreviewSdkService, private $previewAppFilesService: IPreviewAppFilesService, private $previewAppPluginsService: IPreviewAppPluginsService, private $previewDevicesService: IPreviewDevicesService, - private $hmrStatusService: IHmrStatusService) { - super(); - } + private $workflowDataService: WorkflowDataService + ) { super(); } @performanceLog() public async initialize(data: IPreviewAppLiveSyncData): Promise { @@ -46,7 +49,15 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA }); } - this.deviceInitializationPromise[device.id] = this.getInitialFilesForDevice(data, device); + this.deviceInitializationPromise[device.id] = this.getInitialFilesForPlatformSafe(data, device.platform); + + this.$platformWatcherService.on(FILES_CHANGE_EVENT_NAME, async (filesChangeData: IFilesChangeEventData) => { + await this.onWebpackCompilationComplete(data, filesChangeData.hmrData, filesChangeData.files, device.platform); + }); + + const { nativePlatformData, projectData, preparePlatformData } = this.$workflowDataService.createWorkflowData(device.platform.toLowerCase(), data.projectDir, data); + await this.$platformWatcherService.startWatchers(nativePlatformData, projectData, preparePlatformData); + try { const payloads = await this.deviceInitializationPromise[device.id]; return payloads; @@ -89,38 +100,6 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA this.$previewDevicesService.updateConnectedDevices([]); } - private async getInitialFilesForDevice(data: IPreviewAppLiveSyncData, device: Device): Promise { - const hookArgs = this.getHookArgs(data, device); - await this.$hooksService.executeBeforeHooks("preview-sync", { hookArgs }); - await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); - const payloads = await this.getInitialFilesForPlatformSafe(data, device.platform); - return payloads; - } - - private getHookArgs(data: IPreviewAppLiveSyncData, device: Device) { - const filesToSyncMap: IDictionary = {}; - const hmrData: IDictionary = {}; - const promise = Promise.resolve(); - const result = { - projectData: this.$projectDataService.getProjectData(data.projectDir), - hmrData, - config: { - env: data.env, - platform: device.platform, - appFilesUpdaterOptions: { - bundle: data.bundle, - useHotModuleReload: data.useHotModuleReload, - release: false - }, - }, - externals: this.$previewAppPluginsService.getExternalPlugins(device), - filesToSyncMap, - startSyncFilesTimeout: async (platform: string) => await this.onWebpackCompilationComplete(data, hmrData, filesToSyncMap, promise, platform) - }; - - return result; - } - private async getInitialFilesForPlatformSafe(data: IPreviewAppLiveSyncData, platform: string): Promise { this.$logger.info(`Start sending initial files for platform ${platform}.`); @@ -153,21 +132,20 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA } @performanceLog() - private async onWebpackCompilationComplete(data: IPreviewAppLiveSyncData, hmrData: IDictionary, filesToSyncMap: IDictionary, promise: Promise, platform: string) { - await promise + private async onWebpackCompilationComplete(data: IPreviewAppLiveSyncData, hmrData: IPlatformHmrData, files: string[], platform: string) { + await this.promise .then(async () => { - const currentHmrData = _.cloneDeep(hmrData); - const platformHmrData = currentHmrData[platform] || {}; + const platformHmrData = _.cloneDeep(hmrData); const projectData = this.$projectDataService.getProjectData(data.projectDir); const platformData = this.$platformsData.getPlatformData(platform, projectData); - const clonedFiles = _.cloneDeep(filesToSyncMap[platform]); + const clonedFiles = _.cloneDeep(files); const filesToSync = _.map(clonedFiles, fileToSync => { const result = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME, path.relative(projectData.getAppDirectoryPath(), fileToSync)); return result; }); - promise = this.syncFilesForPlatformSafe(data, { filesToSync }, platform); - await promise; + this.promise = this.syncFilesForPlatformSafe(data, { filesToSync }, platform); + await this.promise; if (data.useHotModuleReload && platformHmrData.hash) { const devices = this.$previewDevicesService.getDevicesForPlatform(platform); @@ -183,7 +161,6 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA })); } }); - filesToSyncMap[platform] = []; } private showWarningsForNativeFiles(files: string[]): void { diff --git a/lib/services/livesync/playground/preview-qr-code-service.ts b/lib/services/livesync/playground/preview-qr-code-service.ts index 2609a2cd55..3da28180c1 100644 --- a/lib/services/livesync/playground/preview-qr-code-service.ts +++ b/lib/services/livesync/playground/preview-qr-code-service.ts @@ -44,6 +44,8 @@ export class PreviewQrCodeService implements IPreviewQrCodeService { const qrCodeUrl = this.$previewSdkService.getQrCodeUrl(options); const url = await this.getShortenUrl(qrCodeUrl); + this.$logger.info("======== qrCodeUrl ======== ", qrCodeUrl); + this.$logger.info(); const message = `${EOL} Generating qrcode for url ${url}.`; this.$logger.trace(message); From 29d7ad9c21ebcd9ea81e330adb0d311390063a44 Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 5 May 2019 19:47:03 +0300 Subject: [PATCH 19/30] fix: remove livesyncService --- lib/bootstrap.ts | 1 - lib/commands/debug.ts | 5 +- lib/definitions/livesync.d.ts | 52 +- lib/services/livesync/livesync-service.ts | 1115 ----------------- .../platform-environment-requirements.ts | 26 +- 5 files changed, 11 insertions(+), 1188 deletions(-) delete mode 100644 lib/services/livesync/livesync-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index f7fc07c85e..00f47431a8 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -152,7 +152,6 @@ $injector.require("deployCommandHelper", "./helpers/deploy-command-helper"); $injector.require("optionsTracker", "./helpers/options-track-helper"); $injector.requirePublicClass("localBuildService", "./services/local-build-service"); -$injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); $injector.require("LiveSyncSocket", "./services/livesync/livesync-socket"); $injector.requirePublicClass("androidLivesyncTool", "./services/livesync/android-livesync-tool"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index ab386e23c4..0ee43d9069 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,6 +1,7 @@ import { cache } from "../common/decorators"; import { ValidatePlatformCommandBase } from "./command-base"; import { LiveSyncCommandHelper } from "../helpers/livesync-command-helper"; +import { DeviceDebugAppService } from "../services/device/device-debug-app-service"; export class DebugPlatformCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; @@ -16,7 +17,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements protected $logger: ILogger, protected $errors: IErrors, private $debugDataService: IDebugDataService, - private $liveSyncService: IDebugLiveSyncService, + private $deviceDebugAppService: DeviceDebugAppService, private $liveSyncCommandHelper: ILiveSyncCommandHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { super($options, $platformsData, $platformValidationService, $projectData); @@ -41,7 +42,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements const debugData = this.$debugDataService.createDebugData(this.$projectData, { device: selectedDeviceForDebug.deviceInfo.identifier }); if (this.$options.start) { - await this.$liveSyncService.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); + await this.$deviceDebugAppService.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); return; } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 2ccce6badf..77c91f2325 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -66,7 +66,7 @@ declare global { /** * Whether debugging has been enabled for this device or not */ - debugggingEnabled?: boolean; + debuggingEnabled?: boolean; } /** @@ -171,56 +171,6 @@ declare global { getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; } - // TODO: Rename this interface and change method's definition - interface ILiveSyncService2 { - syncInitialDataOnDevice(device: Mobile.IDevice, deviceBuildInfoDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise; - syncChangedDataOnDevice(device: Mobile.IDevice, filesToSync: string[], liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise; - } - - /** - * Describes LiveSync operations while debuggging. - */ - interface IDebugLiveSyncService extends ILiveSyncService { - /** - * Method used to retrieve the glob patterns which CLI will watch for file changes. Defaults to the whole app directory. - * @param {ILiveSyncInfo} liveSyncData Information needed for livesync - for example if bundle is passed or if a release build should be performed. - * @param {IProjectData} projectData Project data. - * @param {string[]} platforms Platforms to start the watcher for. - * @returns {Promise} The glob patterns. - */ - getWatcherPatterns(liveSyncData: ILiveSyncInfo, projectData: IProjectData, platforms: string[]): Promise; - - /** - * Prints debug information. - * @param {IDebugInformation} debugInformation Information to be printed. - * @returns {IDebugInformation} Full url and port where the frontend client can be connected. - */ - printDebugInformation(debugInformation: IDebugInformation): IDebugInformation; - - /** - * Enables debugging for the specified devices - * @param {IEnableDebuggingDeviceOptions[]} deviceOpts Settings used for enabling debugging for each device. - * @param {IDebuggingAdditionalOptions} enableDebuggingOptions Settings used for enabling debugging. - * @returns {Promise[]} Array of promises for each device. - */ - enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], enableDebuggingOptions: IDebuggingAdditionalOptions): Promise[]; - - /** - * Disables debugging for the specified devices - * @param {IDisableDebuggingDeviceOptions[]} deviceOptions Settings used for disabling debugging for each device. - * @param {IDebuggingAdditionalOptions} debuggingAdditionalOptions Settings used for disabling debugging. - * @returns {Promise[]} Array of promises for each device. - */ - disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[]; - - /** - * Attaches a debugger to the specified device. - * @param {IAttachDebuggerOptions} settings Settings used for controling the attaching process. - * @returns {Promise} Full url and port where the frontend client can be connected. - */ - attachDebugger(settings: IAttachDebuggerOptions): Promise; - } - /** * Describes additional debugging settings. */ diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts deleted file mode 100644 index 5e86ddd32d..0000000000 --- a/lib/services/livesync/livesync-service.ts +++ /dev/null @@ -1,1115 +0,0 @@ -import { performanceLog } from "../../common/decorators"; - -export class LiveSyncService implements ILiveSyncService2 { - constructor( - // private $devicesService: Mobile.IDevicesService, - private $errors: IErrors, - private $injector: IInjector, - private $logger: ILogger, - private $mobileHelper: Mobile.IMobileHelper, - ) { } - - public async syncInitialDataOnDevice(device: Mobile.IDevice, liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { - const platformLiveSyncService = this.getLiveSyncService(device.deviceInfo.platform); - const liveSyncResultInfo = await platformLiveSyncService.fullSync({ - projectData, - device, - useHotModuleReload: liveSyncInfo.useHotModuleReload, - watch: !liveSyncInfo.skipWatcher, - force: liveSyncInfo.force, - liveSyncDeviceInfo - }); - - return liveSyncResultInfo; - } - - public async syncChangedDataOnDevice(device: Mobile.IDevice, filesToSync: string[], liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { - // const isInHMRMode = liveSyncInfo.useHotModuleReload; // && platformHmrData.hash; - // if (isInHMRMode) { - // this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); - // } - - const platformLiveSyncService = this.getLiveSyncService(device.deviceInfo.platform); - const liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, { - liveSyncDeviceInfo, - projectData, - filesToRemove: [], - filesToSync, - isReinstalled: false, - hmrData: null, // platformHmrData, - useHotModuleReload: liveSyncInfo.useHotModuleReload, - force: liveSyncInfo.force, - connectTimeout: 1000 - }); - - console.log("============ liveSyncResultInfo ============= ", liveSyncResultInfo); - - // await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - - // // If didRecover is true, this means we were in ErrorActivity and fallback files were already transferred and app will be restarted. - // if (!liveSyncResultInfo.didRecover && isInHMRMode) { - // const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); - // if (status === HmrConstants.HMR_ERROR_STATUS) { - // watchInfo.filesToSync = platformHmrData.fallbackFiles; - // liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); - // // We want to force a restart of the application. - // liveSyncResultInfo.isFullSync = true; - // await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - // } - // } - - // this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - return; - } - - @performanceLog() - public async refreshApplication(liveSyncDeviceInfo: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { - return liveSyncDeviceInfo && liveSyncDeviceInfo.debugggingEnabled ? - this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) : - this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath); - } - - public async attachDebugger(settings: IAttachDebuggerOptions): Promise { - return null; - // Default values - // if (settings.debugOptions) { - // settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; - // settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; - // } else { - // settings.debugOptions = { - // chrome: true, - // start: true - // }; - // } - - // const projectData = this.$projectDataService.getProjectData(settings.projectDir); - // const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); - - // // Of the properties below only `buildForDevice` and `release` are currently used. - // // Leaving the others with placeholder values so that they may not be forgotten in future implementations. - // const buildConfig = this.getInstallApplicationBuildConfig(settings.deviceIdentifier, settings.projectDir, { isEmulator: settings.isEmulator }); - // debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath); - // const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); - // const result = this.printDebugInformation(debugInfo, settings.debugOptions.forceDebuggerAttachedEvent); - // return result; - } - - @performanceLog() - private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { - debugOptions = debugOptions || {}; - if (debugOptions.debugBrk) { - liveSyncResultInfo.waitForDebugger = true; - } - - const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); - - // 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: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - debugOptions: debugOptions, - }; - - return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); - } - - private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IRefreshApplicationSettings): Promise { - const result = { didRestart: false }; - const platform = liveSyncResultInfo.deviceAppData.platform; - const platformLiveSyncService = this.getLiveSyncService(platform); - const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; - try { - let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); - if (!shouldRestart) { - shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); - } - - if (shouldRestart) { - // const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; - // this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); - await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); - result.didRestart = true; - } - } catch (err) { - 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); - if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { - // this.emitLivesyncEvent(LiveSyncEvents.liveSyncNotification, { - // projectDir: projectData.projectDir, - // applicationIdentifier, - // deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - // notification: msg - // }); - } - - if (settings && settings.shouldCheckDeveloperDiscImage) { - // this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); - } - } - - // this.emitLivesyncEvent(LiveSyncEvents.liveSyncExecuted, { - // projectDir: projectData.projectDir, - // applicationIdentifier, - // syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), - // deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - // isFullSync: liveSyncResultInfo.isFullSync - // }); - - return result; - } - - @performanceLog() - private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { - return null; - // const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); - // if (!currentDeviceDescriptor) { - // this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); - // } - - // currentDeviceDescriptor.debugggingEnabled = true; - // currentDeviceDescriptor.debugOptions = deviceOption.debugOptions; - // const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); - // const attachDebuggerOptions: IAttachDebuggerOptions = { - // deviceIdentifier: deviceOption.deviceIdentifier, - // isEmulator: currentDeviceInstance.isEmulator, - // outputPath: currentDeviceDescriptor.outputPath, - // platform: currentDeviceInstance.deviceInfo.platform, - // projectDir: debuggingAdditionalOptions.projectDir, - // debugOptions: deviceOption.debugOptions - // }; - - // let debugInformation: IDebugInformation; - // try { - // debugInformation = await this.attachDebugger(attachDebuggerOptions); - // } catch (err) { - // this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); - // attachDebuggerOptions.debugOptions.start = false; - // try { - // debugInformation = await this.attachDebugger(attachDebuggerOptions); - // } catch (innerErr) { - // this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); - // throw err; - // } - // } - - // return debugInformation; - } - - private getLiveSyncService(platform: string): IPlatformLiveSyncService { - if (this.$mobileHelper.isiOSPlatform(platform)) { - return this.$injector.resolve("iOSLiveSyncService"); - } else if (this.$mobileHelper.isAndroidPlatform(platform)) { - return this.$injector.resolve("androidLiveSyncService"); - } - - this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); - } -} -$injector.register("liveSyncService", LiveSyncService); - -// // import * as path from "path"; -// // import * as choki from "chokidar"; -// import { EOL } from "os"; -// import { EventEmitter } from "events"; -// import { hook } from "../../common/helpers"; -// import { -// PACKAGE_JSON_FILE_NAME, -// USER_INTERACTION_NEEDED_EVENT_NAME, -// DEBUGGER_ATTACHED_EVENT_NAME, -// DEBUGGER_DETACHED_EVENT_NAME, -// TrackActionNames, -// LiveSyncEvents -// } from "../../constants"; -// import { DeviceTypes, DeviceDiscoveryEventNames, HmrConstants } from "../../common/constants"; -// import { cache } from "../../common/decorators"; -// import { performanceLog } from "../../common/decorators"; - -// const deviceDescriptorPrimaryKey = "identifier"; - -// export class LiveSyncService extends EventEmitter implements IDebugLiveSyncService { -// // key is projectDir -// protected liveSyncProcessesInfo: IDictionary = {}; - -// constructor(private $platformService: IPlatformService, -// private $projectDataService: IProjectDataService, -// private $devicesService: Mobile.IDevicesService, -// private $mobileHelper: Mobile.IMobileHelper, -// private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, -// private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, -// private $logger: ILogger, -// private $hooksService: IHooksService, -// private $pluginsService: IPluginsService, -// private $debugService: IDebugService, -// private $errors: IErrors, -// private $debugDataService: IDebugDataService, -// private $analyticsService: IAnalyticsService, -// private $usbLiveSyncService: DeprecatedUsbLiveSyncService, -// private $previewAppLiveSyncService: IPreviewAppLiveSyncService, -// private $previewQrCodeService: IPreviewQrCodeService, -// private $previewSdkService: IPreviewSdkService, -// private $hmrStatusService: IHmrStatusService, -// private $injector: IInjector) { -// super(); -// } - -// public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { -// const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); -// await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); -// await this.liveSyncOperation(deviceDescriptors, liveSyncData, projectData); -// } - -// public async liveSyncToPreviewApp(data: IPreviewAppLiveSyncData): Promise { -// this.attachToPreviewAppLiveSyncError(); - -// await this.liveSync([], { -// syncToPreviewApp: true, -// projectDir: data.projectDir, -// bundle: data.bundle, -// useHotModuleReload: data.useHotModuleReload, -// release: false, -// env: data.env, -// }); - -// const url = this.$previewSdkService.getQrCodeUrl({ projectDir: data.projectDir, useHotModuleReload: data.useHotModuleReload }); -// const result = await this.$previewQrCodeService.getLiveSyncQrCode(url); -// return result; -// } - -// public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { -// const liveSyncProcessInfo = this.liveSyncProcessesInfo[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. -// const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; - -// const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); - -// const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) -// .map(descriptor => descriptor.identifier); - -// // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. -// if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { -// if (liveSyncProcessInfo.timer) { -// clearTimeout(liveSyncProcessInfo.timer); -// } - -// if (liveSyncProcessInfo.watcherInfo && liveSyncProcessInfo.watcherInfo.watcher) { -// liveSyncProcessInfo.watcherInfo.watcher.close(); -// } - -// liveSyncProcessInfo.watcherInfo = null; -// liveSyncProcessInfo.isStopped = true; - -// if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) { -// await liveSyncProcessInfo.actionsChain; -// } - -// liveSyncProcessInfo.deviceDescriptors = []; - -// if (liveSyncProcessInfo.syncToPreviewApp) { -// await this.$previewAppLiveSyncService.stopLiveSync(); -// this.$previewAppLiveSyncService.removeAllListeners(); -// } - -// // Kill typescript watcher -// const projectData = this.$projectDataService.getProjectData(projectDir); -// await this.$hooksService.executeAfterHooks('watch', { -// hookArgs: { -// projectData -// } -// }); - -// // In case we are stopping the LiveSync we must set usbLiveSyncService.isInitialized to false, -// // as in case we execute nativescript-dev-typescript's before-prepare hook again in the same process, it MUST transpile the files. -// this.$usbLiveSyncService.isInitialized = false; -// } else if (liveSyncProcessInfo.currentSyncAction && shouldAwaitPendingOperation) { -// await liveSyncProcessInfo.currentSyncAction; -// } - -// // Emit LiveSync stopped when we've really stopped. -// _.each(removedDeviceIdentifiers, deviceIdentifier => { -// this.emitLivesyncEvent(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier }); -// }); -// } -// } - -// public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { -// const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; -// const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; -// return currentDescriptors || []; -// } - -// private attachToPreviewAppLiveSyncError(): void { -// if (!this.$usbLiveSyncService.isInitialized) { -// this.$previewAppLiveSyncService.on(LiveSyncEvents.previewAppLiveSyncError, liveSyncData => { -// this.$logger.error(liveSyncData.error); -// this.emit(LiveSyncEvents.previewAppLiveSyncError, liveSyncData); -// }); -// } -// } - -// @performanceLog() -// private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { -// const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir); - -// return deviceDescriptor && deviceDescriptor.debugggingEnabled ? -// this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) : -// this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath); -// } - -// private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IRefreshApplicationSettings): Promise { -// const result = { didRestart: false }; -// const platform = liveSyncResultInfo.deviceAppData.platform; -// const platformLiveSyncService = this.getLiveSyncService(platform); -// const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; -// try { -// let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); -// if (!shouldRestart) { -// shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); -// } - -// if (shouldRestart) { -// const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; -// this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); -// await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); -// result.didRestart = true; -// } -// } catch (err) { -// 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); -// if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { -// this.emitLivesyncEvent(LiveSyncEvents.liveSyncNotification, { -// projectDir: projectData.projectDir, -// applicationIdentifier, -// deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, -// notification: msg -// }); -// } - -// if (settings && settings.shouldCheckDeveloperDiscImage) { -// this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, debugOpts, outputPath); -// } -// } - -// this.emitLivesyncEvent(LiveSyncEvents.liveSyncExecuted, { -// projectDir: projectData.projectDir, -// applicationIdentifier, -// syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), -// deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, -// isFullSync: liveSyncResultInfo.isFullSync -// }); - -// return result; -// } - -// @performanceLog() -// private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { -// debugOptions = debugOptions || {}; -// if (debugOptions.debugBrk) { -// liveSyncResultInfo.waitForDebugger = true; -// } - -// const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true }); - -// // 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: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, -// debugOptions: debugOptions, -// }; - -// return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); -// } - -// private handleDeveloperDiskImageError(err: any, liveSyncResultInfo: ILiveSyncResultInfo, projectData: IProjectData, debugOpts: IDebugOptions, outputPath: string) { -// if ((err.message || err) === "Could not find developer disk image") { -// const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; -// const attachDebuggerOptions: IAttachDebuggerOptions = { -// platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, -// isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, -// projectDir: projectData.projectDir, -// deviceIdentifier, -// debugOptions: debugOpts, -// outputPath -// }; -// this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); -// } -// } - -// public async attachDebugger(settings: IAttachDebuggerOptions): Promise { -// // Default values -// if (settings.debugOptions) { -// settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; -// settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; -// } else { -// settings.debugOptions = { -// chrome: true, -// start: true -// }; -// } - -// const projectData = this.$projectDataService.getProjectData(settings.projectDir); -// const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); - -// // Of the properties below only `buildForDevice` and `release` are currently used. -// // Leaving the others with placeholder values so that they may not be forgotten in future implementations. -// const buildConfig = this.getInstallApplicationBuildConfig(settings.deviceIdentifier, settings.projectDir, { isEmulator: settings.isEmulator }); -// debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath); -// const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); -// const result = this.printDebugInformation(debugInfo, settings.debugOptions.forceDebuggerAttachedEvent); -// return result; -// } - -// 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 enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { -// return _.map(deviceOpts, d => this.enableDebuggingCore(d, debuggingAdditionalOptions)); -// } - -// private getDeviceDescriptor(deviceIdentifier: string, projectDir: string) { -// const deviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); - -// return _.find(deviceDescriptors, d => d.identifier === deviceIdentifier); -// } - -// @performanceLog() -// private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { -// const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); -// if (!currentDeviceDescriptor) { -// this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); -// } - -// currentDeviceDescriptor.debugggingEnabled = true; -// currentDeviceDescriptor.debugOptions = deviceOption.debugOptions; -// const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); -// const attachDebuggerOptions: IAttachDebuggerOptions = { -// deviceIdentifier: deviceOption.deviceIdentifier, -// isEmulator: currentDeviceInstance.isEmulator, -// outputPath: currentDeviceDescriptor.outputPath, -// platform: currentDeviceInstance.deviceInfo.platform, -// projectDir: debuggingAdditionalOptions.projectDir, -// debugOptions: deviceOption.debugOptions -// }; - -// let debugInformation: IDebugInformation; -// try { -// debugInformation = await this.attachDebugger(attachDebuggerOptions); -// } catch (err) { -// this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); -// attachDebuggerOptions.debugOptions.start = false; -// try { -// debugInformation = await this.attachDebugger(attachDebuggerOptions); -// } catch (innerErr) { -// this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); -// throw err; -// } -// } - -// return debugInformation; -// } - -// private async enableDebuggingCore(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { -// const liveSyncProcessInfo: ILiveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; -// if (liveSyncProcessInfo && liveSyncProcessInfo.currentSyncAction) { -// await liveSyncProcessInfo.currentSyncAction; -// } - -// return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, debuggingAdditionalOptions); -// } - -// public disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { -// return _.map(deviceOptions, d => this.disableDebuggingCore(d, debuggingAdditionalOptions)); -// } - -// @hook('watchPatterns') -// public async getWatcherPatterns(liveSyncData: ILiveSyncInfo, projectData: IProjectData, platforms: string[]): Promise { -// // liveSyncData and platforms are used by plugins that make use of the watchPatterns hook -// // TODO: ignore getAppDirectoryRelativePath -// // TODO: watch platforms folder of node_modules e.g native source folders of nativescript plugins -// return [projectData.getAppDirectoryRelativePath(), projectData.getAppResourcesRelativeDirectoryPath()]; -// } - -// public async disableDebuggingCore(deviceOption: IDisableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { -// const liveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; -// if (liveSyncProcessInfo.currentSyncAction) { -// await liveSyncProcessInfo.currentSyncAction; -// } - -// const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); -// if (currentDeviceDescriptor) { -// currentDeviceDescriptor.debugggingEnabled = false; -// } else { -// this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}`); -// } - -// const currentDevice = this.$devicesService.getDeviceByIdentifier(currentDeviceDescriptor.identifier); -// if (!currentDevice) { -// this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}. Could not find device.`); -// } - -// await this.$debugService.debugStop(currentDevice.deviceInfo.identifier); -// this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier: currentDeviceDescriptor.identifier }); -// } - -// @hook("liveSync") -// private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise { -// let deviceDescriptorsForInitialSync: ILiveSyncDeviceInfo[] = []; - -// if (liveSyncData.syncToPreviewApp) { -// await this.$previewAppLiveSyncService.initialize({ -// projectDir: projectData.projectDir, -// bundle: liveSyncData.bundle, -// useHotModuleReload: liveSyncData.useHotModuleReload, -// env: liveSyncData.env -// }); -// } else { -// // In case liveSync is called for a second time for the same projectDir. -// const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; - -// // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. -// const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir); -// deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; -// } - -// this.setLiveSyncProcessInfo(liveSyncData, deviceDescriptors); - -// const shouldStartWatcher = !liveSyncData.skipWatcher && (liveSyncData.syncToPreviewApp || this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length); -// if (shouldStartWatcher) { -// // Should be set after prepare -// this.$usbLiveSyncService.isInitialized = true; -// await this.startWatcher(projectData, liveSyncData, deviceDescriptors); -// } - -// await this.initialSync(projectData, liveSyncData, deviceDescriptorsForInitialSync); -// } - -// private setLiveSyncProcessInfo(liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): void { -// const { projectDir } = liveSyncData; -// this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); -// this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); -// this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; -// this.liveSyncProcessesInfo[projectDir].isStopped = false; -// this.liveSyncProcessesInfo[projectDir].syncToPreviewApp = liveSyncData.syncToPreviewApp; - -// const currentDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); -// this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); -// } - -// private getLiveSyncService(platform: string): IPlatformLiveSyncService { -// if (this.$mobileHelper.isiOSPlatform(platform)) { -// return this.$injector.resolve("iOSLiveSyncService"); -// } else if (this.$mobileHelper.isAndroidPlatform(platform)) { -// return this.$injector.resolve("androidLiveSyncService"); -// } - -// this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); -// } - -// private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions, nativePrepare?: INativePrepare): Promise { -// const platform = options.device.deviceInfo.platform; -// const appInstalledOnDeviceResult: IAppInstalledOnDeviceResult = { appInstalled: false }; -// if (options.preparedPlatforms.indexOf(platform) === -1) { -// options.preparedPlatforms.push(platform); -// } - -// const buildResult = await this.installedCachedAppPackage(platform, options); -// if (buildResult) { -// appInstalledOnDeviceResult.appInstalled = true; -// return appInstalledOnDeviceResult; -// } - -// const shouldBuild = await this.$platformService.shouldBuild(platform, -// options.projectData, -// { buildForDevice: !options.device.isEmulator, clean: options.liveSyncData && options.liveSyncData.clean }, -// options.deviceBuildInfoDescriptor.outputPath); -// let pathToBuildItem = null; -// if (shouldBuild) { -// pathToBuildItem = await options.deviceBuildInfoDescriptor.buildAction(); -// options.rebuiltInformation.push({ isEmulator: options.device.isEmulator, platform, pathToBuildItem }); -// } else { -// await this.$analyticsService.trackEventActionInGoogleAnalytics({ -// action: TrackActionNames.LiveSync, -// device: options.device, -// projectDir: options.projectData.projectDir -// }); -// } - -// await this.$platformService.validateInstall(options.device, options.projectData, options, options.deviceBuildInfoDescriptor.outputPath); -// const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options, options.deviceBuildInfoDescriptor.outputPath); -// if (shouldInstall) { -// const buildConfig = this.getInstallApplicationBuildConfig(options.device.deviceInfo.identifier, options.projectData.projectDir, { isEmulator: options.device.isEmulator }); -// await this.$platformService.installApplication(options.device, buildConfig, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); -// appInstalledOnDeviceResult.appInstalled = true; -// } - -// return appInstalledOnDeviceResult; -// } - -// private async installedCachedAppPackage(platform: string, options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions): Promise { -// const rebuildInfo = _.find(options.rebuiltInformation, info => info.platform === platform && (this.$mobileHelper.isAndroidPlatform(platform) || info.isEmulator === options.device.isEmulator)); - -// if (rebuildInfo) { -// // Case where we have three devices attached, a change that requires build is found, -// // we'll rebuild the app only for the first device, but we should install new package on all three devices. -// const buildConfig = this.getInstallApplicationBuildConfig(options.device.deviceInfo.identifier, options.projectData.projectDir, { isEmulator: options.device.isEmulator }); -// await this.$platformService.installApplication(options.device, buildConfig, options.projectData, rebuildInfo.pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); -// return rebuildInfo.pathToBuildItem; -// } - -// return null; -// } - -// private async initialSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { -// if (!liveSyncData.syncToPreviewApp) { -// await this.initialCableSync(projectData, liveSyncData, deviceDescriptors); -// } -// } - -// private async initialCableSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { -// const preparedPlatforms: string[] = []; -// const rebuiltInformation: ILiveSyncBuildInfo[] = []; - -// const settings = this.getDefaultLatestAppPackageInstalledSettings(); -// // Now fullSync -// const deviceAction = async (device: Mobile.IDevice): Promise => { -// const platform = device.deviceInfo.platform; -// try { -// const platformLiveSyncService = this.getLiveSyncService(platform); - -// const deviceBuildInfoDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - -// await this.ensureLatestAppPackageIsInstalledOnDevice({ -// device, -// preparedPlatforms, -// rebuiltInformation, -// projectData, -// deviceBuildInfoDescriptor, -// liveSyncData, -// settings, -// bundle: liveSyncData.bundle, -// release: liveSyncData.release, -// env: liveSyncData.env -// }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); - -// const liveSyncResultInfo = await platformLiveSyncService.fullSync({ -// projectData, -// device, -// useHotModuleReload: liveSyncData.useHotModuleReload, -// watch: !liveSyncData.skipWatcher, -// force: liveSyncData.force, -// liveSyncDeviceInfo: deviceBuildInfoDescriptor -// }); - -// await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - -// this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - -// this.emitLivesyncEvent(LiveSyncEvents.liveSyncStarted, { -// projectDir: projectData.projectDir, -// deviceIdentifier: device.deviceInfo.identifier, -// applicationIdentifier: projectData.projectIdentifiers[platform.toLowerCase()] -// }); -// } catch (err) { -// this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); - -// this.emitLivesyncEvent(LiveSyncEvents.liveSyncError, { -// error: err, -// deviceIdentifier: device.deviceInfo.identifier, -// projectDir: projectData.projectDir, -// applicationIdentifier: projectData.projectIdentifiers[platform.toLowerCase()] -// }); - -// await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier], { shouldAwaitAllActions: false }); -// } -// }; - -// // Execute the action only on the deviceDescriptors passed to initialSync. -// // In case where we add deviceDescriptors to already running application, we've already executed initialSync for them. -// await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); - -// this.attachDeviceLostHandler(); -// } - -// private getDefaultLatestAppPackageInstalledSettings(): ILatestAppPackageInstalledSettings { -// return { -// [this.$devicePlatformsConstants.Android]: { -// [DeviceTypes.Device]: false, -// [DeviceTypes.Emulator]: false -// }, -// [this.$devicePlatformsConstants.iOS]: { -// [DeviceTypes.Device]: false, -// [DeviceTypes.Emulator]: false -// } -// }; -// } - -// private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { -// const devicesIds = deviceDescriptors.map(dd => dd.identifier); -// const devices = _.filter(this.$devicesService.getDeviceInstances(), device => _.includes(devicesIds, device.deviceInfo.identifier)); -// const platforms = _(devices).map(device => device.deviceInfo.platform).uniq().value(); -// const patterns = await this.getWatcherPatterns(liveSyncData, projectData, platforms); - -// if (liveSyncData.useHotModuleReload) { -// this.$hmrStatusService.attachToHmrStatusEvent(); -// } - -// if (liveSyncData.watchAllFiles) { -// const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir); -// patterns.push(PACKAGE_JSON_FILE_NAME); - -// // watch only production node_module/packages same one prepare uses -// for (const index in productionDependencies) { -// patterns.push(productionDependencies[index].directory); -// } -// } - -// const currentWatcherInfo = this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo; -// const areWatcherPatternsDifferent = () => _.xor(currentWatcherInfo.patterns, patterns).length; -// if (!currentWatcherInfo || areWatcherPatternsDifferent()) { -// if (currentWatcherInfo) { -// currentWatcherInfo.watcher.close(); -// } - -// let filesToSync: string[] = []; -// const hmrData: IDictionary = {}; -// const filesToSyncMap: IDictionary = {}; -// let filesToRemove: string[] = []; -// let timeoutTimer: NodeJS.Timer; - -// const startSyncFilesTimeout = (files: string[], platform?: string, opts?: { calledFromHook: boolean }) => { -// timeoutTimer = setTimeout(async () => { -// if (platform && liveSyncData.bundle) { -// filesToSync = filesToSyncMap[platform]; -// } - -// if (files) { -// filesToSync = files; -// } - -// if ((filesToSync && filesToSync.length) || (filesToRemove && filesToRemove.length)) { -// console.log("============================== FILES_TO_SYNC ==================== ", filesToSync); -// const currentFilesToSync = _.cloneDeep(filesToSync); -// filesToSync.splice(0, filesToSync.length); - -// const currentFilesToRemove = _.cloneDeep(filesToRemove); -// filesToRemove = []; - -// if (liveSyncData.syncToPreviewApp) { -// await this.addActionToChain(projectData.projectDir, async () => { -// await this.$previewAppLiveSyncService.syncFiles({ -// projectDir: projectData.projectDir, -// bundle: liveSyncData.bundle, -// useHotModuleReload: liveSyncData.useHotModuleReload, -// env: liveSyncData.env -// }, currentFilesToSync, currentFilesToRemove); -// }); -// } else { -// // Push actions to the queue, do not start them simultaneously -// await this.addActionToChain(projectData.projectDir, async () => { -// try { -// const currentHmrData = _.cloneDeep(hmrData); - -// const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); - -// const preparedPlatforms: string[] = []; -// const rebuiltInformation: ILiveSyncBuildInfo[] = []; - -// const latestAppPackageInstalledSettings = this.getDefaultLatestAppPackageInstalledSettings(); - -// await this.$devicesService.execute(async (device: Mobile.IDevice) => { -// const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; -// const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); -// const platformHmrData = (currentHmrData && currentHmrData[device.deviceInfo.platform]) || {}; - -// const settings: ILiveSyncWatchInfo = { -// liveSyncDeviceInfo: deviceBuildInfoDescriptor, -// projectData, -// filesToRemove: currentFilesToRemove, -// filesToSync: currentFilesToSync, -// isReinstalled: false, -// syncAllFiles: liveSyncData.watchAllFiles, -// hmrData: platformHmrData, -// useHotModuleReload: liveSyncData.useHotModuleReload, -// force: liveSyncData.force, -// connectTimeout: 1000 -// }; - -// const service = this.getLiveSyncService(device.deviceInfo.platform); - -// const watchAction = async (watchInfo: ILiveSyncWatchInfo): Promise => { -// const isInHMRMode = liveSyncData.useHotModuleReload && platformHmrData.hash; -// if (isInHMRMode) { -// this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); -// } - -// let liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); - -// await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - -// // If didRecover is true, this means we were in ErrorActivity and fallback files were already transferred and app will be restarted. -// if (!liveSyncResultInfo.didRecover && isInHMRMode) { -// const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, platformHmrData.hash); -// if (status === HmrConstants.HMR_ERROR_STATUS) { -// watchInfo.filesToSync = platformHmrData.fallbackFiles; -// liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); -// // We want to force a restart of the application. -// liveSyncResultInfo.isFullSync = true; -// await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); -// } -// } - -// this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); -// }; - -// if (liveSyncData.useHotModuleReload && opts && opts.calledFromHook) { -// try { -// this.$logger.trace("Try executing watch action without any preparation of files."); -// await watchAction(settings); -// this.$logger.trace("Successfully executed watch action without any preparation of files."); -// return; -// } catch (err) { -// this.$logger.trace(`Error while trying to execute fast sync. Now we'll check the state of the app and we'll try to resurrect from the error. The error is: ${err}`); -// } -// } - -// const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({ -// device, -// preparedPlatforms, -// rebuiltInformation, -// projectData, -// deviceBuildInfoDescriptor, -// // the clean option should be respected only during initial sync -// liveSyncData: _.assign({}, liveSyncData, { clean: false }), -// settings: latestAppPackageInstalledSettings, -// modifiedFiles: allModifiedFiles, -// filesToRemove: currentFilesToRemove, -// filesToSync: currentFilesToSync, -// bundle: liveSyncData.bundle, -// release: liveSyncData.release, -// env: liveSyncData.env, -// skipModulesNativeCheck: !liveSyncData.watchAllFiles -// }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); - -// settings.isReinstalled = appInstalledOnDeviceResult.appInstalled; -// settings.connectTimeout = null; - -// if (liveSyncData.useHotModuleReload && appInstalledOnDeviceResult.appInstalled) { -// _.each(platformHmrData.fallbackFiles, fileToSync => currentFilesToSync.push(fileToSync)); -// } - -// await watchAction(settings); -// }, -// // Ensure the livesync process will be triggered only for the initial devices -// (device: Mobile.IDevice) => { -// const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; -// return (!platform || platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); -// } -// ); -// } catch (err) { -// const allErrors = (err).allErrors; - -// if (allErrors && _.isArray(allErrors)) { -// for (const deviceError of allErrors) { -// this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); -// const device = this.$devicesService.getDeviceByIdentifier(deviceError.deviceIdentifier); -// this.emitLivesyncEvent(LiveSyncEvents.liveSyncError, { -// error: deviceError, -// deviceIdentifier: deviceError.deviceIdentifier, -// projectDir: projectData.projectDir, -// applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] -// }); - -// await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier], { shouldAwaitAllActions: false }); -// } -// } -// } -// }); -// } -// } -// }, liveSyncData.useHotModuleReload ? 0 : 250); - -// this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; -// }; - -// await this.$hooksService.executeBeforeHooks('watch', { -// hookArgs: { -// projectData, -// config: { -// env: liveSyncData.env, -// appFilesUpdaterOptions: { -// bundle: liveSyncData.bundle, -// release: liveSyncData.release, -// watchAllFiles: liveSyncData.watchAllFiles, -// useHotModuleReload: liveSyncData.useHotModuleReload -// }, -// platforms -// }, -// filesToSync, -// filesToSyncMap, -// hmrData, -// filesToRemove, -// // startSyncFilesTimeout: async (platform: string) => { -// // const opts = { calledFromHook: true }; -// // if (platform) { -// // await startSyncFilesTimeout(platform, opts); -// // } else { -// // // This code is added for backwards compatibility with old versions of nativescript-dev-webpack plugin. -// // await startSyncFilesTimeout(null, opts); -// // } -// // } -// } -// }); - -// let isFirstSync = true; -// this.$platformService.on("changedFiles", async files => { -// console.log("===================== CHANGED FILES =============== ", files); -// if (!isFirstSync) { -// // filesToSyncMap["ios"] = files; -// await startSyncFilesTimeout(files, "ios", { calledFromHook: true }); -// } else { -// isFirstSync = false; -// } -// }); - -// // const platformSpecificOptions = options.deviceBuildInfoDescriptor.platformSpecificOptions || {}; -// const prepareInfo: IPreparePlatformInfo = { -// platform: "ios", -// appFilesUpdaterOptions: { -// bundle: true, -// release: false, -// watchAllFiles: false, -// useHotModuleReload: false -// }, -// projectData: this.$projectDataService.getProjectData(liveSyncData.projectDir), -// env: liveSyncData.env, -// nativePrepare: null, -// // filesToSync: options.filesToSync, -// // filesToRemove: options.filesToRemove, -// // skipModulesNativeCheck: liveSyncData.skipModulesNativeCheck, -// config: {}, -// webpackCompilerConfig: { -// watch: true, -// env: liveSyncData.env -// } -// }; - -// await this.$platformService.preparePlatform(prepareInfo); - -// // const watcherOptions: choki.WatchOptions = { -// // ignoreInitial: true, -// // cwd: liveSyncData.projectDir, -// // awaitWriteFinish: { -// // pollInterval: 100, -// // stabilityThreshold: 500 -// // }, -// // ignored: ["**/.*", ".*"] // hidden files -// // }; - -// // const watcher = choki.watch(patterns, watcherOptions) -// // .on("all", async (event: string, filePath: string) => { - -// // clearTimeout(timeoutTimer); - -// // filePath = path.join(liveSyncData.projectDir, filePath); - -// // this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); - -// // if (event === "add" || event === "addDir" || event === "change" /* <--- what to do when change event is raised ? */) { -// // filesToSync.push(filePath); -// // } else if (event === "unlink" || event === "unlinkDir") { -// // filesToRemove.push(filePath); -// // } - -// // startSyncFilesTimeout(); -// // }); - -// // this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, patterns }; -// this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; -// } -// } - -// @cache() -// private attachDeviceLostHandler(): void { -// 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.liveSyncProcessesInfo) { -// try { -// if (_.find(this.liveSyncProcessesInfo[projectDir].deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { -// await this.stopLiveSync(projectDir, [device.deviceInfo.identifier]); -// } -// } catch (err) { -// this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); -// } -// } -// }); -// } - -// private async addActionToChain(projectDir: string, action: () => Promise): Promise { -// const liveSyncInfo = this.liveSyncProcessesInfo[projectDir]; -// if (liveSyncInfo) { -// liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { -// if (!liveSyncInfo.isStopped) { -// liveSyncInfo.currentSyncAction = action(); -// const res = await liveSyncInfo.currentSyncAction; -// return res; -// } -// }); - -// const result = await liveSyncInfo.actionsChain; -// return result; -// } -// } - -// private getInstallApplicationBuildConfig(deviceIdentifier: string, projectDir: string, opts: { isEmulator: boolean }): IBuildConfig { -// const buildConfig: IBuildConfig = { -// buildForDevice: !opts.isEmulator, -// iCloudContainerEnvironment: null, -// release: false, -// device: deviceIdentifier, -// provision: null, -// teamId: null, -// projectDir -// }; - -// return buildConfig; -// } - -// public emitLivesyncEvent(event: string, livesyncData: ILiveSyncEventData): boolean { -// this.$logger.trace(`Will emit event ${event} with data`, livesyncData); -// return this.emit(event, livesyncData); -// } -// } - -// $injector.register("liveSyncService", LiveSyncService); - -// /** -// * This class is used only for old versions of nativescript-dev-typescript plugin. -// * It should be replaced with liveSyncService.isInitalized. -// * Consider adding get and set methods for isInitialized, -// * so whenever someone tries to access the value of isInitialized, -// * they'll get a warning to update the plugins (like nativescript-dev-typescript). -// */ -// export class DeprecatedUsbLiveSyncService { -// public isInitialized = false; -// } - -// $injector.register("usbLiveSyncService", DeprecatedUsbLiveSyncService); diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index 9f334615b0..8a6b327606 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -1,7 +1,6 @@ import { NATIVESCRIPT_CLOUD_EXTENSION_NAME, TrackActionNames } from "../constants"; import { isInteractive } from "../common/helpers"; import { EOL } from "os"; -// import { cache } from "../common/decorators"; export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequirements { constructor(private $commandsService: ICommandsService, @@ -12,14 +11,9 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private $prompter: IPrompter, private $staticConfig: IStaticConfig, private $analyticsService: IAnalyticsService, - // private $injector: IInjector, + private $previewAppLiveSyncService: IPreviewAppLiveSyncService, private $previewQrCodeService: IPreviewQrCodeService) { } - // @cache() - // private get $liveSyncService(): ILiveSyncService { - // return this.$injector.resolve("liveSyncService"); - // } - 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"; @@ -181,18 +175,12 @@ 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.$liveSyncService.liveSync([], { - // syncToPreviewApp: true, - // projectDir, - // skipWatcher: !options.watch, - // clean: options.clean, - // release: options.release, - // webpackCompilerConfig: { - // env: options.env, - // }, - // timeout: options.timeout, - // useHotModuleReload: options.hmr - // }); + await this.$previewAppLiveSyncService.initialize({ + projectDir, + env: options.env, + useHotModuleReload: options.hmr, + bundle: true + }); await this.$previewQrCodeService.printLiveSyncQrCode({ projectDir, useHotModuleReload: options.hmr, link: options.link }); } From 4a9c9675e6961ab8d755b86e7b16f409c7a4dcea Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 5 May 2019 19:47:38 +0300 Subject: [PATCH 20/30] fix: fix typo error in debugggingEnabled --- lib/controllers/run-on-devices-controller.ts | 4 ++-- lib/helpers/deploy-command-helper.ts | 2 +- lib/helpers/livesync-command-helper.ts | 4 ++-- lib/services/device/device-debug-app-service.ts | 2 +- lib/services/device/device-refresh-app-service.ts | 2 +- lib/services/test-execution-service.ts | 5 ++++- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/controllers/run-on-devices-controller.ts b/lib/controllers/run-on-devices-controller.ts index 4664958d30..1f2288a31a 100644 --- a/lib/controllers/run-on-devices-controller.ts +++ b/lib/controllers/run-on-devices-controller.ts @@ -47,7 +47,7 @@ export class RunOnDevicesController extends EventEmitter { isFullSync: liveSyncResultInfo.isFullSync }); - if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) { + if (liveSyncResultInfo && deviceDescriptor.debuggingEnabled) { await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo); } @@ -136,7 +136,7 @@ export class RunOnDevicesController extends EventEmitter { isFullSync: liveSyncResultInfo.isFullSync }); - if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) { + if (liveSyncResultInfo && deviceDescriptor.debuggingEnabled) { await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo); } } diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index 2b3452449f..602cd53a9b 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -53,7 +53,7 @@ export class DeployCommandHelper { const info: ILiveSyncDeviceInfo = { identifier: d.deviceInfo.identifier, buildAction, - debugggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], + debuggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], debugOptions: this.$options, outputPath, skipNativePrepare: additionalOptions && additionalOptions.skipNativePrepare, diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 70a8568a4a..8dcb6f0b28 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -104,7 +104,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const info: ILiveSyncDeviceInfo = { identifier: d.deviceInfo.identifier, buildAction, - debugggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], + debuggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], debugOptions: this.$options, outputPath, skipNativePrepare: additionalOptions && additionalOptions.skipNativePrepare, @@ -115,7 +115,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, - skipWatcher: !this.$options.watch, + skipWatcher: !this.$options.watch || this.$options.justlaunch, clean: this.$options.clean, release: this.$options.release, env: this.$options.env, diff --git a/lib/services/device/device-debug-app-service.ts b/lib/services/device/device-debug-app-service.ts index 643f01c210..aac9393130 100644 --- a/lib/services/device/device-debug-app-service.ts +++ b/lib/services/device/device-debug-app-service.ts @@ -71,7 +71,7 @@ export class DeviceDebugAppService { this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); } - deviceDescriptor.debugggingEnabled = true; + deviceDescriptor.debuggingEnabled = true; deviceDescriptor.debugOptions = deviceOption.debugOptions; const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); const attachDebuggerOptions: IAttachDebuggerOptions = { diff --git a/lib/services/device/device-refresh-app-service.ts b/lib/services/device/device-refresh-app-service.ts index 7786118110..aa9085d7b5 100644 --- a/lib/services/device/device-refresh-app-service.ts +++ b/lib/services/device/device-refresh-app-service.ts @@ -12,7 +12,7 @@ export class DeviceRefreshAppService { @performanceLog() public async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, settings?: IRefreshApplicationSettings): Promise { - if (deviceDescriptor && deviceDescriptor.debugggingEnabled) { + if (deviceDescriptor && deviceDescriptor.debuggingEnabled) { liveSyncResultInfo.waitForDebugger = deviceDescriptor.debugOptions && deviceDescriptor.debugOptions.debugBrk; } diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 7ce9faa8ae..1da23b2227 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -78,7 +78,10 @@ export class TestExecutionService implements ITestExecutionService { if (!this.$options.env) { this.$options.env = { }; } this.$options.env.unitTesting = true; - await this.$liveSyncCommandHelper.executeLiveSyncOperation(devices, this.platform, {}); + const deviceDebugMap: IDictionary = {}; + devices.forEach(device => deviceDebugMap[device.deviceInfo.identifier] = this.$options.debugBrk); + + await this.$liveSyncCommandHelper.executeLiveSyncOperation(devices, this.platform, { deviceDebugMap }); }; karmaRunner.on("message", (karmaData: any) => { From 241049344e06503f46ef8e4e5590a06a248c7f0b Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 5 May 2019 20:39:25 +0300 Subject: [PATCH 21/30] fix: fix mainController tests, delete test/livesync-service and move all appropriate tests from test/livesync-service to main-controller --- lib/controllers/main-controller.ts | 2 +- lib/services/run-on-devices-data-service.ts | 3 +- test/controllers/main-controller.ts | 108 ++++++++-- test/services/livesync-service.ts | 209 -------------------- 4 files changed, 95 insertions(+), 227 deletions(-) delete mode 100644 test/services/livesync-service.ts diff --git a/lib/controllers/main-controller.ts b/lib/controllers/main-controller.ts index 9108d3905a..98954b4d94 100644 --- a/lib/controllers/main-controller.ts +++ b/lib/controllers/main-controller.ts @@ -76,7 +76,7 @@ export class MainController extends EventEmitter { // TODO: Consider to handle correctly the descriptors when livesync is executed for second time for the same projectDir - this.$runOnDevicesDataService.persistData(projectDir, liveSyncInfo, deviceDescriptors); + this.$runOnDevicesDataService.persistData(projectDir, deviceDescriptors); const shouldStartWatcher = !liveSyncInfo.skipWatcher && (liveSyncInfo.syncToPreviewApp || this.$runOnDevicesDataService.hasDeviceDescriptors(projectDir)); if (shouldStartWatcher) { diff --git a/lib/services/run-on-devices-data-service.ts b/lib/services/run-on-devices-data-service.ts index 6e100b4ec4..a314afc404 100644 --- a/lib/services/run-on-devices-data-service.ts +++ b/lib/services/run-on-devices-data-service.ts @@ -19,12 +19,11 @@ export class RunOnDevicesDataService { return this.liveSyncProcessesInfo[projectDir].deviceDescriptors.length; } - public persistData(projectDir: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): void { + public persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]): void { this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; this.liveSyncProcessesInfo[projectDir].isStopped = false; - this.liveSyncProcessesInfo[projectDir].syncToPreviewApp = liveSyncInfo.syncToPreviewApp; const currentDeviceDescriptors = this.getDeviceDescriptors(projectDir); this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); diff --git a/test/controllers/main-controller.ts b/test/controllers/main-controller.ts index 096ee10665..1c540f9845 100644 --- a/test/controllers/main-controller.ts +++ b/test/controllers/main-controller.ts @@ -2,6 +2,11 @@ import { Yok } from "../../lib/common/yok"; import { assert } from "chai"; import { AddPlatformService } from "../../lib/services/platform/add-platform-service"; import { MainController } from "../../lib/controllers/main-controller"; +import { RunOnDeviceEvents } from "../../lib/constants"; +import { RunOnDevicesEmitter } from "../../lib/run-on-devices-emitter"; +import { WorkflowDataService } from "../../lib/services/workflow/workflow-data-service"; +import { RunOnDevicesDataService } from "../../lib/services/run-on-devices-data-service"; +import { PlatformWatcherService } from "../../lib/services/platform/platform-watcher-service"; const deviceMap: IDictionary = { myiOSDevice: { @@ -22,7 +27,15 @@ function createTestInjector(): IInjector { const injector = new Yok(); injector.register("devicesService", ({ - getDeviceByIdentifier: (identifier: string) => { return deviceMap[identifier]; } + on: () => ({}), + getDeviceByIdentifier: (identifier: string) => { return deviceMap[identifier]; }, + getPlatformsFromDeviceDescriptors: (deviceDescriptors: ILiveSyncDeviceInfo[]) => { + return _(deviceDescriptors) + .map(device => deviceMap[device.identifier]) + .map(device => device.deviceInfo.platform) + .uniq() + .value(); + } })); injector.register("deviceWorkflowService", ({})); injector.register("errors", ({ @@ -40,7 +53,7 @@ function createTestInjector(): IInjector { injector.register("platformWatcherService", ({ on: () => ({}), emit: () => ({}), - startWatcher: () => ({}) + startWatchers: () => ({}) })); injector.register("mainController", MainController); injector.register("pluginsService", ({})); @@ -50,11 +63,23 @@ function createTestInjector(): IInjector { }) })); injector.register("buildArtefactsService", ({})); + injector.register("addPlatformService", {}); injector.register("buildPlatformService", ({})); - injector.register("platformAddService", ({})); - injector.register("platformService", ({})); - injector.register("projectChangesService", ({})); + injector.register("preparePlatformService", ({})); + injector.register("deviceInstallAppService", {}); + injector.register("deviceRefreshAppService", {}); + injector.register("deviceDebugAppService", {}); injector.register("fs", ({})); + injector.register("hooksService", { + executeAfterHooks: () => ({}) + }); + injector.register("projectChangesService", ({})); + injector.register("runOnDevicesController", { + on: () => ({}) + }); + injector.register("runOnDevicesDataService", RunOnDevicesDataService); + injector.register("runOnDevicesEmitter", RunOnDevicesEmitter); + injector.register("workflowDataService", WorkflowDataService); return injector; } @@ -73,7 +98,7 @@ const liveSyncInfo = { }; describe("MainController", () => { - describe("start", () => { + describe("runOnDevices", () => { describe("when the run on device is called for second time for the same projectDir", () => { it("should run only for new devies (for which the initial sync is still not executed)", async () => { return; @@ -87,15 +112,14 @@ describe("MainController", () => { const injector = createTestInjector(); let isAddPlatformIfNeededCalled = false; - const platformAddService: AddPlatformService = injector.resolve("platformAddService"); - platformAddService.addPlatformIfNeeded = async () => { isAddPlatformIfNeededCalled = true; }; + const addPlatformService: AddPlatformService = injector.resolve("addPlatformService"); + addPlatformService.addPlatformIfNeeded = async () => { isAddPlatformIfNeededCalled = true; }; let isStartWatcherCalled = false; - const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); - (platformWatcherService).startWatcher = async () => { + const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); + platformWatcherService.startWatchers = async () => { assert.isTrue(isAddPlatformIfNeededCalled); isStartWatcherCalled = true; - return true; }; const mainController: MainController = injector.resolve("mainController"); @@ -127,13 +151,13 @@ describe("MainController", () => { const injector = createTestInjector(); const actualAddedPlatforms: IPlatformData[] = []; - const platformAddService: AddPlatformService = injector.resolve("platformAddService"); - platformAddService.addPlatformIfNeeded = async (platformData: IPlatformData) => { + const addPlatformService: AddPlatformService = injector.resolve("addPlatformService"); + addPlatformService.addPlatformIfNeeded = async (platformData: IPlatformData) => { actualAddedPlatforms.push(platformData); }; - const mainController = injector.resolve("mainController"); - await mainController.runPlatform(projectDir, testCase.connectedDevices, liveSyncInfo); + const mainController: MainController = injector.resolve("mainController"); + await mainController.runOnDevices(projectDir, testCase.connectedDevices, liveSyncInfo); assert.deepEqual(actualAddedPlatforms.map(pData => pData.platformNameLowerCase), testCase.expectedAddedPlatforms); }); @@ -198,4 +222,58 @@ describe("MainController", () => { }); }); }); + describe("stopRunOnDevices", () => { + const testCases = [ + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device2", "device3"] + }, + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device3"], + deviceIdentifiersToBeStopped: ["device1", "device3"] + }, + { + name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1", "device4"] + } + ]; + + for (const testCase of testCases) { + it(testCase.name, async () => { + const testInjector = createTestInjector(); + const mainController = testInjector.resolve("mainController"); + + const runOnDevicesDataService: RunOnDevicesDataService = testInjector.resolve("runOnDevicesDataService"); + runOnDevicesDataService.persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier }))); + + const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; + + const runOnDevicesEmitter = testInjector.resolve("runOnDevicesEmitter"); + runOnDevicesEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { + assert.equal(data.projectDir, projectDir); + emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); + }); + + await mainController.stopRunOnDevices(projectDir, testCase.deviceIdentifiersToBeStopped); + + assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); + }); + } + }); }); diff --git a/test/services/livesync-service.ts b/test/services/livesync-service.ts deleted file mode 100644 index 653e3d0f4b..0000000000 --- a/test/services/livesync-service.ts +++ /dev/null @@ -1,209 +0,0 @@ -// import { Yok } from "../../lib/common/yok"; -// import { assert } from "chai"; -// import { LiveSyncService, DeprecatedUsbLiveSyncService } from "../../lib/services/livesync/livesync-service"; -// import { LoggerStub } from "../stubs"; - -// const createTestInjector = (): IInjector => { -// const testInjector = new Yok(); - -// testInjector.register("platformService", {}); -// testInjector.register("hmrStatusService", {}); -// testInjector.register("projectDataService", { -// getProjectData: (projectDir: string): IProjectData => ({}) -// }); - -// testInjector.register("devicesService", {}); -// testInjector.register("mobileHelper", {}); -// testInjector.register("devicePlatformsConstants", {}); -// testInjector.register("nodeModulesDependenciesBuilder", {}); -// testInjector.register("logger", LoggerStub); -// testInjector.register("debugService", {}); -// testInjector.register("errors", {}); -// testInjector.register("debugDataService", {}); -// testInjector.register("hooksService", { -// executeAfterHooks: (commandName: string, hookArguments?: IDictionary): Promise => Promise.resolve() -// }); - -// testInjector.register("pluginsService", {}); -// testInjector.register("analyticsService", {}); -// testInjector.register("injector", testInjector); -// testInjector.register("usbLiveSyncService", { -// isInitialized: false -// }); -// testInjector.register("platformsData", { -// availablePlatforms: { -// Android: "Android", -// iOS: "iOS" -// } -// }); -// testInjector.register("previewAppLiveSyncService", {}); -// testInjector.register("previewQrCodeService", {}); -// testInjector.register("previewSdkService", {}); - -// return testInjector; -// }; - -// class LiveSyncServiceInheritor extends LiveSyncService { -// constructor($platformService: IPlatformService, -// $projectDataService: IProjectDataService, -// $devicesService: Mobile.IDevicesService, -// $mobileHelper: Mobile.IMobileHelper, -// $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, -// $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, -// $logger: ILogger, -// $hooksService: IHooksService, -// $pluginsService: IPluginsService, -// $debugService: IDebugService, -// $errors: IErrors, -// $debugDataService: IDebugDataService, -// $analyticsService: IAnalyticsService, -// $usbLiveSyncService: DeprecatedUsbLiveSyncService, -// $injector: IInjector, -// $previewAppLiveSyncService: IPreviewAppLiveSyncService, -// $previewQrCodeService: IPreviewQrCodeService, -// $previewSdkService: IPreviewSdkService, -// $hmrStatusService: IHmrStatusService, -// $platformsData: IPlatformsData) { - -// super( -// $platformService, -// $projectDataService, -// $devicesService, -// $mobileHelper, -// $devicePlatformsConstants, -// $nodeModulesDependenciesBuilder, -// $logger, -// $hooksService, -// $pluginsService, -// $debugService, -// $errors, -// $debugDataService, -// $analyticsService, -// $usbLiveSyncService, -// $previewAppLiveSyncService, -// $previewQrCodeService, -// $previewSdkService, -// $hmrStatusService, -// $injector -// ); -// } - -// public liveSyncProcessesInfo: IDictionary = {}; -// } - -// interface IStopLiveSyncTestCase { -// name: string; -// currentDeviceIdentifiers: string[]; -// expectedDeviceIdentifiers: string[]; -// deviceIdentifiersToBeStopped?: string[]; -// } - -// describe("liveSyncService", () => { -// describe("stopLiveSync", () => { -// const getLiveSyncProcessInfo = (): ILiveSyncProcessInfo => ({ -// actionsChain: Promise.resolve(), -// currentSyncAction: Promise.resolve(), -// isStopped: false, -// timer: setTimeout(() => undefined, 1000), -// watcherInfo: { -// watcher: { -// close: (): any => undefined -// }, -// patterns: ["pattern"] -// }, -// deviceDescriptors: [], -// syncToPreviewApp: false -// }); - -// const getDeviceDescriptor = (identifier: string): ILiveSyncDeviceInfo => ({ -// identifier, -// outputPath: "", -// skipNativePrepare: false, -// platformSpecificOptions: null, -// buildAction: () => Promise.resolve("") -// }); - -// const testCases: IStopLiveSyncTestCase[] = [ -// { -// name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", -// currentDeviceIdentifiers: ["device1", "device2", "device3"], -// expectedDeviceIdentifiers: ["device1", "device2", "device3"] -// }, -// { -// name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", -// currentDeviceIdentifiers: ["device1"], -// expectedDeviceIdentifiers: ["device1"] -// }, -// { -// name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", -// currentDeviceIdentifiers: ["device1"], -// expectedDeviceIdentifiers: ["device1"], -// deviceIdentifiersToBeStopped: ["device1"] -// }, -// { -// name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", -// currentDeviceIdentifiers: ["device1", "device2", "device3"], -// expectedDeviceIdentifiers: ["device1", "device3"], -// deviceIdentifiersToBeStopped: ["device1", "device3"] -// }, -// { -// name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", -// currentDeviceIdentifiers: ["device1", "device2", "device3"], -// expectedDeviceIdentifiers: ["device1"], -// deviceIdentifiersToBeStopped: ["device1", "device4"] -// } -// ]; - -// for (const testCase of testCases) { -// it(testCase.name, async () => { -// const testInjector = createTestInjector(); -// const liveSyncService = testInjector.resolve(LiveSyncServiceInheritor); -// const projectDir = "projectDir"; -// const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; -// liveSyncService.on("liveSyncStopped", (data: { projectDir: string, deviceIdentifier: string }) => { -// assert.equal(data.projectDir, projectDir); -// emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); -// }); - -// // Setup liveSyncProcessesInfo for current test -// liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo(); -// const deviceDescriptors = testCase.currentDeviceIdentifiers.map(d => getDeviceDescriptor(d)); -// liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors); - -// await liveSyncService.stopLiveSync(projectDir, testCase.deviceIdentifiersToBeStopped); - -// assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); -// }); -// } - -// const prepareTestForUsbLiveSyncService = (): any => { -// const testInjector = createTestInjector(); -// const liveSyncService = testInjector.resolve(LiveSyncServiceInheritor); -// const projectDir = "projectDir"; -// const usbLiveSyncService = testInjector.resolve("usbLiveSyncService"); -// usbLiveSyncService.isInitialized = true; - -// // Setup liveSyncProcessesInfo for current test -// liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo(); -// const deviceDescriptors = ["device1", "device2", "device3"].map(d => getDeviceDescriptor(d)); -// liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors); -// return { projectDir, liveSyncService, usbLiveSyncService }; -// }; - -// it("sets usbLiveSyncService.isInitialized to false when LiveSync is stopped for all devices", async () => { -// const { projectDir, liveSyncService, usbLiveSyncService } = prepareTestForUsbLiveSyncService(); -// await liveSyncService.stopLiveSync(projectDir, ["device1", "device2", "device3"]); - -// assert.isFalse(usbLiveSyncService.isInitialized, "When the LiveSync process is stopped, we must set usbLiveSyncService.isInitialized to false"); -// }); - -// it("does not set usbLiveSyncService.isInitialized to false when LiveSync is stopped for some of devices only", async () => { -// const { projectDir, liveSyncService, usbLiveSyncService } = prepareTestForUsbLiveSyncService(); -// await liveSyncService.stopLiveSync(projectDir, ["device1", "device2"]); - -// assert.isTrue(usbLiveSyncService.isInitialized, "When the LiveSync process is stopped only for some of the devices, we must not set usbLiveSyncService.isInitialized to false"); -// }); - -// }); - -// }); From 666044df2bb1e0593f41a7494179a2e11ae02e0c Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 6 May 2019 09:21:13 +0300 Subject: [PATCH 22/30] refactor: refactor the prepare-platform tests --- .../platform/platform-commands-service.ts | 5 +- test/platform-service.ts | 247 ------------------ .../services/platform/add-platform-service.ts | 107 ++++++++ .../platform/platform-commands-service.ts | 86 ++++++ test/stubs.ts | 41 ++- 5 files changed, 225 insertions(+), 261 deletions(-) create mode 100644 test/services/platform/add-platform-service.ts create mode 100644 test/services/platform/platform-commands-service.ts diff --git a/lib/services/platform/platform-commands-service.ts b/lib/services/platform/platform-commands-service.ts index d56b5299a1..1fddb9b9d3 100644 --- a/lib/services/platform/platform-commands-service.ts +++ b/lib/services/platform/platform-commands-service.ts @@ -3,17 +3,18 @@ import * as semver from "semver"; import * as temp from "temp"; import * as constants from "../../constants"; import { AddPlatformService } from "./add-platform-service"; +import { PlatformValidationService } from "./platform-validation-service"; export class PlatformCommandsService implements IPlatformCommandsService { constructor( + private $addPlatformService: AddPlatformService, private $fs: IFileSystem, private $errors: IErrors, private $logger: ILogger, private $packageInstallationManager: IPackageInstallationManager, private $pacoteService: IPacoteService, - private $addPlatformService: AddPlatformService, private $platformsData: IPlatformsData, - private $platformValidationService: IPlatformValidationService, + private $platformValidationService: PlatformValidationService, private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService ) { } diff --git a/test/platform-service.ts b/test/platform-service.ts index 42ac196641..8fd0c7d2a4 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -142,237 +142,6 @@ // console.log("============ PLATFORM SERVICE ========== ", platformService); // }); -// describe("add platform unit tests", () => { -// describe("#add platform()", () => { -// it("should not fail if platform is not normalized", async () => { -// const fs = testInjector.resolve("fs"); -// fs.exists = () => false; -// const projectData: IProjectData = testInjector.resolve("projectData"); -// await platformCommandsService.addPlatforms(["Android"], projectData, ""); -// await platformCommandsService.addPlatforms(["ANDROID"], projectData, ""); -// await platformCommandsService.addPlatforms(["AnDrOiD"], projectData, ""); -// await platformCommandsService.addPlatforms(["androiD"], projectData, ""); - -// await platformCommandsService.addPlatforms(["iOS"], projectData, ""); -// await platformCommandsService.addPlatforms(["IOS"], projectData, ""); -// await platformCommandsService.addPlatforms(["IoS"], projectData, ""); -// await platformCommandsService.addPlatforms(["iOs"], projectData, ""); -// }); - -// it("should fail if platform is already installed", async () => { -// const projectData: IProjectData = testInjector.resolve("projectData"); -// // By default fs.exists returns true, so the platforms directory should exists -// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); -// await assert.isRejected(platformCommandsService.addPlatforms(["ios"], projectData, ""), "Platform ios already added"); -// }); - -// it("should fail if unable to extract runtime package", async () => { -// const fs = testInjector.resolve("fs"); -// fs.exists = () => false; - -// const pacoteService = testInjector.resolve("pacoteService"); -// const errorMessage = "Pacote service unable to extract package"; -// pacoteService.extractPackage = async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => { -// throw new Error(errorMessage); -// }; - -// const projectData: IProjectData = testInjector.resolve("projectData"); -// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), errorMessage); -// }); - -// it("fails when path passed to frameworkPath does not exist", async () => { -// const fs = testInjector.resolve("fs"); -// fs.exists = () => false; - -// const projectData: IProjectData = testInjector.resolve("projectData"); -// const frameworkPath = "invalidPath"; -// const errorMessage = format(AddPlaformErrors.InvalidFrameworkPathStringFormat, frameworkPath); -// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, frameworkPath), errorMessage); -// }); - -// const assertCorrectDataIsPassedToPacoteService = async (versionString: string): Promise => { -// const fs = testInjector.resolve("fs"); -// fs.exists = () => false; - -// const pacoteService = testInjector.resolve("pacoteService"); -// let packageNamePassedToPacoteService = ""; -// pacoteService.extractPackage = async (name: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => { -// packageNamePassedToPacoteService = name; -// }; - -// const platformsData = testInjector.resolve("platformsData"); -// const packageName = "packageName"; -// platformsData.getPlatformData = (platform: string, pData: IProjectData): IPlatformData => { -// return { -// frameworkPackageName: packageName, -// platformNameLowerCase: "", -// platformProjectService: new stubs.PlatformProjectServiceStub(), -// projectRoot: "", -// normalizedPlatformName: "", -// appDestinationDirectoryPath: "", -// getBuildOutputPath: () => "", -// getValidBuildOutputData: (buildOptions: IBuildOutputOptions) => ({ packageNames: [] }), -// frameworkFilesExtensions: [], -// relativeToFrameworkConfigurationFilePath: "", -// fastLivesyncFileExtensions: [] -// }; -// }; -// const projectData: IProjectData = testInjector.resolve("projectData"); - -// await platformCommandsService.addPlatforms(["android"], projectData, ""); -// assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); -// await platformCommandsService.addPlatforms(["ios"], projectData, ""); -// assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); -// }; -// it("should respect platform version in package.json's nativescript key", async () => { -// const versionString = "2.5.0"; -// const nsValueObject: any = { -// [VERSION_STRING]: versionString -// }; -// const projectDataService = testInjector.resolve("projectDataService"); -// projectDataService.getNSValue = () => nsValueObject; - -// await assertCorrectDataIsPassedToPacoteService(versionString); -// }); - -// it("should install latest platform if no information found in package.json's nativescript key", async () => { - -// const projectDataService = testInjector.resolve("projectDataService"); -// projectDataService.getNSValue = (): any => null; - -// const latestCompatibleVersion = "1.0.0"; -// const packageInstallationManager = testInjector.resolve("packageInstallationManager"); -// packageInstallationManager.getLatestCompatibleVersion = async (packageName: string, referenceVersion?: string): Promise => { -// return latestCompatibleVersion; -// }; - -// await assertCorrectDataIsPassedToPacoteService(latestCompatibleVersion); -// }); - -// // Workflow: tns preview; tns platform add -// it(`should add platform when only .js part of the platform has already been added (nativePlatformStatus is ${constants.NativePlatformStatus.requiresPlatformAdd})`, async () => { -// const fs = testInjector.resolve("fs"); -// fs.exists = () => true; -// const projectChangesService = testInjector.resolve("projectChangesService"); -// projectChangesService.getPrepareInfo = () => ({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); -// const projectData = testInjector.resolve("projectData"); -// let isJsPlatformAdded = false; -// const preparePlatformJSService = testInjector.resolve("preparePlatformJSService"); -// preparePlatformJSService.addPlatform = async () => isJsPlatformAdded = true; -// let isNativePlatformAdded = false; -// const preparePlatformService = testInjector.resolve("preparePlatformService"); -// preparePlatformService.addNativePlatform = async () => isNativePlatformAdded = true; - -// await platformCommandsService.addPlatforms(["android"], projectData, ""); - -// assert.isTrue(isJsPlatformAdded); -// assert.isTrue(isNativePlatformAdded); -// }); - -// // Workflow: tns platform add; tns platform add -// it("shouldn't add platform when platforms folder exist and no .nsprepare file", async () => { -// const fs = testInjector.resolve("fs"); -// fs.exists = () => true; -// const projectChangesService = testInjector.resolve("projectChangesService"); -// projectChangesService.getPrepareInfo = () => null; -// const projectData = testInjector.resolve("projectData"); - -// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); -// }); - -// // Workflow: tns run; tns platform add -// it(`shouldn't add platform when both native and .js parts of the platform have already been added (nativePlatformStatus is ${constants.NativePlatformStatus.alreadyPrepared})`, async () => { -// const fs = testInjector.resolve("fs"); -// fs.exists = () => true; -// const projectChangesService = testInjector.resolve("projectChangesService"); -// projectChangesService.getPrepareInfo = () => ({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); -// const projectData = testInjector.resolve("projectData"); - -// await assert.isRejected(platformCommandsService.addPlatforms(["android"], projectData, ""), "Platform android already added"); -// }); -// }); -// }); - -// describe("remove platform unit tests", () => { -// it("should fail when platforms are not added", async () => { -// const ExpectedErrorsCaught = 2; -// let errorsCaught = 0; -// const projectData: IProjectData = testInjector.resolve("projectData"); -// testInjector.resolve("fs").exists = () => false; - -// try { -// await platformCommandsService.removePlatforms(["android"], projectData); -// } catch (e) { -// errorsCaught++; -// } - -// try { -// await platformCommandsService.removePlatforms(["ios"], projectData); -// } catch (e) { -// errorsCaught++; -// } - -// assert.isTrue(errorsCaught === ExpectedErrorsCaught); -// }); -// it("shouldn't fail when platforms are added", async () => { -// const projectData: IProjectData = testInjector.resolve("projectData"); -// testInjector.resolve("fs").exists = () => false; -// await platformCommandsService.addPlatforms(["android"], projectData, ""); - -// testInjector.resolve("fs").exists = () => true; -// await platformCommandsService.removePlatforms(["android"], projectData); -// }); -// }); - -// describe("clean platform unit tests", () => { -// it("should preserve the specified in the project nativescript version", async () => { -// const versionString = "2.4.1"; -// const fs = testInjector.resolve("fs"); -// fs.exists = () => false; - -// const nsValueObject: any = {}; -// nsValueObject[VERSION_STRING] = versionString; -// const projectDataService = testInjector.resolve("projectDataService"); -// projectDataService.getNSValue = () => nsValueObject; - -// const packageInstallationManager = testInjector.resolve("packageInstallationManager"); -// packageInstallationManager.install = (packageName: string, packageDir: string, options: INpmInstallOptions) => { -// assert.deepEqual(options.version, versionString); -// return ""; -// }; - -// const projectData: IProjectData = testInjector.resolve("projectData"); -// platformCommandsService.removePlatforms = (platforms: string[], prjctData: IProjectData): Promise => { -// nsValueObject[VERSION_STRING] = undefined; -// return Promise.resolve(); -// }; - -// await platformCommandsService.cleanPlatforms(["android"], projectData, ""); - -// nsValueObject[VERSION_STRING] = versionString; -// await platformCommandsService.cleanPlatforms(["ios"], projectData, ""); -// }); -// }); - -// // TODO: Commented as it doesn't seem correct. Check what's the case and why it's been expected to fail. -// // describe("list platform unit tests", () => { -// // it("fails when platforms are not added", () => { -// // assert.throws(async () => await platformService.getAvailablePlatforms()); -// // }); -// // }); - -// describe("update Platform", () => { -// describe("#updatePlatform(platform)", () => { -// it("should fail when the versions are the same", async () => { -// const packageInstallationManager: IPackageInstallationManager = testInjector.resolve("packageInstallationManager"); -// packageInstallationManager.getLatestVersion = async () => "0.2.0"; -// const projectData: IProjectData = testInjector.resolve("projectData"); - -// await assert.isRejected(platformCommandsService.updatePlatforms(["android"], projectData)); -// }); -// }); -// }); - // // TODO: check this tests with QAs // // describe("prepare platform unit tests", () => { // // let fs: IFileSystem; @@ -532,22 +301,6 @@ // // fs.writeFile(fileToUpdate, content); // // } -// // it("should process only files in app folder when preparing for iOS platform", async () => { -// // await testPreparePlatform("iOS"); -// // }); - -// // it("should process only files in app folder when preparing for Android platform", async () => { -// // await testPreparePlatform("Android"); -// // }); - -// // it("should process only files in app folder when preparing for iOS platform", async () => { -// // await testPreparePlatform("iOS", true); -// // }); - -// // it("should process only files in app folder when preparing for Android platform", async () => { -// // await testPreparePlatform("Android", true); -// // }); - // // function getDefaultFolderVerificationData(platform: string, appDestFolderPath: string) { // // const data: any = {}; // // if (platform.toLowerCase() === "ios") { diff --git a/test/services/platform/add-platform-service.ts b/test/services/platform/add-platform-service.ts new file mode 100644 index 0000000000..6bcc6f491d --- /dev/null +++ b/test/services/platform/add-platform-service.ts @@ -0,0 +1,107 @@ +import { InjectorStub } from "../../stubs"; +import { AddPlatformService } from "../../../lib/services/platform/add-platform-service"; +import { PacoteService } from "../../../lib/services/pacote-service"; +import { assert } from "chai"; +import { format } from "util"; +import { AddPlaformErrors } from "../../../lib/constants"; + +let extractedPackageFromPacote: string = null; + +function createTestInjector() { + const injector = new InjectorStub(); + injector.register("pacoteService", { + extractPackage: async (name: string): Promise => { extractedPackageFromPacote = name; } + }); + injector.register("terminalSpinnerService", { + createSpinner: () => { + return { + start: () => ({}), + stop: () => ({}) + }; + } + }); + injector.register("addPlatformService", AddPlatformService); + + const fs = injector.resolve("fs"); + fs.exists = () => false; + + return injector; +} + +describe("AddPlatformService", () => { + describe("addPlatform", () => { + let injector: IInjector, addPlatformService: AddPlatformService, projectData: IProjectData; + beforeEach(() => { + injector = createTestInjector(); + addPlatformService = injector.resolve("addPlatformService"); + projectData = injector.resolve("projectData"); + }); + + _.each(["ios", "android"], platform => { + it(`should fail if unable to extract runtime package for ${platform}`, async () => { + const errorMessage = "Pacote service unable to extract package"; + + const pacoteService: PacoteService = injector.resolve("pacoteService"); + pacoteService.extractPackage = async (): Promise => { throw new Error(errorMessage); }; + + await assert.isRejected(addPlatformService.addPlatform(projectData, { platformParam: platform }), errorMessage); + }); + it(`should fail when path passed to frameworkPath does not exist for ${platform}`, async () => { + const frameworkPath = "invalidPath"; + const errorMessage = format(AddPlaformErrors.InvalidFrameworkPathStringFormat, frameworkPath); + + await assert.isRejected(addPlatformService.addPlatform(projectData, { platformParam: platform, frameworkPath }), errorMessage); + }); + it(`should respect platform version in package.json's nativescript key for ${platform}`, async () => { + const version = "2.5.0"; + + const projectDataService = injector.resolve("projectDataService"); + projectDataService.getNSValue = () => ({ version }); + + await addPlatformService.addPlatform(projectData, { platformParam: platform }); + + const expectedPackageToAdd = `tns-${platform}@${version}`; + assert.deepEqual(extractedPackageFromPacote, expectedPackageToAdd); + }); + it(`should install latest platform if no information found in package.json's nativescript key for ${platform}`, async () => { + const latestCompatibleVersion = "5.0.0"; + + const packageInstallationManager = injector.resolve("packageInstallationManager"); + packageInstallationManager.getLatestCompatibleVersion = async () => latestCompatibleVersion; + const projectDataService = injector.resolve("projectDataService"); + projectDataService.getNSValue = () => null; + + await addPlatformService.addPlatform(projectData, { platformParam: platform }); + + const expectedPackageToAdd = `tns-${platform}@${latestCompatibleVersion}`; + assert.deepEqual(extractedPackageFromPacote, expectedPackageToAdd); + }); + it(`shouldn't add native platform when skipNativePrepare is provided for ${platform}`, async () => { + const projectDataService = injector.resolve("projectDataService"); + projectDataService.getNSValue = () => ({ version: "4.2.0" }); + + let isCreateNativeProjectCalled = false; + const platformsData = injector.resolve("platformsData"); + const platformData = platformsData.getPlatformData(platform, injector.resolve("projectData")); + platformData.platformProjectService.createProject = () => isCreateNativeProjectCalled = true; + platformsData.getPlatformData = () => platformData; + + await addPlatformService.addPlatform(projectData, { platformParam: platform, nativePrepare: { skipNativePrepare: true } }); + assert.isFalse(isCreateNativeProjectCalled); + }); + it(`should add native platform when skipNativePrepare is not provided for ${platform}`, async () => { + const projectDataService = injector.resolve("projectDataService"); + projectDataService.getNSValue = () => ({ version: "4.2.0" }); + + let isCreateNativeProjectCalled = false; + const platformsData = injector.resolve("platformsData"); + const platformData = platformsData.getPlatformData(platform, injector.resolve("projectData")); + platformData.platformProjectService.createProject = () => isCreateNativeProjectCalled = true; + platformsData.getPlatformData = () => platformData; + + await addPlatformService.addPlatform(projectData, { platformParam: platform }); + assert.isTrue(isCreateNativeProjectCalled); + }); + }); + }); +}); diff --git a/test/services/platform/platform-commands-service.ts b/test/services/platform/platform-commands-service.ts new file mode 100644 index 0000000000..989cd9075a --- /dev/null +++ b/test/services/platform/platform-commands-service.ts @@ -0,0 +1,86 @@ +import { PlatformCommandsService } from "../../../lib/services/platform/platform-commands-service"; +import { assert } from "chai"; +import { InjectorStub } from "../../stubs"; + +let isAddPlatformCalled = false; + +const projectDir = "/my/path/to/project"; +const projectData: any = { + projectDir, + platformsDir: "/my/path/to/project/platforms" +}; + +function createTestInjector() { + const injector = new InjectorStub(); + injector.register("addPlatformService", { + addPlatform: () => isAddPlatformCalled = true + }); + + injector.register("pacoteService", { + extractPackage: () => ({}) + }); + injector.register("platformValidationService", { + validatePlatform: () => ({}), + validatePlatformInstalled: () => ({}) + }); + + injector.register("platformCommandsService", PlatformCommandsService); + + return injector; +} + +describe("PlatformCommandsService", () => { + let injector: IInjector = null; + let platformCommandsService: PlatformCommandsService = null; + beforeEach(() => { + injector = createTestInjector(); + platformCommandsService = injector.resolve("platformCommandsService"); + }); + + describe("add platforms unit tests", () => { + _.each(["Android", "ANDROID", "android", "iOS", "IOS", "ios"], platform => { + beforeEach(() => { + isAddPlatformCalled = false; + }); + + it(`should not fail if platform is not normalized - ${platform}`, async () => { + const fs = injector.resolve("fs"); + fs.exists = () => false; + + await platformCommandsService.addPlatforms([platform], projectData, null); + + assert.isTrue(isAddPlatformCalled); + }); + }); + _.each(["ios", "android"], platform => { + it(`should fail if ${platform} platform is already installed`, async () => { + (platformCommandsService).isPlatformAdded = () => true; + + await assert.isRejected(platformCommandsService.addPlatforms([platform], projectData, ""), `Platform ${platform} already added`); + }); + }); + }); + describe("clean platforms unit tests", () => { + _.each(["ios", "anroid"], platform => { + it(`should preserve the specified in the project nativescript version for ${platform}`, async () => { + let versionData = { version: "5.3.1" }; + + const projectDataService = injector.resolve("projectDataService"); + projectDataService.getNSValue = () => versionData; + projectDataService.removeNSProperty = () => { versionData = null; }; + + (platformCommandsService).isPlatformAdded = () => false; + + await platformCommandsService.cleanPlatforms([platform], injector.resolve("projectData"), ""); + }); + }); + }); + describe("update platforms unit tests", () => { + it("should fail when tha native platform cannot be updated", async () => { + const packageInstallationManager: IPackageInstallationManager = injector.resolve("packageInstallationManager"); + packageInstallationManager.getLatestVersion = async () => "0.2.0"; + + await assert.isRejected(platformCommandsService.updatePlatforms(["android"], projectData), "Native Platform cannot be updated."); + }); + }); +}); diff --git a/test/stubs.ts b/test/stubs.ts index 7b8f54e77e..5fe26d8b30 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -301,13 +301,16 @@ export class ProjectDataStub implements IProjectData { projectDir: string; projectName: string; get platformsDir(): string { - return this.plafromsDir || (this.projectDir && join(this.projectDir, "platforms")) || ""; + return this.platformsDirCache || (this.projectDir && join(this.projectDir, "platforms")) || ""; } set platformsDir(value) { - this.plafromsDir = value; + this.platformsDirCache = value; } projectFilePath: string; - projectIdentifiers: Mobile.IProjectIdentifier; + projectIdentifiers: Mobile.IProjectIdentifier = { + android: "org.nativescirpt.myiOSApp", + ios: "org.nativescript.myProjectApp" + }; projectId: string; dependencies: any; nsConfig: any; @@ -315,7 +318,7 @@ export class ProjectDataStub implements IProjectData { devDependencies: IStringDictionary; projectType: string; appResourcesDirectoryPath: string; - private plafromsDir: string = ""; + private platformsDirCache: string = ""; public androidManifestPath: string; public infoPlistPath: string; public appGradlePath: string; @@ -367,11 +370,15 @@ export class AndroidPluginBuildServiceStub implements IAndroidPluginBuildService } export class PlatformProjectServiceStub extends EventEmitter implements IPlatformProjectService { + constructor(private platform: string) { + super(); + } + getPlatformData(projectData: IProjectData): IPlatformData { return { - frameworkPackageName: "", - normalizedPlatformName: "", - platformNameLowerCase: "", + frameworkPackageName: `tns-${this.platform.toLowerCase()}`, + normalizedPlatformName: this.platform.toLowerCase() === "ios" ? "iOS" : "Android", + platformNameLowerCase: this.platform.toLowerCase(), platformProjectService: this, projectRoot: "", getBuildOutputPath: (buildConfig: IBuildConfig) => "", @@ -471,11 +478,11 @@ export class PlatformsDataStub extends EventEmitter implements IPlatformsData { public getPlatformData(platform: string, projectData: IProjectData): IPlatformData { return { - frameworkPackageName: "", - platformProjectService: new PlatformProjectServiceStub(), - platformNameLowerCase: "", + frameworkPackageName: `tns-${platform.toLowerCase()}`, + platformProjectService: new PlatformProjectServiceStub(platform), + platformNameLowerCase: platform.toLowerCase(), projectRoot: "", - normalizedPlatformName: "", + normalizedPlatformName: platform.toLowerCase() === "ios" ? "iOS" : "Android", appDestinationDirectoryPath: "", getBuildOutputPath: () => "", getValidBuildOutputData: (buildOptions: IBuildOutputOptions) => ({ packageNames: [] }), @@ -501,7 +508,17 @@ export class ProjectDataService implements IProjectDataService { removeDependency(dependencyName: string): void { } - getProjectData(projectDir: string): IProjectData { return null; } + getProjectData(projectDir: string): IProjectData { + return { + projectDir: "/path/to/my/projecDir", + projectName: "myTestProjectName", + platformsDir: "/path/to/my/projecDir/platforms", + projectIdentifiers: { + ios: "org.nativescript.myiosApp", + android: "org.nativescript.myAndroidApp" + }, + }; + } async getAssetsStructure(opts: IProjectDir): Promise { return null; From 6ba3274bf461bc3e18c297d0c343575a11432114 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 6 May 2019 17:16:06 +0300 Subject: [PATCH 23/30] test: fix unit tests --- .vscode/launch.json | 2 +- lib/commands/appstore-upload.ts | 75 ++++------- lib/commands/test.ts | 40 +++++- lib/common/yok.ts | 8 +- lib/controllers/main-controller.ts | 2 +- lib/declarations.d.ts | 6 - lib/definitions/livesync.d.ts | 1 - lib/definitions/project.d.ts | 2 +- lib/device-path-provider.ts | 2 +- lib/helpers/livesync-command-helper.ts | 59 ++++++--- lib/options.ts | 1 - lib/services/build-artefacts-service.ts | 27 ++-- .../device/device-install-app-service.ts | 22 ++-- lib/services/ios-project-service.ts | 5 + .../preview-app-livesync-service.ts | 16 +-- .../platform-environment-requirements.ts | 14 +-- .../platform/build-platform-service.ts | 27 ++-- .../platform/prepare-platform-service.ts | 2 +- lib/services/project-changes-service.ts | 11 +- lib/services/test-execution-service.ts | 51 ++------ lib/services/webpack/webpack.d.ts | 6 +- .../workflow/workflow-data-service.ts | 7 +- test/nativescript-cli-lib.ts | 2 +- test/platform-commands.ts | 2 +- test/project-changes-service.ts | 13 +- test/services/android-plugin-build-service.ts | 4 +- .../platform/build-platform-service.ts | 3 + .../platform/platform-watcher-service.ts | 14 +-- .../preview-app-livesync-service.ts | 19 +-- ...on-serice.ts => test-execution-service.ts} | 1 + test/stubs.ts | 2 +- test/tns-appstore-upload.ts | 58 ++++----- test/update.ts | 119 +++++++++--------- 33 files changed, 305 insertions(+), 318 deletions(-) create mode 100644 test/services/platform/build-platform-service.ts rename test/services/{test-execution-serice.ts => test-execution-service.ts} (98%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 45ce643d97..66b4d96941 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "program": "${workspaceRoot}/lib/nativescript-cli.js", // example commands - "args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--hmr"] + "args": [ "test", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle"] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] // "args": [ "platform", "remove", "android", "--path", "cliapp"] diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 89cc1a1612..7d119763d3 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -1,5 +1,8 @@ import * as path from "path"; import { StringCommandParameter } from "../common/command-params"; +import { BuildPlatformService } from "../services/platform/build-platform-service"; +import { WorkflowDataService } from "../services/workflow/workflow-data-service"; +import { MainController } from "../controllers/main-controller"; export class PublishIOS implements ICommand { public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector), @@ -13,29 +16,20 @@ export class PublishIOS implements ICommand { private $options: IOptions, private $prompter: IPrompter, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $mainController: MainController, private $platformValidationService: IPlatformValidationService, - // private $buildPlatformService: BuildPlatformService, - // private $xcodebuildService: IXcodebuildService - ) { + private $buildPlatformService: BuildPlatformService, + private $workflowDataService: WorkflowDataService + ) { this.$projectData.initializeProjectData(); } - // private get $platformsData(): IPlatformsData { - // return this.$injector.resolve("platformsData"); - // } - - // This property was introduced due to the fact that the $platformService dependency - // ultimately tries to resolve the current project's dir and fails if not executed from within a project - // private get $platformService(): IPlatformService { - // return this.$injector.resolve("platformService"); - // } - public async execute(args: string[]): Promise { let username = args[0]; let password = args[1]; const mobileProvisionIdentifier = args[2]; const codeSignIdentity = args[3]; - const ipaFilePath = this.$options.ipa ? path.resolve(this.$options.ipa) : null; + let ipaFilePath = this.$options.ipa ? path.resolve(this.$options.ipa) : null; if (!username) { username = await this.$prompter.getString("Apple ID", { allowEmpty: false }); @@ -54,47 +48,24 @@ export class PublishIOS implements ICommand { } this.$options.release = true; + const platform = this.$devicePlatformsConstants.iOS.toLowerCase(); if (!ipaFilePath) { - // const platform = this.$devicePlatformsConstants.iOS; // No .ipa path provided, build .ipa on out own. - // const platformWorkflowData = { - // release: this.$options.release, - // useHotModuleReload: false, - // env: this.$options.env, - // platformParam: platform, - // signingOptions: { - // teamId: this.$options.teamId, - // provision: this.$options.provision - // } - // }; - // const buildConfig: IBuildConfig = { - // projectDir: this.$options.path, - // release: this.$options.release, - // device: this.$options.device, - // provision: this.$options.provision, - // teamId: this.$options.teamId, - // buildForDevice: true, - // iCloudContainerEnvironment: this.$options.iCloudContainerEnvironment, - // mobileProvisionIdentifier, - // codeSignIdentity - // }; - - // const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); - - // if (mobileProvisionIdentifier || codeSignIdentity) { - // this.$logger.info("Building .ipa with the selected mobile provision and/or certificate."); - // // This is not very correct as if we build multiple targets we will try to sign all of them using the signing identity here. - // await this.$platformService.preparePlatform(platformData, this.$projectData, platformWorkflowData); - // await this.$platformBuildService.buildPlatform(platformData, this.$projectData, buildConfig); - // ipaFilePath = this.$platformService.lastOutputPath(platform, buildConfig, this.$projectData); - // } else { - // this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); - // await this.$platformService.preparePlatform(platformData, this.$projectData, platformWorkflowData); - - // ipaFilePath = await this.$xcodebuildService.buildForAppStore(platformData, this.$projectData, buildConfig); - // this.$logger.info(`Export at: ${ipaFilePath}`); - // } + if (mobileProvisionIdentifier || codeSignIdentity) { + // This is not very correct as if we build multiple targets we will try to sign all of them using the signing identity here. + this.$logger.info("Building .ipa with the selected mobile provision and/or certificate."); + + // As we need to build the package for device + this.$options.forDevice = true; + + const { nativePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, this.$projectData.projectDir, this.$options); + ipaFilePath = await this.$buildPlatformService.buildPlatform(nativePlatformData, this.$projectData, buildPlatformData); + } else { + this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); + ipaFilePath = await this.$mainController.buildPlatform(platform, this.$projectData.projectDir, { ...this.$options, buildForAppStore: true }) + this.$logger.info(`Export at: ${ipaFilePath}`); + } } await this.$itmsTransporterService.upload({ diff --git a/lib/commands/test.ts b/lib/commands/test.ts index e96d981793..2d45a126ef 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -1,8 +1,7 @@ -import * as helpers from "../common/helpers"; +import { LiveSyncCommandHelper } from "../helpers/livesync-command-helper"; abstract class TestCommandBase { public allowedParameters: ICommandParameter[] = []; - private projectFilesConfig: IProjectFilesConfig; protected abstract platform: string; protected abstract $projectData: IProjectData; protected abstract $testExecutionService: ITestExecutionService; @@ -11,16 +10,41 @@ abstract class TestCommandBase { protected abstract $platformEnvironmentRequirements: IPlatformEnvironmentRequirements; protected abstract $errors: IErrors; protected abstract $cleanupService: ICleanupService; + protected abstract $liveSyncCommandHelper: LiveSyncCommandHelper; + protected abstract $devicesService: Mobile.IDevicesService; async execute(args: string[]): Promise { - await this.$testExecutionService.startKarmaServer(this.platform, this.$projectData, this.projectFilesConfig); + let devices = []; + if (this.$options.debugBrk) { + const selectedDeviceForDebug = await this.$devicesService.pickSingleDevice({ + onlyEmulators: this.$options.emulator, + onlyDevices: this.$options.forDevice, + deviceId: this.$options.device + }); + devices = [selectedDeviceForDebug]; + // const debugData = this.getDebugData(platform, projectData, deployOptions, { device: selectedDeviceForDebug.deviceInfo.identifier }); + // await this.$debugService.debug(debugData, this.$options); + } else { + devices = await this.$liveSyncCommandHelper.getDeviceInstances(this.platform); + } + + if (!this.$options.env) { this.$options.env = { }; } + this.$options.env.unitTesting = true; + + const liveSyncInfo = this.$liveSyncCommandHelper.createLiveSyncInfo(); + + const deviceDebugMap: IDictionary = {}; + devices.forEach(device => deviceDebugMap[device.deviceInfo.identifier] = this.$options.debugBrk); + + const deviceDescriptors = await this.$liveSyncCommandHelper.createDeviceDescriptors(devices, this.platform, { deviceDebugMap }); + + await this.$testExecutionService.startKarmaServer(this.platform, liveSyncInfo, deviceDescriptors); } async canExecute(args: string[]): Promise { this.$projectData.initializeProjectData(); this.$analyticsService.setShouldDispose(this.$options.justlaunch || !this.$options.watch); this.$cleanupService.setShouldDispose(this.$options.justlaunch || !this.$options.watch); - this.projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: this.$options.release }); const output = await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ platform: this.platform, @@ -54,7 +78,9 @@ class TestAndroidCommand extends TestCommandBase implements ICommand { protected $options: IOptions, protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, protected $errors: IErrors, - protected $cleanupService: ICleanupService) { + protected $cleanupService: ICleanupService, + protected $liveSyncCommandHelper: LiveSyncCommandHelper, + protected $devicesService: Mobile.IDevicesService) { super(); } @@ -69,7 +95,9 @@ class TestIosCommand extends TestCommandBase implements ICommand { protected $options: IOptions, protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, protected $errors: IErrors, - protected $cleanupService: ICleanupService) { + protected $cleanupService: ICleanupService, + protected $liveSyncCommandHelper: LiveSyncCommandHelper, + protected $devicesService: Mobile.IDevicesService) { super(); } diff --git a/lib/common/yok.ts b/lib/common/yok.ts index ef64c199b3..556e267c7f 100644 --- a/lib/common/yok.ts +++ b/lib/common/yok.ts @@ -6,11 +6,11 @@ import { CommandsDelimiters } from "./constants"; let indent = ""; function trace(formatStr: string, ...args: any[]) { // uncomment following lines when debugging dependency injection - // var args = []; - // for (var _i = 1; _i < arguments.length; _i++) { - // args[_i - 1] = arguments[_i]; + // const items: any[] = []; + // for (let _i = 1; _i < arguments.length; _i++) { + // items[_i - 1] = arguments[_i]; // } - // var util = require("util"); + // const util = require("util"); // console.log(util.format.apply(util, [indent + formatStr].concat(args))); } diff --git a/lib/controllers/main-controller.ts b/lib/controllers/main-controller.ts index 98954b4d94..e085dd00e5 100644 --- a/lib/controllers/main-controller.ts +++ b/lib/controllers/main-controller.ts @@ -38,7 +38,7 @@ export class MainController extends EventEmitter { await this.$preparePlatformService.preparePlatform(nativePlatformData, projectData, preparePlatformData); } - public async buildPlatform(platform: string, projectDir: string, options: IOptions): Promise { + public async buildPlatform(platform: string, projectDir: string, options: IOptions | any): Promise { const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); await this.preparePlatform(platform, projectDir, options); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 9dbe021fee..51b7bb05eb 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -556,7 +556,6 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai javascript: boolean; androidTypings: boolean; production: boolean; //npm flag - syncAllFiles: boolean; chrome: boolean; inspector: boolean; // the counterpart to --chrome background: string; @@ -1038,11 +1037,6 @@ interface IPlatformValidationService { isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean; } -interface IBuildArtefactsService { - getLastBuiltPackagePath(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): Promise; - getAllBuiltApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[]; -} - interface IPlatformCommandsService { addPlatforms(platforms: string[], projectData: IProjectData, frameworkPath: string): Promise; cleanPlatforms(platforms: string[], projectData: IProjectData, framworkPath: string): Promise; diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 77c91f2325..1848dee1ec 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -433,7 +433,6 @@ declare global { interface IDeviceProjectRootOptions { appIdentifier: string; getDirname?: boolean; - syncAllFiles?: boolean; watch?: boolean; } diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 117a77eaef..e19e5fb7d8 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -358,7 +358,7 @@ interface IValidatePlatformOutput { } interface ITestExecutionService { - startKarmaServer(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; + startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise; canStartKarmaServer(projectData: IProjectData): Promise; } diff --git a/lib/device-path-provider.ts b/lib/device-path-provider.ts index bdd9c24721..b80ec0fe90 100644 --- a/lib/device-path-provider.ts +++ b/lib/device-path-provider.ts @@ -25,7 +25,7 @@ export class DevicePathProvider implements IDevicePathProvider { projectRoot = `${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${options.appIdentifier}`; if (!options.getDirname) { const hashService = (device).fileSystem.getDeviceHashService(options.appIdentifier); - const hashFile = options.syncAllFiles ? null : await hashService.doesShasumFileExistsOnDevice(); + const hashFile = await hashService.doesShasumFileExistsOnDevice(); const syncFolderName = options.watch || hashFile ? LiveSyncPaths.SYNC_DIR_NAME : LiveSyncPaths.FULLSYNC_DIR_NAME; projectRoot = path.join(projectRoot, syncFolderName); } diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 8dcb6f0b28..91d5af3a7a 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -13,35 +13,24 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $devicesService: Mobile.IDevicesService, - private $platformsData: IPlatformsData, + private $injector: IInjector, private $buildPlatformService: BuildPlatformService, private $analyticsService: IAnalyticsService, private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, - private $logger: ILogger, private $cleanupService: ICleanupService ) { } - public getPlatformsForOperation(platform: string): string[] { - const availablePlatforms = platform ? [platform] : _.values(this.$platformsData.availablePlatforms); - return availablePlatforms; + private get $platformsData(): IPlatformsData { + return this.$injector.resolve("platformsData"); } - public async executeCommandLiveSync(platform?: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions) { - if (additionalOptions && additionalOptions.syncToPreviewApp) { - return; - } - - if (!this.$options.syncAllFiles) { - this.$logger.info("Skipping node_modules folder! Use the syncAllFiles option to sync files from this folder."); - } - - const emulator = this.$options.emulator; + public async getDeviceInstances(platform?: string): Promise { await this.$devicesService.initialize({ - deviceId: this.$options.device, platform, - emulator, + deviceId: this.$options.device, + emulator: this.$options.emulator, skipInferPlatform: !platform, sdk: this.$options.sdk }); @@ -49,10 +38,26 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const devices = this.$devicesService.getDeviceInstances() .filter(d => !platform || d.deviceInfo.platform.toLowerCase() === platform.toLowerCase()); - await this.executeLiveSyncOperation(devices, platform, additionalOptions); + return devices; } - public async executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { + 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 { 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."); @@ -113,6 +118,22 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return info; }); + return deviceDescriptors; + } + + public getPlatformsForOperation(platform: string): string[] { + const availablePlatforms = platform ? [platform] : _.values(this.$platformsData.availablePlatforms); + return availablePlatforms; + } + + public async executeCommandLiveSync(platform?: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions) { + const devices = await this.getDeviceInstances(platform); + await this.executeLiveSyncOperation(devices, platform, additionalOptions); + } + + public async executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { + const deviceDescriptors = await this.createDeviceDescriptors(devices, platform, additionalOptions); + const liveSyncInfo: ILiveSyncInfo = { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, diff --git a/lib/options.ts b/lib/options.ts index ddc03b0459..c63e9b8909 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -105,7 +105,6 @@ export class Options { bundle: { type: OptionType.String, hasSensitiveValue: false }, all: { type: OptionType.Boolean, hasSensitiveValue: false }, teamId: { type: OptionType.Object, hasSensitiveValue: true }, - syncAllFiles: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, chrome: { type: OptionType.Boolean, hasSensitiveValue: false }, inspector: { type: OptionType.Boolean, hasSensitiveValue: false }, clean: { type: OptionType.Boolean, hasSensitiveValue: false }, diff --git a/lib/services/build-artefacts-service.ts b/lib/services/build-artefacts-service.ts index 2ca1bc9afe..c03a2353ea 100644 --- a/lib/services/build-artefacts-service.ts +++ b/lib/services/build-artefacts-service.ts @@ -1,16 +1,17 @@ import * as path from "path"; +import { BuildPlatformDataBase } from "./workflow/workflow-data-service"; -export class BuildArtefactsService implements IBuildArtefactsService { +export class BuildArtefactsService { constructor( private $errors: IErrors, private $fs: IFileSystem, private $logger: ILogger ) { } - public async getLastBuiltPackagePath(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): Promise { - const packageFile = buildConfig.buildForDevice ? - this.getLatestApplicationPackageForDevice(platformData, buildConfig, outputPath).packageName : - this.getLatestApplicationPackageForEmulator(platformData, buildConfig, outputPath).packageName; + public async getLatestApplicationPackagePath(platformData: IPlatformData, buildPlatformData: BuildPlatformDataBase, outputPath?: string): Promise { + outputPath = outputPath || platformData.getBuildOutputPath(buildPlatformData); + const applicationPackage = this.getLatestApplicationPackage(outputPath, platformData.getValidBuildOutputData(buildPlatformData)); + const packageFile = applicationPackage.packageName; if (!packageFile || !this.$fs.exists(packageFile)) { this.$errors.failWithoutHelp(`Unable to find built application. Try 'tns build ${platformData.platformNameLowerCase}'.`); @@ -19,7 +20,7 @@ export class BuildArtefactsService implements IBuildArtefactsService { return packageFile; } - public getAllBuiltApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[] { + public getAllApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[] { const rootFiles = this.$fs.readDirectory(buildOutputPath).map(filename => path.join(buildOutputPath, filename)); let result = this.getApplicationPackagesCore(rootFiles, validBuildOutputData.packageNames); if (result) { @@ -40,20 +41,8 @@ export class BuildArtefactsService implements IBuildArtefactsService { return []; } - private getLatestApplicationPackageForDevice(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage { - outputPath = outputPath || platformData.getBuildOutputPath(buildConfig); - const buildOutputOptions = { buildForDevice: true, release: buildConfig.release, androidBundle: buildConfig.androidBundle }; - return this.getLatestApplicationPackage(outputPath, platformData.getValidBuildOutputData(buildOutputOptions)); - } - - private getLatestApplicationPackageForEmulator(platformData: IPlatformData, buildConfig: IBuildConfig, outputPath?: string): IApplicationPackage { - outputPath = outputPath || platformData.getBuildOutputPath(buildConfig); - const buildOutputOptions = { buildForDevice: false, release: buildConfig.release, androidBundle: buildConfig.androidBundle }; - return this.getLatestApplicationPackage(outputPath, platformData.getValidBuildOutputData(buildOutputOptions)); - } - private getLatestApplicationPackage(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage { - let packages = this.getAllBuiltApplicationPackages(buildOutputPath, validBuildOutputData); + let packages = this.getAllApplicationPackages(buildOutputPath, validBuildOutputData); const packageExtName = path.extname(validBuildOutputData.packageNames[0]); if (packages.length === 0) { this.$errors.fail(`No ${packageExtName} found in ${buildOutputPath} directory.`); diff --git a/lib/services/device/device-install-app-service.ts b/lib/services/device/device-install-app-service.ts index 23221f58d9..ac9964bcb4 100644 --- a/lib/services/device/device-install-app-service.ts +++ b/lib/services/device/device-install-app-service.ts @@ -1,15 +1,17 @@ import { TrackActionNames, HASHES_FILE_NAME } from "../../constants"; +import { BuildArtefactsService } from "../build-artefacts-service"; +import { BuildPlatformService } from "../platform/build-platform-service"; import { MobileHelper } from "../../common/mobile/mobile-helper"; import * as helpers from "../../common/helpers"; import * as path from "path"; -import { BuildPlatformService } from "../platform/build-platform-service"; +import { BuildPlatformDataBase } from "../workflow/workflow-data-service"; const buildInfoFileName = ".nsbuildinfo"; export class DeviceInstallAppService { constructor( private $analyticsService: IAnalyticsService, - private $buildArtefactsService: IBuildArtefactsService, + private $buildArtefactsService: BuildArtefactsService, private $devicePathProvider: IDevicePathProvider, private $fs: IFileSystem, private $logger: ILogger, @@ -17,7 +19,7 @@ export class DeviceInstallAppService { private $buildPlatformService: BuildPlatformService ) { } - public async installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise { + public async installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildPlatformData: BuildPlatformDataBase, packageFile?: string, outputFilePath?: string): Promise { this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); await this.$analyticsService.trackEventActionInGoogleAnalytics({ @@ -27,7 +29,7 @@ export class DeviceInstallAppService { }); if (!packageFile) { - packageFile = await this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildConfig, outputFilePath); + packageFile = await this.$buildArtefactsService.getLatestApplicationPackagePath(platformData, buildPlatformData, outputFilePath); } await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); @@ -42,11 +44,11 @@ export class DeviceInstallAppService { platformData }); - if (!buildConfig.release) { + if (!buildPlatformData.release) { const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - const options = buildConfig; + const options = buildPlatformData; options.buildForDevice = !device.isEmulator; - const buildInfoFilePath = outputFilePath || platformData.getBuildOutputPath(buildConfig); + const buildInfoFilePath = outputFilePath || platformData.getBuildOutputPath(buildPlatformData); const appIdentifier = projectData.projectIdentifiers[platform]; await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); @@ -55,10 +57,10 @@ export class DeviceInstallAppService { this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); } - public async installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig, packageFile?: string, outputFilePath?: string): Promise { - const shouldInstall = await this.shouldInstall(device, platformData, projectData, buildConfig); + public async installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildPlatformData: BuildPlatformDataBase, packageFile?: string, outputFilePath?: string): Promise { + const shouldInstall = await this.shouldInstall(device, platformData, projectData, buildPlatformData); if (shouldInstall) { - await this.installOnDevice(device, platformData, projectData, buildConfig, packageFile, outputFilePath); + await this.installOnDevice(device, platformData, projectData, buildPlatformData, packageFile, outputFilePath); } } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index a204f1fde0..f2d75722f8 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -205,6 +205,11 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.$childProcess, handler, this.$xcodebuildService.buildForDevice(platformData, projectData, buildPlatformData)); + } else if (buildPlatformData.buildForAppStore) { + await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, + this.$childProcess, + handler, + this.$xcodebuildService.buildForAppStore(platformData, projectData, buildPlatformData)); } else { await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index 8289015a6e..bac66128e3 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -1,6 +1,5 @@ -import * as path from "path"; import { Device, FilesPayload } from "nativescript-preview-sdk"; -import { APP_RESOURCES_FOLDER_NAME, APP_FOLDER_NAME, TrackActionNames, FILES_CHANGE_EVENT_NAME } from "../../../constants"; +import { APP_RESOURCES_FOLDER_NAME, TrackActionNames, FILES_CHANGE_EVENT_NAME } from "../../../constants"; import { PreviewAppLiveSyncEvents } from "./preview-app-constants"; import { HmrConstants } from "../../../common/constants"; import { stringify } from "../../../common/helpers"; @@ -19,9 +18,7 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA private $errors: IErrors, private $hmrStatusService: IHmrStatusService, private $logger: ILogger, - private $platformsData: IPlatformsData, private $platformWatcherService: PlatformWatcherService, - private $projectDataService: IProjectDataService, private $previewSdkService: IPreviewSdkService, private $previewAppFilesService: IPreviewAppFilesService, private $previewAppPluginsService: IPreviewAppPluginsService, @@ -49,12 +46,14 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA }); } + await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); this.deviceInitializationPromise[device.id] = this.getInitialFilesForPlatformSafe(data, device.platform); this.$platformWatcherService.on(FILES_CHANGE_EVENT_NAME, async (filesChangeData: IFilesChangeEventData) => { await this.onWebpackCompilationComplete(data, filesChangeData.hmrData, filesChangeData.files, device.platform); }); + // TODO: Stop native watcher here!!!! -> maybe with skipNativePrepare const { nativePlatformData, projectData, preparePlatformData } = this.$workflowDataService.createWorkflowData(device.platform.toLowerCase(), data.projectDir, data); await this.$platformWatcherService.startWatchers(nativePlatformData, projectData, preparePlatformData); @@ -136,15 +135,8 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA await this.promise .then(async () => { const platformHmrData = _.cloneDeep(hmrData); - const projectData = this.$projectDataService.getProjectData(data.projectDir); - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const clonedFiles = _.cloneDeep(files); - const filesToSync = _.map(clonedFiles, fileToSync => { - const result = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME, path.relative(projectData.getAppDirectoryPath(), fileToSync)); - return result; - }); - this.promise = this.syncFilesForPlatformSafe(data, { filesToSync }, platform); + this.promise = this.syncFilesForPlatformSafe(data, { filesToSync: files }, platform); await this.promise; if (data.useHotModuleReload && platformHmrData.hash) { diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index 8a6b327606..c7cf8f52c6 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -11,7 +11,7 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private $prompter: IPrompter, private $staticConfig: IStaticConfig, private $analyticsService: IAnalyticsService, - private $previewAppLiveSyncService: IPreviewAppLiveSyncService, + // private $previewAppLiveSyncService: IPreviewAppLiveSyncService, private $previewQrCodeService: IPreviewQrCodeService) { } public static CLOUD_SETUP_OPTION_NAME = "Configure for Cloud Builds"; @@ -175,12 +175,12 @@ 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.$previewAppLiveSyncService.initialize({ + // projectDir, + // env: options.env, + // useHotModuleReload: options.hmr, + // bundle: true + // }); await this.$previewQrCodeService.printLiveSyncQrCode({ projectDir, useHotModuleReload: options.hmr, link: options.link }); } diff --git a/lib/services/platform/build-platform-service.ts b/lib/services/platform/build-platform-service.ts index f85925d85d..6a4abfa8d5 100644 --- a/lib/services/platform/build-platform-service.ts +++ b/lib/services/platform/build-platform-service.ts @@ -1,16 +1,17 @@ import * as constants from "../../constants"; import { Configurations } from "../../common/constants"; -import { EventEmitter } from "events"; import { attachAwaitDetach } from "../../common/helpers"; -import * as path from "path"; +import { BuildArtefactsService } from "../build-artefacts-service"; import { BuildPlatformDataBase } from "../workflow/workflow-data-service"; +import { EventEmitter } from "events"; +import * as path from "path"; const buildInfoFileName = ".nsbuildinfo"; export class BuildPlatformService extends EventEmitter { constructor( private $analyticsService: IAnalyticsService, - private $buildArtefactsService: IBuildArtefactsService, + private $buildArtefactsService: BuildArtefactsService, private $fs: IFileSystem, private $logger: ILogger, private $mobileHelper: Mobile.IMobileHelper, @@ -49,10 +50,10 @@ export class BuildPlatformService extends EventEmitter { this.$logger.out("Project successfully built."); - const result = await this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildPlatformData); + const result = await this.$buildArtefactsService.getLatestApplicationPackagePath(platformData, buildPlatformData); // if (this.$options.copyTo) { - // this.$platformService.copyLastOutput(platform, this.$options.copyTo, buildConfig, this.$projectData); + // this.$platformService.copyLastOutput(platform, this.$options.copyTo, buildPlatformData, this.$projectData); // } else { // this.$logger.info(`The build result is located at: ${outputPath}`); // } @@ -84,8 +85,8 @@ export class BuildPlatformService extends EventEmitter { this.$fs.writeJson(buildInfoFile, buildInfo); } - public getBuildInfoFromFile(platformData: IPlatformData, buildConfig: BuildPlatformDataBase, buildOutputPath?: string): IBuildInfo { - buildOutputPath = buildOutputPath || platformData.getBuildOutputPath(buildConfig); + public getBuildInfoFromFile(platformData: IPlatformData, buildPlatformData: BuildPlatformDataBase, buildOutputPath?: string): IBuildInfo { + buildOutputPath = buildOutputPath || platformData.getBuildOutputPath(buildPlatformData); const buildInfoFile = path.join(buildOutputPath, buildInfoFileName); if (this.$fs.exists(buildInfoFile)) { try { @@ -99,8 +100,8 @@ export class BuildPlatformService extends EventEmitter { return null; } - private async shouldBuildPlatform(platformData: IPlatformData, projectData: IProjectData, buildConfig: BuildPlatformDataBase, outputPath: string): Promise { - if (buildConfig.release && this.$projectChangesService.currentChanges.hasChanges) { + private async shouldBuildPlatform(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: BuildPlatformDataBase, outputPath: string): Promise { + if (buildPlatformData.release && this.$projectChangesService.currentChanges.hasChanges) { return true; } @@ -112,19 +113,19 @@ export class BuildPlatformService extends EventEmitter { return true; } - const validBuildOutputData = platformData.getValidBuildOutputData(buildConfig); - const packages = this.$buildArtefactsService.getAllBuiltApplicationPackages(outputPath, validBuildOutputData); + const validBuildOutputData = platformData.getValidBuildOutputData(buildPlatformData); + const packages = this.$buildArtefactsService.getAllApplicationPackages(outputPath, validBuildOutputData); if (packages.length === 0) { return true; } const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); - const buildInfo = this.getBuildInfoFromFile(platformData, buildConfig, outputPath); + const buildInfo = this.getBuildInfoFromFile(platformData, buildPlatformData, outputPath); if (!prepareInfo || !buildInfo) { return true; } - if (buildConfig.clean) { + if (buildPlatformData.clean) { return true; } diff --git a/lib/services/platform/prepare-platform-service.ts b/lib/services/platform/prepare-platform-service.ts index cd9a463df7..e4f2de32a4 100644 --- a/lib/services/platform/prepare-platform-service.ts +++ b/lib/services/platform/prepare-platform-service.ts @@ -34,7 +34,7 @@ export class PreparePlatformService { return false; } - const changesInfo = await this.$projectChangesService.checkForChanges(platformData.platformNameLowerCase, projectData, preparePlatformData); + const changesInfo = await this.$projectChangesService.checkForChanges(platformData, projectData, preparePlatformData); const hasModulesChange = !changesInfo || changesInfo.modulesChanged; const hasConfigChange = !changesInfo || changesInfo.configChanged; diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 07ad1e5223..3ca01c3050 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -44,7 +44,6 @@ export class ProjectChangesService implements IProjectChangesService { private _outputProjectCTime: number; constructor( - private $platformsData: IPlatformsData, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $fs: IFileSystem, private $logger: ILogger, @@ -56,10 +55,9 @@ export class ProjectChangesService implements IProjectChangesService { } @hook("checkForChanges") - public async checkForChanges(platform: string, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - const platformData = this.$platformsData.getPlatformData(platform, projectData); + public async checkForChanges(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { this._changesInfo = new ProjectChangesInfo(); - const isNewPrepareInfo = await this.ensurePrepareInfo(platform, projectData, preparePlatformData); + const isNewPrepareInfo = await this.ensurePrepareInfo(platformData, projectData, preparePlatformData); if (!isNewPrepareInfo) { this._newFiles = 0; @@ -81,7 +79,7 @@ export class ProjectChangesService implements IProjectChangesService { this._changesInfo.modulesChanged = true; } - if (platform === this.$devicePlatformsConstants.iOS.toLowerCase()) { + if (platformData.platformNameLowerCase === this.$devicePlatformsConstants.iOS.toLowerCase()) { this._changesInfo.configChanged = this.filesChanged([path.join(platformResourcesDir, platformData.configurationFileName), path.join(platformResourcesDir, "LaunchScreen.storyboard"), path.join(platformResourcesDir, BUILD_XCCONFIG_FILE_NAME) @@ -169,8 +167,7 @@ export class ProjectChangesService implements IProjectChangesService { this.savePrepareInfo(platformData); } - private async ensurePrepareInfo(platform: string, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - const platformData = this.$platformsData.getPlatformData(platform, projectData); + private async ensurePrepareInfo(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { this._prepareInfo = this.getPrepareInfo(platformData); if (this._prepareInfo) { const prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName); diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 1da23b2227..285eff9137 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -1,6 +1,7 @@ import * as constants from "../constants"; import * as path from 'path'; import * as os from 'os'; +import { MainController } from "../controllers/main-controller"; interface IKarmaConfigOptions { debugBrk: boolean; @@ -12,76 +13,50 @@ export class TestExecutionService implements ITestExecutionService { private static SOCKETIO_JS_FILE_NAME = `node_modules/${constants.TEST_RUNNER_NAME}/socket.io.js`; constructor( - private $liveSyncCommandHelper: ILiveSyncCommandHelper, + private $mainController: MainController, private $httpClient: Server.IHttpClient, private $config: IConfiguration, private $logger: ILogger, private $fs: IFileSystem, private $options: IOptions, private $pluginsService: IPluginsService, - private $devicesService: Mobile.IDevicesService, - private $childProcess: IChildProcess) { - } + private $projectDataService: IProjectDataService, + private $childProcess: IChildProcess) { } public platform: string; - public async startKarmaServer(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { + public async startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { platform = platform.toLowerCase(); this.platform = platform; + const projectData = this.$projectDataService.getProjectData(liveSyncInfo.projectDir); + // We need the dependencies installed here, so we can start the Karma server. await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); - const projectDir = projectData.projectDir; - await this.$devicesService.initialize({ - platform: platform, - deviceId: this.$options.device, - emulator: this.$options.emulator - }); - - const karmaConfig = this.getKarmaConfiguration(platform, projectData), + const karmaConfig = this.getKarmaConfiguration(platform, projectData); // In case you want to debug the unit test runner, add "--inspect-brk=" as a first element in the array of args. - karmaRunner = this.$childProcess.spawn(process.execPath, [path.join(__dirname, "karma-execution.js")], { stdio: ["inherit", "inherit", "inherit", "ipc"] }), - launchKarmaTests = async (karmaData: any) => { + const karmaRunner = this.$childProcess.spawn(process.execPath, [path.join(__dirname, "karma-execution.js")], { stdio: ["inherit", "inherit", "inherit", "ipc"] }); + const launchKarmaTests = async (karmaData: any) => { this.$logger.trace("## Unit-testing: Parent process received message", karmaData); let port: string; if (karmaData.url) { port = karmaData.url.port; const socketIoJsUrl = `http://${karmaData.url.host}/socket.io/socket.io.js`; const socketIoJs = (await this.$httpClient.httpRequest(socketIoJsUrl)).body; - this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs); + this.$fs.writeFile(path.join(liveSyncInfo.projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs); } if (karmaData.launcherConfig) { const configOptions: IKarmaConfigOptions = JSON.parse(karmaData.launcherConfig); const configJs = this.generateConfig(port, configOptions); - this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs); + this.$fs.writeFile(path.join(liveSyncInfo.projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs); } // Prepare the project AFTER the TestExecutionService.CONFIG_FILE_NAME file is created in node_modules // so it will be sent to device. - let devices = []; - if (this.$options.debugBrk) { - const selectedDeviceForDebug = await this.$devicesService.pickSingleDevice({ - onlyEmulators: this.$options.emulator, - onlyDevices: this.$options.forDevice, - deviceId: this.$options.device - }); - devices = [selectedDeviceForDebug]; - // const debugData = this.getDebugData(platform, projectData, deployOptions, { device: selectedDeviceForDebug.deviceInfo.identifier }); - // await this.$debugService.debug(debugData, this.$options); - } else { - devices = this.$devicesService.getDeviceInstances(); - } - - if (!this.$options.env) { this.$options.env = { }; } - this.$options.env.unitTesting = true; - - const deviceDebugMap: IDictionary = {}; - devices.forEach(device => deviceDebugMap[device.deviceInfo.identifier] = this.$options.debugBrk); - - await this.$liveSyncCommandHelper.executeLiveSyncOperation(devices, this.platform, { deviceDebugMap }); + await this.$mainController.runOnDevices(liveSyncInfo.projectDir, deviceDescriptors, liveSyncInfo); }; karmaRunner.on("message", (karmaData: any) => { diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index 0512d6290b..d17141eae8 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -17,7 +17,7 @@ declare global { } interface IProjectChangesService { - checkForChanges(platform: string, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; + checkForChanges(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; getPrepareInfoFilePath(platformData: IPlatformData): string; getPrepareInfo(platformData: IPlatformData): IPrepareInfo; savePrepareInfo(platformData: IPlatformData): void; @@ -25,10 +25,6 @@ declare global { currentChanges: IProjectChangesInfo; } - interface IPlatformWatcherService extends EventEmitter { - startWatcher(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; - } - interface IFilesChangeEventData { platform: string; files: string[]; diff --git a/lib/services/workflow/workflow-data-service.ts b/lib/services/workflow/workflow-data-service.ts index b8fc2f2df7..e624e49d95 100644 --- a/lib/services/workflow/workflow-data-service.ts +++ b/lib/services/workflow/workflow-data-service.ts @@ -4,10 +4,14 @@ export type IOSPrepareData = PreparePlatformData & Pick { deviceLogProvider: null, packageManager: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], - liveSyncService: ["liveSync", "stopLiveSync", "enableDebugging", "disableDebugging", "attachDebugger"], + // liveSyncService: ["liveSync", "stopLiveSync", "enableDebugging", "disableDebugging", "attachDebugger"], debugService: ["debug"], analyticsSettingsService: ["getClientId"], devicesService: [ diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 46fde5a5f5..b7144ba9ff 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -102,7 +102,6 @@ function createTestInjector() { testInjector.register("hooksService", stubs.HooksServiceStub); testInjector.register("staticConfig", StaticConfigLib.StaticConfig); testInjector.register("nodeModulesDependenciesBuilder", {}); - // testInjector.register('platformService', PlatformServiceLib.PlatformService); testInjector.register('platformCommandsService', PlatformCommandsService); testInjector.register('platformValidationService', PlatformValidationService); testInjector.register('errors', ErrorsNoFailStub); @@ -185,6 +184,7 @@ function createTestInjector() { testInjector.register("cleanupService", { setShouldDispose: (shouldDispose: boolean): void => undefined }); + testInjector.register("addPlatformService", {}); return testInjector; } diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index 25d1a2ab72..e9d30c2dd3 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -60,7 +60,12 @@ class ProjectChangesServiceTest extends BaseServiceTest { getPlatformData(platform: string): IPlatformData { return { - projectRoot: path.join(this.projectDir, "platforms", platform.toLowerCase()) + projectRoot: path.join(this.projectDir, "platforms", platform.toLowerCase()), + platformProjectService: { + checkForChanges: async (changesInfo: IProjectChangesInfo) => { + changesInfo.signingChanged = true; + } + } }; } } @@ -155,7 +160,7 @@ describe("Project Changes Service Tests", () => { describe("Accumulates Changes From Project Services", () => { it("accumulates changes from the project service", async () => { - const iOSChanges = await serviceTest.projectChangesService.checkForChanges("ios", serviceTest.projectData, { + const iOSChanges = await serviceTest.projectChangesService.checkForChanges(serviceTest.getPlatformData("ios"), serviceTest.projectData, { provision: undefined, teamId: undefined }); @@ -176,7 +181,7 @@ describe("Project Changes Service Tests", () => { it(`shouldn't reset prepare info when native platform status is ${Constants.NativePlatformStatus.alreadyPrepared} and there is existing prepare info`, async () => { for (const platform of ["ios", "android"]) { - await serviceTest.projectChangesService.checkForChanges(platform, serviceTest.projectData, {}); + await serviceTest.projectChangesService.checkForChanges(serviceTest.getPlatformData(platform), serviceTest.projectData, {}); serviceTest.projectChangesService.savePrepareInfo(serviceTest.getPlatformData(platform)); const prepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); @@ -191,7 +196,7 @@ describe("Project Changes Service Tests", () => { _.each([Constants.NativePlatformStatus.requiresPlatformAdd, Constants.NativePlatformStatus.requiresPrepare], nativePlatformStatus => { it(`should reset prepare info when native platform status is ${nativePlatformStatus} and there is existing prepare info`, async () => { for (const platform of ["ios", "android"]) { - await serviceTest.projectChangesService.checkForChanges(platform, serviceTest.projectData, {}); + await serviceTest.projectChangesService.checkForChanges(serviceTest.getPlatformData(platform), serviceTest.projectData, {}); serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), { nativePlatformStatus: nativePlatformStatus }); const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); diff --git a/test/services/android-plugin-build-service.ts b/test/services/android-plugin-build-service.ts index 304c0ea6ee..461311fc8f 100644 --- a/test/services/android-plugin-build-service.ts +++ b/test/services/android-plugin-build-service.ts @@ -314,8 +314,8 @@ dependencies { }); it('builds aar with the specified runtime gradle versions when the project runtime has gradle versions', async () => { - const expectedGradleVersion = "2.2.2"; - const expectedAndroidVersion = "3.3"; + const expectedGradleVersion = "4.4.4"; + const expectedAndroidVersion = "5.5.5"; const config: IPluginBuildOptions = setup({ addManifest: true, addProjectDir: true, diff --git a/test/services/platform/build-platform-service.ts b/test/services/platform/build-platform-service.ts new file mode 100644 index 0000000000..5cbf856163 --- /dev/null +++ b/test/services/platform/build-platform-service.ts @@ -0,0 +1,3 @@ +describe("buildPlatformService", () => { + +}); diff --git a/test/services/platform/platform-watcher-service.ts b/test/services/platform/platform-watcher-service.ts index ec7b51e876..ff43494abd 100644 --- a/test/services/platform/platform-watcher-service.ts +++ b/test/services/platform/platform-watcher-service.ts @@ -31,7 +31,7 @@ function createTestInjector(data: { hasNativeChanges: boolean }): IInjector { })); injector.register("platformWatcherService", PlatformWatcherService); - const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); + const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); platformWatcherService.emit = (eventName: string, eventData: any) => { emittedEventNames.push(eventName); emittedEventData.push(eventData); @@ -56,8 +56,8 @@ describe("PlatformWatcherService", () => { const injector = createTestInjector({ hasNativeChanges }); const platformData = { platformNameLowerCase: platform.toLowerCase(), normalizedPlatformName: platform }; - const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); - await platformWatcherService.startWatcher(platformData, projectData, preparePlatformData); + const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); + await platformWatcherService.startWatchers(platformData, projectData, preparePlatformData); assert.lengthOf(emittedEventNames, 1); assert.lengthOf(emittedEventData, 1); @@ -72,7 +72,7 @@ describe("PlatformWatcherService", () => { it(`should respect native changes that are made before the initial preparation of the project had been done for ${platform}`, async () => { const injector = createTestInjector({ hasNativeChanges: false }); - const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); + const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); const preparePlatformService = injector.resolve("preparePlatformService"); preparePlatformService.prepareNativePlatform = async () => { @@ -83,7 +83,7 @@ describe("PlatformWatcherService", () => { }; const platformData = { platformNameLowerCase: platform.toLowerCase(), normalizedPlatformName: platform }; - await platformWatcherService.startWatcher(platformData, projectData, preparePlatformData); + await platformWatcherService.startWatchers(platformData, projectData, preparePlatformData); assert.lengthOf(emittedEventNames, 1); assert.lengthOf(emittedEventData, 1); @@ -106,9 +106,9 @@ describe("PlatformWatcherService", () => { return hasNativeChanges; }; - const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); + const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); const platformData = { platformNameLowerCase: platform.toLowerCase(), normalizedPlatformName: platform }; - await platformWatcherService.startWatcher(platformData, projectData, preparePlatformData); + await platformWatcherService.startWatchers(platformData, projectData, preparePlatformData); assert.lengthOf(emittedEventNames, 1); assert.lengthOf(emittedEventData, 1); diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index 4706a00439..9fda21ce37 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -8,6 +8,7 @@ import * as path from "path"; import { ProjectFilesManager } from "../../../lib/common/services/project-files-manager"; import { EventEmitter } from "events"; import { PreviewAppFilesService } from "../../../lib/services/livesync/playground/preview-app-files-service"; +import { WorkflowDataService, PreparePlatformData } from "../../../lib/services/workflow/workflow-data-service"; interface ITestCase { name: string; @@ -37,7 +38,7 @@ interface IActInput { } let isComparePluginsOnDeviceCalled = false; -let isHookCalledWithHMR = false; +let isHMRPassedToEnv = false; let applyChangesParams: FilePayload[] = []; let initialFiles: FilePayload[] = []; let readTextParams: string[] = []; @@ -149,11 +150,6 @@ function createTestInjector(options?: { }, mapFilePath: (filePath: string) => path.join(path.join(platformsDirPath, "app"), path.relative(path.join(projectDirPath, "app"), filePath)) }); - injector.register("hooksService", { - executeBeforeHooks: (name: string, args: any) => { - isHookCalledWithHMR = args.hookArgs.config.appFilesUpdaterOptions.useHotModuleReload; - } - }); injector.register("previewDevicesService", { getConnectedDevices: () => [deviceMockData] }); @@ -161,6 +157,13 @@ function createTestInjector(options?: { injector.register("analyticsService", { trackEventActionInGoogleAnalytics: () => ({}) }); + injector.register("platformWatcherService", { + startWatchers: (platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData) => { + isHMRPassedToEnv = preparePlatformData.env.hmr; + }, + on: () => ({}) + }); + injector.register("workflowDataService", WorkflowDataService); return injector; } @@ -209,7 +212,7 @@ async function assert(expectedFiles: string[], options?: IAssertOptions) { options = options || {}; const actualFiles = options.checkInitialFiles ? initialFiles : applyChangesParams; - chai.assert.equal(isHookCalledWithHMR, options.hmr || false); + chai.assert.equal(isHMRPassedToEnv, options.hmr || false); chai.assert.deepEqual(actualFiles, mapFiles(expectedFiles)); if (options.checkWarnings) { @@ -223,7 +226,7 @@ async function assert(expectedFiles: string[], options?: IAssertOptions) { function reset() { isComparePluginsOnDeviceCalled = false; - isHookCalledWithHMR = false; + isHMRPassedToEnv = false; applyChangesParams = []; initialFiles = []; readTextParams = []; diff --git a/test/services/test-execution-serice.ts b/test/services/test-execution-service.ts similarity index 98% rename from test/services/test-execution-serice.ts rename to test/services/test-execution-service.ts index 9d9db7d547..5a42a3c0eb 100644 --- a/test/services/test-execution-serice.ts +++ b/test/services/test-execution-service.ts @@ -8,6 +8,7 @@ const unitTestsPluginName = "nativescript-unit-test-runner"; function getTestExecutionService(): ITestExecutionService { const injector = new InjectorStub(); injector.register("testExecutionService", TestExecutionService); + injector.register("mainController", {}); return injector.resolve("testExecutionService"); } diff --git a/test/stubs.ts b/test/stubs.ts index 5fe26d8b30..77095580aa 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -741,7 +741,7 @@ export class ChildProcessStub extends EventEmitter { } export class ProjectChangesService implements IProjectChangesService { - public async checkForChanges(platform: string, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + public async checkForChanges(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { return {}; } diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index 9a0d69e0f4..cb2edfdf03 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -2,6 +2,10 @@ import { PublishIOS } from "../lib/commands/appstore-upload"; import { PrompterStub, LoggerStub, ProjectDataStub } from "./stubs"; import * as chai from "chai"; import * as yok from "../lib/common/yok"; +import { BuildPlatformService } from "../lib/services/platform/build-platform-service"; +import { PreparePlatformService } from "../lib/services/platform/prepare-platform-service"; +import { MainController } from "../lib/controllers/main-controller"; +import { WorkflowDataService } from "../lib/services/workflow/workflow-data-service"; class AppStore { static itunesconnect = { @@ -15,16 +19,19 @@ class AppStore { options: any; prompter: PrompterStub; projectData: ProjectDataStub; - platformService: any; + buildPlatformService: BuildPlatformService; + preparePlatformService: PreparePlatformService; + platformCommandsService: any; + platformValidationService: any; + mainController: MainController; iOSPlatformData: any; iOSProjectService: any; - xcodebuildService: IXcodebuildService; + workflowDataService: WorkflowDataService; loggerService: LoggerStub; itmsTransporterService: any; // Counters preparePlatformCalls: number = 0; - expectedPreparePlatformCalls: number = 0; archiveCalls: number = 0; expectedArchiveCalls: number = 0; exportArchiveCalls: number = 0; @@ -52,24 +59,25 @@ class AppStore { "devicePlatformsConstants": { "iOS": "iOS" }, - "platformService": this.platformService = {}, + "preparePlatformService": this.preparePlatformService = {}, + "platformCommandsService": this.platformCommandsService = {}, + "platformValidationService": this.platformValidationService = {}, + "mainController": this.mainController = { + buildPlatform: () => ({}) + }, + "buildPlatformService": this.buildPlatformService = { + buildPlatform: async () => { + this.archiveCalls++; + return "/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"; + } + }, "platformsData": { getPlatformData: (platform: string) => { chai.assert.equal(platform, "iOS"); return this.iOSPlatformData; } }, - "xcodebuildService": this.xcodebuildService = { - buildForDevice: async () => { - this.archiveCalls++; - return "/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"; - }, - buildForSimulator: () => Promise.resolve(), - buildForAppStore: async () => { - this.archiveCalls++; - return "/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"; - } - } + "workflowDataService": this.workflowDataService = {}, } }); this.projectData.initializeProjectData(this.iOSPlatformData.projectRoot); @@ -90,7 +98,6 @@ class AppStore { assert() { this.prompter.assert(); - chai.assert.equal(this.preparePlatformCalls, this.expectedPreparePlatformCalls, "Mismatched number of $platformService.preparePlatform calls."); chai.assert.equal(this.archiveCalls, this.expectedArchiveCalls, "Mismatched number of iOSProjectService.archive calls."); chai.assert.equal(this.itmsTransporterServiceUploadCalls, this.expectedItmsTransporterServiceUploadCalls, "Mismatched number of itmsTransporterService.upload calls."); } @@ -102,21 +109,13 @@ class AppStore { }); } - expectPreparePlatform() { - this.expectedPreparePlatformCalls = 1; - this.platformService.preparePlatform = (platformInfo: any) => { - chai.assert.equal(platformInfo.platform, "iOS"); - this.preparePlatformCalls++; - return Promise.resolve(true); - }; - } - expectArchive() { this.expectedArchiveCalls = 1; - this.xcodebuildService.buildForDevice = (platformData: any, projectData: IProjectData) => { + this.mainController.buildPlatform = (platform: string, projectDir: string, options) => { this.archiveCalls++; - chai.assert.equal(projectData.projectDir, "/Users/person/git/MyProject"); - return Promise.resolve("/Users/person/git/MyProject/platforms/ios/archive/MyProject.xcarchive"); + chai.assert.equal(projectDir, "/Users/person/git/MyProject"); + chai.assert.isTrue(options.buildForAppStore); + return Promise.resolve("/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"); }; } @@ -134,7 +133,6 @@ class AppStore { async noArgs() { this.expectItunesPrompt(); - this.expectPreparePlatform(); this.expectArchive(); this.expectITMSTransporterUpload(); @@ -144,7 +142,6 @@ class AppStore { } async itunesconnectArgs() { - this.expectPreparePlatform(); this.expectArchive(); this.expectITMSTransporterUpload(); @@ -155,7 +152,6 @@ class AppStore { async teamIdOption() { this.expectItunesPrompt(); - this.expectPreparePlatform(); this.expectArchive(); this.expectITMSTransporterUpload(); diff --git a/test/update.ts b/test/update.ts index 80eb387f02..53516bd288 100644 --- a/test/update.ts +++ b/test/update.ts @@ -47,7 +47,7 @@ function createTestInjector( return "1.0.0"; } }); - testInjector.register("platformService", { + testInjector.register("platformCommandsService", { getInstalledPlatforms: function(): string[] { return installedPlatforms; }, @@ -57,6 +57,7 @@ function createTestInjector( removePlatforms: async (): Promise => undefined, addPlatforms: async (): Promise => undefined, }); + testInjector.register("platformValidationService", {}); testInjector.register("platformsData", { availablePlatforms: { Android: "Android", @@ -93,15 +94,14 @@ describe("update command method tests", () => { validated = true; return Promise.resolve(); }); + const updateCommand = testInjector.resolve(UpdateCommand); - const canExecute = updateCommand.canExecute(["3.3.0"]); + await updateCommand.canExecute(["3.3.0"]); - return canExecute.then(() => { - assert.equal(validated, true); - }); + assert.equal(validated, true); }); - it("returns false if too many artuments", async () => { + it("returns false if too many arguments", async () => { const testInjector = createTestInjector([], ["android"]); const updateCommand = testInjector.resolve(UpdateCommand); const canExecuteOutput = await updateCommand.canExecute(["333", "111", "444"]); @@ -109,7 +109,7 @@ describe("update command method tests", () => { return assert.equal(canExecuteOutput.canExecute, false); }); - it("returns false if projectDir empty string", async () => { + it("returns false when projectDir is an empty string", async () => { const testInjector = createTestInjector([], ["android"], ""); const updateCommand = testInjector.resolve(UpdateCommand); const canExecuteOutput = await updateCommand.canExecute([]); @@ -117,7 +117,7 @@ describe("update command method tests", () => { return assert.equal(canExecuteOutput.canExecute, false); }); - it("returns true all ok", async () => { + it("returns true when the setup is correct", async () => { const testInjector = createTestInjector([], ["android"]); const updateCommand = testInjector.resolve(UpdateCommand); const canExecuteOutput = await updateCommand.canExecute(["3.3.0"]); @@ -142,17 +142,17 @@ describe("update command method tests", () => { const testInjector = createTestInjector(installedPlatforms); const fs = testInjector.resolve("fs"); const deleteDirectory: sinon.SinonStub = sandbox.stub(fs, "deleteDirectory"); - const platformService = testInjector.resolve("platformService"); + const platformCommandsService = testInjector.resolve("platformCommandsService"); sandbox.stub(fs, "copyFile").throws(); - sandbox.spy(platformService, "addPlatforms"); - sandbox.spy(platformService, "removePlatforms"); + sandbox.spy(platformCommandsService, "addPlatforms"); + sandbox.spy(platformCommandsService, "removePlatforms"); const updateCommand = testInjector.resolve(UpdateCommand); - return updateCommand.execute(["3.3.0"]).then(() => { - assert.isTrue(deleteDirectory.calledWith(path.join(projectFolder, UpdateCommand.tempFolder))); - assert.isFalse(platformService.removePlatforms.calledWith(installedPlatforms)); - assert.isFalse(platformService.addPlatforms.calledWith(installedPlatforms)); - }); + await updateCommand.execute(["3.3.0"]); + + assert.isTrue(deleteDirectory.calledWith(path.join(projectFolder, UpdateCommand.tempFolder))); + assert.isFalse(platformCommandsService.removePlatforms.calledWith(installedPlatforms)); + assert.isFalse(platformCommandsService.addPlatforms.calledWith(installedPlatforms)); }); it("calls copy to temp for package.json and folders(backup)", async () => { @@ -160,17 +160,18 @@ describe("update command method tests", () => { const fs = testInjector.resolve("fs"); const copyFileStub = sandbox.stub(fs, "copyFile"); const updateCommand = testInjector.resolve(UpdateCommand); - return updateCommand.execute(["3.3.0"]).then( () => { - assert.isTrue(copyFileStub.calledWith(path.join(projectFolder, "package.json"))); - for (const folder of UpdateCommand.folders) { - assert.isTrue(copyFileStub.calledWith(path.join(projectFolder, folder))); - } - }); + + await updateCommand.execute(["3.3.0"]); + + assert.isTrue(copyFileStub.calledWith(path.join(projectFolder, "package.json"))); + for (const folder of UpdateCommand.folders) { + assert.isTrue(copyFileStub.calledWith(path.join(projectFolder, folder))); + } }); it("calls copy from temp for package.json and folders to project folder(restore)", async () => { const testInjector = createTestInjector(); - testInjector.resolve("platformService").removePlatforms = () => { + testInjector.resolve("platformCommandsService").removePlatforms = () => { throw new Error(); }; const fs = testInjector.resolve("fs"); @@ -179,13 +180,13 @@ describe("update command method tests", () => { const updateCommand = testInjector.resolve(UpdateCommand); const tempDir = path.join(projectFolder, UpdateCommand.tempFolder); - return updateCommand.execute(["3.3.0"]).then(() => { - assert.isTrue(copyFileStub.calledWith(path.join(tempDir, "package.json"), projectFolder)); - for (const folder of UpdateCommand.folders) { - assert.isTrue(deleteDirectoryStub.calledWith(path.join(projectFolder, folder))); - assert.isTrue(copyFileStub.calledWith(path.join(tempDir, folder), projectFolder)); - } - }); + await updateCommand.execute(["3.3.0"]); + + assert.isTrue(copyFileStub.calledWith(path.join(tempDir, "package.json"), projectFolder)); + for (const folder of UpdateCommand.folders) { + assert.isTrue(deleteDirectoryStub.calledWith(path.join(projectFolder, folder))); + assert.isTrue(copyFileStub.calledWith(path.join(tempDir, folder), projectFolder)); + } }); it("calls remove for all folders", async () => { @@ -193,37 +194,40 @@ describe("update command method tests", () => { const fs = testInjector.resolve("fs"); const deleteDirectory: sinon.SinonStub = sandbox.stub(fs, "deleteDirectory"); const updateCommand = testInjector.resolve(UpdateCommand); - return updateCommand.execute([]).then(() => { - for (const folder of UpdateCommand.folders) { - assert.isTrue(deleteDirectory.calledWith(path.join(projectFolder, folder))); - } - }); + + await updateCommand.execute([]); + + for (const folder of UpdateCommand.folders) { + assert.isTrue(deleteDirectory.calledWith(path.join(projectFolder, folder))); + } }); it("calls remove platforms and add platforms", async () => { const installedPlatforms: string[] = ["android"]; const testInjector = createTestInjector(installedPlatforms); - const platformService = testInjector.resolve("platformService"); - sandbox.spy(platformService, "addPlatforms"); - sandbox.spy(platformService, "removePlatforms"); + const platformCommandsService = testInjector.resolve("platformCommandsService"); + sandbox.spy(platformCommandsService, "addPlatforms"); + sandbox.spy(platformCommandsService, "removePlatforms"); const updateCommand = testInjector.resolve(UpdateCommand); - return updateCommand.execute([]).then(() => { - assert(platformService.removePlatforms.calledWith(installedPlatforms)); - assert(platformService.addPlatforms.calledWith(installedPlatforms)); - }); + + await updateCommand.execute([]); + + assert(platformCommandsService.removePlatforms.calledWith(installedPlatforms)); + assert(platformCommandsService.addPlatforms.calledWith(installedPlatforms)); }); it("call add platforms with specific verison", async () => { const version = "3.3.0"; const installedPlatforms: string[] = ["android"]; const testInjector = createTestInjector(installedPlatforms); - const platformService = testInjector.resolve("platformService"); - sandbox.spy(platformService, "addPlatforms"); - sandbox.spy(platformService, "removePlatforms"); + const platformCommandsService = testInjector.resolve("platformCommandsService"); + sandbox.spy(platformCommandsService, "addPlatforms"); + sandbox.spy(platformCommandsService, "removePlatforms"); + const updateCommand = testInjector.resolve(UpdateCommand); - return updateCommand.execute([version]).then(() => { - assert(platformService.addPlatforms.calledWith([`${installedPlatforms}@${version}`])); - }); + await updateCommand.execute([version]); + + assert(platformCommandsService.addPlatforms.calledWith([`${installedPlatforms}@${version}`])); }); it("calls remove and add of core modules and widgets", async () => { @@ -239,12 +243,12 @@ describe("update command method tests", () => { }; const updateCommand = testInjector.resolve(UpdateCommand); - return updateCommand.execute([]).then(() => { - assert(pluginsService.add.calledWith("tns-core-modules")); - assert(pluginsService.remove.calledWith("tns-core-modules")); - assert(pluginsService.remove.calledWith("tns-core-modules-widgets")); - assert(pluginsService.ensureAllDependenciesAreInstalled.called); - }); + await updateCommand.execute([]); + + assert(pluginsService.add.calledWith("tns-core-modules")); + assert(pluginsService.remove.calledWith("tns-core-modules")); + assert(pluginsService.remove.calledWith("tns-core-modules-widgets")); + assert(pluginsService.ensureAllDependenciesAreInstalled.called); }); it("calls add of core modules with specific version", async () => { @@ -254,10 +258,11 @@ describe("update command method tests", () => { sandbox.spy(pluginsService, "remove"); sandbox.spy(pluginsService, "add"); sandbox.spy(pluginsService, "ensureAllDependenciesAreInstalled"); + const updateCommand = testInjector.resolve(UpdateCommand); - return updateCommand.execute([version]).then(() => { - assert(pluginsService.add.calledWith(`tns-core-modules@${version}`)); - }); + await updateCommand.execute([version]); + + assert(pluginsService.add.calledWith(`tns-core-modules@${version}`)); }); }); }); From 762fcd635929cdaac33d14e3ac30c746f203e1ab Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 8 May 2019 08:26:48 +0300 Subject: [PATCH 24/30] fix: build the platform when there are native changes on initial sync and add unit tests for that functionality --- .vscode/launch.json | 2 +- lib/commands/preview.ts | 1 - lib/constants.ts | 4 +- lib/controllers/main-controller.ts | 61 ++++---- lib/controllers/run-on-devices-controller.ts | 26 ++-- lib/declarations.d.ts | 4 - lib/definitions/livesync.d.ts | 1 + lib/definitions/preview-app-livesync.d.ts | 2 +- lib/helpers/bundle-validator-helper.ts | 22 ++- lib/helpers/livesync-command-helper.ts | 2 - .../device/device-refresh-app-service.ts | 2 +- .../playground/preview-app-files-service.ts | 8 +- .../preview-app-livesync-service.ts | 9 +- .../playground/preview-app-plugins-service.ts | 12 +- .../platform/platform-watcher-service.ts | 6 +- lib/services/run-on-devices-data-service.ts | 5 +- lib/services/test-execution-service.ts | 6 +- .../webpack/webpack-compiler-service.ts | 2 +- test/controllers/main-controller.ts | 87 ++++++----- test/controllers/run-on-devices-controller.ts | 144 ++++++++++++++++++ .../platform/platform-watcher-service.ts | 14 +- .../playground/preview-app-files-service.ts | 3 +- .../preview-app-livesync-service.ts | 4 +- .../playground/preview-app-plugins-service.ts | 138 ----------------- 24 files changed, 287 insertions(+), 278 deletions(-) create mode 100644 test/controllers/run-on-devices-controller.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 66b4d96941..e1dd227cc5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "program": "${workspaceRoot}/lib/nativescript-cli.js", // example commands - "args": [ "test", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle"] + "args": [ "create", "cliapp", "--path", "${workspaceRoot}/scratch"] // "args": [ "test", "android", "--justlaunch"] // "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"] // "args": [ "platform", "remove", "android", "--path", "cliapp"] diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index 67092e6bf8..815f6f4d7a 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -26,7 +26,6 @@ export class PreviewCommand implements ICommand { await this.$previewAppLiveSyncService.initialize({ projectDir: this.$projectData.projectDir, - bundle: !!this.$options.bundle, useHotModuleReload: this.$options.hmr, env: this.$options.env }); diff --git a/lib/constants.ts b/lib/constants.ts index a8b179a400..2fc462d1f7 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -139,8 +139,8 @@ export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; export const ANDROID_RELEASE_BUILD_ERROR_MESSAGE = "When producing a release build, you need to specify all --key-store-* options."; export const CACACHE_DIRECTORY_NAME = "_cacache"; -export const FILES_CHANGE_EVENT_NAME = "filesChangeEventData"; -export const INITIAL_SYNC_EVENT_NAME = "initialSyncEventData"; +export const FILES_CHANGE_EVENT_NAME = "filesChangeEvent"; +export const INITIAL_SYNC_EVENT_NAME = "initialSyncEvent"; export class DebugCommandErrors { public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them."; diff --git a/lib/controllers/main-controller.ts b/lib/controllers/main-controller.ts index e085dd00e5..29e160c508 100644 --- a/lib/controllers/main-controller.ts +++ b/lib/controllers/main-controller.ts @@ -31,11 +31,13 @@ export class MainController extends EventEmitter { private $workflowDataService: WorkflowDataService ) { super(); } - public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise { + public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise { const { nativePlatformData, projectData, addPlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); await this.$addPlatformService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); - await this.$preparePlatformService.preparePlatform(nativePlatformData, projectData, preparePlatformData); + const result = await this.$preparePlatformService.preparePlatform(nativePlatformData, projectData, preparePlatformData); + + return result; } public async buildPlatform(platform: string, projectDir: string, options: IOptions | any): Promise { @@ -74,34 +76,40 @@ export class MainController extends EventEmitter { await this.$addPlatformService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); } - // TODO: Consider to handle correctly the descriptors when livesync is executed for second time for the same projectDir + const currentRunOnDevicesData = this.$runOnDevicesDataService.getDataForProject(projectData.projectDir); + const isAlreadyLiveSyncing = currentRunOnDevicesData && !currentRunOnDevicesData.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, currentRunOnDevicesData.deviceDescriptors, "identifier") : deviceDescriptors; - this.$runOnDevicesDataService.persistData(projectDir, deviceDescriptors); + this.$runOnDevicesDataService.persistData(projectDir, deviceDescriptors, platforms); const shouldStartWatcher = !liveSyncInfo.skipWatcher && (liveSyncInfo.syncToPreviewApp || this.$runOnDevicesDataService.hasDeviceDescriptors(projectDir)); if (shouldStartWatcher) { - this.handleRunOnDeviceEvents(projectDir); + this.handleRunOnDeviceError(projectDir); this.$platformWatcherService.on(INITIAL_SYNC_EVENT_NAME, async (data: IInitialSyncEventData) => { - await this.$runOnDevicesController.syncInitialDataOnDevice(data, projectData, liveSyncInfo, deviceDescriptors); + await this.$runOnDevicesController.syncInitialDataOnDevices(data, projectData, liveSyncInfo, deviceDescriptorsForInitialSync); }); this.$platformWatcherService.on(FILES_CHANGE_EVENT_NAME, async (data: IFilesChangeEventData) => { - await this.$runOnDevicesController.syncChangedDataOnDevice(data, projectData, liveSyncInfo, deviceDescriptors); + await this.$runOnDevicesController.syncChangedDataOnDevices(data, projectData, liveSyncInfo, deviceDescriptors); }); for (const platform of platforms) { const { nativePlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, liveSyncInfo); await this.$platformWatcherService.startWatchers(nativePlatformData, projectData, preparePlatformData); } + } else { + for (const platform of platforms) { + const hasNativeChanges = await this.preparePlatform(platform, projectDir, liveSyncInfo); + await this.$runOnDevicesController.syncInitialDataOnDevices({ platform, hasNativeChanges }, projectData, liveSyncInfo, deviceDescriptorsForInitialSync); + } } - // TODO: Consider how to handle --justlaunch - this.attachDeviceLostHandler(); } public async stopRunOnDevices(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { - const liveSyncProcessInfo = this.$runOnDevicesDataService.getData(projectDir); + const liveSyncProcessInfo = this.$runOnDevicesDataService.getDataForProject(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. @@ -112,15 +120,22 @@ export class MainController extends EventEmitter { const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) .map(descriptor => descriptor.identifier); + // Handle the case when no more devices left for any of the persisted platforms + _.each(liveSyncProcessInfo.platforms, platform => { + const devices = this.$devicesService.getDevicesForPlatform(platform); + if (!devices || !devices.length) { + this.$platformWatcherService.stopWatchers(projectDir, platform); + } + }); + // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { if (liveSyncProcessInfo.timer) { clearTimeout(liveSyncProcessInfo.timer); } - _.each(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => { - const device = this.$devicesService.getDeviceByIdentifier(deviceDescriptor.identifier); - this.$platformWatcherService.stopWatchers(projectDir, device.deviceInfo.platform); + _.each(liveSyncProcessInfo.platforms, platform => { + this.$platformWatcherService.stopWatchers(projectDir, platform); }); liveSyncProcessInfo.isStopped = true; @@ -131,12 +146,6 @@ export class MainController extends EventEmitter { liveSyncProcessInfo.deviceDescriptors = []; - if (liveSyncProcessInfo.syncToPreviewApp) { - // await this.$previewAppLiveSyncService.stopLiveSync(); - // this.$previewAppLiveSyncService.removeAllListeners(); - } - - // Kill typescript watcher const projectData = this.$projectDataService.getProjectData(projectDir); await this.$hooksService.executeAfterHooks('watch', { hookArgs: { @@ -158,22 +167,12 @@ export class MainController extends EventEmitter { return this.$runOnDevicesDataService.getDeviceDescriptors(projectDir); } - private handleRunOnDeviceEvents(projectDir: string): void { - this.$runOnDevicesController.on(RunOnDeviceEvents.runOnDeviceError, async data => { + private handleRunOnDeviceError(projectDir: string): void { + this.$runOnDevicesEmitter.on(RunOnDeviceEvents.runOnDeviceError, async data => { await this.stopRunOnDevices(projectDir, [data.deviceIdentifier], { shouldAwaitAllActions: false }); }); } - // TODO: expose previewOnDevice() method { } - // TODO: enableDebugging -> mainController - // TODO: disableDebugging -> mainController - // TODO: attachDebugger -> mainController - // mainController.runOnDevices(), runOnDevicesController.on("event", () => {}) - - // debugOnDevicesController.enableDebugging() - // debugOnDevicesController.disableDebugging() - // debugOnDevicesController.attachDebugger - private async initializeSetup(projectData: IProjectData): Promise { try { await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); diff --git a/lib/controllers/run-on-devices-controller.ts b/lib/controllers/run-on-devices-controller.ts index 1f2288a31a..bb7c6e5ff4 100644 --- a/lib/controllers/run-on-devices-controller.ts +++ b/lib/controllers/run-on-devices-controller.ts @@ -8,6 +8,7 @@ import { RunOnDevicesDataService } from "../services/run-on-devices-data-service import { RunOnDevicesEmitter } from "../run-on-devices-emitter"; import { WorkflowDataService } from "../services/workflow/workflow-data-service"; import { HmrConstants } from "../common/constants"; +import { PreparePlatformService } from "../services/platform/prepare-platform-service"; export class RunOnDevicesController extends EventEmitter { constructor( @@ -20,19 +21,22 @@ export class RunOnDevicesController extends EventEmitter { public $hooksService: IHooksService, private $liveSyncServiceResolver: LiveSyncServiceResolver, private $logger: ILogger, + private $preparePlatformService: PreparePlatformService, private $runOnDevicesDataService: RunOnDevicesDataService, private $runOnDevicesEmitter: RunOnDevicesEmitter, private $workflowDataService: WorkflowDataService ) { super(); } - public async syncInitialDataOnDevice(data: IInitialSyncEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + public async syncInitialDataOnDevices(data: IInitialSyncEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); try { const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); - const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); + const packageFilePath = data.hasNativeChanges ? + await this.$buildPlatformService.buildPlatform(platformData, projectData, buildPlatformData) : + await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); @@ -40,9 +44,9 @@ export class RunOnDevicesController extends EventEmitter { const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor }); - const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor); + const refreshInfo = await this.$deviceRefreshAppService.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); - this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, device, { syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); @@ -58,22 +62,20 @@ export class RunOnDevicesController extends EventEmitter { this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); this.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, err); - - // TODO: Consider to call here directly stopRunOnDevices } }; await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === data.platform.toLowerCase() && _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } - public async syncChangedDataOnDevice(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + public async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - const { nativePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + const { nativePlatformData, preparePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); try { if (data.hasNativeChanges) { - // TODO: Consider to handle nativePluginsChange here (aar rebuilt) + await this.$preparePlatformService.prepareNativePlatform(nativePlatformData, projectData, preparePlatformData); await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); } @@ -123,13 +125,13 @@ export class RunOnDevicesController extends EventEmitter { }; await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => { - const liveSyncProcessInfo = this.$runOnDevicesDataService.getData(projectData.projectDir); + const liveSyncProcessInfo = this.$runOnDevicesDataService.getDataForProject(projectData.projectDir); return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); })); } private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo) { - const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor); + const refreshInfo = await this.$deviceRefreshAppService.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), @@ -142,7 +144,7 @@ export class RunOnDevicesController extends EventEmitter { } private async addActionToChain(projectDir: string, action: () => Promise): Promise { - const liveSyncInfo = this.$runOnDevicesDataService.getData(projectDir); + const liveSyncInfo = this.$runOnDevicesDataService.getDataForProject(projectDir); if (liveSyncInfo) { liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { if (!liveSyncInfo.isStopped) { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 51b7bb05eb..37127033f9 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -433,10 +433,6 @@ interface IOpener { open(target: string, appname: string): void; } -interface IBundle { - bundle: boolean; -} - interface IBundleString { bundle: string; } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 1848dee1ec..a6e4dfb120 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -8,6 +8,7 @@ declare global { deviceDescriptors: ILiveSyncDeviceInfo[]; currentSyncAction: Promise; syncToPreviewApp: boolean; + platforms: string[]; } interface IOptionalOutputPath { diff --git a/lib/definitions/preview-app-livesync.d.ts b/lib/definitions/preview-app-livesync.d.ts index 123ae2e721..6dadd2ae38 100644 --- a/lib/definitions/preview-app-livesync.d.ts +++ b/lib/definitions/preview-app-livesync.d.ts @@ -18,7 +18,7 @@ declare global { filesToRemove?: string[]; } - interface IPreviewAppLiveSyncData extends IProjectDir, IHasUseHotModuleReloadOption, IBundle, IEnvOptions { } + interface IPreviewAppLiveSyncData extends IProjectDir, IHasUseHotModuleReloadOption, IEnvOptions { } interface IPreviewSdkService extends EventEmitter { getQrCodeUrl(options: IGetQrCodeUrlOptions): string; diff --git a/lib/helpers/bundle-validator-helper.ts b/lib/helpers/bundle-validator-helper.ts index 4eedac652b..241ac84598 100644 --- a/lib/helpers/bundle-validator-helper.ts +++ b/lib/helpers/bundle-validator-helper.ts @@ -15,19 +15,17 @@ export class BundleValidatorHelper extends VersionValidatorHelper implements IBu } public validate(minSupportedVersion?: string): void { - if (this.$options.bundle) { - const bundlePluginName = this.bundlersMap[this.$options.bundle]; - const bundlerVersionInDependencies = this.$projectData.dependencies && this.$projectData.dependencies[bundlePluginName]; - const bundlerVersionInDevDependencies = this.$projectData.devDependencies && this.$projectData.devDependencies[bundlePluginName]; - if (!bundlePluginName || (!bundlerVersionInDependencies && !bundlerVersionInDevDependencies)) { - this.$errors.failWithoutHelp(BundleValidatorMessages.MissingBundlePlugin); - } + const bundlePluginName = this.bundlersMap["webpack"]; + const bundlerVersionInDependencies = this.$projectData.dependencies && this.$projectData.dependencies[bundlePluginName]; + const bundlerVersionInDevDependencies = this.$projectData.devDependencies && this.$projectData.devDependencies[bundlePluginName]; + if (!bundlePluginName || (!bundlerVersionInDependencies && !bundlerVersionInDevDependencies)) { + this.$errors.failWithoutHelp(BundleValidatorMessages.MissingBundlePlugin); + } - const currentVersion = bundlerVersionInDependencies || bundlerVersionInDevDependencies; - const shouldThrowError = minSupportedVersion && this.isValidVersion(currentVersion) && this.isVersionLowerThan(currentVersion, minSupportedVersion); - if (shouldThrowError) { - this.$errors.failWithoutHelp(util.format(BundleValidatorMessages.NotSupportedVersion, minSupportedVersion)); - } + const currentVersion = bundlerVersionInDependencies || bundlerVersionInDevDependencies; + const shouldThrowError = minSupportedVersion && this.isValidVersion(currentVersion) && this.isVersionLowerThan(currentVersion, minSupportedVersion); + if (shouldThrowError) { + this.$errors.failWithoutHelp(util.format(BundleValidatorMessages.NotSupportedVersion, minSupportedVersion)); } } } diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 91d5af3a7a..86a1024346 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,8 +1,6 @@ import { BuildPlatformService } from "../services/platform/build-platform-service"; import { MainController } from "../controllers/main-controller"; -// import { LiveSyncEvents } from "../constants"; - export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; diff --git a/lib/services/device/device-refresh-app-service.ts b/lib/services/device/device-refresh-app-service.ts index aa9085d7b5..e5db853823 100644 --- a/lib/services/device/device-refresh-app-service.ts +++ b/lib/services/device/device-refresh-app-service.ts @@ -11,7 +11,7 @@ export class DeviceRefreshAppService { ) { } @performanceLog() - public async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, settings?: IRefreshApplicationSettings): Promise { + public async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, settings?: IRefreshApplicationSettings): Promise { if (deviceDescriptor && deviceDescriptor.debuggingEnabled) { liveSyncResultInfo.waitForDebugger = deviceDescriptor.debugOptions && deviceDescriptor.debugOptions.debugBrk; } diff --git a/lib/services/livesync/playground/preview-app-files-service.ts b/lib/services/livesync/playground/preview-app-files-service.ts index 1c39d4acfe..b3026bd49d 100644 --- a/lib/services/livesync/playground/preview-app-files-service.ts +++ b/lib/services/livesync/playground/preview-app-files-service.ts @@ -85,13 +85,7 @@ export class PreviewAppFilesService implements IPreviewAppFilesService { private getRootFilesDir(data: IPreviewAppLiveSyncData, platform: string): string { const projectData = this.$projectDataService.getProjectData(data.projectDir); const platformData = this.$platformsData.getPlatformData(platform, projectData); - - let rootFilesDir = null; - if (data.bundle) { - rootFilesDir = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); - } else { - rootFilesDir = projectData.getAppDirectoryPath(); - } + const rootFilesDir = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); return rootFilesDir; } diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index bac66128e3..f17a2c37d1 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -53,8 +53,15 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA await this.onWebpackCompilationComplete(data, filesChangeData.hmrData, filesChangeData.files, device.platform); }); - // TODO: Stop native watcher here!!!! -> maybe with skipNativePrepare const { nativePlatformData, projectData, preparePlatformData } = this.$workflowDataService.createWorkflowData(device.platform.toLowerCase(), data.projectDir, data); + + // Setup externals + if (!preparePlatformData.env) { preparePlatformData.env = {}; } + preparePlatformData.env.externals = this.$previewAppPluginsService.getExternalPlugins(device); + + // skipNativePrepare so no native watcher is started + preparePlatformData.nativePrepare = { skipNativePrepare: true }; + await this.$platformWatcherService.startWatchers(nativePlatformData, projectData, preparePlatformData); try { diff --git a/lib/services/livesync/playground/preview-app-plugins-service.ts b/lib/services/livesync/playground/preview-app-plugins-service.ts index 1c52a6ae48..490c10e42e 100644 --- a/lib/services/livesync/playground/preview-app-plugins-service.ts +++ b/lib/services/livesync/playground/preview-app-plugins-service.ts @@ -69,15 +69,11 @@ export class PreviewAppPluginsService implements IPreviewAppPluginsService { } private getWarningForPlugin(data: IPreviewAppLiveSyncData, localPlugin: string, localPluginVersion: string, devicePluginVersion: string, device: Device): string { - if (data && data.bundle) { - const pluginPackageJsonPath = path.join(data.projectDir, NODE_MODULES_DIR_NAME, localPlugin, PACKAGE_JSON_FILE_NAME); - const isNativeScriptPlugin = this.$pluginsService.isNativeScriptPlugin(pluginPackageJsonPath); - if (!isNativeScriptPlugin || (isNativeScriptPlugin && !this.hasNativeCode(localPlugin, device.platform, data.projectDir))) { - return null; - } - } + const pluginPackageJsonPath = path.join(data.projectDir, NODE_MODULES_DIR_NAME, localPlugin, PACKAGE_JSON_FILE_NAME); + const isNativeScriptPlugin = this.$pluginsService.isNativeScriptPlugin(pluginPackageJsonPath); + const shouldCompare = isNativeScriptPlugin && this.hasNativeCode(localPlugin, device.platform, data.projectDir); - return this.getWarningForPluginCore(localPlugin, localPluginVersion, devicePluginVersion, device.id); + return shouldCompare ? this.getWarningForPluginCore(localPlugin, localPluginVersion, devicePluginVersion, device.id) : null; } private getWarningForPluginCore(localPlugin: string, localPluginVersion: string, devicePluginVersion: string, deviceId: string): string { diff --git a/lib/services/platform/platform-watcher-service.ts b/lib/services/platform/platform-watcher-service.ts index 945998ff01..0f0dd734f7 100644 --- a/lib/services/platform/platform-watcher-service.ts +++ b/lib/services/platform/platform-watcher-service.ts @@ -24,8 +24,6 @@ export class PlatformWatcherService extends EventEmitter { ) { super(); } public async startWatchers(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - this.$logger.out("Starting watchers..."); - if (!this.watchersData[projectData.projectDir]) { this.watchersData[projectData.projectDir] = {}; } @@ -43,7 +41,7 @@ export class PlatformWatcherService extends EventEmitter { this.emitInitialSyncEvent({ platform: platformData.platformNameLowerCase, hasNativeChanges }); } - public async stopWatchers(projectDir: string, platform: string) { + public stopWatchers(projectDir: string, platform: string): void { const platformLowerCase = platform.toLowerCase(); if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) { @@ -52,7 +50,7 @@ export class PlatformWatcherService extends EventEmitter { } if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess) { - this.$webpackCompilerService.stopWebpackCompile(platform); + this.$webpackCompilerService.stopWebpackCompiler(platform); this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess = null; } } diff --git a/lib/services/run-on-devices-data-service.ts b/lib/services/run-on-devices-data-service.ts index a314afc404..f1d024c483 100644 --- a/lib/services/run-on-devices-data-service.ts +++ b/lib/services/run-on-devices-data-service.ts @@ -1,7 +1,7 @@ export class RunOnDevicesDataService { private liveSyncProcessesInfo: IDictionary = {}; - public getData(projectDir: string): ILiveSyncProcessInfo { + public getDataForProject(projectDir: string): ILiveSyncProcessInfo { return this.liveSyncProcessesInfo[projectDir]; } @@ -19,11 +19,12 @@ export class RunOnDevicesDataService { return this.liveSyncProcessesInfo[projectDir].deviceDescriptors.length; } - public persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]): void { + public persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], platforms: string[]): void { this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; this.liveSyncProcessesInfo[projectDir].isStopped = false; + this.liveSyncProcessesInfo[projectDir].platforms = platforms; const currentDeviceDescriptors = this.getDeviceDescriptors(projectDir); this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 285eff9137..f64d33a9a3 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -35,7 +35,7 @@ export class TestExecutionService implements ITestExecutionService { await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); const karmaConfig = this.getKarmaConfiguration(platform, projectData); - // In case you want to debug the unit test runner, add "--inspect-brk=" as a first element in the array of args. + // In case you want to debug the unit test runner, add "--inspect-brk=" as a first element in the array of args. const karmaRunner = this.$childProcess.spawn(process.execPath, [path.join(__dirname, "karma-execution.js")], { stdio: ["inherit", "inherit", "inherit", "ipc"] }); const launchKarmaTests = async (karmaData: any) => { this.$logger.trace("## Unit-testing: Parent process received message", karmaData); @@ -132,7 +132,7 @@ export class TestExecutionService implements ITestExecutionService { debugTransport: this.$options.debugTransport, debugBrk: this.$options.debugBrk, watch: !!this.$options.watch, - bundle: !!this.$options.bundle, + bundle: true, appDirectoryRelativePath: projectData.getAppDirectoryRelativePath() } }, @@ -152,7 +152,7 @@ export class TestExecutionService implements ITestExecutionService { } karmaConfig.projectDir = projectData.projectDir; - karmaConfig.bundle = this.$options.bundle; + karmaConfig.bundle = true; karmaConfig.platform = platform.toLowerCase(); this.$logger.debug(JSON.stringify(karmaConfig, null, 4)); diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 2707192244..794cd32666 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -87,7 +87,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp }); } - public stopWebpackCompile(platform: string) { + public stopWebpackCompiler(platform: string) { if (platform) { this.stopWebpackForPlatform(platform); } else { diff --git a/test/controllers/main-controller.ts b/test/controllers/main-controller.ts index 1c540f9845..def4d9e6b2 100644 --- a/test/controllers/main-controller.ts +++ b/test/controllers/main-controller.ts @@ -7,6 +7,9 @@ import { RunOnDevicesEmitter } from "../../lib/run-on-devices-emitter"; import { WorkflowDataService } from "../../lib/services/workflow/workflow-data-service"; import { RunOnDevicesDataService } from "../../lib/services/run-on-devices-data-service"; import { PlatformWatcherService } from "../../lib/services/platform/platform-watcher-service"; +import { LiveSyncServiceResolver } from "../../lib/resolvers/livesync-service-resolver"; +import { MobileHelper } from "../../lib/common/mobile/mobile-helper"; +import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants"; const deviceMap: IDictionary = { myiOSDevice: { @@ -35,13 +38,13 @@ function createTestInjector(): IInjector { .map(device => device.deviceInfo.platform) .uniq() .value(); - } + }, + getDevicesForPlatform: (platform: string) => [] })); - injector.register("deviceWorkflowService", ({})); + injector.register("devicePlatformsConstants", DevicePlatformsConstants); injector.register("errors", ({ failWithoutHelp: () => ({}) })); - injector.register("liveSyncService", ({})); injector.register("logger", ({ trace: () => ({}) })); @@ -53,19 +56,23 @@ function createTestInjector(): IInjector { injector.register("platformWatcherService", ({ on: () => ({}), emit: () => ({}), - startWatchers: () => ({}) + startWatchers: () => ({}), + stopWatchers: () => ({}) })); injector.register("mainController", MainController); - injector.register("pluginsService", ({})); + injector.register("pluginsService", { + ensureAllDependenciesAreInstalled: () => ({}) + }); injector.register("projectDataService", ({ getProjectData: () => ({ projectDir }) })); + injector.register("addPlatformService", { + addPlatformIfNeeded: () => ({}) + }); injector.register("buildArtefactsService", ({})); - injector.register("addPlatformService", {}); injector.register("buildPlatformService", ({})); - injector.register("preparePlatformService", ({})); injector.register("deviceInstallAppService", {}); injector.register("deviceRefreshAppService", {}); injector.register("deviceDebugAppService", {}); @@ -73,9 +80,15 @@ function createTestInjector(): IInjector { injector.register("hooksService", { executeAfterHooks: () => ({}) }); + injector.register("hmrStatusService", {}); + injector.register("liveSyncServiceResolver", LiveSyncServiceResolver); + injector.register("mobileHelper", MobileHelper); + injector.register("preparePlatformService", { + preparePlatform: () => ({}) + }); injector.register("projectChangesService", ({})); injector.register("runOnDevicesController", { - on: () => ({}) + syncInitialDataOnDevices: () => ({}) }); injector.register("runOnDevicesDataService", RunOnDevicesDataService); injector.register("runOnDevicesEmitter", RunOnDevicesEmitter); @@ -99,7 +112,7 @@ const liveSyncInfo = { describe("MainController", () => { describe("runOnDevices", () => { - describe("when the run on device is called for second time for the same projectDir", () => { + describe("when runOnDevices() is called for second time for the same projectDir", () => { it("should run only for new devies (for which the initial sync is still not executed)", async () => { return; }); @@ -107,6 +120,30 @@ describe("MainController", () => { return; }); }); + describe("no watch", () => { + it("shouldn't start the watcher when skipWatcher flag is provided", async () => { + const injector = createTestInjector(); + let isStartWatchersCalled = false; + const platformWatcherService = injector.resolve("platformWatcherService"); + platformWatcherService.startWatchers = async () => isStartWatchersCalled = true; + + const mainController: MainController = injector.resolve("mainController"); + await mainController.runOnDevices(projectDir, [iOSDeviceDescriptor], { ...liveSyncInfo, skipWatcher: true }); + + assert.isFalse(isStartWatchersCalled); + }); + it("shouldn't start the watcher when no devices to sync", async () => { + const injector = createTestInjector(); + let isStartWatchersCalled = false; + const platformWatcherService = injector.resolve("platformWatcherService"); + platformWatcherService.startWatchers = async () => isStartWatchersCalled = true; + + const mainController: MainController = injector.resolve("mainController"); + await mainController.runOnDevices(projectDir, [], liveSyncInfo ); + + assert.isFalse(isStartWatchersCalled); + }); + }); describe("when platform is still not added", () => { it("should add platform before start watchers", async () => { const injector = createTestInjector(); @@ -163,7 +200,7 @@ describe("MainController", () => { }); }); }); - describe("on initialSyncEventData", () => { + describe("on initialSyncEvent", () => { let injector: IInjector; let isBuildPlatformCalled = false; beforeEach(() => { @@ -174,26 +211,14 @@ describe("MainController", () => { const buildPlatformService = injector.resolve("buildPlatformService"); buildPlatformService.buildPlatform = async () => { isBuildPlatformCalled = true; return buildOutputPath; }; - }); - console.log("============== isBuildPlatformCalled ============= ", isBuildPlatformCalled); + console.log("========== isBuildPlatformCalled ============= ", isBuildPlatformCalled); + }); afterEach(() => { isBuildPlatformCalled = false; }); - // _.each(["ios", "android"], platform => { - // it(`should build for ${platform} platform if there are native changes`, async () => { - // const platformWatcherService: IPlatformWatcherService = injector.resolve("platformWatcherService"); - // platformWatcherService.emit(INITIAL_SYNC_EVENT_NAME, { platform, hasNativeChanges: true }); - - // const mainController: MainController = injector.resolve("mainController"); - // await mainController.start(projectDir, [iOSDeviceDescriptor], liveSyncInfo); - - // assert.isTrue(isBuildPlatformCalled); - // }); - // }); - it("shouldn't build for second android device", async () => { // shouldn't build for second iOS device or second iOS simulator return; }); @@ -210,16 +235,8 @@ describe("MainController", () => { return; }); }); - describe("on filesChangeEventData", () => { - // TODO: add test cases heres - }); - describe("no watch", () => { - it("shouldn't start the watcher when skipWatcher flag is provided", () => { - return; - }); - it("shouldn't start the watcher when no devices to sync", () => { - return; - }); + describe("on filesChangeEvent", () => { + // TODO: add test cases here }); }); describe("stopRunOnDevices", () => { @@ -260,7 +277,7 @@ describe("MainController", () => { const mainController = testInjector.resolve("mainController"); const runOnDevicesDataService: RunOnDevicesDataService = testInjector.resolve("runOnDevicesDataService"); - runOnDevicesDataService.persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier }))); + runOnDevicesDataService.persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier })), ["ios"]); const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; diff --git a/test/controllers/run-on-devices-controller.ts b/test/controllers/run-on-devices-controller.ts new file mode 100644 index 0000000000..875277063f --- /dev/null +++ b/test/controllers/run-on-devices-controller.ts @@ -0,0 +1,144 @@ +import { RunOnDevicesController } from "../../lib/controllers/run-on-devices-controller"; +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 { RunOnDevicesDataService } from "../../lib/services/run-on-devices-data-service"; +import { RunOnDevicesEmitter } from "../../lib/run-on-devices-emitter"; +import { WorkflowDataService } from "../../lib/services/workflow/workflow-data-service"; + +let isBuildPlatformCalled = false; +const appIdentifier = "org.nativescript.myCoolApp"; + +function getFullSyncResult(): ILiveSyncResultInfo { + return { + modifiedFilesData: [], + isFullSync: true, + deviceAppData: { + appIdentifier + } + }; +} + +function mockDevicesService(injector: IInjector, devices: Mobile.IDevice[]) { + const devicesService: Mobile.IDevicesService = injector.resolve("devicesService"); + devicesService.execute = async (action: (device: Mobile.IDevice) => Promise, canExecute?: (dev: Mobile.IDevice) => boolean, options?: { allowNoDevices?: boolean }) => { + for (const d of devices) { + if (canExecute(d)) { + await action(d); + } + } + + return null; + }; +} + +function createTestInjector() { + const injector = new InjectorStub(); + + injector.register("addPlatformService", {}); + injector.register("buildArtefactsService", ({})); + injector.register("buildPlatformService", { + buildPlatform: async () => { + isBuildPlatformCalled = true; + return buildOutputPath; + }, + buildPlatformIfNeeded: async () => ({}) + }); + injector.register("deviceInstallAppService", { + installOnDeviceIfNeeded: () => ({}) + }); + injector.register("deviceRefreshAppService", { + refreshApplication: () => ({}) + }); + injector.register("deviceDebugAppService", { + enableDebugging: () => ({}) + }); + injector.register("iOSLiveSyncService", { + fullSync: async () => getFullSyncResult(), + liveSyncWatchAction: () => ({}) + }); + injector.register("androidLiveSyncService", { + fullSync: async () => getFullSyncResult(), + liveSyncWatchAction: () => ({}) + }); + injector.register("hmrStatusService", {}); + injector.register("liveSyncServiceResolver", LiveSyncServiceResolver); + injector.register("mobileHelper", MobileHelper); + injector.register("preparePlatformService", ({})); + injector.register("projectChangesService", ({})); + injector.register("runOnDevicesController", RunOnDevicesController); + injector.register("runOnDevicesDataService", RunOnDevicesDataService); + injector.register("runOnDevicesEmitter", RunOnDevicesEmitter); + injector.register("workflowDataService", WorkflowDataService); + + return injector; +} + +const projectDir = "path/to/my/projectDir"; +const projectData = { projectDir, projectIdentifiers: { ios: appIdentifier, android: appIdentifier }}; +const buildOutputPath = `${projectDir}/platform/ios/build/myproject.app`; + +const iOSDevice = { deviceInfo: { identifier: "myiOSDevice", platform: "ios" } }; +const iOSDeviceDescriptor = { identifier: "myiOSDevice", buildAction: async () => buildOutputPath }; +const androidDevice = { deviceInfo: { identifier: "myAndroidDevice", platform: "android" } }; +const androidDeviceDescriptor = { identifier: "myAndroidDevice", buildAction: async () => buildOutputPath }; + +const map: IDictionary<{device: Mobile.IDevice, descriptor: ILiveSyncDeviceInfo}> = { + ios: { + device: iOSDevice, + descriptor: iOSDeviceDescriptor + }, + android: { + device: androidDevice, + descriptor: androidDeviceDescriptor + } +}; + +const liveSyncInfo = { + projectDir, + release: false, + useHotModuleReload: false, + env: {} +}; + +describe("RunOnDevicesController", () => { + let injector: IInjector = null; + let runOnDevicesController: RunOnDevicesController = null; + let runOnDevicesDataService: RunOnDevicesDataService = null; + + beforeEach(() => { + injector = createTestInjector(); + runOnDevicesController = injector.resolve("runOnDevicesController"); + runOnDevicesDataService = injector.resolve("runOnDevicesDataService"); + }); + + describe("syncInitialDataOnDevices", () => { + afterEach(() => { + isBuildPlatformCalled = false; + }); + + _.each(["ios", "android"], platform => { + it(`should build for ${platform} platform when there are native changes`, async () => { + const initialSyncEventData = { platform, hasNativeChanges: true }; + const deviceDescriptors = [map[platform].descriptor]; + runOnDevicesDataService.persistData(projectDir, deviceDescriptors, [platform]); + mockDevicesService(injector, [map[platform].device]); + + await runOnDevicesController.syncInitialDataOnDevices(initialSyncEventData, projectData, liveSyncInfo, deviceDescriptors); + + assert.isTrue(isBuildPlatformCalled); + }); + it(`shouldn't build for ${platform} platform when no native changes`, async () => { + const initialSyncEventData = { platform, hasNativeChanges: false }; + const deviceDescriptors = [map[platform].descriptor]; + runOnDevicesDataService.persistData(projectDir, deviceDescriptors, [platform]); + mockDevicesService(injector, [map[platform].device]); + + await runOnDevicesController.syncInitialDataOnDevices(initialSyncEventData, projectData, liveSyncInfo, deviceDescriptors); + + assert.isFalse(isBuildPlatformCalled); + }); + }); + }); +}); diff --git a/test/services/platform/platform-watcher-service.ts b/test/services/platform/platform-watcher-service.ts index ff43494abd..0c4566f37e 100644 --- a/test/services/platform/platform-watcher-service.ts +++ b/test/services/platform/platform-watcher-service.ts @@ -1,6 +1,7 @@ import { Yok } from "../../../lib/common/yok"; import { PlatformWatcherService } from "../../../lib/services/platform/platform-watcher-service"; import { assert } from "chai"; +import { INITIAL_SYNC_EVENT_NAME } from "../../../lib/constants"; const projectData = { projectDir: "myProjectDir", getAppResourcesRelativeDirectoryPath: () => "/my/app_resources/dir/path" }; const preparePlatformData = { }; @@ -49,7 +50,7 @@ describe("PlatformWatcherService", () => { emittedEventData = []; }); describe("startWatcher", () => { - describe("initialSyncEventData event", () => { + describe("initialSyncEvent", () => { _.each(["iOS", "Android"], platform => { _.each([true, false], hasNativeChanges => { it(`should emit after native prepare and webpack's compilation are done for ${platform} platform and hasNativeChanges is ${hasNativeChanges}`, async () => { @@ -61,13 +62,12 @@ describe("PlatformWatcherService", () => { assert.lengthOf(emittedEventNames, 1); assert.lengthOf(emittedEventData, 1); - assert.deepEqual(emittedEventNames[0], "initialSyncEventData"); + assert.deepEqual(emittedEventNames[0], INITIAL_SYNC_EVENT_NAME); assert.deepEqual(emittedEventData[0], { platform: platform.toLowerCase(), hasNativeChanges }); }); }); }); - // TODO: Consider to write similar test for JS part if appropriate _.each(["iOS", "Android"], platform => { it(`should respect native changes that are made before the initial preparation of the project had been done for ${platform}`, async () => { const injector = createTestInjector({ hasNativeChanges: false }); @@ -87,14 +87,14 @@ describe("PlatformWatcherService", () => { assert.lengthOf(emittedEventNames, 1); assert.lengthOf(emittedEventData, 1); - assert.deepEqual(emittedEventNames[0], "initialSyncEventData"); + assert.deepEqual(emittedEventNames[0], INITIAL_SYNC_EVENT_NAME); assert.deepEqual(emittedEventData[0], { platform: platform.toLowerCase(), hasNativeChanges: true }); }); }); }); describe("filesChangeEventData event", () => { _.each(["iOS", "Android"], platform => { - it(`shouldn't emit filesChangeEventData before initialSyncEventData if js code is changed before the initial preparation of project has been done for ${platform}`, async () => { + it(`shouldn't emit filesChangeEventData before initialSyncEvent if js code is changed before the initial preparation of project has been done for ${platform}`, async () => { const injector = createTestInjector({ hasNativeChanges: false }); const hasNativeChanges = false; @@ -112,10 +112,8 @@ describe("PlatformWatcherService", () => { assert.lengthOf(emittedEventNames, 1); assert.lengthOf(emittedEventData, 1); - assert.deepEqual(emittedEventNames[0], "initialSyncEventData"); + assert.deepEqual(emittedEventNames[0], INITIAL_SYNC_EVENT_NAME); assert.deepEqual(emittedEventData[0], { platform: platform.toLowerCase(), hasNativeChanges }); - - // TODO: assert /some/file/path is emitted }); }); }); diff --git a/test/services/playground/preview-app-files-service.ts b/test/services/playground/preview-app-files-service.ts index c58dd13702..5ed2e97593 100644 --- a/test/services/playground/preview-app-files-service.ts +++ b/test/services/playground/preview-app-files-service.ts @@ -48,12 +48,11 @@ function createTestInjector(data?: { files: string[] }) { } function getExpectedResult(data: IPreviewAppLiveSyncData, injector: IInjector, expectedFiles: string[], platform: string): FilesPayload { - const projectData = injector.resolve("projectDataService").getProjectData(); const platformData = injector.resolve("platformsData").getPlatformData(platform); const files = _.map(expectedFiles, expectedFile => { return { event: 'change', - file: data.bundle ? path.relative(path.join(platformData.appDestinationDirectoryPath, "app"), expectedFile) : path.relative(projectData.appDirectoryPath(), expectedFile), + file: path.relative(path.join(platformData.appDestinationDirectoryPath, "app"), expectedFile), binary: false, fileContents: undefined }; diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index 9fda21ce37..74e7441c35 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -241,7 +241,7 @@ function mapFiles(files: string[]): FilePayload[] { return files.map(file => { return { event: "change", - file: path.join("..", "platforms", "app", file), + file, fileContents: undefined, binary: false }; @@ -371,7 +371,7 @@ describe("previewAppLiveSyncService", () => { testCases: noAppFilesTestCases }, { - name: "should pass the hmr option to the hook", + name: "should pass the hmr option to the env", testCases: hmrTestCases } ]; diff --git a/test/services/playground/preview-app-plugins-service.ts b/test/services/playground/preview-app-plugins-service.ts index 5e239819cc..75c52f4bf6 100644 --- a/test/services/playground/preview-app-plugins-service.ts +++ b/test/services/playground/preview-app-plugins-service.ts @@ -86,144 +86,6 @@ function setup(localPlugins: IStringDictionary, previewAppPlugins: IStringDictio } describe("previewAppPluginsService", () => { - describe("comparePluginsOnDevice without bundle", () => { - const testCases = [ - { - name: "should show warning for plugin not included in preview app", - localPlugins: { - "nativescript-facebook": "2.2.3", - "nativescript-theme-core": "~1.0.4", - "tns-core-modules": "~4.2.0" - }, - previewAppPlugins: { - "nativescript-theme-core": "~1.0.4", - "tns-core-modules": "~4.2.0" - }, - expectedWarnings: [ - util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "nativescript-facebook", deviceId) - ] - }, - { - name: "should show warnings for plugins not included in preview app", - localPlugins: { - "nativescript-facebook": "2.2.3", - "nativescript-theme-core": "~1.0.4", - "tns-core-modules": "~4.2.0" - }, - previewAppPlugins: { - }, - expectedWarnings: [ - util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "nativescript-facebook", deviceId), - util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "nativescript-theme-core", deviceId), - util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "tns-core-modules", deviceId) - ] - }, - { - name: "should not show warnings when all plugins are included in preview app", - localPlugins: { - "nativescript-theme-core": "1.0.4", - "nativescript-facebook": "2.2.3" - }, - previewAppPlugins: { - "nativescript-theme-core": "1.1.4", - "nativescript-facebook": "2.2.3" - }, - expectedWarnings: [] - }, - { - name: "should show warning when local plugin has lower major version", - localPlugins: { - "nativescript-theme-core": "2.0.0" - }, - previewAppPlugins: { - "nativescript-theme-core": "3.4.0" - }, - expectedWarnings: [ - util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, "nativescript-theme-core", "2.0.0", "3.4.0") - ] - }, - { - name: "should show warning when local plugin has greater major version", - localPlugins: { - "nativescript-theme-core": "4.0.0" - }, - previewAppPlugins: { - "nativescript-theme-core": "3.0.0" - }, - expectedWarnings: [ - util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, "nativescript-theme-core", "4.0.0", "3.0.0") - ] - }, - { - name: "should show warning when local plugin has greater minor version and the same major version", - localPlugins: { - "nativescript-theme-core": "3.5.0" - }, - previewAppPlugins: { - "nativescript-theme-core": "3.0.0" - }, - expectedWarnings: [ - util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_GREATHER_MINOR_VERSION, "nativescript-theme-core", "3.5.0", "3.0.0") - ] - }, - { - name: "should not show warning when local plugin has lower minor version and the same major version", - localPlugins: { - "nativescript-theme-core": "3.1.0" - }, - previewAppPlugins: { - "nativescript-theme-core": "3.2.0" - }, - expectedWarnings: [] - }, - { - name: "should not show warning when plugins differ only in patch versions (lower local patch version)", - localPlugins: { - "nativescript-theme-core": "3.5.0" - }, - previewAppPlugins: { - "nativescript-theme-core": "3.5.1" - }, - expectedWarnings: [] - }, - { - name: "should not show warning when plugins differ only in patch versions (greater local patch version)", - localPlugins: { - "nativescript-theme-core": "3.5.1" - }, - previewAppPlugins: { - "nativescript-theme-core": "3.5.0" - }, - expectedWarnings: [] - }, - { - name: "should not show warning when the local plugin version is tag", - localPlugins: { - "tns-core-modules": "rc" - }, - previewAppPlugins: { - "tns-core-modules": "5.0.0" - }, - expectedWarnings: [] - } - ]; - - afterEach(() => { - warnParams = []; - readJsonParams = []; - }); - - for (const testCase of testCases) { - it(`${testCase.name}`, async () => { - const { previewAppPluginsService, device } = setup(testCase.localPlugins, testCase.previewAppPlugins); - - await previewAppPluginsService.comparePluginsOnDevice(createPreviewLiveSyncData({ bundle: false }), device); - - assert.equal(warnParams.length, testCase.expectedWarnings.length); - testCase.expectedWarnings.forEach(warning => assert.include(warnParams, warning)); - }); - } - }); describe("comparePluginsOnDevice with bundle", () => { const testCases = [ { From 6769a810f86f418f3ccf7dd0d7646ae59159f9fd Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 8 May 2019 08:40:14 +0300 Subject: [PATCH 25/30] chore: fix lint errors --- lib/commands/appstore-upload.ts | 2 +- .../webpack/webpack-compiler-service.ts | 2 +- .../workflow/workflow-data-service.ts | 98 +++++++------------ test/controllers/run-on-devices-controller.ts | 53 +++++----- .../platform/build-platform-service.ts | 3 - .../platform/platform-watcher-service.ts | 2 +- 6 files changed, 66 insertions(+), 94 deletions(-) delete mode 100644 test/services/platform/build-platform-service.ts diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 7d119763d3..2c58df2f62 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -63,7 +63,7 @@ export class PublishIOS implements ICommand { ipaFilePath = await this.$buildPlatformService.buildPlatform(nativePlatformData, this.$projectData, buildPlatformData); } else { this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); - ipaFilePath = await this.$mainController.buildPlatform(platform, this.$projectData.projectDir, { ...this.$options, buildForAppStore: true }) + ipaFilePath = await this.$mainController.buildPlatform(platform, this.$projectData.projectDir, { ...this.$options, buildForAppStore: true }); this.$logger.info(`Export at: ${ipaFilePath}`); } } diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 794cd32666..2d0fbf39e6 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -157,7 +157,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp envValue = [envValue]; } - envValue.map((value: any) => args.push(`--env.${item}=${value}`)) + envValue.map((value: any) => args.push(`--env.${item}=${value}`)); } }); diff --git a/lib/services/workflow/workflow-data-service.ts b/lib/services/workflow/workflow-data-service.ts index e624e49d95..9135e3ee6b 100644 --- a/lib/services/workflow/workflow-data-service.ts +++ b/lib/services/workflow/workflow-data-service.ts @@ -2,6 +2,43 @@ export type AddPlatformData = Pick & Partial & Pick; export type IOSPrepareData = PreparePlatformData & Pick & Pick; +export class BuildPlatformDataBase { + constructor(protected options: IOptions | any) { } + + public release = this.options.release; + public clean = this.options.clean; + public device = this.options.device; + public iCloudContainerEnvironment = this.options.iCloudContainerEnvironment; + public buildForDevice = this.options.forDevice; + public buildOutputStdio = this.options.buildOutputStdio || "inherit"; +} + +export class IOSBuildData extends BuildPlatformDataBase { + constructor(options: IOptions) { super(options); } + + public teamId = this.options.teamId; + public provision = this.options.provision; + public buildForAppStore = this.options.buildForAppStore; +} + +export class AndroidBuildData extends BuildPlatformDataBase { + constructor(options: IOptions) { super(options); } + + public keyStoreAlias = this.options.keyStoreAlias; + public keyStorePath = this.options.keyStorePath; + public keyStoreAliasPassword = this.options.keyStoreAliasPassword; + public keyStorePassword = this.options.keyStorePassword; + public androidBundle = this.options.aab; +} + +export class DeployPlatformData { + constructor(private options: IOptions) { } + + public clean = this.options.clean; + public release = this.options.release; + public forceInstall = true; +} + export class WorkflowDataService { constructor( private $injector: IInjector, @@ -85,64 +122,3 @@ export class WorkflowData { public liveSyncData: any; public restartOnDeviceData: any; } - -// export class AddPlatformData { -// constructor(private platform: string, private options: IOptions | any) { } - -// public platformParam = this.options.platformParam || this.platform; -// public frameworkPath = this.options.frameworkPath; -// public nativePrepare = this.options.nativePrepare; -// } - -// export class PreparePlatformData { -// constructor(protected options: IOptions | any) { } - -// public env = this.options.env; -// public release = this.options.release; -// public nativePrepare = this.options.nativePrepare; -// } - -// export class IOSPrepareData extends PreparePlatformData { -// constructor(options: IOptions | any) { super(options); } - -// public teamId = this.options.teamId; -// public provision = this.options.provision; -// public mobileProvisionData = this.options.mobileProvisionData; -// } - -export class BuildPlatformDataBase { - constructor(protected options: IOptions | any) { } - - public release = this.options.release; - public clean = this.options.clean; - public device = this.options.device; - public iCloudContainerEnvironment = this.options.iCloudContainerEnvironment; - public buildForDevice = this.options.forDevice; - public buildOutputStdio = this.options.buildOutputStdio || "inherit"; -} - -export class IOSBuildData extends BuildPlatformDataBase { - constructor(options: IOptions) { super(options); } - - public teamId = this.options.teamId; - public provision = this.options.provision; - public buildForAppStore = this.options.buildForAppStore; -} - -export class AndroidBuildData extends BuildPlatformDataBase { - constructor(options: IOptions) { super(options); } - - public keyStoreAlias = this.options.keyStoreAlias; - public keyStorePath = this.options.keyStorePath; - public keyStoreAliasPassword = this.options.keyStoreAliasPassword; - public keyStorePassword = this.options.keyStorePassword; - public androidBundle = this.options.aab; -} - -export class DeployPlatformData { - constructor(private options: IOptions) { } - - public clean = this.options.clean; - public release = this.options.release; - public forceInstall = true; -} diff --git a/test/controllers/run-on-devices-controller.ts b/test/controllers/run-on-devices-controller.ts index 875277063f..caba4b608c 100644 --- a/test/controllers/run-on-devices-controller.ts +++ b/test/controllers/run-on-devices-controller.ts @@ -9,6 +9,32 @@ import { WorkflowDataService } from "../../lib/services/workflow/workflow-data-s let isBuildPlatformCalled = false; const appIdentifier = "org.nativescript.myCoolApp"; +const projectDir = "path/to/my/projectDir"; +const projectData = { projectDir, projectIdentifiers: { ios: appIdentifier, android: appIdentifier }}; +const buildOutputPath = `${projectDir}/platform/ios/build/myproject.app`; + +const iOSDevice = { deviceInfo: { identifier: "myiOSDevice", platform: "ios" } }; +const iOSDeviceDescriptor = { identifier: "myiOSDevice", buildAction: async () => buildOutputPath }; +const androidDevice = { deviceInfo: { identifier: "myAndroidDevice", platform: "android" } }; +const androidDeviceDescriptor = { identifier: "myAndroidDevice", buildAction: async () => buildOutputPath }; + +const map: IDictionary<{device: Mobile.IDevice, descriptor: ILiveSyncDeviceInfo}> = { + ios: { + device: iOSDevice, + descriptor: iOSDeviceDescriptor + }, + android: { + device: androidDevice, + descriptor: androidDeviceDescriptor + } +}; + +const liveSyncInfo = { + projectDir, + release: false, + useHotModuleReload: false, + env: {} +}; function getFullSyncResult(): ILiveSyncResultInfo { return { @@ -75,33 +101,6 @@ function createTestInjector() { return injector; } -const projectDir = "path/to/my/projectDir"; -const projectData = { projectDir, projectIdentifiers: { ios: appIdentifier, android: appIdentifier }}; -const buildOutputPath = `${projectDir}/platform/ios/build/myproject.app`; - -const iOSDevice = { deviceInfo: { identifier: "myiOSDevice", platform: "ios" } }; -const iOSDeviceDescriptor = { identifier: "myiOSDevice", buildAction: async () => buildOutputPath }; -const androidDevice = { deviceInfo: { identifier: "myAndroidDevice", platform: "android" } }; -const androidDeviceDescriptor = { identifier: "myAndroidDevice", buildAction: async () => buildOutputPath }; - -const map: IDictionary<{device: Mobile.IDevice, descriptor: ILiveSyncDeviceInfo}> = { - ios: { - device: iOSDevice, - descriptor: iOSDeviceDescriptor - }, - android: { - device: androidDevice, - descriptor: androidDeviceDescriptor - } -}; - -const liveSyncInfo = { - projectDir, - release: false, - useHotModuleReload: false, - env: {} -}; - describe("RunOnDevicesController", () => { let injector: IInjector = null; let runOnDevicesController: RunOnDevicesController = null; diff --git a/test/services/platform/build-platform-service.ts b/test/services/platform/build-platform-service.ts deleted file mode 100644 index 5cbf856163..0000000000 --- a/test/services/platform/build-platform-service.ts +++ /dev/null @@ -1,3 +0,0 @@ -describe("buildPlatformService", () => { - -}); diff --git a/test/services/platform/platform-watcher-service.ts b/test/services/platform/platform-watcher-service.ts index 0c4566f37e..dccc734b66 100644 --- a/test/services/platform/platform-watcher-service.ts +++ b/test/services/platform/platform-watcher-service.ts @@ -76,7 +76,7 @@ describe("PlatformWatcherService", () => { const preparePlatformService = injector.resolve("preparePlatformService"); preparePlatformService.prepareNativePlatform = async () => { - const nativeFilesWatcher = (platformWatcherService).watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher; + const nativeFilesWatcher = (platformWatcherService).watchersData[projectData.projectDir][platform.toLowerCase()].nativeFilesWatcher; nativeFilesWatcher.emit("all", "change", "my/project/App_Resources/some/file"); isNativePrepareCalled = true; return false; From e6c7f98cf29ce4272528d8d08948b389d9f0c8d3 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 9 May 2019 11:15:39 +0300 Subject: [PATCH 26/30] chore: fix show the log needed from preview tests --- lib/services/webpack/webpack-compiler-service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 2d0fbf39e6..5ba1e14736 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -24,6 +24,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp childProcess.on("message", (message: any) => { if (message === "Webpack compilation complete.") { + this.$logger.info("Webpack build done!"); resolve(childProcess); } From 66cfacf59ae267c8140cb2ef3e2b1215e0a88d95 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 9 May 2019 13:45:33 +0300 Subject: [PATCH 27/30] fix: fix the initial sync to preview app --- .../playground/preview-app-livesync-service.ts | 7 +++++-- .../playground/preview-app-livesync-service.ts | 15 +++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index f17a2c37d1..5799a04d86 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -1,5 +1,5 @@ import { Device, FilesPayload } from "nativescript-preview-sdk"; -import { APP_RESOURCES_FOLDER_NAME, TrackActionNames, FILES_CHANGE_EVENT_NAME } from "../../../constants"; +import { APP_RESOURCES_FOLDER_NAME, TrackActionNames, FILES_CHANGE_EVENT_NAME, INITIAL_SYNC_EVENT_NAME } from "../../../constants"; import { PreviewAppLiveSyncEvents } from "./preview-app-constants"; import { HmrConstants } from "../../../common/constants"; import { stringify } from "../../../common/helpers"; @@ -47,12 +47,15 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA } await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); - this.deviceInitializationPromise[device.id] = this.getInitialFilesForPlatformSafe(data, device.platform); this.$platformWatcherService.on(FILES_CHANGE_EVENT_NAME, async (filesChangeData: IFilesChangeEventData) => { await this.onWebpackCompilationComplete(data, filesChangeData.hmrData, filesChangeData.files, device.platform); }); + this.$platformWatcherService.on(INITIAL_SYNC_EVENT_NAME, async (initialSyncData: IInitialSyncEventData) => { + this.deviceInitializationPromise[device.id] = this.getInitialFilesForPlatformSafe(data, device.platform); + }); + const { nativePlatformData, projectData, preparePlatformData } = this.$workflowDataService.createWorkflowData(device.platform.toLowerCase(), data.projectDir, data); // Setup externals diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index 74e7441c35..92cf8f77a2 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -9,6 +9,7 @@ import { ProjectFilesManager } from "../../../lib/common/services/project-files- import { EventEmitter } from "events"; import { PreviewAppFilesService } from "../../../lib/services/livesync/playground/preview-app-files-service"; import { WorkflowDataService, PreparePlatformData } from "../../../lib/services/workflow/workflow-data-service"; +import { INITIAL_SYNC_EVENT_NAME } from "../../../lib/constants"; interface ITestCase { name: string; @@ -96,6 +97,13 @@ class LoggerMock extends LoggerStub { } } +class PlatformWatcherServiceMock extends EventEmitter { + public startWatchers(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData) { + isHMRPassedToEnv = preparePlatformData.env.hmr; + this.emit(INITIAL_SYNC_EVENT_NAME, {}); + } +} + function createTestInjector(options?: { projectFiles?: string[] }) { @@ -157,12 +165,7 @@ function createTestInjector(options?: { injector.register("analyticsService", { trackEventActionInGoogleAnalytics: () => ({}) }); - injector.register("platformWatcherService", { - startWatchers: (platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData) => { - isHMRPassedToEnv = preparePlatformData.env.hmr; - }, - on: () => ({}) - }); + injector.register("platformWatcherService", PlatformWatcherServiceMock); injector.register("workflowDataService", WorkflowDataService); return injector; From 40d5b563048f57f8b409e1d99c2e9a8b358e7884 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 14 May 2019 17:38:34 +0300 Subject: [PATCH 28/30] feat: introduce controller for each command --- PublicAPI.md | 1 - lib/bootstrap.ts | 18 +- lib/commands/appstore-upload.ts | 18 +- lib/commands/build.ts | 25 +- lib/commands/deploy.ts | 2 +- lib/commands/prepare.ts | 15 +- lib/commands/preview.ts | 5 +- .../mobile/mobile-core/devices-service.ts | 2 +- lib/common/services/hooks-service.ts | 2 +- lib/common/services/livesync/sync-batch.ts | 57 -- lib/constants.ts | 2 + lib/controllers/add-platform-controller.ts | 80 ++ lib/controllers/build-controller.ts | 131 +++ .../debug-on-devices-controller.ts | 0 .../deploy-on-devices-controller.ts | 27 + lib/controllers/main-controller.ts | 203 ----- lib/controllers/prepare-controller.ts | 156 ++++ lib/controllers/preview-app-controller.ts | 123 +++ lib/controllers/run-on-devices-controller.ts | 173 +++- lib/data/add-platform-data.ts | 11 + lib/data/build-data.ts | 59 ++ lib/data/data-base.ts | 7 + lib/data/prepare-data.ts | 36 + lib/data/run-on-devices-data.ts | 3 + lib/declarations.d.ts | 4 - lib/definitions/ios.d.ts | 42 + lib/definitions/platform.d.ts | 19 +- lib/definitions/preview-app-livesync.d.ts | 3 +- lib/definitions/xcode.d.ts | 39 - lib/helpers/deploy-command-helper.ts | 16 +- lib/helpers/livesync-command-helper.ts | 16 +- lib/preview-app-emitter.ts | 14 + lib/services/android-project-service.ts | 13 +- lib/services/build-artefacts-service.ts | 25 +- lib/services/build-data-service.ts | 14 + lib/services/build-info-file-service.ts | 38 + .../device/device-install-app-service.ts | 35 +- lib/services/ios-project-service.ts | 47 +- lib/services/ios/ios-signing-service.ts | 7 +- .../preview-app-livesync-service.ts | 130 +-- lib/services/local-build-service.ts | 35 - lib/services/platform/add-platform-service.ts | 79 +- .../platform/build-platform-service.ts | 139 ---- .../platform/platform-commands-service.ts | 22 +- .../platform/platform-watcher-service.ts | 121 --- ....ts => prepare-native-platform-service.ts} | 37 +- lib/services/prepare-data-service.ts | 14 + lib/services/project-changes-service.ts | 22 +- lib/services/run-on-devices-data-service.ts | 5 +- lib/services/test-execution-service.ts | 10 +- .../webpack/webpack-compiler-service.ts | 30 +- lib/services/webpack/webpack.d.ts | 19 +- .../workflow/workflow-data-service.ts | 124 --- .../node-modules/node-modules-builder.ts | 2 + test/controllers/add-platform-controller.ts | 115 +++ test/controllers/main-controller.ts | 296 ------- test/controllers/prepare-controller.ts | 121 +++ test/controllers/run-on-devices-controller.ts | 197 ++++- test/nativescript-cli-lib.ts | 2 +- test/platform-commands.ts | 3 +- test/platform-service.ts | 756 ------------------ test/plugins-service.ts | 2 - test/services/android-plugin-build-service.ts | 5 - test/services/ios-device-debug-service.ts | 1 - .../services/platform/add-platform-service.ts | 42 +- .../platform/platform-commands-service.ts | 4 + .../platform/platform-watcher-service.ts | 121 --- .../preview-app-livesync-service.ts | 92 +-- test/services/test-execution-service.ts | 2 +- test/stubs.ts | 35 +- test/tns-appstore-upload.ts | 32 +- 71 files changed, 1651 insertions(+), 2452 deletions(-) delete mode 100644 lib/common/services/livesync/sync-batch.ts create mode 100644 lib/controllers/add-platform-controller.ts create mode 100644 lib/controllers/build-controller.ts delete mode 100644 lib/controllers/debug-on-devices-controller.ts create mode 100644 lib/controllers/deploy-on-devices-controller.ts delete mode 100644 lib/controllers/main-controller.ts create mode 100644 lib/controllers/prepare-controller.ts create mode 100644 lib/controllers/preview-app-controller.ts create mode 100644 lib/data/add-platform-data.ts create mode 100644 lib/data/build-data.ts create mode 100644 lib/data/data-base.ts create mode 100644 lib/data/prepare-data.ts create mode 100644 lib/data/run-on-devices-data.ts create mode 100644 lib/definitions/ios.d.ts create mode 100644 lib/preview-app-emitter.ts create mode 100644 lib/services/build-data-service.ts create mode 100644 lib/services/build-info-file-service.ts delete mode 100644 lib/services/local-build-service.ts delete mode 100644 lib/services/platform/build-platform-service.ts delete mode 100644 lib/services/platform/platform-watcher-service.ts rename lib/services/platform/{prepare-platform-service.ts => prepare-native-platform-service.ts} (80%) create mode 100644 lib/services/prepare-data-service.ts delete mode 100644 lib/services/workflow/workflow-data-service.ts create mode 100644 test/controllers/add-platform-controller.ts delete mode 100644 test/controllers/main-controller.ts create mode 100644 test/controllers/prepare-controller.ts delete mode 100644 test/platform-service.ts delete mode 100644 test/services/platform/platform-watcher-service.ts diff --git a/PublicAPI.md b/PublicAPI.md index db09729335..066640eb0d 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -1,4 +1,3 @@ - Public API == diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 00f47431a8..4841ff1fdf 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -33,13 +33,11 @@ $injector.require("projectNameService", "./services/project-name-service"); $injector.require("tnsModulesService", "./services/tns-modules-service"); $injector.require("platformsData", "./platforms-data"); -$injector.require("platformService", "./services/platform-service"); $injector.require("addPlatformService", "./services/platform/add-platform-service"); -$injector.require("buildPlatformService", "./services/platform/build-platform-service"); -$injector.require("preparePlatformService", "./services/platform/prepare-platform-service"); +$injector.require("buildInfoFileService", "./services/build-info-file-service"); +$injector.require("prepareNativePlatformService", "./services/platform/prepare-native-platform-service"); $injector.require("platformValidationService", "./services/platform/platform-validation-service"); $injector.require("platformCommandsService", "./services/platform/platform-commands-service"); -$injector.require("platformWatcherService", "./services/platform/platform-watcher-service"); $injector.require("buildArtefactsService", "./services/build-artefacts-service"); @@ -47,12 +45,20 @@ $injector.require("deviceDebugAppService", "./services/device/device-debug-app-s $injector.require("deviceInstallAppService", "./services/device/device-install-app-service"); $injector.require("deviceRefreshAppService", "./services/device/device-refresh-app-service"); -$injector.require("workflowDataService", "./services/workflow/workflow-data-service"); $injector.require("runOnDevicesDataService", "./services/run-on-devices-data-service"); + $injector.require("runOnDevicesEmitter", "./run-on-devices-emitter"); +$injector.require("previewAppEmitter", "./preview-app-emitter"); -$injector.require("mainController", "./controllers/main-controller"); +$injector.require("addPlatformController", "./controllers/add-platform-controller"); +$injector.require("prepareController", "./controllers/prepare-controller"); +$injector.require("buildController", "./controllers/build-controller"); +$injector.require("deployOnDevicesController", "./controllers/deploy-on-devices-controller"); $injector.require("runOnDevicesController", "./controllers/run-on-devices-controller"); +$injector.require("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"); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 2c58df2f62..503b66abd0 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -1,8 +1,7 @@ import * as path from "path"; import { StringCommandParameter } from "../common/command-params"; -import { BuildPlatformService } from "../services/platform/build-platform-service"; -import { WorkflowDataService } from "../services/workflow/workflow-data-service"; -import { MainController } from "../controllers/main-controller"; +import { BuildController } from "../controllers/build-controller"; +import { IOSBuildData } from "../data/build-data"; export class PublishIOS implements ICommand { public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector), @@ -16,10 +15,8 @@ export class PublishIOS implements ICommand { private $options: IOptions, private $prompter: IPrompter, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $mainController: MainController, - private $platformValidationService: IPlatformValidationService, - private $buildPlatformService: BuildPlatformService, - private $workflowDataService: WorkflowDataService + private $buildController: BuildController, + private $platformValidationService: IPlatformValidationService ) { this.$projectData.initializeProjectData(); } @@ -59,11 +56,12 @@ export class PublishIOS implements ICommand { // As we need to build the package for device this.$options.forDevice = true; - const { nativePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, this.$projectData.projectDir, this.$options); - ipaFilePath = await this.$buildPlatformService.buildPlatform(nativePlatformData, this.$projectData, buildPlatformData); + const buildData = new IOSBuildData(this.$projectData.projectDir, platform, this.$options); + ipaFilePath = await this.$buildController.prepareAndBuildPlatform(buildData); } else { this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); - ipaFilePath = await this.$mainController.buildPlatform(platform, this.$projectData.projectDir, { ...this.$options, buildForAppStore: true }); + const buildData = new IOSBuildData(this.$projectData.projectDir, platform, { ...this.$options, buildForAppStore: true }); + ipaFilePath = await this.$buildController.prepareAndBuildPlatform(buildData); this.$logger.info(`Export at: ${ipaFilePath}`); } } diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 8c93b29e3c..a90d2d192d 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -1,6 +1,7 @@ import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE, AndroidAppBundleMessages } from "../constants"; import { ValidatePlatformCommandBase } from "./command-base"; -import { MainController } from "../controllers/main-controller"; +import { BuildController } from "../controllers/build-controller"; +import { BuildDataService } from "../services/build-data-service"; export abstract class BuildCommandBase extends ValidatePlatformCommandBase { constructor($options: IOptions, @@ -8,17 +9,23 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { $projectData: IProjectData, $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - protected $mainController: MainController, + protected $buildController: BuildController, $platformValidationService: IPlatformValidationService, private $bundleValidatorHelper: IBundleValidatorHelper, + private $buildDataService: BuildDataService, protected $logger: ILogger) { super($options, $platformsData, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } + public dashedOptions = { + watch: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, + }; + public async executeCore(args: string[]): Promise { const platform = args[0].toLowerCase(); - const outputPath = await this.$mainController.buildPlatform(platform, this.$projectData.projectDir, this.$options); + const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, platform, this.$options); + const outputPath = await this.$buildController.prepareAndBuildPlatform(buildData); return outputPath; } @@ -57,11 +64,12 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $mainController: MainController, + $buildController: BuildController, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, - $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $mainController, $platformValidationService, $bundleValidatorHelper, $logger); + $logger: ILogger, + $buildDataService: BuildDataService) { + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); } public async execute(args: string[]): Promise { @@ -92,12 +100,13 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $mainController: MainController, + $buildController: BuildController, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, + $buildDataService: BuildDataService, protected $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $mainController, $platformValidationService, $bundleValidatorHelper, $logger); + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); } public async execute(args: string[]): Promise { diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index 535554e0c1..66a672007c 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -21,7 +21,7 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement public async execute(args: string[]): Promise { const platform = args[0].toLowerCase(); - await this.$deployCommandHelper.deploy(platform, { release: true }); + await this.$deployCommandHelper.deploy(platform); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index b4f6adcef8..966a42f9ef 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -1,15 +1,21 @@ import { ValidatePlatformCommandBase } from "./command-base"; -import { MainController } from "../controllers/main-controller"; +import { PrepareController } from "../controllers/prepare-controller"; +import { PrepareDataService } from "../services/prepare-data-service"; export class PrepareCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters = [this.$platformCommandParameter]; + public dashedOptions = { + watch: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, + }; + constructor($options: IOptions, - private $mainController: MainController, + private $prepareController: PrepareController, $platformValidationService: IPlatformValidationService, $projectData: IProjectData, private $platformCommandParameter: ICommandParameter, - $platformsData: IPlatformsData) { + $platformsData: IPlatformsData, + private $prepareDataService: PrepareDataService) { super($options, $platformsData, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } @@ -17,7 +23,8 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm public async execute(args: string[]): Promise { const platform = args[0]; - await this.$mainController.preparePlatform(platform, this.$projectData.projectDir, this.$options); + const prepareData = this.$prepareDataService.getPrepareData(this.$projectData.projectDir, platform, this.$options); + await this.$prepareController.preparePlatform(prepareData); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index 815f6f4d7a..1194f6e620 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -1,4 +1,5 @@ import { DEVICE_LOG_EVENT_NAME } from "../common/constants"; +import { PreviewAppController } from "../controllers/preview-app-controller"; export class PreviewCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; @@ -8,7 +9,7 @@ export class PreviewCommand implements ICommand { private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, private $logger: ILogger, - private $previewAppLiveSyncService: IPreviewAppLiveSyncService, + private $previewAppController: PreviewAppController, private $networkConnectivityValidator: INetworkConnectivityValidator, private $projectData: IProjectData, private $options: IOptions, @@ -24,7 +25,7 @@ export class PreviewCommand implements ICommand { this.$logger.info(message); }); - await this.$previewAppLiveSyncService.initialize({ + await this.$previewAppController.preview({ projectDir: this.$projectData.projectDir, useHotModuleReload: this.$options.hmr, env: this.$options.env diff --git a/lib/common/mobile/mobile-core/devices-service.ts b/lib/common/mobile/mobile-core/devices-service.ts index f89768ca67..d71df7cd0a 100644 --- a/lib/common/mobile/mobile-core/devices-service.ts +++ b/lib/common/mobile/mobile-core/devices-service.ts @@ -604,7 +604,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi public getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceInfo[]): string[] { const platforms = _(deviceDescriptors) .map(device => this.getDeviceByIdentifier(device.identifier)) - .map(device => device.deviceInfo.platform) + .map(device => device.deviceInfo.platform.toLowerCase()) .uniq() .value(); diff --git a/lib/common/services/hooks-service.ts b/lib/common/services/hooks-service.ts index e1bd095e64..8822da5d3a 100644 --- a/lib/common/services/hooks-service.ts +++ b/lib/common/services/hooks-service.ts @@ -84,7 +84,7 @@ export class HooksService implements IHooksService { results.push(await this.executeHooksInDirectory(hooksDirectory, hookName, hookArguments)); } } catch (err) { - this.$logger.trace("Failed during hook execution."); + this.$logger.trace(`Failed during hook execution ${hookName}.`); this.$errors.failWithoutHelp(err.message || err); } diff --git a/lib/common/services/livesync/sync-batch.ts b/lib/common/services/livesync/sync-batch.ts deleted file mode 100644 index 867c4b01bf..0000000000 --- a/lib/common/services/livesync/sync-batch.ts +++ /dev/null @@ -1,57 +0,0 @@ -// https://github.com/Microsoft/TypeScript/blob/master/src/compiler/tsc.ts#L487-L489 -export const SYNC_WAIT_THRESHOLD = 250; //milliseconds - -export class SyncBatch { - private timer: NodeJS.Timer = null; - private syncQueue: string[] = []; - private syncInProgress: boolean = false; - - constructor(private $logger: ILogger, - private $projectFilesManager: IProjectFilesManager, - private done: () => Promise) { } - - private get filesToSync(): string[] { - const filteredFiles = _.remove(this.syncQueue, syncFile => this.$projectFilesManager.isFileExcluded(syncFile)); - this.$logger.trace("Removed files from syncQueue: ", filteredFiles); - return this.syncQueue; - } - - public get syncPending(): boolean { - return this.syncQueue.length > 0; - } - - public async syncFiles(syncAction: (filesToSync: string[]) => Promise): Promise { - if (this.filesToSync.length > 0) { - await syncAction(this.filesToSync); - this.reset(); - } - } - - public async addFile(file: string): Promise { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - - this.syncQueue.push(file); - - if (!this.syncInProgress) { - this.timer = setTimeout(async () => { - if (this.syncQueue.length > 0) { - this.$logger.trace("Syncing %s", this.syncQueue.join(", ")); - try { - this.syncInProgress = true; - await this.done(); - } finally { - this.syncInProgress = false; - } - } - this.timer = null; - }, SYNC_WAIT_THRESHOLD); - } - } - - private reset(): void { - this.syncQueue = []; - } -} diff --git a/lib/constants.ts b/lib/constants.ts index 2fc462d1f7..65280f1197 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -141,6 +141,8 @@ export const CACACHE_DIRECTORY_NAME = "_cacache"; export const FILES_CHANGE_EVENT_NAME = "filesChangeEvent"; export const INITIAL_SYNC_EVENT_NAME = "initialSyncEvent"; +export const PREPARE_READY_EVENT_NAME = "prepareReadyEvent"; +export const WEBPACK_COMPILATION_COMPLETE = "webpackCompilationComplete"; export class DebugCommandErrors { public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them."; diff --git a/lib/controllers/add-platform-controller.ts b/lib/controllers/add-platform-controller.ts new file mode 100644 index 0000000000..a4e8190373 --- /dev/null +++ b/lib/controllers/add-platform-controller.ts @@ -0,0 +1,80 @@ +import { AddPlatformData } from "../data/add-platform-data"; +import { AddPlatformService } from "../services/platform/add-platform-service"; +import { NativePlatformStatus } from "../constants"; +import * as path from "path"; + +export class AddPlatformController { + constructor( + private $addPlatformService: AddPlatformService, + private $errors: IErrors, + private $fs: IFileSystem, + private $logger: ILogger, + private $packageInstallationManager: IPackageInstallationManager, + private $projectDataService: IProjectDataService, + private $platformsData: IPlatformsData, + private $projectChangesService: IProjectChangesService, + ) { } + + public async addPlatform(addPlatformData: AddPlatformData): Promise { + const [ platform, version ] = addPlatformData.platform.toLowerCase().split("@"); + const projectData = this.$projectDataService.getProjectData(addPlatformData.projectDir); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + + this.$logger.trace(`Creating NativeScript project for the ${platform} platform`); + this.$logger.trace(`Path: ${platformData.projectRoot}`); + this.$logger.trace(`Package: ${projectData.projectIdentifiers[platform]}`); + this.$logger.trace(`Name: ${projectData.projectName}`); + + this.$logger.out("Copying template files..."); + + const packageToInstall = await this.getPackageToInstall(platformData, projectData, addPlatformData.frameworkPath, version); + + const installedPlatformVersion = await this.$addPlatformService.addPlatformSafe(projectData, platformData, packageToInstall, addPlatformData.nativePrepare); + + this.$fs.ensureDirectoryExists(path.join(projectData.platformsDir, platform)); + this.$logger.out(`Platform ${platform} successfully added. v${installedPlatformVersion}`); + } + + public async addPlatformIfNeeded(addPlatformData: AddPlatformData): Promise { + const [ platform ] = addPlatformData.platform.toLowerCase().split("@"); + const projectData = this.$projectDataService.getProjectData(addPlatformData.projectDir); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + + const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, addPlatformData.nativePrepare); + if (shouldAddPlatform) { + await this.addPlatform(addPlatformData); + } + } + + private async getPackageToInstall(platformData: IPlatformData, projectData: IProjectData, frameworkPath?: string, version?: string): Promise { + let result = null; + if (frameworkPath) { + if (!this.$fs.exists(frameworkPath)) { + this.$errors.fail(`Invalid frameworkPath: ${frameworkPath}. Please ensure the specified frameworkPath exists.`); + } + result = path.resolve(frameworkPath); + } else { + if (!version) { + const currentPlatformData = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); + version = (currentPlatformData && currentPlatformData.version) || + await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); + } + + result = `${platformData.frameworkPackageName}@${version}`; + } + + return result; + } + + private shouldAddPlatform(platformData: IPlatformData, projectData: IProjectData, nativePrepare: INativePrepare): boolean { + const platformName = platformData.platformNameLowerCase; + const hasPlatformDirectory = this.$fs.exists(path.join(projectData.platformsDir, platformName)); + const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); + const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === NativePlatformStatus.requiresPlatformAdd; + const result = !hasPlatformDirectory || (shouldAddNativePlatform && requiresNativePlatformAdd); + + return !!result; + } +} +$injector.register("addPlatformController", AddPlatformController); diff --git a/lib/controllers/build-controller.ts b/lib/controllers/build-controller.ts new file mode 100644 index 0000000000..dd2229689f --- /dev/null +++ b/lib/controllers/build-controller.ts @@ -0,0 +1,131 @@ +import { PrepareController } from "./prepare-controller"; +import { BuildData } from "../data/build-data"; +import * as constants from "../constants"; +import { BuildArtefactsService } from "../services/build-artefacts-service"; +import { Configurations } from "../common/constants"; +import { EventEmitter } from "events"; +import { attachAwaitDetach } from "../common/helpers"; +import { BuildInfoFileService } from "../services/build-info-file-service"; + +export class BuildController extends EventEmitter { + constructor( + private $analyticsService: IAnalyticsService, + private $buildArtefactsService: BuildArtefactsService, + private $buildInfoFileService: BuildInfoFileService, + private $fs: IFileSystem, + private $logger: ILogger, + private $injector: IInjector, + private $mobileHelper: Mobile.IMobileHelper, + private $projectDataService: IProjectDataService, + private $projectChangesService: IProjectChangesService, + private $prepareController: PrepareController, + ) { super(); } + + private get $platformsData(): IPlatformsData { + return this.$injector.resolve("platformsData"); + } + + public async prepareAndBuildPlatform(buildData: BuildData): Promise { + await this.$prepareController.preparePlatform(buildData); + const result = await this.buildPlatform(buildData); + + return result; + } + + public async buildPlatform(buildData: BuildData) { + this.$logger.out("Building project..."); + + const platform = buildData.platform.toLowerCase(); + const projectData = this.$projectDataService.getProjectData(buildData.projectDir); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + + const action = constants.TrackActionNames.Build; + const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildData && buildData.buildForDevice; + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action, + isForDevice, + platform, + projectDir: projectData.projectDir, + additionalData: `${buildData.release ? Configurations.Release : Configurations.Debug}_${buildData.clean ? constants.BuildStates.Clean : constants.BuildStates.Incremental}` + }); + + if (buildData.clean) { + await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); + } + + const handler = (data: any) => { + this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); + this.$logger.printInfoMessageOnSameLine(data.data.toString()); + }; + + await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildData)); + + const buildInfoFileDir = platformData.getBuildOutputPath(buildData); + this.$buildInfoFileService.saveBuildInfoFile(platformData, buildInfoFileDir); + + this.$logger.out("Project successfully built."); + + const result = await this.$buildArtefactsService.getLatestApplicationPackagePath(platformData, buildData); + + if (buildData.copyTo) { + this.$buildArtefactsService.copyLastOutput(buildData.copyTo, platformData, buildData); + this.$logger.info(`The build result is located at: ${buildInfoFileDir}`); + } + + return result; + } + + public async buildPlatformIfNeeded(buildData: BuildData): Promise { + let result = null; + + const platform = buildData.platform.toLowerCase(); + const projectData = this.$projectDataService.getProjectData(buildData.projectDir); + const platformData = this.$platformsData.getPlatformData(platform, projectData); + + const outputPath = buildData.outputPath || platformData.getBuildOutputPath(buildData); + const shouldBuildPlatform = await this.shouldBuildPlatform(buildData, platformData, outputPath); + if (shouldBuildPlatform) { + result = await this.buildPlatform(buildData); + } + + return result; + } + + private async shouldBuildPlatform(buildData: BuildData, platformData: IPlatformData, outputPath: string): Promise { + if (buildData.release && this.$projectChangesService.currentChanges.hasChanges) { + return true; + } + + if (this.$projectChangesService.currentChanges.changesRequireBuild) { + return true; + } + + if (!this.$fs.exists(outputPath)) { + return true; + } + + const validBuildOutputData = platformData.getValidBuildOutputData(buildData); + const packages = this.$buildArtefactsService.getAllApplicationPackages(outputPath, validBuildOutputData); + if (packages.length === 0) { + return true; + } + + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); + const buildInfo = this.$buildInfoFileService.getBuildInfoFromFile(platformData, buildData, outputPath); + if (!prepareInfo || !buildInfo) { + return true; + } + + if (buildData.clean) { + return true; + } + + if (prepareInfo.time === buildInfo.prepareTime) { + return false; + } + + return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; + } +} +$injector.register("buildController", BuildController); diff --git a/lib/controllers/debug-on-devices-controller.ts b/lib/controllers/debug-on-devices-controller.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/controllers/deploy-on-devices-controller.ts b/lib/controllers/deploy-on-devices-controller.ts new file mode 100644 index 0000000000..8dae51a6f7 --- /dev/null +++ b/lib/controllers/deploy-on-devices-controller.ts @@ -0,0 +1,27 @@ +import { DeviceInstallAppService } from "../services/device/device-install-app-service"; +import { RunOnDevicesData } from "../data/run-on-devices-data"; +import { BuildController } from "./build-controller"; +import { BuildDataService } from "../services/build-data-service"; + +export class DeployOnDevicesController { + + constructor( + private $buildDataService: BuildDataService, + private $buildController: BuildController, + private $deviceInstallAppService: DeviceInstallAppService, + private $devicesService: Mobile.IDevicesService + ) { } + + public async deployOnDevices(data: RunOnDevicesData): Promise { + const { projectDir, liveSyncInfo, deviceDescriptors } = data; + + const executeAction = async (device: Mobile.IDevice) => { + const buildData = this.$buildDataService.getBuildData(projectDir, device.deviceInfo.platform, liveSyncInfo); + await this.$buildController.prepareAndBuildPlatform(buildData); + await this.$deviceInstallAppService.installOnDevice(device, buildData); + }; + + await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); + } +} +$injector.register("deployOnDevicesController", DeployOnDevicesController); diff --git a/lib/controllers/main-controller.ts b/lib/controllers/main-controller.ts deleted file mode 100644 index 29e160c508..0000000000 --- a/lib/controllers/main-controller.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { AddPlatformService } from "../services/platform/add-platform-service"; -import { BuildPlatformService } from "../services/platform/build-platform-service"; -import { DeviceInstallAppService } from "../services/device/device-install-app-service"; -import { EventEmitter } from "events"; -import { FILES_CHANGE_EVENT_NAME, INITIAL_SYNC_EVENT_NAME, RunOnDeviceEvents } from "../constants"; -import { PreparePlatformService } from "../services/platform/prepare-platform-service"; -import { WorkflowDataService } from "../services/workflow/workflow-data-service"; -import { RunOnDevicesController } from "./run-on-devices-controller"; -import { RunOnDevicesDataService } from "../services/run-on-devices-data-service"; -import { cache } from "../common/decorators"; -import { DeviceDiscoveryEventNames } from "../common/constants"; -import { RunOnDevicesEmitter } from "../run-on-devices-emitter"; -import { PlatformWatcherService } from "../services/platform/platform-watcher-service"; - -export class MainController extends EventEmitter { - constructor( - private $addPlatformService: AddPlatformService, - private $buildPlatformService: BuildPlatformService, - private $deviceInstallAppService: DeviceInstallAppService, - private $devicesService: Mobile.IDevicesService, - private $errors: IErrors, - private $hooksService: IHooksService, - private $logger: ILogger, - private $platformWatcherService: PlatformWatcherService, - private $pluginsService: IPluginsService, - private $preparePlatformService: PreparePlatformService, - private $projectDataService: IProjectDataService, - private $runOnDevicesController: RunOnDevicesController, - private $runOnDevicesDataService: RunOnDevicesDataService, - private $runOnDevicesEmitter: RunOnDevicesEmitter, - private $workflowDataService: WorkflowDataService - ) { super(); } - - public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise { - const { nativePlatformData, projectData, addPlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); - - await this.$addPlatformService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); - const result = await this.$preparePlatformService.preparePlatform(nativePlatformData, projectData, preparePlatformData); - - return result; - } - - public async buildPlatform(platform: string, projectDir: string, options: IOptions | any): Promise { - const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options); - - await this.preparePlatform(platform, projectDir, options); - const result = await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); - - return result; - } - - public async deployOnDevices(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { - const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); - - for (const platform of platforms) { - await this.preparePlatform(platform, projectDir, liveSyncInfo); - } - - const executeAction = async (device: Mobile.IDevice) => { - const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectDir, liveSyncInfo); - await this.$buildPlatformService.buildPlatformIfNeeded(nativePlatformData, projectData, buildPlatformData); - await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, nativePlatformData, projectData, buildPlatformData); - }; - - await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => true); - } - - public async runOnDevices(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { - const projectData = this.$projectDataService.getProjectData(projectDir); - await this.initializeSetup(projectData); - - const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); - - for (const platform of platforms) { - const { nativePlatformData, addPlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, { ...liveSyncInfo, platformParam: platform }); - await this.$addPlatformService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData); - } - - const currentRunOnDevicesData = this.$runOnDevicesDataService.getDataForProject(projectData.projectDir); - const isAlreadyLiveSyncing = currentRunOnDevicesData && !currentRunOnDevicesData.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, currentRunOnDevicesData.deviceDescriptors, "identifier") : deviceDescriptors; - - this.$runOnDevicesDataService.persistData(projectDir, deviceDescriptors, platforms); - - const shouldStartWatcher = !liveSyncInfo.skipWatcher && (liveSyncInfo.syncToPreviewApp || this.$runOnDevicesDataService.hasDeviceDescriptors(projectDir)); - if (shouldStartWatcher) { - this.handleRunOnDeviceError(projectDir); - - this.$platformWatcherService.on(INITIAL_SYNC_EVENT_NAME, async (data: IInitialSyncEventData) => { - await this.$runOnDevicesController.syncInitialDataOnDevices(data, projectData, liveSyncInfo, deviceDescriptorsForInitialSync); - }); - this.$platformWatcherService.on(FILES_CHANGE_EVENT_NAME, async (data: IFilesChangeEventData) => { - await this.$runOnDevicesController.syncChangedDataOnDevices(data, projectData, liveSyncInfo, deviceDescriptors); - }); - - for (const platform of platforms) { - const { nativePlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, liveSyncInfo); - await this.$platformWatcherService.startWatchers(nativePlatformData, projectData, preparePlatformData); - } - } else { - for (const platform of platforms) { - const hasNativeChanges = await this.preparePlatform(platform, projectDir, liveSyncInfo); - await this.$runOnDevicesController.syncInitialDataOnDevices({ platform, hasNativeChanges }, projectData, liveSyncInfo, deviceDescriptorsForInitialSync); - } - } - - this.attachDeviceLostHandler(); - } - - public async stopRunOnDevices(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { - const liveSyncProcessInfo = this.$runOnDevicesDataService.getDataForProject(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. - const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; - - const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); - - const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) - .map(descriptor => descriptor.identifier); - - // Handle the case when no more devices left for any of the persisted platforms - _.each(liveSyncProcessInfo.platforms, platform => { - const devices = this.$devicesService.getDevicesForPlatform(platform); - if (!devices || !devices.length) { - this.$platformWatcherService.stopWatchers(projectDir, platform); - } - }); - - // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. - if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { - if (liveSyncProcessInfo.timer) { - clearTimeout(liveSyncProcessInfo.timer); - } - - _.each(liveSyncProcessInfo.platforms, platform => { - this.$platformWatcherService.stopWatchers(projectDir, platform); - }); - - liveSyncProcessInfo.isStopped = true; - - if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) { - await liveSyncProcessInfo.actionsChain; - } - - liveSyncProcessInfo.deviceDescriptors = []; - - const projectData = this.$projectDataService.getProjectData(projectDir); - await this.$hooksService.executeAfterHooks('watch', { - hookArgs: { - projectData - } - }); - } else if (liveSyncProcessInfo.currentSyncAction && shouldAwaitPendingOperation) { - await liveSyncProcessInfo.currentSyncAction; - } - - // Emit RunOnDevice stopped when we've really stopped. - _.each(removedDeviceIdentifiers, deviceIdentifier => { - this.$runOnDevicesEmitter.emitRunOnDeviceStoppedEvent(projectDir, deviceIdentifier); - }); - } - } - - public getRunOnDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { - return this.$runOnDevicesDataService.getDeviceDescriptors(projectDir); - } - - private handleRunOnDeviceError(projectDir: string): void { - this.$runOnDevicesEmitter.on(RunOnDeviceEvents.runOnDeviceError, async data => { - await this.stopRunOnDevices(projectDir, [data.deviceIdentifier], { shouldAwaitAllActions: false }); - }); - } - - private async initializeSetup(projectData: IProjectData): Promise { - try { - await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); - } catch (err) { - this.$logger.trace(err); - this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); - } - } - - @cache() - private attachDeviceLostHandler(): void { - 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.$runOnDevicesDataService.getAllData()) { - try { - const deviceDescriptors = this.$runOnDevicesDataService.getDeviceDescriptors(projectDir); - if (_.find(deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { - await this.stopRunOnDevices(projectDir, [device.deviceInfo.identifier]); - } - } catch (err) { - this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); - } - } - }); - } -} -$injector.register("mainController", MainController); diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts new file mode 100644 index 0000000000..62d121c03a --- /dev/null +++ b/lib/controllers/prepare-controller.ts @@ -0,0 +1,156 @@ +import * as child_process from "child_process"; +import * as choki from "chokidar"; +import { hook } from "../common/helpers"; +import { PrepareNativePlatformService } from "../services/platform/prepare-native-platform-service"; +import { performanceLog } from "../common/decorators"; +import { EventEmitter } from "events"; +import * as path from "path"; +import { WebpackCompilerService } from "../services/webpack/webpack-compiler-service"; +import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE } from "../constants"; +import { HooksService } from "../common/services/hooks-service"; +import { AddPlatformController } from "./add-platform-controller"; +import { PrepareData } from "../data/prepare-data"; + +interface IPlatformWatcherData { + webpackCompilerProcess: child_process.ChildProcess; + nativeFilesWatcher: choki.FSWatcher; +} + +export class PrepareController extends EventEmitter { + private watchersData: IDictionary> = {}; + private isInitialPrepareReady = false; + private persistedData: IFilesChangeEventData[] = []; + + constructor( + private $addPlatformController: AddPlatformController, + public $hooksService: HooksService, + private $logger: ILogger, + private $platformsData: IPlatformsData, + private $prepareNativePlatformService: PrepareNativePlatformService, + private $projectChangesService: IProjectChangesService, + private $projectDataService: IProjectDataService, + private $webpackCompilerService: WebpackCompilerService + ) { super(); } + + @performanceLog() + @hook("prepare") + public async preparePlatform(prepareData: PrepareData): Promise { + await this.$addPlatformController.addPlatformIfNeeded(prepareData); + + this.$logger.out("Preparing project..."); + let result = null; + + const projectData = this.$projectDataService.getProjectData(prepareData.projectDir); + const platformData = this.$platformsData.getPlatformData(prepareData.platform, projectData); + + if (prepareData.watch) { + result = await this.startWatchersWithPrepare(platformData, projectData, prepareData); + } else { + await this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, { watch: false, env: prepareData.env }); + await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); + } + + this.$projectChangesService.savePrepareInfo(platformData); + + this.$logger.out(`Project successfully prepared (${prepareData.platform.toLowerCase()})`); + + return result; + } + + public stopWatchers(projectDir: string, platform: string): void { + const platformLowerCase = platform.toLowerCase(); + + if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) { + this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher.close(); + this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher = null; + } + + if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess) { + this.$webpackCompilerService.stopWebpackCompiler(platform); + this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess = null; + } + } + + @hook("watch") + private async startWatchersWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { + if (!this.watchersData[projectData.projectDir]) { + this.watchersData[projectData.projectDir] = {}; + } + + if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase]) { + this.watchersData[projectData.projectDir][platformData.platformNameLowerCase] = { + nativeFilesWatcher: null, + webpackCompilerProcess: null + }; + } + + await this.startJSWatcherWithPrepare(platformData, projectData, { env: prepareData.env }); // -> start watcher + initial compilation + const hasNativeChanges = await this.startNativeWatcherWithPrepare(platformData, projectData, prepareData); // -> start watcher + initial prepare + + const result = { platform: platformData.platformNameLowerCase, hasNativeChanges }; + const hasPersistedDataWithNativeChanges = this.persistedData.find(data => data.platform === result.platform && data.hasNativeChanges); + if (hasPersistedDataWithNativeChanges) { + result.hasNativeChanges = true; + } + + this.isInitialPrepareReady = true; + + if (this.persistedData && this.persistedData.length) { + this.emitPrepareEvent({ files: [], hasNativeChanges: result.hasNativeChanges, hmrData: null, platform: platformData.platformNameLowerCase }); + } + + return result; + } + + private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { + this.$webpackCompilerService.on(WEBPACK_COMPILATION_COMPLETE, data => { + this.emitPrepareEvent({ ...data, hasNativeChanges: false, platform: platformData.platformNameLowerCase }); + }); + + const childProcess = await this.$webpackCompilerService.compileWithWatch(platformData, projectData, config); + this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess = childProcess; + } + } + + private async startNativeWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { + if ((prepareData.nativePrepare && prepareData.nativePrepare.skipNativePrepare) || this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher) { + return false; + } + + const patterns = [ + path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), + `node_modules/**/platforms/${platformData.platformNameLowerCase}/` + ]; + const watcherOptions: choki.WatchOptions = { + ignoreInitial: true, + cwd: projectData.projectDir, + awaitWriteFinish: { + pollInterval: 100, + stabilityThreshold: 500 + }, + ignored: ["**/.*", ".*"] // hidden files + }; + const watcher = choki.watch(patterns, watcherOptions) + .on("all", async (event: string, filePath: string) => { + filePath = path.join(projectData.projectDir, filePath); + this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); + this.emitPrepareEvent({ files: [], hmrData: null, hasNativeChanges: true, platform: platformData.platformNameLowerCase }); + }); + + this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher; + + const hasNativeChanges = await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); + + return hasNativeChanges; + } + + private emitPrepareEvent(filesChangeEventData: IFilesChangeEventData) { + if (this.isInitialPrepareReady) { + this.emit(PREPARE_READY_EVENT_NAME, filesChangeEventData); + } else { + this.persistedData.push(filesChangeEventData); + } + } +} +$injector.register("prepareController", PrepareController); diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts new file mode 100644 index 0000000000..366c4e79a0 --- /dev/null +++ b/lib/controllers/preview-app-controller.ts @@ -0,0 +1,123 @@ +import { Device, FilesPayload } from "nativescript-preview-sdk"; +import { TrackActionNames, PREPARE_READY_EVENT_NAME } from "../constants"; +import { PrepareController } from "./prepare-controller"; +import { performanceLog } from "../common/decorators"; +import { stringify } from "../common/helpers"; +import { HmrConstants } from "../common/constants"; +import { EventEmitter } from "events"; +import { PreviewAppEmitter } from "../preview-app-emitter"; +import { PrepareDataService } from "../services/prepare-data-service"; + +export class PreviewAppController extends EventEmitter { + private deviceInitializationPromise: IDictionary> = {}; + private promise = Promise.resolve(); + + constructor( + private $analyticsService: IAnalyticsService, + private $errors: IErrors, + 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 $previewSdkService: IPreviewSdkService, + private $prepareDataService: PrepareDataService + ) { super(); } + + public async preview(data: IPreviewAppLiveSyncData): Promise { + await this.$previewSdkService.initialize(data.projectDir, async (device: Device) => { + try { + if (!device) { + this.$errors.failWithoutHelp("Sending initial preview files without a specified device is not supported."); + } + + if (this.deviceInitializationPromise[device.id]) { + return this.deviceInitializationPromise[device.id]; + } + + if (device.uniqueId) { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.PreviewAppData, + platform: device.platform, + additionalData: device.uniqueId + }); + } + + if (data.useHotModuleReload) { + this.$hmrStatusService.attachToHmrStatusEvent(); + } + + await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); + + this.$prepareController.on(PREPARE_READY_EVENT_NAME, async currentPrepareData => { + await this.handlePrepareReadyEvent(data, currentPrepareData.hmrData, currentPrepareData.files, device.platform); + }); + + if (!data.env) { data.env = { }; } + data.env.externals = this.$previewAppPluginsService.getExternalPlugins(device); + + const prepareData = this.$prepareDataService.getPrepareData(data.projectDir, device.platform.toLowerCase(), { ...data, skipNativePrepare: true } ); + await this.$prepareController.preparePlatform(prepareData); + + this.deviceInitializationPromise[device.id] = this.getInitialFilesForPlatformSafe(data, device.platform); + + try { + const payloads = await this.deviceInitializationPromise[device.id]; + return payloads; + } finally { + this.deviceInitializationPromise[device.id] = null; + } + } 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); + } + }); + 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); + await this.promise; + + if (data.useHotModuleReload && platformHmrData.hash) { + const devices = this.$previewDevicesService.getDevicesForPlatform(platform); + + await Promise.all(_.map(devices, async (previewDevice: Device) => { + const status = await this.$hmrStatusService.getHmrStatus(previewDevice.id, platformHmrData.hash); + if (status === HmrConstants.HMR_ERROR_STATUS) { + const originalUseHotModuleReload = data.useHotModuleReload; + data.useHotModuleReload = false; + await this.$previewAppLiveSyncService.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id ); + data.useHotModuleReload = originalUseHotModuleReload; + } + })); + } + }); + } + + private async getInitialFilesForPlatformSafe(data: IPreviewAppLiveSyncData, platform: string): Promise { + this.$logger.info(`Start sending initial files for platform ${platform}.`); + + try { + const payloads = this.$previewAppFilesService.getInitialFilesPayload(data, platform); + this.$logger.info(`Successfully sent initial files for platform ${platform}.`); + return payloads; + } catch (err) { + this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${stringify(err)}`); + } + } +} +$injector.register("previewAppController", PreviewAppController); diff --git a/lib/controllers/run-on-devices-controller.ts b/lib/controllers/run-on-devices-controller.ts index bb7c6e5ff4..1d3c173c5f 100644 --- a/lib/controllers/run-on-devices-controller.ts +++ b/lib/controllers/run-on-devices-controller.ts @@ -1,4 +1,3 @@ -import { BuildPlatformService } from "../services/platform/build-platform-service"; import { DeviceDebugAppService } from "../services/device/device-debug-app-service"; import { DeviceInstallAppService } from "../services/device/device-install-app-service"; import { DeviceRefreshAppService } from "../services/device/device-refresh-app-service"; @@ -6,39 +5,175 @@ import { EventEmitter } from "events"; import { LiveSyncServiceResolver } from "../resolvers/livesync-service-resolver"; import { RunOnDevicesDataService } from "../services/run-on-devices-data-service"; import { RunOnDevicesEmitter } from "../run-on-devices-emitter"; -import { WorkflowDataService } from "../services/workflow/workflow-data-service"; -import { HmrConstants } from "../common/constants"; -import { PreparePlatformService } from "../services/platform/prepare-platform-service"; +import { HmrConstants, DeviceDiscoveryEventNames } from "../common/constants"; +import { PrepareNativePlatformService } from "../services/platform/prepare-native-platform-service"; +import { PrepareController } from "./prepare-controller"; +import { PREPARE_READY_EVENT_NAME } from "../constants"; +import { cache } from "../common/decorators"; +import { RunOnDevicesData } from "../data/run-on-devices-data"; +import { PrepareDataService } from "../services/prepare-data-service"; +import { BuildController } from "./build-controller"; +import { BuildDataService } from "../services/build-data-service"; export class RunOnDevicesController extends EventEmitter { constructor( - private $buildPlatformService: BuildPlatformService, + private $buildDataService: BuildDataService, + private $buildController: BuildController, private $deviceDebugAppService: DeviceDebugAppService, private $deviceInstallAppService: DeviceInstallAppService, private $deviceRefreshAppService: DeviceRefreshAppService, private $devicesService: Mobile.IDevicesService, + private $errors: IErrors, private $hmrStatusService: IHmrStatusService, public $hooksService: IHooksService, private $liveSyncServiceResolver: LiveSyncServiceResolver, private $logger: ILogger, - private $preparePlatformService: PreparePlatformService, + private $pluginsService: IPluginsService, + private $platformsData: IPlatformsData, + private $prepareNativePlatformService: PrepareNativePlatformService, + private $prepareController: PrepareController, + private $prepareDataService: PrepareDataService, + private $projectDataService: IProjectDataService, private $runOnDevicesDataService: RunOnDevicesDataService, - private $runOnDevicesEmitter: RunOnDevicesEmitter, - private $workflowDataService: WorkflowDataService + private $runOnDevicesEmitter: RunOnDevicesEmitter ) { super(); } - public async syncInitialDataOnDevices(data: IInitialSyncEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + public async runOnDevices(runOnDevicesData: RunOnDevicesData): Promise { + const { projectDir, liveSyncInfo, deviceDescriptors } = runOnDevicesData; + + const projectData = this.$projectDataService.getProjectData(projectDir); + await this.initializeSetup(projectData); + + const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); + const deviceDescriptorsForInitialSync = this.getDeviceDescriptorsForInitialSync(projectDir, deviceDescriptors); + + this.$runOnDevicesDataService.persistData(projectDir, deviceDescriptors, platforms); + + const shouldStartWatcher = !liveSyncInfo.skipWatcher && this.$runOnDevicesDataService.hasDeviceDescriptors(projectData.projectDir); + if (shouldStartWatcher && liveSyncInfo.useHotModuleReload) { + this.$hmrStatusService.attachToHmrStatusEvent(); + } + + this.$prepareController.on(PREPARE_READY_EVENT_NAME, async data => { + await this.syncChangedDataOnDevices(data, projectData, liveSyncInfo, deviceDescriptors); + }); + + for (const platform of platforms) { + const prepareData = this.$prepareDataService.getPrepareData(projectDir, platform, { ...liveSyncInfo, watch: !liveSyncInfo.skipWatcher }); + const prepareResult = await this.$prepareController.preparePlatform(prepareData); + await this.syncInitialDataOnDevices(prepareResult, projectData, liveSyncInfo, deviceDescriptorsForInitialSync); + } + + this.attachDeviceLostHandler(); + } + + public async stopRunOnDevices(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { + const liveSyncProcessInfo = this.$runOnDevicesDataService.getDataForProject(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. + const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; + + const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); + + const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) + .map(descriptor => descriptor.identifier); + + // Handle the case when no more devices left for any of the persisted platforms + _.each(liveSyncProcessInfo.platforms, platform => { + const devices = this.$devicesService.getDevicesForPlatform(platform); + if (!devices || !devices.length) { + this.$prepareController.stopWatchers(projectDir, platform); + } + }); + + // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. + if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { + if (liveSyncProcessInfo.timer) { + clearTimeout(liveSyncProcessInfo.timer); + } + + _.each(liveSyncProcessInfo.platforms, platform => { + this.$prepareController.stopWatchers(projectDir, platform); + }); + + liveSyncProcessInfo.isStopped = true; + + if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) { + await liveSyncProcessInfo.actionsChain; + } + + liveSyncProcessInfo.deviceDescriptors = []; + + const projectData = this.$projectDataService.getProjectData(projectDir); + await this.$hooksService.executeAfterHooks('watch', { + hookArgs: { + projectData + } + }); + } else if (liveSyncProcessInfo.currentSyncAction && shouldAwaitPendingOperation) { + await liveSyncProcessInfo.currentSyncAction; + } + + // Emit RunOnDevice stopped when we've really stopped. + _.each(removedDeviceIdentifiers, deviceIdentifier => { + this.$runOnDevicesEmitter.emitRunOnDeviceStoppedEvent(projectDir, deviceIdentifier); + }); + } + } + + public getRunOnDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + return this.$runOnDevicesDataService.getDeviceDescriptors(projectDir); + } + + private getDeviceDescriptorsForInitialSync(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]) { + const currentRunOnDevicesData = this.$runOnDevicesDataService.getDataForProject(projectDir); + const isAlreadyLiveSyncing = currentRunOnDevicesData && !currentRunOnDevicesData.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, currentRunOnDevicesData.deviceDescriptors, "identifier") : deviceDescriptors; + + return deviceDescriptorsForInitialSync; + } + + private async initializeSetup(projectData: IProjectData): Promise { + try { + await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); + } catch (err) { + this.$logger.trace(err); + this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); + } + } + + @cache() + private attachDeviceLostHandler(): void { + 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.$runOnDevicesDataService.getAllData()) { + try { + const deviceDescriptors = this.$runOnDevicesDataService.getDeviceDescriptors(projectDir); + if (_.find(deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { + await this.stopRunOnDevices(projectDir, [device.deviceInfo.identifier]); + } + } catch (err) { + this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); + } + } + }); + } + + private async syncInitialDataOnDevices(data: IPrepareOutputData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + const platformData = this.$platformsData.getPlatformData(data.platform, projectData); + const buildData = this.$buildDataService.getBuildData(projectData.projectDir, data.platform, { ...liveSyncInfo, outputPath: deviceDescriptor.outputPath }); try { - const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData); const packageFilePath = data.hasNativeChanges ? - await this.$buildPlatformService.buildPlatform(platformData, projectData, buildPlatformData) : - await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath); + await this.$buildController.prepareAndBuildPlatform(buildData) : + await this.$buildController.buildPlatformIfNeeded(buildData); - await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath); + await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, buildData, packageFilePath); const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(platformData.platformNameLowerCase); const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; @@ -68,15 +203,17 @@ export class RunOnDevicesController extends EventEmitter { await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === data.platform.toLowerCase() && _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } - public async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - const { nativePlatformData, preparePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo); + const platformData = this.$platformsData.getPlatformData(data.platform, projectData); + const prepareData = this.$prepareDataService.getPrepareData(projectData.projectDir, data.platform, { ...liveSyncInfo, watch: !liveSyncInfo.skipWatcher }); + const buildData = this.$buildDataService.getBuildData(projectData.projectDir, data.platform, { ...liveSyncInfo, outputPath: deviceDescriptor.outputPath }); try { if (data.hasNativeChanges) { - await this.$preparePlatformService.prepareNativePlatform(nativePlatformData, projectData, preparePlatformData); - await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); + await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); + await this.$buildController.prepareAndBuildPlatform(buildData); } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; diff --git a/lib/data/add-platform-data.ts b/lib/data/add-platform-data.ts new file mode 100644 index 0000000000..c43aeef36f --- /dev/null +++ b/lib/data/add-platform-data.ts @@ -0,0 +1,11 @@ +import { DataBase } from "./data-base"; + +export class AddPlatformData extends DataBase { + public frameworkPath?: string; + + constructor(public projectDir: string, public platform: string, data: any) { + super(projectDir, platform, data); + + this.frameworkPath = data.frameworkPath; + } +} diff --git a/lib/data/build-data.ts b/lib/data/build-data.ts new file mode 100644 index 0000000000..04e967b03c --- /dev/null +++ b/lib/data/build-data.ts @@ -0,0 +1,59 @@ +import { PrepareData } from "./prepare-data"; + +export class BuildData extends PrepareData { + public device?: string; + public emulator?: boolean; + public clean: boolean; + public buildForDevice?: boolean; + public buildOutputStdio?: string; + public outputPath?: string; + public copyTo?: string; + + constructor(projectDir: string, platform: string, data: any) { + super(projectDir, platform, data); + + this.device = data.device; + this.emulator = data.emulator; + this.clean = data.clean; + this.buildForDevice = data.buildForDevice; + this.buildOutputStdio = data.buildOutputStdio; + this.outputPath = data.outputPath; + this.copyTo = data.copyTo; + } +} + +export class IOSBuildData extends BuildData { + public teamId: string; + public provision: string; + public mobileProvisionData: any; + public buildForAppStore: boolean; + public iCloudContainerEnvironment: string; + + constructor(projectDir: string, platform: string, data: any) { + super(projectDir, platform, data); + + this.teamId = data.teamId; + this.provision = data.provision; + this.mobileProvisionData = data.mobileProvisionData; + this.buildForAppStore = data.buildForAppStore; + this.iCloudContainerEnvironment = data.iCloudContainerEnvironment; + } +} + +export class AndroidBuildData extends BuildData { + public keyStoreAlias: string; + public keyStorePath: string; + public keyStoreAliasPassword: string; + public keyStorePassword: string; + public androidBundle: boolean; + + constructor(projectDir: string, platform: string, data: any) { + super(projectDir, platform, data); + + this.keyStoreAlias = data.keyStoreAlias; + this.keyStorePath = data.keyStorePath; + this.keyStoreAliasPassword = data.keyStoreAliasPassword; + this.keyStorePassword = data.keyStorePassword; + this.androidBundle = data.androidBundle; + } +} diff --git a/lib/data/data-base.ts b/lib/data/data-base.ts new file mode 100644 index 0000000000..4d630c022f --- /dev/null +++ b/lib/data/data-base.ts @@ -0,0 +1,7 @@ +export class DataBase { + public nativePrepare?: INativePrepare; + + constructor(public projectDir: string, public platform: string, data: any) { + this.nativePrepare = data.nativePrepare; + } +} diff --git a/lib/data/prepare-data.ts b/lib/data/prepare-data.ts new file mode 100644 index 0000000000..8a7d06de13 --- /dev/null +++ b/lib/data/prepare-data.ts @@ -0,0 +1,36 @@ +import { DataBase } from "./data-base"; + +export class PrepareData extends DataBase { + public release: boolean; + public hmr: boolean; + public env: any; + public watch?: boolean; + + constructor(public projectDir: string, public platform: string, data: any) { + super(projectDir, platform, data); + + this.release = data.release; + this.hmr = data.hmr || data.useHotModuleReload; + this.env = { + ...data.env, + hmr: data.hmr || data.useHotModuleReload + }; + this.watch = data.watch; + } +} + +export class IOSPrepareData extends PrepareData { + public teamId: string; + public provision: string; + public mobileProvisionData: any; + + constructor(projectDir: string, platform: string, data: any) { + super(projectDir, platform, data); + + this.teamId = data.teamId; + this.provision = data.provision; + this.mobileProvisionData = data.mobileProvisionData; + } +} + +export class AndroidPrepareData extends PrepareData { } diff --git a/lib/data/run-on-devices-data.ts b/lib/data/run-on-devices-data.ts new file mode 100644 index 0000000000..1ceb7b5515 --- /dev/null +++ b/lib/data/run-on-devices-data.ts @@ -0,0 +1,3 @@ +export class RunOnDevicesData { + constructor(public projectDir: string, public liveSyncInfo: ILiveSyncInfo, public deviceDescriptors: ILiveSyncDeviceInfo[]) { } +} diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 37127033f9..77a1f6d9b4 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -465,10 +465,6 @@ interface INpmInstallConfigurationOptions extends INpmInstallConfigurationOption disableNpmInstall: boolean; } -interface ICreateProjectOptions extends INpmInstallConfigurationOptionsBase { - pathToTemplate?: string; -} - interface IGenerateOptions { collection?: string; } diff --git a/lib/definitions/ios.d.ts b/lib/definitions/ios.d.ts new file mode 100644 index 0000000000..9ca1fa5932 --- /dev/null +++ b/lib/definitions/ios.d.ts @@ -0,0 +1,42 @@ +import { IOSBuildData } from "../data/build-data"; + +declare global { + interface IiOSSigningService { + setupSigningForDevice(projectRoot: string, projectData: IProjectData, buildConfig: IOSBuildData): Promise; + setupSigningFromTeam(projectRoot: string, projectData: IProjectData, teamId: string): Promise; + setupSigningFromProvision(projectRoot: string, projectData: IProjectData, provision?: string, mobileProvisionData?: any): Promise; + } + + interface IXcodebuildService { + buildForSimulator(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; + buildForDevice(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; + buildForAppStore(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; + } + + interface IXcodebuildArgsService { + getBuildForSimulatorArgs(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; + getBuildForDeviceArgs(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; + } + + interface IXcodebuildCommandService { + executeCommand(args: string[], options: IXcodebuildCommandOptions): Promise; + } + + interface IXcodebuildCommandOptions { + message?: string; + cwd: string; + stdio?: string; + spawnOptions?: any; + } + + interface IExportOptionsPlistService { + createDevelopmentExportOptionsPlist(archivePath: string, projectData: IProjectData, buildConfig: IBuildConfig): IExportOptionsPlistOutput; + createDistributionExportOptionsPlist(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): IExportOptionsPlistOutput; + } + + interface IExportOptionsPlistOutput { + exportFileDir: string; + exportFilePath: string; + exportOptionsPlistFilePath: string; + } +} \ No newline at end of file diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index f02893d430..d6714c247b 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -14,21 +14,6 @@ interface IBuildPlatformAction { buildPlatform(platform: string, buildConfig: IBuildConfig, projectData: IProjectData): Promise; } -/** - * Platform specific data required for project preparation. - */ -interface IPlatformSpecificData extends IProvision, ITeamIdentifier { - /** - * Target SDK for Android. - */ - sdk: string; - - /** - * Data from mobileProvision. - */ - mobileProvisionData?: any; -} - interface IPlatformData { frameworkPackageName: string; platformProjectService: IPlatformProjectService; @@ -53,7 +38,9 @@ interface IValidBuildOutputData { regexes?: RegExp[]; } -interface IBuildOutputOptions extends Partial, IRelease, IHasAndroidBundle { } +interface IBuildOutputOptions extends Partial, IRelease, IHasAndroidBundle { + outputPath?: string; +} interface IPlatformsData { availablePlatforms: any; diff --git a/lib/definitions/preview-app-livesync.d.ts b/lib/definitions/preview-app-livesync.d.ts index 6dadd2ae38..1fb10a0972 100644 --- a/lib/definitions/preview-app-livesync.d.ts +++ b/lib/definitions/preview-app-livesync.d.ts @@ -3,9 +3,8 @@ import { EventEmitter } from "events"; declare global { interface IPreviewAppLiveSyncService extends EventEmitter { - initialize(data: IPreviewAppLiveSyncData): void; syncFiles(data: IPreviewAppLiveSyncData, filesToSync: string[], filesToRemove: string[]): Promise; - stopLiveSync(): Promise; + syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): Promise; } interface IPreviewAppFilesService { diff --git a/lib/definitions/xcode.d.ts b/lib/definitions/xcode.d.ts index d1d524d45e..7bcf86ca6e 100644 --- a/lib/definitions/xcode.d.ts +++ b/lib/definitions/xcode.d.ts @@ -58,43 +58,4 @@ declare module "nativescript-dev-xcode" { uuid: string; pbxGroup: Object; } -} - -interface IiOSSigningService { - setupSigningForDevice(projectRoot: string, projectData: IProjectData, buildConfig: IiOSBuildConfig): Promise; - setupSigningFromTeam(projectRoot: string, projectData: IProjectData, teamId: string): Promise; - setupSigningFromProvision(projectRoot: string, projectData: IProjectData, provision?: string, mobileProvisionData?: any): Promise; -} - -interface IXcodebuildService { - buildForSimulator(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; - buildForDevice(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; - buildForAppStore(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; -} - -interface IXcodebuildArgsService { - getBuildForSimulatorArgs(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; - getBuildForDeviceArgs(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise; -} - -interface IXcodebuildCommandService { - executeCommand(args: string[], options: IXcodebuildCommandOptions): Promise; -} - -interface IXcodebuildCommandOptions { - message?: string; - cwd: string; - stdio?: string; - spawnOptions?: any; -} - -interface IExportOptionsPlistService { - createDevelopmentExportOptionsPlist(archivePath: string, projectData: IProjectData, buildConfig: IBuildConfig): IExportOptionsPlistOutput; - createDistributionExportOptionsPlist(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): IExportOptionsPlistOutput; -} - -interface IExportOptionsPlistOutput { - exportFileDir: string; - exportFilePath: string; - exportOptionsPlistFilePath: string; } \ No newline at end of file diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index 602cd53a9b..b609fa4ea6 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -1,11 +1,11 @@ -import { BuildPlatformService } from "../services/platform/build-platform-service"; -import { MainController } from "../controllers/main-controller"; +import { DeployOnDevicesController } from "../controllers/deploy-on-devices-controller"; +import { BuildController } from "../controllers/build-controller"; export class DeployCommandHelper { constructor( - private $buildPlatformService: BuildPlatformService, + private $buildController: BuildController, private $devicesService: Mobile.IDevicesService, - private $mainController: MainController, + private $deployOnDevicesController: DeployOnDevicesController, private $options: IOptions, private $projectData: IProjectData ) { } @@ -42,7 +42,7 @@ export class DeployCommandHelper { const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : - this.$buildPlatformService.buildPlatform.bind(this.$buildPlatformService, d.deviceInfo.platform, buildConfig, this.$projectData); + this.$buildController.prepareAndBuildPlatform.bind(this.$buildController, d.deviceInfo.platform, buildConfig, this.$projectData); const outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ platform: d.deviceInfo.platform, @@ -74,7 +74,11 @@ export class DeployCommandHelper { emulator: this.$options.emulator }; - await this.$mainController.deployOnDevices(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); + await this.$deployOnDevicesController.deployOnDevices({ + projectDir: this.$projectData.projectDir, + liveSyncInfo, + deviceDescriptors + }); } } $injector.register("deployCommandHelper", DeployCommandHelper); diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 86a1024346..8347b99ec7 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,5 +1,5 @@ -import { BuildPlatformService } from "../services/platform/build-platform-service"; -import { MainController } from "../controllers/main-controller"; +import { RunOnDevicesController } from "../controllers/run-on-devices-controller"; +import { BuildController } from "../controllers/build-controller"; export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; @@ -7,12 +7,12 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { constructor( private $projectData: IProjectData, private $options: IOptions, - private $mainController: MainController, + private $runOnDevicesController: RunOnDevicesController, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $devicesService: Mobile.IDevicesService, private $injector: IInjector, - private $buildPlatformService: BuildPlatformService, + private $buildController: BuildController, private $analyticsService: IAnalyticsService, private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, @@ -96,7 +96,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : - this.$buildPlatformService.buildPlatform.bind(this.$buildPlatformService, d.deviceInfo.platform, buildConfig, this.$projectData); + this.$buildController.prepareAndBuildPlatform.bind(this.$buildController, d.deviceInfo.platform, buildConfig, this.$projectData); const outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ platform: d.deviceInfo.platform, @@ -150,7 +150,11 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { // return; // } - await this.$mainController.runOnDevices(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); + await this.$runOnDevicesController.runOnDevices({ + projectDir: this.$projectData.projectDir, + liveSyncInfo, + deviceDescriptors + }); // const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); // this.$liveSyncService.on(LiveSyncEvents.liveSyncStopped, (data: { projectDir: string, deviceIdentifier: string }) => { diff --git a/lib/preview-app-emitter.ts b/lib/preview-app-emitter.ts new file mode 100644 index 0000000000..40c05a555c --- /dev/null +++ b/lib/preview-app-emitter.ts @@ -0,0 +1,14 @@ +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/services/android-project-service.ts b/lib/services/android-project-service.ts index 978c859889..56604202c9 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -6,7 +6,6 @@ import * as projectServiceBaseLib from "./platform-project-service-base"; import { DeviceAndroidDebugBridge } from "../common/mobile/android/device-android-debug-bridge"; import { Configurations, LiveSyncPaths } from "../common/constants"; import { performanceLog } from ".././common/decorators"; -import { PreparePlatformData } from "./workflow/workflow-data-service"; export class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase { private static VALUES_DIRNAME = "values"; @@ -51,8 +50,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject appDestinationDirectoryPath: path.join(...appDestinationDirectoryArr), platformProjectService: this, projectRoot: projectRoot, - getBuildOutputPath: (buildConfig: IBuildConfig) => { - if (buildConfig.androidBundle) { + getBuildOutputPath: (buildOptions: IBuildOutputOptions) => { + if (buildOptions.androidBundle) { return path.join(projectRoot, constants.APP_FOLDER_NAME, constants.BUILD_DIR, constants.OUTPUTS_DIR, constants.BUNDLE_DIR); } @@ -130,7 +129,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject }; } - public async createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise { + public async createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData): Promise { if (semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) { this.$errors.failWithoutHelp(`The NativeScript CLI requires Android runtime ${AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE} or later to work properly.`); } @@ -167,9 +166,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject _.map(directoriesToClean, dir => this.$fs.deleteDirectory(dir)); } - public async interpolateData(projectData: IProjectData, signingOptions: any): Promise { + public async interpolateData(projectData: IProjectData): Promise { // Interpolate the apilevel and package - this.interpolateConfigurationFile(projectData, signingOptions); + this.interpolateConfigurationFile(projectData); const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); let stringsFilePath: string; @@ -199,7 +198,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - public interpolateConfigurationFile(projectData: IProjectData, preparePlatformData: PreparePlatformData): void { + public interpolateConfigurationFile(projectData: IProjectData): void { const manifestPath = this.getPlatformData(projectData).configurationFilePath; shell.sed('-i', /__PACKAGE__/, projectData.projectIdentifiers.android, manifestPath); } diff --git a/lib/services/build-artefacts-service.ts b/lib/services/build-artefacts-service.ts index c03a2353ea..c30d8612bf 100644 --- a/lib/services/build-artefacts-service.ts +++ b/lib/services/build-artefacts-service.ts @@ -1,5 +1,4 @@ import * as path from "path"; -import { BuildPlatformDataBase } from "./workflow/workflow-data-service"; export class BuildArtefactsService { constructor( @@ -8,9 +7,9 @@ export class BuildArtefactsService { private $logger: ILogger ) { } - public async getLatestApplicationPackagePath(platformData: IPlatformData, buildPlatformData: BuildPlatformDataBase, outputPath?: string): Promise { - outputPath = outputPath || platformData.getBuildOutputPath(buildPlatformData); - const applicationPackage = this.getLatestApplicationPackage(outputPath, platformData.getValidBuildOutputData(buildPlatformData)); + public async getLatestApplicationPackagePath(platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): Promise { + const outputPath = buildOutputOptions.outputPath || platformData.getBuildOutputPath(buildOutputOptions); + const applicationPackage = this.getLatestApplicationPackage(outputPath, platformData.getValidBuildOutputData(buildOutputOptions)); const packageFile = applicationPackage.packageName; if (!packageFile || !this.$fs.exists(packageFile)) { @@ -41,6 +40,24 @@ export class BuildArtefactsService { return []; } + public copyLastOutput(targetPath: string, platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): void { + targetPath = path.resolve(targetPath); + + const outputPath = buildOutputOptions.outputPath || platformData.getBuildOutputPath(buildOutputOptions); + const applicationPackage = this.getLatestApplicationPackage(outputPath, platformData.getValidBuildOutputData(buildOutputOptions)); + const packageFile = applicationPackage.packageName; + + this.$fs.ensureDirectoryExists(path.dirname(targetPath)); + + if (this.$fs.exists(targetPath) && this.$fs.getFsStats(targetPath).isDirectory()) { + const sourceFileName = path.basename(packageFile); + this.$logger.trace(`Specified target path: '${targetPath}' is directory. Same filename will be used: '${sourceFileName}'.`); + targetPath = path.join(targetPath, sourceFileName); + } + this.$fs.copyFile(packageFile, targetPath); + this.$logger.info(`Copied file '${packageFile}' to '${targetPath}'.`); + } + private getLatestApplicationPackage(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage { let packages = this.getAllApplicationPackages(buildOutputPath, validBuildOutputData); const packageExtName = path.extname(validBuildOutputData.packageNames[0]); diff --git a/lib/services/build-data-service.ts b/lib/services/build-data-service.ts new file mode 100644 index 0000000000..1934aa3307 --- /dev/null +++ b/lib/services/build-data-service.ts @@ -0,0 +1,14 @@ +import { AndroidBuildData, IOSBuildData } from "../data/build-data"; + +export class BuildDataService { + constructor(private $mobileHelper: Mobile.IMobileHelper) { } + + public getBuildData(projectDir: string, platform: string, data: any) { + if (this.$mobileHelper.isiOSPlatform(platform)) { + return new IOSBuildData(projectDir, platform, data); + } else if (this.$mobileHelper.isAndroidPlatform(platform)) { + return new AndroidBuildData(projectDir, platform, data); + } + } +} +$injector.register("buildDataService", BuildDataService); diff --git a/lib/services/build-info-file-service.ts b/lib/services/build-info-file-service.ts new file mode 100644 index 0000000000..cf6b78651a --- /dev/null +++ b/lib/services/build-info-file-service.ts @@ -0,0 +1,38 @@ +import * as path from "path"; + +const buildInfoFileName = ".nsbuildinfo"; + +export class BuildInfoFileService { + constructor( + private $fs: IFileSystem, + private $projectChangesService: IProjectChangesService + ) { } + + public saveBuildInfoFile(platformData: IPlatformData, buildInfoFileDirname: string): void { + const buildInfoFile = path.join(buildInfoFileDirname, buildInfoFileName); + + const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); + const buildInfo: IBuildInfo = { + prepareTime: prepareInfo.changesRequireBuildTime, + buildTime: new Date().toString() + }; + + this.$fs.writeJson(buildInfoFile, buildInfo); + } + + public getBuildInfoFromFile(platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions, buildOutputPath?: string): IBuildInfo { + buildOutputPath = buildOutputPath || platformData.getBuildOutputPath(buildOutputOptions); + const buildInfoFile = path.join(buildOutputPath, buildInfoFileName); + if (this.$fs.exists(buildInfoFile)) { + try { + const buildInfo = this.$fs.readJson(buildInfoFile); + return buildInfo; + } catch (e) { + return null; + } + } + + return null; + } +} +$injector.register("buildInfoFileService", BuildInfoFileService); diff --git a/lib/services/device/device-install-app-service.ts b/lib/services/device/device-install-app-service.ts index ac9964bcb4..cf5a6521f0 100644 --- a/lib/services/device/device-install-app-service.ts +++ b/lib/services/device/device-install-app-service.ts @@ -1,10 +1,10 @@ import { TrackActionNames, HASHES_FILE_NAME } from "../../constants"; import { BuildArtefactsService } from "../build-artefacts-service"; -import { BuildPlatformService } from "../platform/build-platform-service"; +import { BuildInfoFileService } from "../build-info-file-service"; import { MobileHelper } from "../../common/mobile/mobile-helper"; import * as helpers from "../../common/helpers"; import * as path from "path"; -import { BuildPlatformDataBase } from "../workflow/workflow-data-service"; +import { BuildData } from "../../data/build-data"; const buildInfoFileName = ".nsbuildinfo"; @@ -16,12 +16,17 @@ export class DeviceInstallAppService { private $fs: IFileSystem, private $logger: ILogger, private $mobileHelper: MobileHelper, - private $buildPlatformService: BuildPlatformService + private $buildInfoFileService: BuildInfoFileService, + private $projectDataService: IProjectDataService, + private $platformsData: IPlatformsData ) { } - public async installOnDevice(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildPlatformData: BuildPlatformDataBase, packageFile?: string, outputFilePath?: string): Promise { + public async installOnDevice(device: Mobile.IDevice, buildData: BuildData, packageFile?: string): Promise { this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); + const projectData = this.$projectDataService.getProjectData(buildData.projectDir); + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + await this.$analyticsService.trackEventActionInGoogleAnalytics({ action: TrackActionNames.Deploy, device, @@ -29,7 +34,7 @@ export class DeviceInstallAppService { }); if (!packageFile) { - packageFile = await this.$buildArtefactsService.getLatestApplicationPackagePath(platformData, buildPlatformData, outputFilePath); + packageFile = await this.$buildArtefactsService.getLatestApplicationPackagePath(platformData, buildData); } await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); @@ -37,6 +42,8 @@ export class DeviceInstallAppService { const platform = device.deviceInfo.platform.toLowerCase(); await device.applicationManager.reinstallApplication(projectData.projectIdentifiers[platform], packageFile); + const outputFilePath = buildData.outputPath; + await this.updateHashesOnDevice({ device, appIdentifier: projectData.projectIdentifiers[platform], @@ -44,11 +51,11 @@ export class DeviceInstallAppService { platformData }); - if (!buildPlatformData.release) { + if (!buildData.release) { const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - const options = buildPlatformData; + const options = buildData; options.buildForDevice = !device.isEmulator; - const buildInfoFilePath = outputFilePath || platformData.getBuildOutputPath(buildPlatformData); + const buildInfoFilePath = outputFilePath || platformData.getBuildOutputPath(buildData); const appIdentifier = projectData.projectIdentifiers[platform]; await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); @@ -57,10 +64,10 @@ export class DeviceInstallAppService { this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); } - public async installOnDeviceIfNeeded(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, buildPlatformData: BuildPlatformDataBase, packageFile?: string, outputFilePath?: string): Promise { - const shouldInstall = await this.shouldInstall(device, platformData, projectData, buildPlatformData); + public async installOnDeviceIfNeeded(device: Mobile.IDevice, buildData: BuildData, packageFile?: string): Promise { + const shouldInstall = await this.shouldInstall(device, buildData); if (shouldInstall) { - await this.installOnDevice(device, platformData, projectData, buildPlatformData, packageFile, outputFilePath); + await this.installOnDevice(device, buildData, packageFile); } } @@ -89,14 +96,16 @@ export class DeviceInstallAppService { await device.fileSystem.updateHashesOnDevice(hashes, appIdentifier); } - private async shouldInstall(device: Mobile.IDevice, platformData: IPlatformData, projectData: IProjectData, release: IRelease, outputPath?: string): Promise { + private async shouldInstall(device: Mobile.IDevice, buildData: BuildData, outputPath?: string): Promise { + const projectData = this.$projectDataService.getProjectData(buildData.projectDir); + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); const platform = device.deviceInfo.platform; if (!(await device.applicationManager.isApplicationInstalled(projectData.projectIdentifiers[platform.toLowerCase()]))) { return true; } const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); - const localBuildInfo = this.$buildPlatformService.getBuildInfoFromFile(platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); + const localBuildInfo = this.$buildInfoFileService.getBuildInfoFromFile(platformData, { buildForDevice: !device.isEmulator, release: buildData.release }, outputPath); return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index f2d75722f8..3c6cfc85e7 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -12,7 +12,8 @@ import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants } from "../constants"; -import { IOSBuildData, IOSPrepareData } from "./workflow/workflow-data-service"; +import { IOSBuildData } from "../data/build-data"; +import { IOSPrepareData } from "../data/prepare-data"; interface INativeSourceCodeGroup { name: string; @@ -139,23 +140,13 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ }; } - // TODO: Remove Promise, reason: readDirectory - unable until androidProjectService has async operations. - public async createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise { + public async createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData): Promise { this.$fs.ensureDirectoryExists(path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)); - if (config.pathToTemplate) { - // Copy everything except the template from the runtime - this.$fs.readDirectory(frameworkDir) - .filter(dirName => dirName.indexOf(IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER) === -1) - .forEach(dirName => shell.cp("-R", path.join(frameworkDir, dirName), this.getPlatformData(projectData).projectRoot)); - shell.cp("-rf", path.join(config.pathToTemplate, "*"), this.getPlatformData(projectData).projectRoot); - } else { - shell.cp("-R", path.join(frameworkDir, "*"), this.getPlatformData(projectData).projectRoot); - } - + shell.cp("-R", path.join(frameworkDir, "*"), this.getPlatformData(projectData).projectRoot); } //TODO: plamen5kov: revisit this method, might have unnecessary/obsolete logic - public async interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { + public async interpolateData(projectData: IProjectData): Promise { const projectRootFilePath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER); // Starting with NativeScript for iOS 1.6.0, the project Info.plist file resides not in the platform project, // but in the hello-world app template as a platform specific resource. @@ -184,7 +175,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.replaceFileContent(pbxprojFilePath, projectData); } - public interpolateConfigurationFile(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): void { + public interpolateConfigurationFile(projectData: IProjectData): void { return undefined; } @@ -193,28 +184,28 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ path.join(projectRoot, projectData.projectName)); } - public async buildProject(projectRoot: string, projectData: IProjectData, buildPlatformData: IOSBuildData): Promise { + public async buildProject(projectRoot: string, projectData: IProjectData, iOSBuildData: IOSBuildData): Promise { const platformData = this.getPlatformData(projectData); const handler = (data: any) => { this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); }; - if (buildPlatformData.buildForDevice) { - await this.$iOSSigningService.setupSigningForDevice(projectRoot, projectData, buildPlatformData); + if (iOSBuildData.buildForDevice) { + await this.$iOSSigningService.setupSigningForDevice(projectRoot, projectData, iOSBuildData); await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, - this.$xcodebuildService.buildForDevice(platformData, projectData, buildPlatformData)); - } else if (buildPlatformData.buildForAppStore) { + this.$xcodebuildService.buildForDevice(platformData, projectData, iOSBuildData)); + } else if (iOSBuildData.buildForAppStore) { await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, - this.$xcodebuildService.buildForAppStore(platformData, projectData, buildPlatformData)); + this.$xcodebuildService.buildForAppStore(platformData, projectData, iOSBuildData)); } else { await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, - this.$xcodebuildService.buildForSimulator(platformData, projectData, buildPlatformData)); + this.$xcodebuildService.buildForSimulator(platformData, projectData, iOSBuildData)); } this.validateApplicationIdentifier(projectData); @@ -282,13 +273,13 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return contentIsTheSame; } - public async prepareProject(projectData: IProjectData, preparePlatformData: IOSPrepareData): Promise { + public async prepareProject(projectData: IProjectData, prepareData: IOSPrepareData): Promise { const projectRoot = path.join(projectData.platformsDir, "ios"); - const provision = preparePlatformData && preparePlatformData.provision; - const teamId = preparePlatformData && preparePlatformData.teamId; + const provision = prepareData && prepareData.provision; + const teamId = prepareData && prepareData.teamId; if (provision) { - await this.$iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision, preparePlatformData.mobileProvisionData); + await this.$iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision, prepareData.mobileProvisionData); } if (teamId) { await this.$iOSSigningService.setupSigningFromTeam(projectRoot, projectData, teamId); @@ -513,8 +504,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return Promise.resolve(); } - public async checkForChanges(changesInfo: IProjectChangesInfo, signingOptions: IOSPrepareData, projectData: IProjectData): Promise { - const { provision, teamId } = signingOptions; + public async checkForChanges(changesInfo: IProjectChangesInfo, prepareData: IOSPrepareData, projectData: IProjectData): Promise { + const { provision, teamId } = prepareData; const hasProvision = provision !== undefined; const hasTeamId = teamId !== undefined; if (hasProvision || hasTeamId) { diff --git a/lib/services/ios/ios-signing-service.ts b/lib/services/ios/ios-signing-service.ts index ae92d8a28d..3669f53a6d 100644 --- a/lib/services/ios/ios-signing-service.ts +++ b/lib/services/ios/ios-signing-service.ts @@ -3,6 +3,7 @@ import * as mobileProvisionFinder from "ios-mobileprovision-finder"; import { BUILD_XCCONFIG_FILE_NAME, iOSAppResourcesFolderName } from "../../constants"; import * as helpers from "../../common/helpers"; import { IOSProvisionService } from "../ios-provision-service"; +import { IOSBuildData } from "../../data/build-data"; export class IOSSigningService implements IiOSSigningService { constructor( @@ -16,7 +17,7 @@ export class IOSSigningService implements IiOSSigningService { private $xcprojService: IXcprojService ) { } - public async setupSigningForDevice(projectRoot: string, projectData: IProjectData, buildConfig: IiOSBuildConfig): Promise { + public async setupSigningForDevice(projectRoot: string, projectData: IProjectData, iOSBuildData: IOSBuildData): Promise { const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData, projectRoot)); const signing = xcode.getSigning(projectData.projectName); @@ -29,8 +30,8 @@ export class IOSSigningService implements IiOSSigningService { if (hasProvisioningProfileInXCConfig && (!signing || signing.style !== "Manual")) { xcode.setManualSigningStyle(projectData.projectName); xcode.save(); - } else if (!buildConfig.provision && !(signing && signing.style === "Manual" && !buildConfig.teamId)) { - const teamId = await this.getDevelopmentTeam(projectData, projectRoot, buildConfig.teamId); + } else if (!iOSBuildData.provision && !(signing && signing.style === "Manual" && !iOSBuildData.teamId)) { + const teamId = await this.getDevelopmentTeam(projectData, projectRoot, iOSBuildData.teamId); await this.setupSigningFromTeam(projectRoot, projectData, teamId); } } diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index 5799a04d86..5772d5074e 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -1,90 +1,19 @@ -import { Device, FilesPayload } from "nativescript-preview-sdk"; -import { APP_RESOURCES_FOLDER_NAME, TrackActionNames, FILES_CHANGE_EVENT_NAME, INITIAL_SYNC_EVENT_NAME } from "../../../constants"; -import { PreviewAppLiveSyncEvents } from "./preview-app-constants"; -import { HmrConstants } from "../../../common/constants"; -import { stringify } from "../../../common/helpers"; +import { APP_RESOURCES_FOLDER_NAME } from "../../../constants"; import { EventEmitter } from "events"; import { performanceLog } from "../../../common/decorators"; -import { WorkflowDataService } from "../../workflow/workflow-data-service"; -import { PlatformWatcherService } from "../../platform/platform-watcher-service"; +import { PreviewAppEmitter } from "../../../preview-app-emitter"; export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewAppLiveSyncService { - private deviceInitializationPromise: IDictionary> = {}; - private promise = Promise.resolve(); - constructor( - private $analyticsService: IAnalyticsService, - private $errors: IErrors, - private $hmrStatusService: IHmrStatusService, private $logger: ILogger, - private $platformWatcherService: PlatformWatcherService, - private $previewSdkService: IPreviewSdkService, + private $previewAppEmitter: PreviewAppEmitter, private $previewAppFilesService: IPreviewAppFilesService, private $previewAppPluginsService: IPreviewAppPluginsService, private $previewDevicesService: IPreviewDevicesService, - private $workflowDataService: WorkflowDataService + private $previewSdkService: IPreviewSdkService, ) { super(); } - @performanceLog() - public async initialize(data: IPreviewAppLiveSyncData): Promise { - await this.$previewSdkService.initialize(data.projectDir, async (device: Device) => { - try { - if (!device) { - this.$errors.failWithoutHelp("Sending initial preview files without a specified device is not supported."); - } - - if (this.deviceInitializationPromise[device.id]) { - return this.deviceInitializationPromise[device.id]; - } - - if (device.uniqueId) { - await this.$analyticsService.trackEventActionInGoogleAnalytics({ - action: TrackActionNames.PreviewAppData, - platform: device.platform, - additionalData: device.uniqueId - }); - } - - await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); - - this.$platformWatcherService.on(FILES_CHANGE_EVENT_NAME, async (filesChangeData: IFilesChangeEventData) => { - await this.onWebpackCompilationComplete(data, filesChangeData.hmrData, filesChangeData.files, device.platform); - }); - - this.$platformWatcherService.on(INITIAL_SYNC_EVENT_NAME, async (initialSyncData: IInitialSyncEventData) => { - this.deviceInitializationPromise[device.id] = this.getInitialFilesForPlatformSafe(data, device.platform); - }); - - const { nativePlatformData, projectData, preparePlatformData } = this.$workflowDataService.createWorkflowData(device.platform.toLowerCase(), data.projectDir, data); - - // Setup externals - if (!preparePlatformData.env) { preparePlatformData.env = {}; } - preparePlatformData.env.externals = this.$previewAppPluginsService.getExternalPlugins(device); - - // skipNativePrepare so no native watcher is started - preparePlatformData.nativePrepare = { skipNativePrepare: true }; - - await this.$platformWatcherService.startWatchers(nativePlatformData, projectData, preparePlatformData); - - try { - const payloads = await this.deviceInitializationPromise[device.id]; - return payloads; - } finally { - this.deviceInitializationPromise[device.id] = null; - } - } catch (error) { - this.$logger.trace(`Error while sending files on device ${device && device.id}. Error is`, error); - this.emit(PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, { - error, - data, - platform: device.platform, - deviceId: device.id - }); - } - }); - } - @performanceLog() public async syncFiles(data: IPreviewAppLiveSyncData, filesToSync: string[], filesToRemove: string[]): Promise { this.showWarningsForNativeFiles(filesToSync); @@ -104,24 +33,7 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA } } - public async stopLiveSync(): Promise { - this.$previewSdkService.stop(); - this.$previewDevicesService.updateConnectedDevices([]); - } - - private async getInitialFilesForPlatformSafe(data: IPreviewAppLiveSyncData, platform: string): Promise { - this.$logger.info(`Start sending initial files for platform ${platform}.`); - - try { - const payloads = this.$previewAppFilesService.getInitialFilesPayload(data, platform); - this.$logger.info(`Successfully sent initial files for platform ${platform}.`); - return payloads; - } catch (err) { - 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 { + 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) { @@ -131,40 +43,10 @@ export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewA } } 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, - platform, - deviceId - }); + this.$previewAppEmitter.emitPreviewAppLiveSyncError(data, deviceId, error); } } - @performanceLog() - private async onWebpackCompilationComplete(data: IPreviewAppLiveSyncData, hmrData: IPlatformHmrData, files: string[], platform: string) { - await this.promise - .then(async () => { - const platformHmrData = _.cloneDeep(hmrData); - - this.promise = this.syncFilesForPlatformSafe(data, { filesToSync: files }, platform); - await this.promise; - - if (data.useHotModuleReload && platformHmrData.hash) { - const devices = this.$previewDevicesService.getDevicesForPlatform(platform); - - await Promise.all(_.map(devices, async (previewDevice: Device) => { - const status = await this.$hmrStatusService.getHmrStatus(previewDevice.id, platformHmrData.hash); - if (status === HmrConstants.HMR_ERROR_STATUS) { - const originalUseHotModuleReload = data.useHotModuleReload; - data.useHotModuleReload = false; - await this.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id ); - data.useHotModuleReload = originalUseHotModuleReload; - } - })); - } - }); - } - 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.`)); diff --git a/lib/services/local-build-service.ts b/lib/services/local-build-service.ts deleted file mode 100644 index ae8618d1a0..0000000000 --- a/lib/services/local-build-service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { EventEmitter } from "events"; -import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE } from "../constants"; -import { WorkflowDataService } from "./workflow/workflow-data-service"; -import { BuildPlatformService } from "./platform/build-platform-service"; - -export class LocalBuildService extends EventEmitter implements ILocalBuildService { - constructor( - private $errors: IErrors, - private $mobileHelper: Mobile.IMobileHelper, - private $platformsData: IPlatformsData, - private $buildPlatformService: BuildPlatformService, - private $projectDataService: IProjectDataService, - private $workflowDataService: WorkflowDataService - ) { super(); } - - public async build(platform: string, platformBuildOptions: IPlatformBuildData): Promise { - if (this.$mobileHelper.isAndroidPlatform(platform) && platformBuildOptions.release && (!platformBuildOptions.keyStorePath || !platformBuildOptions.keyStorePassword || !platformBuildOptions.keyStoreAlias || !platformBuildOptions.keyStoreAliasPassword)) { - this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); - } - - const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(platform, platformBuildOptions.projectDir, platformBuildOptions); - - const result = await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData); - - return result; - } - - public async cleanNativeApp(data: ICleanNativeAppData): Promise { - const projectData = this.$projectDataService.getProjectData(data.projectDir); - const platformData = this.$platformsData.getPlatformData(data.platform, projectData); - await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); - } -} - -$injector.register("localBuildService", LocalBuildService); diff --git a/lib/services/platform/add-platform-service.ts b/lib/services/platform/add-platform-service.ts index 127fdad01f..af412b5c35 100644 --- a/lib/services/platform/add-platform-service.ts +++ b/lib/services/platform/add-platform-service.ts @@ -1,69 +1,18 @@ import * as path from "path"; import * as temp from "temp"; import { PROJECT_FRAMEWORK_FOLDER_NAME, NativePlatformStatus } from "../../constants"; -import { AddPlatformData } from "../workflow/workflow-data-service"; import { performanceLog } from "../../common/decorators"; export class AddPlatformService { constructor( - private $errors: IErrors, private $fs: IFileSystem, - private $logger: ILogger, - private $packageInstallationManager: IPackageInstallationManager, private $pacoteService: IPacoteService, - private $platformsData: IPlatformsData, private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService, private $terminalSpinnerService: ITerminalSpinnerService ) { } - public async addPlatform(projectData: IProjectData, addPlatformData: AddPlatformData): Promise { - const { platformParam, frameworkPath, nativePrepare } = addPlatformData; - const [ platform, version ] = platformParam.toLowerCase().split("@"); - const platformData = this.$platformsData.getPlatformData(platform, projectData); - - this.$logger.trace(`Creating NativeScript project for the ${platformData.platformNameLowerCase} platform`); - this.$logger.trace(`Path: ${platformData.projectRoot}`); - this.$logger.trace(`Package: ${projectData.projectIdentifiers[platformData.platformNameLowerCase]}`); - this.$logger.trace(`Name: ${projectData.projectName}`); - - this.$logger.out("Copying template files..."); - - const packageToInstall = await this.getPackageToInstall(platformData, projectData, frameworkPath, version); - - const installedPlatformVersion = await this.addPlatformSafe(platformData, projectData, packageToInstall, nativePrepare); - - this.$fs.ensureDirectoryExists(path.join(projectData.platformsDir, platform)); - this.$logger.out(`Platform ${platform} successfully added. v${installedPlatformVersion}`); - } - - public async addPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, addPlatformData: AddPlatformData): Promise { - const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, addPlatformData.nativePrepare); - if (shouldAddPlatform) { - await this.addPlatform(projectData, addPlatformData); - } - } - - private async getPackageToInstall(platformData: IPlatformData, projectData: IProjectData, frameworkPath?: string, version?: string): Promise { - let result = null; - if (frameworkPath) { - if (!this.$fs.exists(frameworkPath)) { - this.$errors.fail(`Invalid frameworkPath: ${frameworkPath}. Please ensure the specified frameworkPath exists.`); - } - result = path.resolve(frameworkPath); - } else { - if (!version) { - version = this.getCurrentPlatformVersion(platformData.platformNameLowerCase, projectData) || - await this.$packageInstallationManager.getLatestCompatibleVersion(platformData.frameworkPackageName); - } - - result = `${platformData.frameworkPackageName}@${version}`; - } - - return result; - } - - private async addPlatformSafe(platformData: IPlatformData, projectData: IProjectData, packageToInstall: string, nativePrepare: INativePrepare): Promise { + public async addPlatformSafe(projectData: IProjectData, platformData: IPlatformData, packageToInstall: string, nativePrepare: INativePrepare): Promise { const spinner = this.$terminalSpinnerService.createSpinner(); try { @@ -98,26 +47,6 @@ export class AddPlatformService { return path.resolve(frameworkDir); } - // TODO: There is the same method in platformService. Consider to reuse it - private getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); - const version = currentPlatformData && currentPlatformData.version; - - return version; - } - - private shouldAddPlatform(platformData: IPlatformData, projectData: IProjectData, nativePrepare: INativePrepare): boolean { - const platformName = platformData.platformNameLowerCase; - const hasPlatformDirectory = this.$fs.exists(path.join(projectData.platformsDir, platformName)); - const shouldAddNativePlatform = !nativePrepare || !nativePrepare.skipNativePrepare; - const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); - const requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === NativePlatformStatus.requiresPlatformAdd; - const result = !hasPlatformDirectory || (shouldAddNativePlatform && requiresNativePlatformAdd); - - return !!result; - } - private async addJSPlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise { const frameworkPackageNameData = { version: frameworkVersion }; this.$projectDataService.setNSValue(projectData.projectDir, platformData.frameworkPackageName, frameworkPackageNameData); @@ -125,14 +54,12 @@ export class AddPlatformService { @performanceLog() private async addNativePlatform(platformData: IPlatformData, projectData: IProjectData, frameworkDirPath: string, frameworkVersion: string): Promise { - const config = {}; - const platformDir = path.join(projectData.platformsDir, platformData.normalizedPlatformName.toLowerCase()); this.$fs.deleteDirectory(platformDir); - await platformData.platformProjectService.createProject(path.resolve(frameworkDirPath), frameworkVersion, projectData, config); + await platformData.platformProjectService.createProject(path.resolve(frameworkDirPath), frameworkVersion, projectData); platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); - await platformData.platformProjectService.interpolateData(projectData, config); + await platformData.platformProjectService.interpolateData(projectData); platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: NativePlatformStatus.requiresPrepare }); } diff --git a/lib/services/platform/build-platform-service.ts b/lib/services/platform/build-platform-service.ts deleted file mode 100644 index 6a4abfa8d5..0000000000 --- a/lib/services/platform/build-platform-service.ts +++ /dev/null @@ -1,139 +0,0 @@ -import * as constants from "../../constants"; -import { Configurations } from "../../common/constants"; -import { attachAwaitDetach } from "../../common/helpers"; -import { BuildArtefactsService } from "../build-artefacts-service"; -import { BuildPlatformDataBase } from "../workflow/workflow-data-service"; -import { EventEmitter } from "events"; -import * as path from "path"; - -const buildInfoFileName = ".nsbuildinfo"; - -export class BuildPlatformService extends EventEmitter { - constructor( - private $analyticsService: IAnalyticsService, - private $buildArtefactsService: BuildArtefactsService, - private $fs: IFileSystem, - private $logger: ILogger, - private $mobileHelper: Mobile.IMobileHelper, - private $projectChangesService: IProjectChangesService - ) { super(); } - - public async buildPlatform(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: T): Promise { - this.$logger.out("Building project..."); - - const platform = platformData.platformNameLowerCase; - - const action = constants.TrackActionNames.Build; - const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildPlatformData && buildPlatformData.buildForDevice; - - await this.$analyticsService.trackEventActionInGoogleAnalytics({ - action, - isForDevice, - platform, - projectDir: projectData.projectDir, - additionalData: `${buildPlatformData.release ? Configurations.Release : Configurations.Debug}_${buildPlatformData.clean ? constants.BuildStates.Clean : constants.BuildStates.Incremental}` - }); - - if (buildPlatformData.clean) { - await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); - } - - const handler = (data: any) => { - this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); - this.$logger.printInfoMessageOnSameLine(data.data.toString()); - }; - - await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, platformData.platformProjectService, handler, platformData.platformProjectService.buildProject(platformData.projectRoot, projectData, buildPlatformData)); - - const buildInfoFileDirname = platformData.getBuildOutputPath(buildPlatformData); - this.saveBuildInfoFile(platformData, projectData, buildInfoFileDirname); - - this.$logger.out("Project successfully built."); - - const result = await this.$buildArtefactsService.getLatestApplicationPackagePath(platformData, buildPlatformData); - - // if (this.$options.copyTo) { - // this.$platformService.copyLastOutput(platform, this.$options.copyTo, buildPlatformData, this.$projectData); - // } else { - // this.$logger.info(`The build result is located at: ${outputPath}`); - // } - - return result; - } - - public async buildPlatformIfNeeded(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: T, outputPath?: string): Promise { - let result = null; - - outputPath = outputPath || platformData.getBuildOutputPath(buildPlatformData); - const shouldBuildPlatform = await this.shouldBuildPlatform(platformData, projectData, buildPlatformData, outputPath); - if (shouldBuildPlatform) { - result = await this.buildPlatform(platformData, projectData, buildPlatformData); - } - - return result; - } - - public saveBuildInfoFile(platformData: IPlatformData, projectData: IProjectData, buildInfoFileDirname: string): void { - const buildInfoFile = path.join(buildInfoFileDirname, buildInfoFileName); - - const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); - const buildInfo: IBuildInfo = { - prepareTime: prepareInfo.changesRequireBuildTime, - buildTime: new Date().toString() - }; - - this.$fs.writeJson(buildInfoFile, buildInfo); - } - - public getBuildInfoFromFile(platformData: IPlatformData, buildPlatformData: BuildPlatformDataBase, buildOutputPath?: string): IBuildInfo { - buildOutputPath = buildOutputPath || platformData.getBuildOutputPath(buildPlatformData); - const buildInfoFile = path.join(buildOutputPath, buildInfoFileName); - if (this.$fs.exists(buildInfoFile)) { - try { - const buildInfo = this.$fs.readJson(buildInfoFile); - return buildInfo; - } catch (e) { - return null; - } - } - - return null; - } - - private async shouldBuildPlatform(platformData: IPlatformData, projectData: IProjectData, buildPlatformData: BuildPlatformDataBase, outputPath: string): Promise { - if (buildPlatformData.release && this.$projectChangesService.currentChanges.hasChanges) { - return true; - } - - if (this.$projectChangesService.currentChanges.changesRequireBuild) { - return true; - } - - if (!this.$fs.exists(outputPath)) { - return true; - } - - const validBuildOutputData = platformData.getValidBuildOutputData(buildPlatformData); - const packages = this.$buildArtefactsService.getAllApplicationPackages(outputPath, validBuildOutputData); - if (packages.length === 0) { - return true; - } - - const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); - const buildInfo = this.getBuildInfoFromFile(platformData, buildPlatformData, outputPath); - if (!prepareInfo || !buildInfo) { - return true; - } - - if (buildPlatformData.clean) { - return true; - } - - if (prepareInfo.time === buildInfo.prepareTime) { - return false; - } - - return prepareInfo.changesRequireBuildTime !== buildInfo.prepareTime; - } -} -$injector.register("buildPlatformService", BuildPlatformService); diff --git a/lib/services/platform/platform-commands-service.ts b/lib/services/platform/platform-commands-service.ts index 1fddb9b9d3..31a414ddc3 100644 --- a/lib/services/platform/platform-commands-service.ts +++ b/lib/services/platform/platform-commands-service.ts @@ -2,12 +2,12 @@ import * as path from "path"; import * as semver from "semver"; import * as temp from "temp"; import * as constants from "../../constants"; -import { AddPlatformService } from "./add-platform-service"; import { PlatformValidationService } from "./platform-validation-service"; +import { AddPlatformController } from "../../controllers/add-platform-controller"; export class PlatformCommandsService implements IPlatformCommandsService { constructor( - private $addPlatformService: AddPlatformService, + private $addPlatformController: AddPlatformController, private $fs: IFileSystem, private $errors: IErrors, private $logger: ILogger, @@ -32,8 +32,11 @@ export class PlatformCommandsService implements IPlatformCommandsService { this.$errors.failWithoutHelp(`Platform ${platform} already added`); } - const addPlatformData = { platformParam: platform.toLowerCase(), frameworkPath }; - await this.$addPlatformService.addPlatform(projectData, addPlatformData); + await this.$addPlatformController.addPlatform({ + projectDir: projectData.projectDir, + platform, + frameworkPath, + }); } } @@ -85,7 +88,10 @@ export class PlatformCommandsService implements IPlatformCommandsService { if (hasPlatformDirectory) { await this.updatePlatform(platform, version, projectData); } else { - await this.$addPlatformService.addPlatform(projectData, { platformParam }); + await this.$addPlatformController.addPlatform({ + projectDir: projectData.projectDir, + platform: platformParam, + }); } } } @@ -168,8 +174,10 @@ export class PlatformCommandsService implements IPlatformCommandsService { let packageName = platformData.normalizedPlatformName.toLowerCase(); await this.removePlatforms([packageName], projectData); packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; - const addPlatformData = { platformParam: packageName, frameworkPath: null, nativePrepare: null}; - await this.$addPlatformService.addPlatform(projectData, addPlatformData); + await this.$addPlatformController.addPlatform({ + projectDir: projectData.projectDir, + platform: packageName + }); this.$logger.out("Successfully updated to version ", updateOptions.newVersion); } diff --git a/lib/services/platform/platform-watcher-service.ts b/lib/services/platform/platform-watcher-service.ts deleted file mode 100644 index 0f0dd734f7..0000000000 --- a/lib/services/platform/platform-watcher-service.ts +++ /dev/null @@ -1,121 +0,0 @@ -import * as child_process from "child_process"; -import * as choki from "chokidar"; -import { EventEmitter } from "events"; -import * as path from "path"; -import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME } from "../../constants"; -import { PreparePlatformData } from "../workflow/workflow-data-service"; -import { PreparePlatformService } from "./prepare-platform-service"; -import { WebpackCompilerService } from "../webpack/webpack-compiler-service"; - -interface IPlatformWatcherData { - webpackCompilerProcess: child_process.ChildProcess; - nativeFilesWatcher: choki.FSWatcher; -} - -export class PlatformWatcherService extends EventEmitter { - private watchersData: IDictionary> = {}; - private isInitialSyncEventEmitted = false; - private persistedFilesChangeEventData: IFilesChangeEventData[] = []; - - constructor( - private $logger: ILogger, - private $preparePlatformService: PreparePlatformService, - private $webpackCompilerService: WebpackCompilerService - ) { super(); } - - public async startWatchers(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - if (!this.watchersData[projectData.projectDir]) { - this.watchersData[projectData.projectDir] = {}; - } - - if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase]) { - this.watchersData[projectData.projectDir][platformData.platformNameLowerCase] = { - nativeFilesWatcher: null, - webpackCompilerProcess: null - }; - } - - await this.startJSWatcherWithPrepare(platformData, projectData, { env: preparePlatformData.env }); // -> start watcher + initial compilation - const hasNativeChanges = await this.startNativeWatcherWithPrepare(platformData, projectData, preparePlatformData); // -> start watcher + initial prepare - - this.emitInitialSyncEvent({ platform: platformData.platformNameLowerCase, hasNativeChanges }); - } - - public stopWatchers(projectDir: string, platform: string): void { - const platformLowerCase = platform.toLowerCase(); - - if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) { - this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher.close(); - this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher = null; - } - - if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess) { - this.$webpackCompilerService.stopWebpackCompiler(platform); - this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess = null; - } - } - - private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { - if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { - this.$webpackCompilerService.on("webpackEmittedFiles", data => { - this.emitFilesChangeEvent({ ...data, hasNativeChanges: false, platform: platformData.platformNameLowerCase }); - }); - - const childProcess = await this.$webpackCompilerService.compileWithWatch(platformData, projectData, config); - this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess = childProcess; - } - } - - private async startNativeWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - if ((preparePlatformData.nativePrepare && preparePlatformData.nativePrepare.skipNativePrepare) || this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher) { - return false; - } - - const patterns = [ - path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), - `node_modules/**/platforms/${platformData.platformNameLowerCase}/` - ]; - const watcherOptions: choki.WatchOptions = { - ignoreInitial: true, - cwd: projectData.projectDir, - awaitWriteFinish: { - pollInterval: 100, - stabilityThreshold: 500 - }, - ignored: ["**/.*", ".*"] // hidden files - }; - const watcher = choki.watch(patterns, watcherOptions) - .on("all", async (event: string, filePath: string) => { - filePath = path.join(projectData.projectDir, filePath); - this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); - this.emitFilesChangeEvent({ files: [], hmrData: null, hasNativeChanges: true, platform: platformData.platformNameLowerCase }); - }); - - this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher; - - const hasNativeChanges = await this.$preparePlatformService.prepareNativePlatform(platformData, projectData, preparePlatformData); - - return hasNativeChanges; - } - - private emitFilesChangeEvent(filesChangeEventData: IFilesChangeEventData) { - if (this.isInitialSyncEventEmitted) { - this.emit(FILES_CHANGE_EVENT_NAME, filesChangeEventData); - } else { - this.persistedFilesChangeEventData.push(filesChangeEventData); - } - } - - private emitInitialSyncEvent(initialSyncEventData: IInitialSyncEventData) { - const hasPersistedDataWithNativeChanges = this.persistedFilesChangeEventData.find(data => data.platform === initialSyncEventData.platform && data.hasNativeChanges); - if (hasPersistedDataWithNativeChanges) { - initialSyncEventData.hasNativeChanges = true; - } - - // TODO: Consider how to handle changed js files between initialSyncEvent and initial preperation of the project - - this.emit(INITIAL_SYNC_EVENT_NAME, initialSyncEventData); - this.isInitialSyncEventEmitted = true; - } -} -$injector.register("platformWatcherService", PlatformWatcherService); diff --git a/lib/services/platform/prepare-platform-service.ts b/lib/services/platform/prepare-native-platform-service.ts similarity index 80% rename from lib/services/platform/prepare-platform-service.ts rename to lib/services/platform/prepare-native-platform-service.ts index e4f2de32a4..713ea8059b 100644 --- a/lib/services/platform/prepare-platform-service.ts +++ b/lib/services/platform/prepare-native-platform-service.ts @@ -1,40 +1,29 @@ + +import { hook } from "../../common/helpers"; import { performanceLog } from "../../common/decorators"; -import { PreparePlatformData } from "../workflow/workflow-data-service"; import * as path from "path"; import { NativePlatformStatus, APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME } from "../../constants"; +import { PrepareData } from "../../data/prepare-data"; + +export class PrepareNativePlatformService { -export class PreparePlatformService { constructor( private $androidResourcesMigrationService: IAndroidResourcesMigrationService, private $fs: IFileSystem, - private $logger: ILogger, + public $hooksService: IHooksService, private $nodeModulesBuilder: INodeModulesBuilder, private $projectChangesService: IProjectChangesService, - private $webpackCompilerService: IWebpackCompilerService, ) { } @performanceLog() - public async preparePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - this.$logger.out("Preparing project..."); - - await this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, { watch: false, env: preparePlatformData.env }); - await this.prepareNativePlatform(platformData, projectData, preparePlatformData); - - this.$projectChangesService.savePrepareInfo(platformData); - - this.$logger.out(`Project successfully prepared (${platformData.platformNameLowerCase})`); - - return true; - } - - @performanceLog() - public async prepareNativePlatform(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { - const { nativePrepare, release } = preparePlatformData; + @hook('prepareNativeApp') + public async prepareNativePlatform(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { + const { nativePrepare, release } = prepareData; if (nativePrepare && nativePrepare.skipNativePrepare) { return false; } - const changesInfo = await this.$projectChangesService.checkForChanges(platformData, projectData, preparePlatformData); + const changesInfo = await this.$projectChangesService.checkForChanges(platformData, projectData, prepareData); const hasModulesChange = !changesInfo || changesInfo.modulesChanged; const hasConfigChange = !changesInfo || changesInfo.configChanged; @@ -52,7 +41,7 @@ export class PreparePlatformService { this.prepareAppResources(platformData, projectData); if (hasChangesRequirePrepare) { - await platformData.platformProjectService.prepareProject(projectData, preparePlatformData); + await platformData.platformProjectService.prepareProject(projectData, prepareData); } if (hasModulesChange) { @@ -64,7 +53,7 @@ export class PreparePlatformService { await platformData.platformProjectService.handleNativeDependenciesChange(projectData, { release }); } - platformData.platformProjectService.interpolateConfigurationFile(projectData, preparePlatformData); + platformData.platformProjectService.interpolateConfigurationFile(projectData); this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: NativePlatformStatus.alreadyPrepared }); return hasChanges; @@ -126,4 +115,4 @@ export class PreparePlatformService { } } } -$injector.register("preparePlatformService", PreparePlatformService); +$injector.register("prepareNativePlatformService", PrepareNativePlatformService); diff --git a/lib/services/prepare-data-service.ts b/lib/services/prepare-data-service.ts new file mode 100644 index 0000000000..07b2170396 --- /dev/null +++ b/lib/services/prepare-data-service.ts @@ -0,0 +1,14 @@ +import { IOSPrepareData, AndroidPrepareData } from "../data/prepare-data"; + +export class PrepareDataService { + constructor(private $mobileHelper: Mobile.IMobileHelper) { } + + public getPrepareData(projectDir: string, platform: string, data: any) { + if (this.$mobileHelper.isiOSPlatform(platform)) { + return new IOSPrepareData(projectDir, platform, data); + } else if (this.$mobileHelper.isAndroidPlatform(platform)) { + return new AndroidPrepareData(projectDir, platform, data); + } + } +} +$injector.register("prepareDataService", PrepareDataService); diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 3ca01c3050..70605294ab 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -1,7 +1,7 @@ import * as path from "path"; import { NODE_MODULES_FOLDER_NAME, NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME } from "../constants"; import { getHash, hook } from "../common/helpers"; -import { PreparePlatformData } from "./workflow/workflow-data-service"; +import { PrepareData } from "../data/prepare-data"; const prepareInfoFileName = ".nsprepareinfo"; @@ -55,9 +55,9 @@ export class ProjectChangesService implements IProjectChangesService { } @hook("checkForChanges") - public async checkForChanges(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + public async checkForChanges(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { this._changesInfo = new ProjectChangesInfo(); - const isNewPrepareInfo = await this.ensurePrepareInfo(platformData, projectData, preparePlatformData); + const isNewPrepareInfo = await this.ensurePrepareInfo(platformData, projectData, prepareData); if (!isNewPrepareInfo) { this._newFiles = 0; @@ -94,16 +94,16 @@ export class ProjectChangesService implements IProjectChangesService { this.$logger.trace(`Set value of configChanged to ${this._changesInfo.configChanged}`); } - if (!preparePlatformData.nativePrepare || !preparePlatformData.nativePrepare.skipNativePrepare) { - await platformData.platformProjectService.checkForChanges(this._changesInfo, preparePlatformData, projectData); + if (!prepareData.nativePrepare || !prepareData.nativePrepare.skipNativePrepare) { + await platformData.platformProjectService.checkForChanges(this._changesInfo, prepareData, projectData); } - if (preparePlatformData.release !== this._prepareInfo.release) { - this.$logger.trace(`Setting all setting to true. Current options are: `, preparePlatformData, " old prepare info is: ", this._prepareInfo); + if (prepareData.release !== this._prepareInfo.release) { + this.$logger.trace(`Setting all setting to true. Current options are: `, prepareData, " old prepare info is: ", this._prepareInfo); this._changesInfo.appResourcesChanged = true; this._changesInfo.modulesChanged = true; this._changesInfo.configChanged = true; - this._prepareInfo.release = preparePlatformData.release; + this._prepareInfo.release = prepareData.release; } if (this._changesInfo.packageChanged) { this.$logger.trace("Set modulesChanged to true as packageChanged is true"); @@ -167,7 +167,7 @@ export class ProjectChangesService implements IProjectChangesService { this.savePrepareInfo(platformData); } - private async ensurePrepareInfo(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + private async ensurePrepareInfo(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { this._prepareInfo = this.getPrepareInfo(platformData); if (this._prepareInfo) { const prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName); @@ -176,12 +176,12 @@ export class ProjectChangesService implements IProjectChangesService { return false; } - const nativePlatformStatus = (!preparePlatformData.nativePrepare || !preparePlatformData.nativePrepare.skipNativePrepare) ? + const nativePlatformStatus = (!prepareData.nativePrepare || !prepareData.nativePrepare.skipNativePrepare) ? NativePlatformStatus.requiresPrepare : NativePlatformStatus.requiresPlatformAdd; this._prepareInfo = { time: "", nativePlatformStatus, - release: preparePlatformData.release, + release: prepareData.release, changesRequireBuild: true, projectFileHash: this.getProjectFileStrippedHash(projectData.projectDir, platformData), changesRequireBuildTime: null diff --git a/lib/services/run-on-devices-data-service.ts b/lib/services/run-on-devices-data-service.ts index f1d024c483..f116f4c600 100644 --- a/lib/services/run-on-devices-data-service.ts +++ b/lib/services/run-on-devices-data-service.ts @@ -1,4 +1,5 @@ export class RunOnDevicesDataService { + // TODO: Rename liveSyncProcessesInfo private liveSyncProcessesInfo: IDictionary = {}; public getDataForProject(projectDir: string): ILiveSyncProcessInfo { @@ -15,8 +16,8 @@ export class RunOnDevicesDataService { return currentDescriptors || []; } - public hasDeviceDescriptors(projectDir: string) { - return this.liveSyncProcessesInfo[projectDir].deviceDescriptors.length; + public hasDeviceDescriptors(projectDir: string): boolean { + return !!this.liveSyncProcessesInfo[projectDir].deviceDescriptors.length; } public persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], platforms: string[]): void { diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index f64d33a9a3..27cb6eebfc 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -1,7 +1,7 @@ import * as constants from "../constants"; import * as path from 'path'; import * as os from 'os'; -import { MainController } from "../controllers/main-controller"; +import { RunOnDevicesController } from "../controllers/run-on-devices-controller"; interface IKarmaConfigOptions { debugBrk: boolean; @@ -13,7 +13,7 @@ export class TestExecutionService implements ITestExecutionService { private static SOCKETIO_JS_FILE_NAME = `node_modules/${constants.TEST_RUNNER_NAME}/socket.io.js`; constructor( - private $mainController: MainController, + private $runOnDevicesController: RunOnDevicesController, private $httpClient: Server.IHttpClient, private $config: IConfiguration, private $logger: ILogger, @@ -56,7 +56,11 @@ export class TestExecutionService implements ITestExecutionService { // Prepare the project AFTER the TestExecutionService.CONFIG_FILE_NAME file is created in node_modules // so it will be sent to device. - await this.$mainController.runOnDevices(liveSyncInfo.projectDir, deviceDescriptors, liveSyncInfo); + await this.$runOnDevicesController.runOnDevices({ + projectDir: liveSyncInfo.projectDir, + liveSyncInfo, + deviceDescriptors + }); }; karmaRunner.on("message", (karmaData: any) => { diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 5ba1e14736..1c106b9562 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -1,18 +1,22 @@ import * as path from "path"; import * as child_process from "child_process"; import { EventEmitter } from "events"; +import { performanceLog } from "../../common/decorators"; +import { hook } from "../../common/helpers"; +import { WEBPACK_COMPILATION_COMPLETE } from "../../constants"; export class WebpackCompilerService extends EventEmitter implements IWebpackCompilerService { private webpackProcesses: IDictionary = {}; constructor( private $childProcess: IChildProcess, + public $hooksService: IHooksService, private $logger: ILogger, - private $projectData: IProjectData + private $projectData: IProjectData, ) { super(); } public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { if (this.webpackProcesses[platformData.platformNameLowerCase]) { resolve(); return; @@ -20,7 +24,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp let isFirstWebpackWatchCompilation = true; config.watch = true; - const childProcess = this.startWebpackProcess(platformData, projectData, config); + const childProcess = await this.startWebpackProcess(platformData, projectData, config); childProcess.on("message", (message: any) => { if (message === "Webpack compilation complete.") { @@ -34,12 +38,12 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return; } - const files = message.emittedFiles + const result = this.getUpdatedEmittedFiles(message.emittedFiles); + + const files = result.emittedFiles .filter((file: string) => file.indexOf("App_Resources") === -1) .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)); - const result = this.getUpdatedEmittedFiles(message.emittedFiles); - const data = { files, hmrData: { @@ -48,7 +52,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp } }; - this.emit("webpackEmittedFiles", data); + this.emit(WEBPACK_COMPILATION_COMPLETE, data); } }); @@ -67,13 +71,13 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp } public async compileWithoutWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { if (this.webpackProcesses[platformData.platformNameLowerCase]) { resolve(); return; } - const childProcess = this.startWebpackProcess(platformData, projectData, config); + const childProcess = await this.startWebpackProcess(platformData, projectData, config); childProcess.on("close", (arg: any) => { const exitCode = typeof arg === "number" ? arg : arg && arg.code; console.log("=========== WEBPACK EXIT WITH CODE ========== ", exitCode); @@ -96,9 +100,11 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp } } - private startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): child_process.ChildProcess { + @performanceLog() + @hook('prepareJSApp') + private async startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { const envData = this.buildEnvData(platformData.platformNameLowerCase, config.env); - const envParams = this.buildEnvCommandLineParams(envData); + const envParams = this.buildEnvCommandLineParams(envData, platformData); const args = [ path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), @@ -135,7 +141,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return envData; } - private buildEnvCommandLineParams(envData: any) { + private buildEnvCommandLineParams(envData: any, platformData: IPlatformData) { const envFlagNames = Object.keys(envData); // const snapshotEnvIndex = envFlagNames.indexOf("snapshot"); // if (snapshotEnvIndex > -1 && !utils.shouldSnapshot(config)) { diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index d17141eae8..a6b771b64f 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -1,5 +1,6 @@ import { EventEmitter } from "events"; -import { PreparePlatformData, BuildPlatformDataBase, WorkflowData } from "../workflow/workflow-data-service"; +import { BuildData } from "../../data/build-data"; +import { PrepareData } from "../../data/prepare-data"; declare global { interface IWebpackCompilerService extends EventEmitter { @@ -17,7 +18,7 @@ declare global { } interface IProjectChangesService { - checkForChanges(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise; + checkForChanges(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise; getPrepareInfoFilePath(platformData: IPlatformData): string; getPrepareInfo(platformData: IPlatformData): IPrepareInfo; savePrepareInfo(platformData: IPlatformData): void; @@ -32,7 +33,7 @@ declare global { hasNativeChanges: boolean; } - interface IInitialSyncEventData { + interface IPrepareOutputData { platform: string; hasNativeChanges: boolean; } @@ -44,9 +45,9 @@ declare global { interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectServiceBase { getPlatformData(projectData: IProjectData): IPlatformData; validate(projectData: IProjectData, options: IOptions, notConfiguredEnvOptions?: INotConfiguredEnvOptions): Promise; - createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise; - interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; - interpolateConfigurationFile(projectData: IProjectData, preparePlatformData: PreparePlatformData): void; + createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData): Promise; + interpolateData(projectData: IProjectData): Promise; + interpolateConfigurationFile(projectData: IProjectData): void; /** * Executes additional actions after native project is created. @@ -64,7 +65,7 @@ declare global { */ validateOptions(projectId?: string, provision?: true | string, teamId?: true | string): Promise; - buildProject(projectRoot: string, projectData: IProjectData, buildConfig: T): Promise; + buildProject(projectRoot: string, projectData: IProjectData, buildConfig: T): Promise; /** * Prepares images in Native project (for iOS). @@ -72,7 +73,7 @@ declare global { * @param {any} platformSpecificData Platform specific data required for project preparation. * @returns {void} */ - prepareProject(projectData: IProjectData, preparePlatformData: T): Promise; + prepareProject(projectData: IProjectData, prepareData: T): Promise; /** * Prepares App_Resources in the native project by clearing data from other platform and applying platform specific rules. @@ -148,7 +149,7 @@ declare global { * Check the current state of the project, and validate against the options. * If there are parts in the project that are inconsistent with the desired options, marks them in the changeset flags. */ - checkForChanges(changeset: IProjectChangesInfo, preparePlatformData: T, projectData: IProjectData): Promise; + checkForChanges(changeset: IProjectChangesInfo, prepareData: T, projectData: IProjectData): Promise; /** * Get the deployment target's version diff --git a/lib/services/workflow/workflow-data-service.ts b/lib/services/workflow/workflow-data-service.ts deleted file mode 100644 index 9135e3ee6b..0000000000 --- a/lib/services/workflow/workflow-data-service.ts +++ /dev/null @@ -1,124 +0,0 @@ -export type AddPlatformData = Pick & Partial> & Partial>; -export type PreparePlatformData = Pick & Pick; -export type IOSPrepareData = PreparePlatformData & Pick & Pick; - -export class BuildPlatformDataBase { - constructor(protected options: IOptions | any) { } - - public release = this.options.release; - public clean = this.options.clean; - public device = this.options.device; - public iCloudContainerEnvironment = this.options.iCloudContainerEnvironment; - public buildForDevice = this.options.forDevice; - public buildOutputStdio = this.options.buildOutputStdio || "inherit"; -} - -export class IOSBuildData extends BuildPlatformDataBase { - constructor(options: IOptions) { super(options); } - - public teamId = this.options.teamId; - public provision = this.options.provision; - public buildForAppStore = this.options.buildForAppStore; -} - -export class AndroidBuildData extends BuildPlatformDataBase { - constructor(options: IOptions) { super(options); } - - public keyStoreAlias = this.options.keyStoreAlias; - public keyStorePath = this.options.keyStorePath; - public keyStoreAliasPassword = this.options.keyStoreAliasPassword; - public keyStorePassword = this.options.keyStorePassword; - public androidBundle = this.options.aab; -} - -export class DeployPlatformData { - constructor(private options: IOptions) { } - - public clean = this.options.clean; - public release = this.options.release; - public forceInstall = true; -} - -export class WorkflowDataService { - constructor( - private $injector: IInjector, - private $projectDataService: IProjectDataService, - ) { } - - private get $platformsData(): IPlatformsData { - return this.$injector.resolve("platformsData"); - } - - public createWorkflowData(platform: string, projectDir: string, options: IOptions | any): WorkflowData { - const projectData = this.$projectDataService.getProjectData(projectDir); - const nativePlatformData = this.$platformsData.getPlatformData(platform, projectData); - - const data: IDictionary = { - ios: { - projectData, - nativePlatformData, - addPlatformData: this.getAddPlatformData("ios", options), - preparePlatformData: this.getIOSPrepareData(options), - buildPlatformData: new IOSBuildData(options), - deployPlatformData: new DeployPlatformData(options), - liveSyncData: {}, - restartOnDeviceData: {} - }, - android: { - projectData, - nativePlatformData, - addPlatformData: this.getAddPlatformData("android", options), - preparePlatformData: this.getPreparePlatformData(options), - buildPlatformData: new AndroidBuildData(options), - deployPlatformData: new DeployPlatformData(options), - liveSyncData: {}, - restartOnDeviceData: {} - } - }; - - return data[platform.toLowerCase()]; - } - - private getAddPlatformData(platform: string, options: IOptions | any) { - const result = { - frameworkPath: options.frameworkPath, - nativePrepare: options.nativePrepare, - platformParam: options.platformParam || platform, - }; - - return result; - } - - private getPreparePlatformData(options: IOptions | any) { - const result = { - env: { ...options.env, hmr: options.hmr || options.useHotModuleReload }, - release: options.release, - nativePrepare: options.nativePrepare - }; - - return result; - } - - private getIOSPrepareData(options: IOptions | any) { - const result = { - ...this.getPreparePlatformData(options), - teamId: options.teamId, - provision: options.provision, - mobileProvisionData: options.mobileProvisionData - }; - - return result; - } -} -$injector.register("workflowDataService", WorkflowDataService); - -export class WorkflowData { - public projectData: IProjectData; - public nativePlatformData: IPlatformData; - public addPlatformData: AddPlatformData; - public preparePlatformData: PreparePlatformData; - public buildPlatformData: any; - public deployPlatformData: DeployPlatformData; - public liveSyncData: any; - public restartOnDeviceData: any; -} diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index 0d1143dfa8..a42d8df47d 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -1,5 +1,6 @@ export class NodeModulesBuilder implements INodeModulesBuilder { constructor( + private $logger: ILogger, private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, private $pluginsService: IPluginsService ) { } @@ -16,6 +17,7 @@ export class NodeModulesBuilder implements INodeModulesBuilder { const dependency = dependencies[dependencyKey]; const isPlugin = !!dependency.nativescript; if (isPlugin) { + this.$logger.debug(`Successfully prepared plugin ${dependency.name} for ${platformData.normalizedPlatformName.toLowerCase()}.`); const pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); await this.$pluginsService.preparePluginNativeCode(pluginData, platformData.normalizedPlatformName.toLowerCase(), projectData); } diff --git a/test/controllers/add-platform-controller.ts b/test/controllers/add-platform-controller.ts new file mode 100644 index 0000000000..ab119fade9 --- /dev/null +++ b/test/controllers/add-platform-controller.ts @@ -0,0 +1,115 @@ +import { InjectorStub, PacoteServiceStub } from "../stubs"; +import { AddPlatformController } from "../../lib/controllers/add-platform-controller"; +import { AddPlatformService } from "../../lib/services/platform/add-platform-service"; +import { assert } from "chai"; +import { format } from "util"; +import { AddPlaformErrors } from "../../lib/constants"; + +let actualMessage: string = null; +const latestFrameworkVersion = "5.3.1"; +let extractedPackageFromPacote: string = null; + +function createInjector(data?: { latestFrameworkVersion: string }) { + const version = (data && data.latestFrameworkVersion) || latestFrameworkVersion; + + const injector = new InjectorStub(); + injector.register("addPlatformController", AddPlatformController); + injector.register("addPlatformService", AddPlatformService); + injector.register("pacoteService", PacoteServiceStub); + + injector.register("pacoteService", { + extractPackage: async (name: string): Promise => { extractedPackageFromPacote = name; } + }); + + const logger = injector.resolve("logger"); + logger.out = (message: string) => actualMessage = message; + + const packageInstallationManager = injector.resolve("packageInstallationManager"); + packageInstallationManager.getLatestCompatibleVersion = async () => version; + + const fs = injector.resolve("fs"); + fs.readJson = () => ({ version }); + + return injector; +} + +const projectDir = "/my/test/dir"; + +describe("AddPlatformController", () => { + const testCases = [ + { + name: "should add the platform (tns platform add @4.2.1)", + latestFrameworkVersion: "4.2.1" + }, + { + name: "should add the latest compatible version (tns platform add )", + latestFrameworkVersion, + getPlatformParam: (platform: string) => `${platform}@${latestFrameworkVersion}` + }, + { + name: "should add the platform when --frameworkPath is provided", + frameworkPath: "/my/path/to/framework.tgz", + latestFrameworkVersion: "5.4.0" + } + ]; + + afterEach(() => { + actualMessage = null; + }); + + _.each(testCases, testCase => { + _.each(["ios", "android"], platform => { + it(`${testCase.name} for ${platform} platform`, async () => { + const injector = createInjector({ latestFrameworkVersion: testCase.latestFrameworkVersion }); + + const platformParam = testCase.getPlatformParam ? testCase.getPlatformParam(platform) : platform; + const addPlatformController: AddPlatformController = injector.resolve("addPlatformController"); + await addPlatformController.addPlatform({ projectDir, platform: platformParam, frameworkPath: testCase.frameworkPath }); + + const expectedMessage = `Platform ${platform} successfully added. v${testCase.latestFrameworkVersion}`; + assert.deepEqual(actualMessage, expectedMessage); + }); + }); + }); + + _.each(["ios", "android"], platform => { + it(`should fail when path passed frameworkPath does not exist for ${platform}`, async () => { + const frameworkPath = "invalidPath"; + const errorMessage = format(AddPlaformErrors.InvalidFrameworkPathStringFormat, frameworkPath); + + const injector = createInjector(); + const fs = injector.resolve("fs"); + fs.exists = (filePath: string) => filePath !== frameworkPath; + + const addPlatformController: AddPlatformController = injector.resolve("addPlatformController"); + + await assert.isRejected(addPlatformController.addPlatform({ projectDir, platform, frameworkPath }), errorMessage); + }); + it(`should respect platform version in package.json's nativescript key for ${platform}`, async () => { + const version = "2.5.0"; + + const injector = createInjector(); + + const projectDataService = injector.resolve("projectDataService"); + projectDataService.getNSValue = () => ({ version }); + + const addPlatformController: AddPlatformController = injector.resolve("addPlatformController"); + await addPlatformController.addPlatform({ projectDir, platform }); + + const expectedPackageToAdd = `tns-${platform}@${version}`; + assert.deepEqual(extractedPackageFromPacote, expectedPackageToAdd); + }); + it(`should install latest platform if no information found in package.json's nativescript key for ${platform}`, async () => { + const injector = createInjector(); + + const projectDataService = injector.resolve("projectDataService"); + projectDataService.getNSValue = () => null; + + const addPlatformController: AddPlatformController = injector.resolve("addPlatformController"); + await addPlatformController.addPlatform({ projectDir, platform }); + + const expectedPackageToAdd = `tns-${platform}@${latestFrameworkVersion}`; + assert.deepEqual(extractedPackageFromPacote, expectedPackageToAdd); + }); + }); +}); diff --git a/test/controllers/main-controller.ts b/test/controllers/main-controller.ts deleted file mode 100644 index def4d9e6b2..0000000000 --- a/test/controllers/main-controller.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { Yok } from "../../lib/common/yok"; -import { assert } from "chai"; -import { AddPlatformService } from "../../lib/services/platform/add-platform-service"; -import { MainController } from "../../lib/controllers/main-controller"; -import { RunOnDeviceEvents } from "../../lib/constants"; -import { RunOnDevicesEmitter } from "../../lib/run-on-devices-emitter"; -import { WorkflowDataService } from "../../lib/services/workflow/workflow-data-service"; -import { RunOnDevicesDataService } from "../../lib/services/run-on-devices-data-service"; -import { PlatformWatcherService } from "../../lib/services/platform/platform-watcher-service"; -import { LiveSyncServiceResolver } from "../../lib/resolvers/livesync-service-resolver"; -import { MobileHelper } from "../../lib/common/mobile/mobile-helper"; -import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants"; - -const deviceMap: IDictionary = { - myiOSDevice: { - deviceInfo: { - identifier: "myiOSDevice", - platform: "ios" - } - }, - myAndroidDevice: { - deviceInfo: { - identifier: "myAndroidDevice", - platform: "android" - } - } -}; - -function createTestInjector(): IInjector { - const injector = new Yok(); - - injector.register("devicesService", ({ - on: () => ({}), - getDeviceByIdentifier: (identifier: string) => { return deviceMap[identifier]; }, - getPlatformsFromDeviceDescriptors: (deviceDescriptors: ILiveSyncDeviceInfo[]) => { - return _(deviceDescriptors) - .map(device => deviceMap[device.identifier]) - .map(device => device.deviceInfo.platform) - .uniq() - .value(); - }, - getDevicesForPlatform: (platform: string) => [] - })); - injector.register("devicePlatformsConstants", DevicePlatformsConstants); - injector.register("errors", ({ - failWithoutHelp: () => ({}) - })); - injector.register("logger", ({ - trace: () => ({}) - })); - injector.register("platformsData", ({ - getPlatformData: (platform: string) => ({ - platformNameLowerCase: platform.toLowerCase() - }) - })); - injector.register("platformWatcherService", ({ - on: () => ({}), - emit: () => ({}), - startWatchers: () => ({}), - stopWatchers: () => ({}) - })); - injector.register("mainController", MainController); - injector.register("pluginsService", { - ensureAllDependenciesAreInstalled: () => ({}) - }); - injector.register("projectDataService", ({ - getProjectData: () => ({ - projectDir - }) - })); - injector.register("addPlatformService", { - addPlatformIfNeeded: () => ({}) - }); - injector.register("buildArtefactsService", ({})); - injector.register("buildPlatformService", ({})); - injector.register("deviceInstallAppService", {}); - injector.register("deviceRefreshAppService", {}); - injector.register("deviceDebugAppService", {}); - injector.register("fs", ({})); - injector.register("hooksService", { - executeAfterHooks: () => ({}) - }); - injector.register("hmrStatusService", {}); - injector.register("liveSyncServiceResolver", LiveSyncServiceResolver); - injector.register("mobileHelper", MobileHelper); - injector.register("preparePlatformService", { - preparePlatform: () => ({}) - }); - injector.register("projectChangesService", ({})); - injector.register("runOnDevicesController", { - syncInitialDataOnDevices: () => ({}) - }); - injector.register("runOnDevicesDataService", RunOnDevicesDataService); - injector.register("runOnDevicesEmitter", RunOnDevicesEmitter); - injector.register("workflowDataService", WorkflowDataService); - - return injector; -} - -const projectDir = "path/to/my/projectDir"; -const buildOutputPath = `${projectDir}/platform/ios/build/myproject.app`; - -const iOSDeviceDescriptor = { identifier: "myiOSDevice", buildAction: async () => buildOutputPath }; -const androidDeviceDescriptor = { identifier: "myAndroidDevice", buildAction: async () => buildOutputPath }; - -const liveSyncInfo = { - projectDir, - release: false, - useHotModuleReload: false, - env: {} -}; - -describe("MainController", () => { - describe("runOnDevices", () => { - describe("when runOnDevices() is called for second time for the same projectDir", () => { - it("should run only for new devies (for which the initial sync is still not executed)", async () => { - return; - }); - it("shouldn't run for old devices (for which initial sync is already executed)", async () => { - return; - }); - }); - describe("no watch", () => { - it("shouldn't start the watcher when skipWatcher flag is provided", async () => { - const injector = createTestInjector(); - let isStartWatchersCalled = false; - const platformWatcherService = injector.resolve("platformWatcherService"); - platformWatcherService.startWatchers = async () => isStartWatchersCalled = true; - - const mainController: MainController = injector.resolve("mainController"); - await mainController.runOnDevices(projectDir, [iOSDeviceDescriptor], { ...liveSyncInfo, skipWatcher: true }); - - assert.isFalse(isStartWatchersCalled); - }); - it("shouldn't start the watcher when no devices to sync", async () => { - const injector = createTestInjector(); - let isStartWatchersCalled = false; - const platformWatcherService = injector.resolve("platformWatcherService"); - platformWatcherService.startWatchers = async () => isStartWatchersCalled = true; - - const mainController: MainController = injector.resolve("mainController"); - await mainController.runOnDevices(projectDir, [], liveSyncInfo ); - - assert.isFalse(isStartWatchersCalled); - }); - }); - describe("when platform is still not added", () => { - it("should add platform before start watchers", async () => { - const injector = createTestInjector(); - - let isAddPlatformIfNeededCalled = false; - const addPlatformService: AddPlatformService = injector.resolve("addPlatformService"); - addPlatformService.addPlatformIfNeeded = async () => { isAddPlatformIfNeededCalled = true; }; - - let isStartWatcherCalled = false; - const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); - platformWatcherService.startWatchers = async () => { - assert.isTrue(isAddPlatformIfNeededCalled); - isStartWatcherCalled = true; - }; - - const mainController: MainController = injector.resolve("mainController"); - await mainController.runOnDevices(projectDir, [iOSDeviceDescriptor], liveSyncInfo); - - assert.isTrue(isStartWatcherCalled); - }); - - const testCases = [ - { - name: "should add only ios platform when only ios devices are connected", - connectedDevices: [iOSDeviceDescriptor], - expectedAddedPlatforms: ["ios"] - }, - { - name: "should add only android platform when only android devices are connected", - connectedDevices: [androidDeviceDescriptor], - expectedAddedPlatforms: ["android"] - }, - { - name: "should add both platforms when ios and android devices are connected", - connectedDevices: [iOSDeviceDescriptor, androidDeviceDescriptor], - expectedAddedPlatforms: ["ios", "android"] - } - ]; - - _.each(testCases, testCase => { - it(testCase.name, async () => { - const injector = createTestInjector(); - - const actualAddedPlatforms: IPlatformData[] = []; - const addPlatformService: AddPlatformService = injector.resolve("addPlatformService"); - addPlatformService.addPlatformIfNeeded = async (platformData: IPlatformData) => { - actualAddedPlatforms.push(platformData); - }; - - const mainController: MainController = injector.resolve("mainController"); - await mainController.runOnDevices(projectDir, testCase.connectedDevices, liveSyncInfo); - - assert.deepEqual(actualAddedPlatforms.map(pData => pData.platformNameLowerCase), testCase.expectedAddedPlatforms); - }); - }); - }); - describe("on initialSyncEvent", () => { - let injector: IInjector; - let isBuildPlatformCalled = false; - beforeEach(() => { - injector = createTestInjector(); - - const addPlatformService = injector.resolve("addPlatformService"); - addPlatformService.addPlatformIfNeeded = async () => { return; }; - - const buildPlatformService = injector.resolve("buildPlatformService"); - buildPlatformService.buildPlatform = async () => { isBuildPlatformCalled = true; return buildOutputPath; }; - - console.log("========== isBuildPlatformCalled ============= ", isBuildPlatformCalled); - }); - - afterEach(() => { - isBuildPlatformCalled = false; - }); - - it("shouldn't build for second android device", async () => { // shouldn't build for second iOS device or second iOS simulator - return; - }); - it("should build for iOS simulator if it is already built for iOS device", () => { - return; - }); - it("should build for iOS device if it is already built for iOS simulator", () => { - return; - }); - it("should install the built package when the project should be build", () => { - return; - }); - it("should install the latest built package when the project shouldn't be build", () => { - return; - }); - }); - describe("on filesChangeEvent", () => { - // TODO: add test cases here - }); - }); - describe("stopRunOnDevices", () => { - const testCases = [ - { - name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", - currentDeviceIdentifiers: ["device1", "device2", "device3"], - expectedDeviceIdentifiers: ["device1", "device2", "device3"] - }, - { - name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", - currentDeviceIdentifiers: ["device1"], - expectedDeviceIdentifiers: ["device1"] - }, - { - name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", - currentDeviceIdentifiers: ["device1"], - expectedDeviceIdentifiers: ["device1"], - deviceIdentifiersToBeStopped: ["device1"] - }, - { - name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", - currentDeviceIdentifiers: ["device1", "device2", "device3"], - expectedDeviceIdentifiers: ["device1", "device3"], - deviceIdentifiersToBeStopped: ["device1", "device3"] - }, - { - name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", - currentDeviceIdentifiers: ["device1", "device2", "device3"], - expectedDeviceIdentifiers: ["device1"], - deviceIdentifiersToBeStopped: ["device1", "device4"] - } - ]; - - for (const testCase of testCases) { - it(testCase.name, async () => { - const testInjector = createTestInjector(); - const mainController = testInjector.resolve("mainController"); - - const runOnDevicesDataService: RunOnDevicesDataService = testInjector.resolve("runOnDevicesDataService"); - runOnDevicesDataService.persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier })), ["ios"]); - - const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; - - const runOnDevicesEmitter = testInjector.resolve("runOnDevicesEmitter"); - runOnDevicesEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { - assert.equal(data.projectDir, projectDir); - emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); - }); - - await mainController.stopRunOnDevices(projectDir, testCase.deviceIdentifiersToBeStopped); - - assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); - }); - } - }); -}); diff --git a/test/controllers/prepare-controller.ts b/test/controllers/prepare-controller.ts new file mode 100644 index 0000000000..c42ca3c7a9 --- /dev/null +++ b/test/controllers/prepare-controller.ts @@ -0,0 +1,121 @@ +import { assert } from "chai"; +import { PrepareController } from "../../lib/controllers/prepare-controller"; +import { InjectorStub } from "../stubs"; +import { PREPARE_READY_EVENT_NAME } from "../../lib/constants"; + +const projectDir = "/path/to/my/projecDir"; +const prepareData = { + projectDir, + release: false, + hmr: false, + env: {}, + watch: true +}; + +let isCompileWithWatchCalled = false; +let isCompileWithoutWatchCalled = false; +let isNativePrepareCalled = false; +let emittedEventNames: string[] = []; +let emittedEventData: any[] = []; + +function createTestInjector(data: { hasNativeChanges: boolean }): IInjector { + const injector = new InjectorStub(); + + injector.register("addPlatformController", { + addPlatformIfNeeded: () => ({}) + }); + + injector.register("prepareNativePlatformService", ({ + prepareNativePlatform: async () => { + isNativePrepareCalled = true; + return data.hasNativeChanges; + } + })); + + injector.register("webpackCompilerService", ({ + on: () => ({}), + emit: () => ({}), + compileWithWatch: async () => { + isCompileWithWatchCalled = true; + }, + compileWithoutWatch: async () => { + isCompileWithoutWatchCalled = true; + } + })); + + injector.register("prepareController", PrepareController); + + const prepareController: PrepareController = injector.resolve("prepareController"); + prepareController.emit = (eventName: string, eventData: any) => { + emittedEventNames.push(eventName); + emittedEventData.push(eventData); + assert.isTrue(isCompileWithWatchCalled); + assert.isTrue(isNativePrepareCalled); + return true; + }; + + return injector; +} + +describe("prepareController", () => { + + afterEach(() => { + isNativePrepareCalled = false; + isCompileWithWatchCalled = false; + isCompileWithoutWatchCalled = false; + + emittedEventNames = []; + emittedEventData = []; + }); + + describe("preparePlatform with watch", () => { + _.each(["iOS", "Android"], platform => { + _.each([true, false], hasNativeChanges => { + it(`should execute native prepare and webpack's compilation for ${platform} platform when hasNativeChanges is ${hasNativeChanges}`, async () => { + const injector = createTestInjector({ hasNativeChanges }); + + const prepareController: PrepareController = injector.resolve("prepareController"); + await prepareController.preparePlatform({ ...prepareData, platform }); + + assert.isTrue(isCompileWithWatchCalled); + assert.isTrue(isNativePrepareCalled); + }); + }); + it(`should respect native changes that are made before the initial preparation of the project had been done for ${platform}`, async () => { + const injector = createTestInjector({ hasNativeChanges: false }); + + const prepareController: PrepareController = injector.resolve("prepareController"); + + const prepareNativePlatformService = injector.resolve("prepareNativePlatformService"); + prepareNativePlatformService.prepareNativePlatform = async () => { + const nativeFilesWatcher = (prepareController).watchersData[projectDir][platform.toLowerCase()].nativeFilesWatcher; + nativeFilesWatcher.emit("all", "change", "my/project/App_Resources/some/file"); + isNativePrepareCalled = true; + return false; + }; + + await prepareController.preparePlatform({ ...prepareData, platform }); + + assert.lengthOf(emittedEventNames, 1); + assert.lengthOf(emittedEventData, 1); + assert.deepEqual(emittedEventNames[0], PREPARE_READY_EVENT_NAME); + assert.deepEqual(emittedEventData[0], { files: [], hasNativeChanges: true, hmrData: null, platform: platform.toLowerCase() }); + }); + }); + }); + + describe("preparePlatform without watch", () => { + _.each(["ios", "android"], platform => { + it("shouldn't start the watcher when watch is false", async () => { + const injector = createTestInjector({ hasNativeChanges: false }); + + const prepareController: PrepareController = injector.resolve("prepareController"); + await prepareController.preparePlatform({ ...prepareData, watch: false, platform }); + + assert.isTrue(isNativePrepareCalled); + assert.isTrue(isCompileWithoutWatchCalled); + assert.isFalse(isCompileWithWatchCalled); + }); + }); + }); +}); diff --git a/test/controllers/run-on-devices-controller.ts b/test/controllers/run-on-devices-controller.ts index caba4b608c..7dfb5ecb31 100644 --- a/test/controllers/run-on-devices-controller.ts +++ b/test/controllers/run-on-devices-controller.ts @@ -5,12 +5,16 @@ import { MobileHelper } from "../../lib/common/mobile/mobile-helper"; import { assert } from "chai"; import { RunOnDevicesDataService } from "../../lib/services/run-on-devices-data-service"; import { RunOnDevicesEmitter } from "../../lib/run-on-devices-emitter"; -import { WorkflowDataService } from "../../lib/services/workflow/workflow-data-service"; +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"; + +let isAttachToHmrStatusCalled = false; +let prepareData: PrepareData = null; -let isBuildPlatformCalled = false; const appIdentifier = "org.nativescript.myCoolApp"; -const projectDir = "path/to/my/projectDir"; -const projectData = { projectDir, projectIdentifiers: { ios: appIdentifier, android: appIdentifier }}; +const projectDir = "/path/to/my/projecDir"; const buildOutputPath = `${projectDir}/platform/ios/build/myproject.app`; const iOSDevice = { deviceInfo: { identifier: "myiOSDevice", platform: "ios" } }; @@ -18,12 +22,12 @@ 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}> = { - ios: { +const map: IDictionary<{ device: Mobile.IDevice, descriptor: ILiveSyncDeviceInfo }> = { + myiOSDevice: { device: iOSDevice, descriptor: iOSDeviceDescriptor }, - android: { + myAndroidDevice: { device: androidDevice, descriptor: androidDeviceDescriptor } @@ -64,9 +68,8 @@ function createTestInjector() { injector.register("addPlatformService", {}); injector.register("buildArtefactsService", ({})); - injector.register("buildPlatformService", { + injector.register("buildController", { buildPlatform: async () => { - isBuildPlatformCalled = true; return buildOutputPath; }, buildPlatformIfNeeded: async () => ({}) @@ -88,15 +91,31 @@ function createTestInjector() { fullSync: async () => getFullSyncResult(), liveSyncWatchAction: () => ({}) }); - injector.register("hmrStatusService", {}); + injector.register("hmrStatusService", { + attachToHmrStatusEvent: () => isAttachToHmrStatusCalled = true + }); injector.register("liveSyncServiceResolver", LiveSyncServiceResolver); injector.register("mobileHelper", MobileHelper); - injector.register("preparePlatformService", ({})); - injector.register("projectChangesService", ({})); + injector.register("prepareController", { + stopWatchers: () => ({}), + preparePlatform: async (currentPrepareData: PrepareData) => { + prepareData = currentPrepareData; + return { platform: prepareData.platform, hasNativeChanges: false }; + }, + on: () => ({}) + }); + injector.register("prepareNativePlatformService", {}); + injector.register("projectChangesService", {}); injector.register("runOnDevicesController", RunOnDevicesController); injector.register("runOnDevicesDataService", RunOnDevicesDataService); injector.register("runOnDevicesEmitter", RunOnDevicesEmitter); - injector.register("workflowDataService", WorkflowDataService); + injector.register("prepareDataService", PrepareDataService); + injector.register("buildDataService", BuildDataService); + + const devicesService = injector.resolve("devicesService"); + devicesService.getDevicesForPlatform = () => [{ identifier: "myTestDeviceId1" }]; + devicesService.getPlatformsFromDeviceDescriptors = (devices: ILiveSyncDeviceInfo[]) => devices.map(d => map[d.identifier].device.deviceInfo.platform); + devicesService.on = () => ({}); return injector; } @@ -105,39 +124,153 @@ describe("RunOnDevicesController", () => { let injector: IInjector = null; let runOnDevicesController: RunOnDevicesController = null; let runOnDevicesDataService: RunOnDevicesDataService = null; + let runOnDevicesEmitter: RunOnDevicesEmitter = null; beforeEach(() => { + isAttachToHmrStatusCalled = false; + prepareData = null; + injector = createTestInjector(); runOnDevicesController = injector.resolve("runOnDevicesController"); runOnDevicesDataService = injector.resolve("runOnDevicesDataService"); + runOnDevicesEmitter = injector.resolve("runOnDevicesEmitter"); }); - describe("syncInitialDataOnDevices", () => { - afterEach(() => { - isBuildPlatformCalled = false; - }); + describe("runOnDevices", () => { + describe("no watch", () => { + it("shouldn't start the watcher when skipWatcher flag is provided", async () => { + mockDevicesService(injector, [iOSDevice]); - _.each(["ios", "android"], platform => { - it(`should build for ${platform} platform when there are native changes`, async () => { - const initialSyncEventData = { platform, hasNativeChanges: true }; - const deviceDescriptors = [map[platform].descriptor]; - runOnDevicesDataService.persistData(projectDir, deviceDescriptors, [platform]); - mockDevicesService(injector, [map[platform].device]); + await runOnDevicesController.runOnDevices({ + projectDir, + liveSyncInfo: { ...liveSyncInfo, skipWatcher: true }, + deviceDescriptors: [iOSDeviceDescriptor] + }); - await runOnDevicesController.syncInitialDataOnDevices(initialSyncEventData, projectData, liveSyncInfo, deviceDescriptors); + assert.isFalse(prepareData.watch); + }); + it("shouldn't attach to hmr status when skipWatcher flag is provided", async () => { + mockDevicesService(injector, [iOSDevice]); + + await runOnDevicesController.runOnDevices({ + projectDir, + liveSyncInfo: { ...liveSyncInfo, skipWatcher: true, useHotModuleReload: true }, + deviceDescriptors: [iOSDeviceDescriptor] + }); - assert.isTrue(isBuildPlatformCalled); + assert.isFalse(isAttachToHmrStatusCalled); }); - it(`shouldn't build for ${platform} platform when no native changes`, async () => { - const initialSyncEventData = { platform, hasNativeChanges: false }; - const deviceDescriptors = [map[platform].descriptor]; - runOnDevicesDataService.persistData(projectDir, deviceDescriptors, [platform]); - mockDevicesService(injector, [map[platform].device]); + it("shouldn't attach to hmr status when useHotModuleReload is false", async () => { + mockDevicesService(injector, [iOSDevice]); + runOnDevicesDataService.hasDeviceDescriptors = () => true; - await runOnDevicesController.syncInitialDataOnDevices(initialSyncEventData, projectData, liveSyncInfo, deviceDescriptors); + await runOnDevicesController.runOnDevices({ + projectDir, + liveSyncInfo, + deviceDescriptors: [iOSDeviceDescriptor] + }); + + assert.isFalse(isAttachToHmrStatusCalled); + }); + it("shouldn't attach to hmr status when no deviceDescriptors are provided", async () => { + await runOnDevicesController.runOnDevices({ + projectDir, + liveSyncInfo, + deviceDescriptors: [] + }); - assert.isFalse(isBuildPlatformCalled); + assert.isFalse(isAttachToHmrStatusCalled); }); }); + describe("watch", () => { + const testCases = [ + { + name: "should prepare only ios platform when only ios devices are connected", + connectedDevices: [iOSDeviceDescriptor], + expectedPreparedPlatforms: ["ios"] + }, + { + name: "should prepare only android platform when only android devices are connected", + connectedDevices: [androidDeviceDescriptor], + expectedPreparedPlatforms: ["android"] + }, + { + name: "should prepare both platforms when ios and android devices are connected", + connectedDevices: [iOSDeviceDescriptor, androidDeviceDescriptor], + expectedPreparedPlatforms: ["ios", "android"] + } + ]; + + _.each(testCases, testCase => { + it(testCase.name, async () => { + mockDevicesService(injector, testCase.connectedDevices.map(d => map[d.identifier].device)); + + const preparedPlatforms: string[] = []; + const prepareController = injector.resolve("prepareController"); + prepareController.preparePlatform = (currentPrepareData: PrepareData) => { + preparedPlatforms.push(currentPrepareData.platform); + return { platform: currentPrepareData.platform, hasNativeChanges: false }; + }; + + await runOnDevicesController.runOnDevices({ + projectDir, + liveSyncInfo, + deviceDescriptors: testCase.connectedDevices + }); + + assert.deepEqual(preparedPlatforms, testCase.expectedPreparedPlatforms); + }); + }); + }); + }); + + describe("stopRunOnDevices", () => { + const testCases = [ + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device2", "device3"] + }, + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device3"], + deviceIdentifiersToBeStopped: ["device1", "device3"] + }, + { + name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1", "device4"] + } + ]; + + for (const testCase of testCases) { + it(testCase.name, async () => { + runOnDevicesDataService.persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier })), ["ios"]); + + const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; + + runOnDevicesEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { + assert.equal(data.projectDir, projectDir); + emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); + }); + + await runOnDevicesController.stopRunOnDevices(projectDir, testCase.deviceIdentifiersToBeStopped); + + assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); + }); + } }); }); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index bbc6fba639..f50dffe8f8 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -24,7 +24,7 @@ describe("nativescript-cli-lib", () => { "getAndroidAssetsStructure" ], constants: ["CONFIG_NS_APP_RESOURCES_ENTRY", "CONFIG_NS_APP_ENTRY", "CONFIG_NS_FILE_NAME"], - localBuildService: ["build"], + // localBuildService: ["build"], deviceLogProvider: null, packageManager: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], diff --git a/test/platform-commands.ts b/test/platform-commands.ts index b7144ba9ff..1e4cb367cd 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -139,7 +139,7 @@ function createTestInjector() { testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("xmlValidator", XmlValidator); testInjector.register("npm", {}); - testInjector.register("preparePlatformService", {}); + testInjector.register("prepareNativePlatformService", {}); testInjector.register("childProcess", ChildProcessLib.ChildProcess); testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); testInjector.register("analyticsService", { @@ -185,6 +185,7 @@ function createTestInjector() { setShouldDispose: (shouldDispose: boolean): void => undefined }); testInjector.register("addPlatformService", {}); + testInjector.register("addPlatformController", {}); return testInjector; } diff --git a/test/platform-service.ts b/test/platform-service.ts deleted file mode 100644 index 8fd0c7d2a4..0000000000 --- a/test/platform-service.ts +++ /dev/null @@ -1,756 +0,0 @@ -// import * as yok from "../lib/common/yok"; -// import * as stubs from "./stubs"; -// import * as PlatformServiceLib from "../lib/services/platform-service"; -// import { VERSION_STRING, PACKAGE_JSON_FILE_NAME, AddPlaformErrors } from "../lib/constants"; -// import * as fsLib from "../lib/common/file-system"; -// import * as optionsLib from "../lib/options"; -// import * as hostInfoLib from "../lib/common/host-info"; -// import * as ProjectFilesManagerLib from "../lib/common/services/project-files-manager"; -// import * as path from "path"; -// import { format } from "util"; -// import { assert } from "chai"; -// import { LocalToDevicePathDataFactory } from "../lib/common/mobile/local-to-device-path-data-factory"; -// import { MobileHelper } from "../lib/common/mobile/mobile-helper"; -// import { ProjectFilesProvider } from "../lib/providers/project-files-provider"; -// import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; -// import { XmlValidator } from "../lib/xml-validator"; -// import { PlatformJSService } from "../lib/services/prepare-platform-js-service"; -// import * as ChildProcessLib from "../lib/common/child-process"; -// import ProjectChangesLib = require("../lib/services/project-changes-service"); -// import { Messages } from "../lib/common/messages/messages"; -// import { SettingsService } from "../lib/common/test/unit-tests/stubs"; -// import { mkdir } from "shelljs"; -// import * as constants from "../lib/constants"; -// import { PlatformCommandsService } from "../lib/services/platform/platform-commands-service"; -// import { StaticConfig } from "../lib/config"; -// import { PlatformNativeService } from "../lib/services/prepare-platform-native-service"; - -// require("should"); -// const temp = require("temp"); -// temp.track(); - -// function createTestInjector() { -// const testInjector = new yok.Yok(); - -// testInjector.register('platformService', PlatformServiceLib.PlatformService); -// testInjector.register("platformCommandsService", PlatformCommandsService); -// testInjector.register('errors', stubs.ErrorsStub); -// testInjector.register('logger', stubs.LoggerStub); -// testInjector.register("nodeModulesDependenciesBuilder", {}); -// testInjector.register('packageInstallationManager', stubs.PackageInstallationManagerStub); -// // TODO: Remove the projectData - it shouldn't be required in the service itself. -// testInjector.register('projectData', stubs.ProjectDataStub); -// testInjector.register('platformsData', stubs.PlatformsDataStub); -// testInjector.register('devicesService', {}); -// testInjector.register('androidEmulatorServices', {}); -// testInjector.register('projectDataService', stubs.ProjectDataService); -// testInjector.register('prompter', {}); -// testInjector.register('sysInfo', {}); -// testInjector.register("commandsService", { -// tryExecuteCommand: () => { /* intentionally left blank */ } -// }); -// testInjector.register("options", optionsLib.Options); -// testInjector.register("hostInfo", hostInfoLib.HostInfo); -// testInjector.register("staticConfig", StaticConfig); -// testInjector.register("nodeModulesBuilder", { -// prepareNodeModules: () => { -// return Promise.resolve(); -// }, -// prepareJSNodeModules: () => { -// return Promise.resolve(); -// } -// }); -// testInjector.register("pluginsService", { -// getAllInstalledPlugins: () => { -// return []; -// }, -// ensureAllDependenciesAreInstalled: () => { -// return Promise.resolve(); -// }, -// validate: (platformData: IPlatformData, projectData: IProjectData) => { -// return Promise.resolve(); -// } -// }); -// testInjector.register("projectFilesManager", ProjectFilesManagerLib.ProjectFilesManager); -// testInjector.register("hooksService", stubs.HooksServiceStub); -// testInjector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); -// testInjector.register("mobileHelper", MobileHelper); -// testInjector.register("projectFilesProvider", ProjectFilesProvider); -// testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); -// testInjector.register("xmlValidator", XmlValidator); -// testInjector.register("preparePlatformService", PlatformNativeService); -// testInjector.register("platformJSService", PlatformJSService); -// testInjector.register("packageManager", { -// uninstall: async () => { -// return true; -// } -// }); -// testInjector.register("childProcess", ChildProcessLib.ChildProcess); -// testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); -// testInjector.register("analyticsService", { -// track: async (): Promise => undefined, -// trackEventActionInGoogleAnalytics: () => Promise.resolve() -// }); -// testInjector.register("messages", Messages); -// testInjector.register("devicePathProvider", {}); -// testInjector.register("helpService", { -// showCommandLineHelp: async (): Promise => (undefined) -// }); -// testInjector.register("settingsService", SettingsService); -// testInjector.register("terminalSpinnerService", { -// createSpinner: (msg: string) => ({ -// start: (): void => undefined, -// stop: (): void => undefined, -// message: (): void => undefined -// }) -// }); -// testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub); -// testInjector.register("filesHashService", { -// generateHashes: () => Promise.resolve(), -// getChanges: () => Promise.resolve({ test: "testHash" }) -// }); -// testInjector.register("pacoteService", { -// extractPackage: async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => { -// mkdir(path.join(destinationDirectory, "framework")); -// (new fsLib.FileSystem(testInjector)).writeFile(path.join(destinationDirectory, PACKAGE_JSON_FILE_NAME), JSON.stringify({ -// name: "package-name", -// version: "1.0.0" -// })); -// } -// }); -// testInjector.register("usbLiveSyncService", () => ({})); -// testInjector.register("doctorService", { -// checkForDeprecatedShortImportsInAppDir: (projectDir: string): void => undefined -// }); -// testInjector.register("cleanupService", { -// setShouldDispose: (shouldDispose: boolean): void => undefined -// }); - -// return testInjector; -// } - -// describe('Platform Service Tests', () => { -// let platformCommandsService: IPlatformCommandsService, testInjector: IInjector; -// let platformService: IPlatformService; - -// beforeEach(() => { -// testInjector = createTestInjector(); -// testInjector.register("fs", stubs.FileSystemStub); -// testInjector.resolve("projectData").initializeProjectData(); -// platformCommandsService = testInjector.resolve("platformCommandsService"); -// platformService = testInjector.resolve("platformService"); -// console.log("============ PLATFORM SERVICE ========== ", platformService); -// }); - -// // TODO: check this tests with QAs -// // describe("prepare platform unit tests", () => { -// // let fs: IFileSystem; - -// // beforeEach(() => { -// // testInjector = createTestInjector(); -// // testInjector.register("fs", fsLib.FileSystem); -// // fs = testInjector.resolve("fs"); -// // testInjector.resolve("projectData").initializeProjectData(); -// // }); - -// // function prepareDirStructure() { -// // const tempFolder = temp.mkdirSync("prepare_platform"); - -// // const appFolderPath = path.join(tempFolder, "app"); -// // fs.createDirectory(appFolderPath); - -// // const nodeModulesPath = path.join(tempFolder, "node_modules"); -// // fs.createDirectory(nodeModulesPath); - -// // const testsFolderPath = path.join(appFolderPath, "tests"); -// // fs.createDirectory(testsFolderPath); - -// // const app1FolderPath = path.join(tempFolder, "app1"); -// // fs.createDirectory(app1FolderPath); - -// // const appDestFolderPath = path.join(tempFolder, "appDest"); -// // const appResourcesFolderPath = path.join(appDestFolderPath, "App_Resources"); -// // const appResourcesPath = path.join(appFolderPath, "App_Resources/Android"); -// // fs.createDirectory(appResourcesPath); -// // fs.writeFile(path.join(appResourcesPath, "test.txt"), "test"); -// // fs.writeJson(path.join(tempFolder, "package.json"), { -// // name: "testname", -// // nativescript: { -// // id: "org.nativescript.testname" -// // } -// // }); - -// // return { tempFolder, appFolderPath, app1FolderPath, appDestFolderPath, appResourcesFolderPath }; -// // } - -// // async function execPreparePlatform(platformToTest: string, testDirData: any, -// // release?: boolean) { -// // const platformsData = testInjector.resolve("platformsData"); -// // platformsData.platformsNames = ["ios", "android"]; -// // platformsData.getPlatformData = (platform: string) => { -// // return { -// // appDestinationDirectoryPath: testDirData.appDestFolderPath, -// // appResourcesDestinationDirectoryPath: testDirData.appResourcesFolderPath, -// // normalizedPlatformName: platformToTest, -// // configurationFileName: platformToTest === "ios" ? INFO_PLIST_FILE_NAME : MANIFEST_FILE_NAME, -// // projectRoot: testDirData.tempFolder, -// // platformProjectService: { -// // prepareProject: (): any => null, -// // validate: () => Promise.resolve(), -// // createProject: (projectRoot: string, frameworkDir: string) => Promise.resolve(), -// // interpolateData: (projectRoot: string) => Promise.resolve(), -// // afterCreateProject: (projectRoot: string): any => null, -// // getAppResourcesDestinationDirectoryPath: (pData: IProjectData, frameworkVersion?: string): string => { -// // if (platform.toLowerCase() === "ios") { -// // const dirPath = path.join(testDirData.appDestFolderPath, "Resources"); -// // fs.ensureDirectoryExists(dirPath); -// // return dirPath; -// // } else { -// // const dirPath = path.join(testDirData.appDestFolderPath, "src", "main", "res"); -// // fs.ensureDirectoryExists(dirPath); -// // return dirPath; -// // } -// // }, -// // processConfigurationFilesFromAppResources: () => Promise.resolve(), -// // handleNativeDependenciesChange: () => Promise.resolve(), -// // ensureConfigurationFileInAppResources: (): any => null, -// // interpolateConfigurationFile: (): void => undefined, -// // isPlatformPrepared: (projectRoot: string) => false, -// // prepareAppResources: (appResourcesDirectoryPath: string, pData: IProjectData): void => undefined, -// // checkForChanges: () => { /* */ } -// // } -// // }; -// // }; - -// // const projectData = testInjector.resolve("projectData"); -// // projectData.projectDir = testDirData.tempFolder; -// // projectData.projectName = "app"; -// // projectData.appDirectoryPath = testDirData.appFolderPath; -// // projectData.appResourcesDirectoryPath = path.join(testDirData.appFolderPath, "App_Resources"); - -// // platformService = testInjector.resolve("platformService"); -// // const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: release, useHotModuleReload: false }; -// // await platformService.preparePlatform({ -// // platform: platformToTest, -// // appFilesUpdaterOptions, -// // platformTemplate: "", -// // projectData, -// // "": { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, -// // env: {} -// // }); -// // } - -// // async function testPreparePlatform(platformToTest: string, release?: boolean): Promise { -// // const testDirData = prepareDirStructure(); -// // const created: CreatedTestData = new CreatedTestData(); -// // created.testDirData = testDirData; - -// // // Add platform specific files to app and app1 folders -// // const platformSpecificFiles = [ -// // "test1.ios.js", "test1-ios-js", "test2.android.js", "test2-android-js", -// // "main.js" -// // ]; - -// // const destinationDirectories = [testDirData.appFolderPath, testDirData.app1FolderPath]; - -// // _.each(destinationDirectories, directoryPath => { -// // _.each(platformSpecificFiles, filePath => { -// // const fileFullPath = path.join(directoryPath, filePath); -// // fs.writeFile(fileFullPath, "testData"); - -// // created.files.push(fileFullPath); -// // }); -// // }); - -// // // Add App_Resources file to app and app1 folders -// // _.each(destinationDirectories, directoryPath => { -// // const iosIconFullPath = path.join(directoryPath, "App_Resources/iOS/icon.png"); -// // fs.writeFile(iosIconFullPath, "test-image"); -// // created.resources.ios.push(iosIconFullPath); - -// // const androidFullPath = path.join(directoryPath, "App_Resources/Android/icon.png"); -// // fs.writeFile(androidFullPath, "test-image"); -// // created.resources.android.push(androidFullPath); -// // }); - -// // await execPreparePlatform(platformToTest, testDirData, release); - -// // const test1FileName = platformToTest.toLowerCase() === "ios" ? "test1.js" : "test2.js"; -// // const test2FileName = platformToTest.toLowerCase() === "ios" ? "test2.js" : "test1.js"; - -// // // Asserts that the files in app folder are process as platform specific -// // assert.isTrue(fs.exists(path.join(testDirData.appDestFolderPath, "app", test1FileName))); -// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app", "test1-js"))); - -// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app", test2FileName))); -// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app", "test2-js"))); - -// // // Asserts that the files in app1 folder aren't process as platform specific -// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "app1")), "Asserts that the files in app1 folder aren't process as platform specific"); - -// // if (release) { -// // // Asserts that the files in tests folder aren't copied -// // assert.isFalse(fs.exists(path.join(testDirData.appDestFolderPath, "tests")), "Asserts that the files in tests folder aren't copied"); -// // } - -// // return created; -// // } - -// // function updateFile(files: string[], fileName: string, content: string) { -// // const fileToUpdate = _.find(files, (f) => f.indexOf(fileName) !== -1); -// // fs.writeFile(fileToUpdate, content); -// // } - -// // function getDefaultFolderVerificationData(platform: string, appDestFolderPath: string) { -// // const data: any = {}; -// // if (platform.toLowerCase() === "ios") { -// // data[path.join(appDestFolderPath, "app")] = { -// // missingFiles: ["test1.ios.js", "test2.android.js", "test2.js"], -// // presentFiles: ["test1.js", "test2-android-js", "test1-ios-js", "main.js"] -// // }; - -// // data[appDestFolderPath] = { -// // filesWithContent: [ -// // { -// // name: "Resources/icon.png", -// // content: "test-image" -// // } -// // ] -// // }; -// // } else { -// // data[path.join(appDestFolderPath, "app")] = { -// // missingFiles: ["test1.android.js", "test2.ios.js", "test1.js"], -// // presentFiles: ["test2.js", "test2-android-js", "test1-ios-js"] -// // }; - -// // data[appDestFolderPath] = { -// // filesWithContent: [ -// // { -// // name: "src/main/res/icon.png", -// // content: "test-image" -// // } -// // ] -// // }; -// // } - -// // return data; -// // } - -// // function mergeModifications(def: any, mod: any) { -// // // custom merge to reflect changes -// // const merged: any = _.cloneDeep(def); -// // _.forOwn(mod, (modFolder, folderRoot) => { -// // // whole folder not present in Default -// // if (!def.hasOwnProperty(folderRoot)) { -// // merged[folderRoot] = _.cloneDeep(modFolder[folderRoot]); -// // } else { -// // const defFolder = def[folderRoot]; -// // merged[folderRoot].filesWithContent = _.merge(defFolder.filesWithContent || [], modFolder.filesWithContent || []); -// // merged[folderRoot].missingFiles = (defFolder.missingFiles || []).concat(modFolder.missingFiles || []); -// // merged[folderRoot].presentFiles = (defFolder.presentFiles || []).concat(modFolder.presentFiles || []); - -// // // remove the missingFiles from the presentFiles if they were initially there -// // if (modFolder.missingFiles) { -// // merged[folderRoot].presentFiles = _.difference(defFolder.presentFiles, modFolder.missingFiles); -// // } - -// // // remove the presentFiles from the missingFiles if they were initially there. -// // if (modFolder.presentFiles) { -// // merged[folderRoot].missingFiles = _.difference(defFolder.presentFiles, modFolder.presentFiles); -// // } -// // } -// // }); - -// // return merged; -// // } - -// // // Executes a changes test case: -// // // 1. Executes Prepare Platform for the Platform -// // // 2. Applies some changes to the App. Persists the expected Modifications -// // // 3. Executes again Prepare Platform for the Platform -// // // 4. Gets the Default Destination App Structure and merges it with the Modifications -// // // 5. Asserts the Destination App matches our expectations -// // async function testChangesApplied(platform: string, applyChangesFn: (createdTestData: CreatedTestData) => any) { -// // const createdTestData = await testPreparePlatform(platform); - -// // const modifications = applyChangesFn(createdTestData); - -// // await execPreparePlatform(platform, createdTestData.testDirData); - -// // const defaultStructure = getDefaultFolderVerificationData(platform, createdTestData.testDirData.appDestFolderPath); - -// // const merged = mergeModifications(defaultStructure, modifications); - -// // DestinationFolderVerifier.verify(merged, fs); -// // } - -// // it("should sync only changed files, without special folders (iOS)", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "updated-content-ios"; -// // updateFile(createdTestData.files, "test1.ios.js", expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { -// // filesWithContent: [ -// // { -// // name: "test1.js", -// // content: expectedFileContent -// // } -// // ] -// // }; -// // return modifications; -// // }; -// // await testChangesApplied("iOS", applyChangesFn); -// // }); - -// // it("should sync only changed files, without special folders (Android) #2697", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "updated-content-android"; -// // updateFile(createdTestData.files, "test2.android.js", expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { -// // filesWithContent: [ -// // { -// // name: "test2.js", -// // content: expectedFileContent -// // } -// // ] -// // }; -// // return modifications; -// // }; -// // await testChangesApplied("Android", applyChangesFn); -// // }); - -// // it("Ensure App_Resources get reloaded after change in the app folder (iOS) #2560", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "updated-icon-content"; -// // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/icon.png"); -// // fs.writeFile(iconPngPath, expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[createdTestData.testDirData.appDestFolderPath] = { -// // filesWithContent: [ -// // { -// // name: "Resources/icon.png", -// // content: expectedFileContent -// // } -// // ] -// // }; - -// // return modifications; -// // }; -// // await testChangesApplied("iOS", applyChangesFn); -// // }); - -// // it("Ensure App_Resources get reloaded after change in the app folder (Android) #2560", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "updated-icon-content"; -// // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/icon.png"); -// // fs.writeFile(iconPngPath, expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[createdTestData.testDirData.appDestFolderPath] = { -// // filesWithContent: [ -// // { -// // name: "src/main/res/icon.png", -// // content: expectedFileContent -// // } -// // ] -// // }; - -// // return modifications; -// // }; -// // await testChangesApplied("Android", applyChangesFn); -// // }); - -// // it("Ensure App_Resources get reloaded after a new file appears in the app folder (iOS) #2560", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "new-file-content"; -// // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/iOS/new-file.png"); -// // fs.writeFile(iconPngPath, expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[createdTestData.testDirData.appDestFolderPath] = { -// // filesWithContent: [ -// // { -// // name: "Resources/new-file.png", -// // content: expectedFileContent -// // } -// // ] -// // }; - -// // return modifications; -// // }; -// // await testChangesApplied("iOS", applyChangesFn); -// // }); - -// // it("Ensure App_Resources get reloaded after a new file appears in the app folder (Android) #2560", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "new-file-content"; -// // const iconPngPath = path.join(createdTestData.testDirData.appFolderPath, "App_Resources/Android/new-file.png"); -// // fs.writeFile(iconPngPath, expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[createdTestData.testDirData.appDestFolderPath] = { -// // filesWithContent: [ -// // { -// // name: "src/main/res/new-file.png", -// // content: expectedFileContent -// // } -// // ] -// // }; - -// // return modifications; -// // }; -// // await testChangesApplied("Android", applyChangesFn); -// // }); - -// // it("should sync new platform specific files (iOS)", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "new-content-ios"; -// // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.ios.js"), expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { -// // filesWithContent: [ -// // { -// // name: "test3.js", -// // content: expectedFileContent -// // } -// // ] -// // }; - -// // return modifications; -// // }; -// // await testChangesApplied("iOS", applyChangesFn); -// // }); - -// // it("should sync new platform specific files (Android)", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "new-content-android"; -// // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.android.js"), expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { -// // filesWithContent: [ -// // { -// // name: "test3.js", -// // content: expectedFileContent -// // } -// // ] -// // }; - -// // return modifications; -// // }; -// // await testChangesApplied("Android", applyChangesFn); -// // }); - -// // it("should sync new common files (iOS)", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "new-content-ios"; -// // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { -// // filesWithContent: [ -// // { -// // name: "test3.js", -// // content: expectedFileContent -// // } -// // ] -// // }; - -// // return modifications; -// // }; -// // await testChangesApplied("iOS", applyChangesFn); -// // }); - -// // it("should sync new common file (Android)", async () => { -// // const applyChangesFn = (createdTestData: CreatedTestData) => { -// // // apply changes -// // const expectedFileContent = "new-content-android"; -// // fs.writeFile(path.join(createdTestData.testDirData.appFolderPath, "test3.js"), expectedFileContent); - -// // // construct the folder modifications data -// // const modifications: any = {}; -// // modifications[path.join(createdTestData.testDirData.appDestFolderPath, "app")] = { -// // filesWithContent: [ -// // { -// // name: "test3.js", -// // content: expectedFileContent -// // } -// // ] -// // }; - -// // return modifications; -// // }; -// // await testChangesApplied("Android", applyChangesFn); -// // }); - -// // it("invalid xml is caught", async () => { -// // require("colors"); -// // const testDirData = prepareDirStructure(); - -// // // generate invalid xml -// // const fileFullPath = path.join(testDirData.appFolderPath, "file.xml"); -// // fs.writeFile(fileFullPath, ""); - -// // const platformsData = testInjector.resolve("platformsData"); -// // platformsData.platformsNames = ["android"]; -// // platformsData.getPlatformData = (platform: string) => { -// // return { -// // appDestinationDirectoryPath: testDirData.appDestFolderPath, -// // appResourcesDestinationDirectoryPath: testDirData.appResourcesFolderPath, -// // normalizedPlatformName: "Android", -// // projectRoot: testDirData.tempFolder, -// // configurationFileName: "configFileName", -// // platformProjectService: { -// // prepareProject: (): any => null, -// // prepareAppResources: (): any => null, -// // validate: () => Promise.resolve(), -// // createProject: (projectRoot: string, frameworkDir: string) => Promise.resolve(), -// // interpolateData: (projectRoot: string) => Promise.resolve(), -// // afterCreateProject: (projectRoot: string): any => null, -// // getAppResourcesDestinationDirectoryPath: () => testDirData.appResourcesFolderPath, -// // processConfigurationFilesFromAppResources: () => Promise.resolve(), -// // handleNativeDependenciesChange: () => Promise.resolve(), -// // ensureConfigurationFileInAppResources: (): any => null, -// // interpolateConfigurationFile: (): void => undefined, -// // isPlatformPrepared: (projectRoot: string) => false, -// // checkForChanges: () => { /* */ } -// // }, -// // frameworkPackageName: "tns-ios" -// // }; -// // }; - -// // const projectData = testInjector.resolve("projectData"); -// // projectData.projectDir = testDirData.tempFolder; -// // projectData.appDirectoryPath = projectData.getAppDirectoryPath(); -// // projectData.appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); - -// // platformService = testInjector.resolve("platformService"); -// // const oldLoggerWarner = testInjector.resolve("$logger").warn; -// // let warnings: string = ""; -// // try { -// // testInjector.resolve("$logger").warn = (text: string) => warnings += text; -// // const appFilesUpdaterOptions: IAppFilesUpdaterOptions = { bundle: false, release: false, useHotModuleReload: false }; -// // await platformService.preparePlatform({ -// // platform: "android", -// // appFilesUpdaterOptions, -// // platformTemplate: "", -// // projectData, -// // "": { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, -// // env: {} -// // }); -// // } finally { -// // testInjector.resolve("$logger").warn = oldLoggerWarner; -// // } - -// // // Asserts that prepare has caught invalid xml -// // assert.isFalse(warnings.indexOf("has errors") !== -1); -// // }); -// // }); - -// // describe("build", () => { -// // function mockData(buildOutput: string[], projectName: string): void { -// // mockPlatformsData(projectName); -// // mockFileSystem(buildOutput); -// // platformService.saveBuildInfoFile = () => undefined; -// // } - -// // function mockPlatformsData(projectName: string): void { -// // const platformsData = testInjector.resolve("platformsData"); -// // platformsData.getPlatformData = (platform: string) => { -// // return { -// // deviceBuildOutputPath: "", -// // normalizedPlatformName: "", -// // getBuildOutputPath: () => "", -// // platformProjectService: { -// // buildProject: () => Promise.resolve(), -// // on: () => ({}), -// // removeListener: () => ({}) -// // }, -// // getValidBuildOutputData: () => ({ -// // packageNames: ["app-debug.apk", "app-release.apk", `${projectName}-debug.apk`, `${projectName}-release.apk`], -// // regexes: [/app-.*-(debug|release).apk/, new RegExp(`${projectName}-.*-(debug|release).apk`)] -// // }) -// // }; -// // }; -// // } - -// // function mockFileSystem(enumeratedFiles: string[]): void { -// // const fs = testInjector.resolve("fs"); -// // fs.enumerateFilesInDirectorySync = () => enumeratedFiles; -// // fs.readDirectory = () => []; -// // fs.getFsStats = () => (({ mtime: new Date() })); -// // } - -// // describe("android platform", () => { -// // function getTestCases(configuration: string, apkName: string) { -// // return [{ -// // name: "no additional options are specified in .gradle file", -// // buildOutput: [`/my/path/${configuration}/${apkName}-${configuration}.apk`], -// // expectedResult: `/my/path/${configuration}/${apkName}-${configuration}.apk` -// // }, { -// // name: "productFlavors are specified in .gradle file", -// // buildOutput: [`/my/path/arm64Demo/${configuration}/${apkName}-arm64-demo-${configuration}.apk`, -// // `/my/path/arm64Full/${configuration}/${apkName}-arm64-full-${configuration}.apk`, -// // `/my/path/armDemo/${configuration}/${apkName}-arm-demo-${configuration}.apk`, -// // `/my/path/armFull/${configuration}/${apkName}-arm-full-${configuration}.apk`, -// // `/my/path/x86Demo/${configuration}/${apkName}-x86-demo-${configuration}.apk`, -// // `/my/path/x86Full/${configuration}/${apkName}-x86-full-${configuration}.apk`], -// // expectedResult: `/my/path/x86Full/${configuration}/${apkName}-x86-full-${configuration}.apk` -// // }, { -// // name: "split options are specified in .gradle file", -// // buildOutput: [`/my/path/${configuration}/${apkName}-arm64-v8a-${configuration}.apk`, -// // `/my/path/${configuration}/${apkName}-armeabi-v7a-${configuration}.apk`, -// // `/my/path/${configuration}/${apkName}-universal-${configuration}.apk`, -// // `/my/path/${configuration}/${apkName}-x86-${configuration}.apk`], -// // expectedResult: `/my/path/${configuration}/${apkName}-x86-${configuration}.apk` -// // }, { -// // name: "android-runtime has version < 4.0.0", -// // buildOutput: [`/my/path/apk/${apkName}-${configuration}.apk`], -// // expectedResult: `/my/path/apk/${apkName}-${configuration}.apk` -// // }]; -// // } - -// // const platform = "Android"; -// // const buildConfigs = [{ buildForDevice: false }, { buildForDevice: true }]; -// // const apkNames = ["app", "testProj"]; -// // const configurations = ["debug", "release"]; - -// // _.each(apkNames, apkName => { -// // _.each(buildConfigs, buildConfig => { -// // _.each(configurations, configuration => { -// // _.each(getTestCases(configuration, apkName), testCase => { -// // it(`should find correct ${configuration} ${apkName}.apk when ${testCase.name} and buildConfig is ${JSON.stringify(buildConfig)}`, async () => { -// // mockData(testCase.buildOutput, apkName); -// // const actualResult = await platformService.buildPlatform(platform, buildConfig, { projectName: "" }); -// // assert.deepEqual(actualResult, testCase.expectedResult); -// // }); -// // }); -// // }); -// // }); -// // }); -// // }); -// // }); -// }); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 13c65167a0..f8c04fb8fe 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -37,7 +37,6 @@ import { PLUGINS_BUILD_DATA_FILENAME } from '../lib/constants'; import { GradleCommandService } from '../lib/services/android/gradle-command-service'; import { GradleBuildService } from '../lib/services/android/gradle-build-service'; import { GradleBuildArgsService } from '../lib/services/android/gradle-build-args-service'; -import { PreparePlatformService } from '../lib/services/platform/prepare-platform-service'; temp.track(); let isErrorThrown = false; @@ -57,7 +56,6 @@ function createTestInjector() { testInjector.register("projectData", ProjectData); testInjector.register("platforsmData", stubs.PlatformsDataStub); testInjector.register("childProcess", ChildProcess); - testInjector.register("platformService", PreparePlatformService); testInjector.register("platformsData", PlatformsData); testInjector.register("androidEmulatorServices", {}); testInjector.register("androidToolsInfo", AndroidToolsInfo); diff --git a/test/services/android-plugin-build-service.ts b/test/services/android-plugin-build-service.ts index 461311fc8f..962a0cdcd0 100644 --- a/test/services/android-plugin-build-service.ts +++ b/test/services/android-plugin-build-service.ts @@ -68,11 +68,6 @@ describe('androidPluginBuildService', () => { return null; } }); - testInjector.register('platformService', { - getCurrentPlatformVersion: (platform: string, projectData: IProjectData): string => { - return options.addProjectRuntime ? "1.0.0" : null; - } - }); testInjector.register('packageManager', setupNpm(options)); testInjector.register('filesHashService', { generateHashes: async (files: string[]): Promise => ({}), diff --git a/test/services/ios-device-debug-service.ts b/test/services/ios-device-debug-service.ts index d97f37863d..df52aaf7cd 100644 --- a/test/services/ios-device-debug-service.ts +++ b/test/services/ios-device-debug-service.ts @@ -26,7 +26,6 @@ class IOSDeviceDebugServiceInheritor extends IOSDeviceDebugService { const createTestInjector = (): IInjector => { const testInjector = new Yok(); testInjector.register("devicesService", {}); - testInjector.register("platformService", {}); testInjector.register("iOSEmulatorServices", {}); testInjector.register("childProcess", {}); diff --git a/test/services/platform/add-platform-service.ts b/test/services/platform/add-platform-service.ts index 6bcc6f491d..4da5880c47 100644 --- a/test/services/platform/add-platform-service.ts +++ b/test/services/platform/add-platform-service.ts @@ -2,15 +2,13 @@ import { InjectorStub } from "../../stubs"; import { AddPlatformService } from "../../../lib/services/platform/add-platform-service"; import { PacoteService } from "../../../lib/services/pacote-service"; import { assert } from "chai"; -import { format } from "util"; -import { AddPlaformErrors } from "../../../lib/constants"; -let extractedPackageFromPacote: string = null; +const nativePrepare: INativePrepare = null; function createTestInjector() { const injector = new InjectorStub(); injector.register("pacoteService", { - extractPackage: async (name: string): Promise => { extractedPackageFromPacote = name; } + extractPackage: async (name: string) => ({}) }); injector.register("terminalSpinnerService", { createSpinner: () => { @@ -44,37 +42,9 @@ describe("AddPlatformService", () => { const pacoteService: PacoteService = injector.resolve("pacoteService"); pacoteService.extractPackage = async (): Promise => { throw new Error(errorMessage); }; - await assert.isRejected(addPlatformService.addPlatform(projectData, { platformParam: platform }), errorMessage); - }); - it(`should fail when path passed to frameworkPath does not exist for ${platform}`, async () => { - const frameworkPath = "invalidPath"; - const errorMessage = format(AddPlaformErrors.InvalidFrameworkPathStringFormat, frameworkPath); - - await assert.isRejected(addPlatformService.addPlatform(projectData, { platformParam: platform, frameworkPath }), errorMessage); - }); - it(`should respect platform version in package.json's nativescript key for ${platform}`, async () => { - const version = "2.5.0"; - - const projectDataService = injector.resolve("projectDataService"); - projectDataService.getNSValue = () => ({ version }); - - await addPlatformService.addPlatform(projectData, { platformParam: platform }); - - const expectedPackageToAdd = `tns-${platform}@${version}`; - assert.deepEqual(extractedPackageFromPacote, expectedPackageToAdd); - }); - it(`should install latest platform if no information found in package.json's nativescript key for ${platform}`, async () => { - const latestCompatibleVersion = "5.0.0"; - - const packageInstallationManager = injector.resolve("packageInstallationManager"); - packageInstallationManager.getLatestCompatibleVersion = async () => latestCompatibleVersion; - const projectDataService = injector.resolve("projectDataService"); - projectDataService.getNSValue = () => null; - - await addPlatformService.addPlatform(projectData, { platformParam: platform }); + const platformData = injector.resolve("platformsData").getPlatformData(platform, projectData); - const expectedPackageToAdd = `tns-${platform}@${latestCompatibleVersion}`; - assert.deepEqual(extractedPackageFromPacote, expectedPackageToAdd); + await assert.isRejected(addPlatformService.addPlatformSafe(projectData, platformData, "somePackage", nativePrepare), errorMessage); }); it(`shouldn't add native platform when skipNativePrepare is provided for ${platform}`, async () => { const projectDataService = injector.resolve("projectDataService"); @@ -86,7 +56,7 @@ describe("AddPlatformService", () => { platformData.platformProjectService.createProject = () => isCreateNativeProjectCalled = true; platformsData.getPlatformData = () => platformData; - await addPlatformService.addPlatform(projectData, { platformParam: platform, nativePrepare: { skipNativePrepare: true } }); + await addPlatformService.addPlatformSafe(projectData, platformData, platform, { skipNativePrepare: true } ); assert.isFalse(isCreateNativeProjectCalled); }); it(`should add native platform when skipNativePrepare is not provided for ${platform}`, async () => { @@ -99,7 +69,7 @@ describe("AddPlatformService", () => { platformData.platformProjectService.createProject = () => isCreateNativeProjectCalled = true; platformsData.getPlatformData = () => platformData; - await addPlatformService.addPlatform(projectData, { platformParam: platform }); + await addPlatformService.addPlatformSafe(projectData, platformData, platform, nativePrepare); assert.isTrue(isCreateNativeProjectCalled); }); }); diff --git a/test/services/platform/platform-commands-service.ts b/test/services/platform/platform-commands-service.ts index 989cd9075a..185afb81b5 100644 --- a/test/services/platform/platform-commands-service.ts +++ b/test/services/platform/platform-commands-service.ts @@ -13,6 +13,10 @@ const projectData: any = { function createTestInjector() { const injector = new InjectorStub(); injector.register("addPlatformService", { + addPlatform: () => ({}) + }); + + injector.register("addPlatformController", { addPlatform: () => isAddPlatformCalled = true }); diff --git a/test/services/platform/platform-watcher-service.ts b/test/services/platform/platform-watcher-service.ts deleted file mode 100644 index dccc734b66..0000000000 --- a/test/services/platform/platform-watcher-service.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Yok } from "../../../lib/common/yok"; -import { PlatformWatcherService } from "../../../lib/services/platform/platform-watcher-service"; -import { assert } from "chai"; -import { INITIAL_SYNC_EVENT_NAME } from "../../../lib/constants"; - -const projectData = { projectDir: "myProjectDir", getAppResourcesRelativeDirectoryPath: () => "/my/app_resources/dir/path" }; -const preparePlatformData = { }; - -let isCompileWithWatchCalled = false; -let isNativePrepareCalled = false; -let emittedEventNames: string[] = []; -let emittedEventData: any[] = []; - -function createTestInjector(data: { hasNativeChanges: boolean }): IInjector { - const injector = new Yok(); - injector.register("logger", ({ - out: () => ({}), - trace: () => ({}) - })); - injector.register("preparePlatformService", ({ - prepareNativePlatform: async () => { - isNativePrepareCalled = true; - return data.hasNativeChanges; - } - })); - injector.register("webpackCompilerService", ({ - on: () => ({}), - emit: () => ({}), - compileWithWatch: async () => { - isCompileWithWatchCalled = true; - } - })); - injector.register("platformWatcherService", PlatformWatcherService); - - const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); - platformWatcherService.emit = (eventName: string, eventData: any) => { - emittedEventNames.push(eventName); - emittedEventData.push(eventData); - assert.isTrue(isCompileWithWatchCalled); - assert.isTrue(isNativePrepareCalled); - return true; - }; - - return injector; -} - -describe("PlatformWatcherService", () => { - beforeEach(() => { - emittedEventNames = []; - emittedEventData = []; - }); - describe("startWatcher", () => { - describe("initialSyncEvent", () => { - _.each(["iOS", "Android"], platform => { - _.each([true, false], hasNativeChanges => { - it(`should emit after native prepare and webpack's compilation are done for ${platform} platform and hasNativeChanges is ${hasNativeChanges}`, async () => { - const injector = createTestInjector({ hasNativeChanges }); - - const platformData = { platformNameLowerCase: platform.toLowerCase(), normalizedPlatformName: platform }; - const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); - await platformWatcherService.startWatchers(platformData, projectData, preparePlatformData); - - assert.lengthOf(emittedEventNames, 1); - assert.lengthOf(emittedEventData, 1); - assert.deepEqual(emittedEventNames[0], INITIAL_SYNC_EVENT_NAME); - assert.deepEqual(emittedEventData[0], { platform: platform.toLowerCase(), hasNativeChanges }); - }); - }); - }); - - _.each(["iOS", "Android"], platform => { - it(`should respect native changes that are made before the initial preparation of the project had been done for ${platform}`, async () => { - const injector = createTestInjector({ hasNativeChanges: false }); - - const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); - - const preparePlatformService = injector.resolve("preparePlatformService"); - preparePlatformService.prepareNativePlatform = async () => { - const nativeFilesWatcher = (platformWatcherService).watchersData[projectData.projectDir][platform.toLowerCase()].nativeFilesWatcher; - nativeFilesWatcher.emit("all", "change", "my/project/App_Resources/some/file"); - isNativePrepareCalled = true; - return false; - }; - - const platformData = { platformNameLowerCase: platform.toLowerCase(), normalizedPlatformName: platform }; - await platformWatcherService.startWatchers(platformData, projectData, preparePlatformData); - - assert.lengthOf(emittedEventNames, 1); - assert.lengthOf(emittedEventData, 1); - assert.deepEqual(emittedEventNames[0], INITIAL_SYNC_EVENT_NAME); - assert.deepEqual(emittedEventData[0], { platform: platform.toLowerCase(), hasNativeChanges: true }); - }); - }); - }); - describe("filesChangeEventData event", () => { - _.each(["iOS", "Android"], platform => { - it(`shouldn't emit filesChangeEventData before initialSyncEvent if js code is changed before the initial preparation of project has been done for ${platform}`, async () => { - const injector = createTestInjector({ hasNativeChanges: false }); - const hasNativeChanges = false; - - const preparePlatformService = injector.resolve("preparePlatformService"); - const webpackCompilerService = injector.resolve("webpackCompilerService"); - preparePlatformService.prepareNativePlatform = async () => { - webpackCompilerService.emit("webpackEmittedFiles", ["/some/file/path"]); - isNativePrepareCalled = true; - return hasNativeChanges; - }; - - const platformWatcherService: PlatformWatcherService = injector.resolve("platformWatcherService"); - const platformData = { platformNameLowerCase: platform.toLowerCase(), normalizedPlatformName: platform }; - await platformWatcherService.startWatchers(platformData, projectData, preparePlatformData); - - assert.lengthOf(emittedEventNames, 1); - assert.lengthOf(emittedEventData, 1); - assert.deepEqual(emittedEventNames[0], INITIAL_SYNC_EVENT_NAME); - assert.deepEqual(emittedEventData[0], { platform: platform.toLowerCase(), hasNativeChanges }); - }); - }); - }); - }); -}); diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index 92cf8f77a2..9baea238cc 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -2,14 +2,19 @@ import { Yok } from "../../../lib/common/yok"; import * as _ from 'lodash'; import { LoggerStub, ErrorsStub } from "../../stubs"; import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk"; -import { PreviewAppLiveSyncService } from "../../../lib/services/livesync/playground/preview-app-livesync-service"; import * as chai from "chai"; import * as path from "path"; import { ProjectFilesManager } from "../../../lib/common/services/project-files-manager"; import { EventEmitter } from "events"; import { PreviewAppFilesService } from "../../../lib/services/livesync/playground/preview-app-files-service"; -import { WorkflowDataService, PreparePlatformData } from "../../../lib/services/workflow/workflow-data-service"; -import { INITIAL_SYNC_EVENT_NAME } from "../../../lib/constants"; +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/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"; +import { PrepareController } from "../../../lib/controllers/prepare-controller"; interface ITestCase { name: string; @@ -32,8 +37,9 @@ interface IAssertOptions { } interface IActInput { - previewAppLiveSyncService?: IPreviewAppLiveSyncService; + previewAppController?: PreviewAppController; previewSdkService?: PreviewSdkServiceMock; + prepareController?: PrepareController; projectFiles?: string[]; actOptions?: IActOptions; } @@ -97,10 +103,10 @@ class LoggerMock extends LoggerStub { } } -class PlatformWatcherServiceMock extends EventEmitter { - public startWatchers(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData) { - isHMRPassedToEnv = preparePlatformData.env.hmr; - this.emit(INITIAL_SYNC_EVENT_NAME, {}); +class PrepareControllerMock extends EventEmitter { + public preparePlatform(prepareData: PrepareData) { + isHMRPassedToEnv = prepareData.env.hmr; + this.emit(PREPARE_READY_EVENT_NAME, { hmrData: {}, files: [] }); } } @@ -111,7 +117,9 @@ function createTestInjector(options?: { const injector = new Yok(); injector.register("logger", LoggerMock); - injector.register("hmrStatusService", {}); + injector.register("hmrStatusService", { + attachToHmrStatusEvent: () => ({}) + }); injector.register("errors", ErrorsStub); injector.register("platformsData", { getPlatformData: () => ({ @@ -134,7 +142,15 @@ function createTestInjector(options?: { getExternalPlugins: () => [] }); injector.register("projectFilesManager", ProjectFilesManager); - injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService); + injector.register("previewAppLiveSyncService", { + syncFilesForPlatformSafe: () => ({}) + }); + injector.register("previewAppEmitter", PreviewAppEmitter); + injector.register("previewAppController", PreviewAppController); + injector.register("prepareController", PrepareControllerMock); + injector.register("prepareDataService", PrepareDataService); + injector.register("mobileHelper", MobileHelper); + injector.register("devicePlatformsConstants", DevicePlatformsConstants); injector.register("fs", { readText: (filePath: string) => { readTextParams.push(filePath); @@ -165,8 +181,6 @@ function createTestInjector(options?: { injector.register("analyticsService", { trackEventActionInGoogleAnalytics: () => ({}) }); - injector.register("platformWatcherService", PlatformWatcherServiceMock); - injector.register("workflowDataService", WorkflowDataService); return injector; } @@ -175,22 +189,24 @@ function arrange(options?: { projectFiles?: string[] }) { options = options || {}; const injector = createTestInjector({ projectFiles: options.projectFiles }); - const previewAppLiveSyncService: IPreviewAppLiveSyncService = injector.resolve("previewAppLiveSyncService"); const previewSdkService: IPreviewSdkService = injector.resolve("previewSdkService"); + const previewAppController: PreviewAppController = injector.resolve("previewAppController"); + const prepareController: PrepareController = injector.resolve("prepareController"); return { - previewAppLiveSyncService, - previewSdkService + previewSdkService, + previewAppController, + prepareController, }; } async function initialSync(input?: IActInput) { input = input || {}; - const { previewAppLiveSyncService, previewSdkService, actOptions } = input; + const { previewAppController, previewSdkService, actOptions } = input; const syncFilesData = _.cloneDeep(syncFilesMockData); syncFilesData.useHotModuleReload = actOptions.hmr; - await previewAppLiveSyncService.initialize(syncFilesData); + await previewAppController.preview(syncFilesData); if (actOptions.callGetInitialFiles) { await previewSdkService.getInitialFiles(deviceMockData); } @@ -199,16 +215,16 @@ async function initialSync(input?: IActInput) { async function syncFiles(input?: IActInput) { input = input || {}; - const { previewAppLiveSyncService, previewSdkService, projectFiles, actOptions } = input; + const { previewAppController, previewSdkService, prepareController, projectFiles, actOptions } = input; const syncFilesData = _.cloneDeep(syncFilesMockData); syncFilesData.useHotModuleReload = actOptions.hmr; - await previewAppLiveSyncService.initialize(syncFilesData); + await previewAppController.preview(syncFilesData); if (actOptions.callGetInitialFiles) { await previewSdkService.getInitialFiles(deviceMockData); } - await previewAppLiveSyncService.syncFiles(syncFilesMockData, projectFiles, []); + prepareController.emit(PREPARE_READY_EVENT_NAME, { files: projectFiles }); } async function assert(expectedFiles: string[], options?: IAssertOptions) { @@ -273,14 +289,14 @@ function execute(options: { it(`${testCase.name}`, async () => { const projectFiles = testCase.appFiles ? testCase.appFiles.map(file => path.join(projectDirPath, "app", file)) : null; - const { previewAppLiveSyncService, previewSdkService } = arrange({ projectFiles }); - await act.apply(null, [{ previewAppLiveSyncService, previewSdkService, projectFiles, actOptions: testCase.actOptions }]); + const { previewAppController, prepareController, previewSdkService } = arrange({ projectFiles }); + await act.apply(null, [{ previewAppController, prepareController, previewSdkService, projectFiles, actOptions: testCase.actOptions }]); await assert(testCase.expectedFiles, testCase.assertOptions); }); }); } -describe("previewAppLiveSyncService", () => { +describe("previewAppController", () => { describe("initialSync", () => { afterEach(() => reset()); @@ -301,29 +317,6 @@ describe("previewAppLiveSyncService", () => { describe("syncFiles", () => { afterEach(() => reset()); - const nativeFilesTestCases: ITestCase[] = [ - { - name: "Android manifest is changed", - appFiles: ["App_Resources/Android/src/main/AndroidManifest.xml"], - expectedFiles: [] - }, - { - name: "Android app.gradle is changed", - appFiles: ["App_Resources/Android/app.gradle"], - expectedFiles: [] - }, - { - name: "iOS Info.plist is changed", - appFiles: ["App_Resources/iOS/Info.plist"], - expectedFiles: [] - }, - { - name: "iOS build.xcconfig is changed", - appFiles: ["App_Resources/iOS/build.xcconfig"], - expectedFiles: [] - } - ]; - const hmrTestCases: ITestCase[] = [ { name: "when set to true", @@ -362,13 +355,6 @@ describe("previewAppLiveSyncService", () => { ]; const testCategories = [ - { - name: "should show warning and not transfer native files when", - testCases: nativeFilesTestCases.map(testCase => { - testCase.assertOptions = { checkWarnings: true }; - return testCase; - }) - }, { name: "should handle correctly when no files are provided", testCases: noAppFilesTestCases diff --git a/test/services/test-execution-service.ts b/test/services/test-execution-service.ts index 5a42a3c0eb..c8c541aad1 100644 --- a/test/services/test-execution-service.ts +++ b/test/services/test-execution-service.ts @@ -8,7 +8,7 @@ const unitTestsPluginName = "nativescript-unit-test-runner"; function getTestExecutionService(): ITestExecutionService { const injector = new InjectorStub(); injector.register("testExecutionService", TestExecutionService); - injector.register("mainController", {}); + injector.register("runOnDevicesController", {}); return injector.resolve("testExecutionService"); } diff --git a/test/stubs.ts b/test/stubs.ts index 77095580aa..90dc1863ed 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -9,7 +9,7 @@ import * as prompt from "inquirer"; import { Yok } from "./../lib/common/yok"; import { HostInfo } from "./../lib/common/host-info"; import { DevicePlatformsConstants } from "./../lib/common/mobile/device-platforms-constants"; -import { PreparePlatformData } from "../lib/services/workflow/workflow-data-service"; +import { PrepareData } from "../lib/data/prepare-data"; export class LoggerStub implements ILogger { getLevel(): string { return undefined; } @@ -517,6 +517,7 @@ export class ProjectDataService implements IProjectDataService { ios: "org.nativescript.myiosApp", android: "org.nativescript.myAndroidApp" }, + getAppResourcesRelativeDirectoryPath: () => "/path/to/my/projecDir/App_Resources" }; } @@ -741,7 +742,7 @@ export class ChildProcessStub extends EventEmitter { } export class ProjectChangesService implements IProjectChangesService { - public async checkForChanges(platformData: IPlatformData, projectData: IProjectData, preparePlatformData: PreparePlatformData): Promise { + public async checkForChanges(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { return {}; } @@ -811,6 +812,35 @@ export class PerformanceService implements IPerformanceService { processExecutionData() { } } +export class PacoteServiceStub implements IPacoteService { + public async manifest(packageName: string, options?: IPacoteManifestOptions): Promise { + return ""; + } + public async extractPackage(packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise { } +} + +class TerminalSpinnerStub { + public text: string; + public start(text?: string): ITerminalSpinner { return this; } + public stop(): ITerminalSpinner { return this; } + public succeed(text?: string): ITerminalSpinner { return this; } + public fail(text?: string): ITerminalSpinner { return this; } + public warn(text?: string): ITerminalSpinner { return this; } + public info(text?: string): ITerminalSpinner { return this; } + public clear(): ITerminalSpinner { return this; } + public render(): ITerminalSpinner { return this; } + public frame(): ITerminalSpinner { return this; } +} + +export class TerminalSpinnerServiceStub implements ITerminalSpinnerService { + public createSpinner(spinnerOptions?: ITerminalSpinnerOptions): ITerminalSpinner { + return new TerminalSpinnerStub(); + } + public async execute(spinnerOptions: ITerminalSpinnerOptions, action: () => Promise): Promise { + return null; + } +} + export class InjectorStub extends Yok implements IInjector { constructor() { super(); @@ -848,5 +878,6 @@ export class InjectorStub extends Yok implements IInjector { getDevice: (): Mobile.IDevice => undefined, getDeviceByIdentifier: (): Mobile.IDevice => undefined }); + this.register("terminalSpinnerService", TerminalSpinnerServiceStub); } } diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index cb2edfdf03..e80a4ff0e5 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -1,11 +1,10 @@ import { PublishIOS } from "../lib/commands/appstore-upload"; -import { PrompterStub, LoggerStub, ProjectDataStub } from "./stubs"; +import { PrompterStub, LoggerStub, ProjectDataStub, ProjectDataService } from "./stubs"; import * as chai from "chai"; import * as yok from "../lib/common/yok"; -import { BuildPlatformService } from "../lib/services/platform/build-platform-service"; -import { PreparePlatformService } from "../lib/services/platform/prepare-platform-service"; -import { MainController } from "../lib/controllers/main-controller"; -import { WorkflowDataService } from "../lib/services/workflow/workflow-data-service"; +import { PrepareNativePlatformService } from "../lib/services/platform/prepare-native-platform-service"; +import { BuildController } from "../lib/controllers/build-controller"; +import { IOSBuildData } from "../lib/data/build-data"; class AppStore { static itunesconnect = { @@ -19,14 +18,12 @@ class AppStore { options: any; prompter: PrompterStub; projectData: ProjectDataStub; - buildPlatformService: BuildPlatformService; - preparePlatformService: PreparePlatformService; + buildController: BuildController; + prepareNativePlatformService: PrepareNativePlatformService; platformCommandsService: any; platformValidationService: any; - mainController: MainController; iOSPlatformData: any; iOSProjectService: any; - workflowDataService: WorkflowDataService; loggerService: LoggerStub; itmsTransporterService: any; @@ -59,13 +56,10 @@ class AppStore { "devicePlatformsConstants": { "iOS": "iOS" }, - "preparePlatformService": this.preparePlatformService = {}, + "prepareNativePlatformService": this.prepareNativePlatformService = {}, "platformCommandsService": this.platformCommandsService = {}, "platformValidationService": this.platformValidationService = {}, - "mainController": this.mainController = { - buildPlatform: () => ({}) - }, - "buildPlatformService": this.buildPlatformService = { + "buildController": this.buildController = { buildPlatform: async () => { this.archiveCalls++; return "/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"; @@ -77,9 +71,9 @@ class AppStore { return this.iOSPlatformData; } }, - "workflowDataService": this.workflowDataService = {}, } }); + this.projectData.initializeProjectData(this.iOSPlatformData.projectRoot); this.command = this.injector.resolveCommand("appstore"); } @@ -94,6 +88,8 @@ class AppStore { this.injector.register(serv, services.services[serv]); } } + + this.injector.register("projectDataService", ProjectDataService); } assert() { @@ -111,10 +107,10 @@ class AppStore { expectArchive() { this.expectedArchiveCalls = 1; - this.mainController.buildPlatform = (platform: string, projectDir: string, options) => { + this.buildController.prepareAndBuildPlatform = (iOSBuildData: IOSBuildData) => { this.archiveCalls++; - chai.assert.equal(projectDir, "/Users/person/git/MyProject"); - chai.assert.isTrue(options.buildForAppStore); + chai.assert.equal(iOSBuildData.projectDir, "/Users/person/git/MyProject"); + chai.assert.isTrue(iOSBuildData.buildForAppStore); return Promise.resolve("/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"); }; } From 4fc6b8ee43c2c274a96fe93f8a9013355a8e72dc Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 15 May 2019 14:00:38 +0300 Subject: [PATCH 29/30] fix: rename platformsData to platformsDataService --- lib/bootstrap.ts | 2 +- lib/commands/add-platform.ts | 4 ++-- lib/commands/build.ts | 16 ++++++++-------- lib/commands/command-base.ts | 4 ++-- lib/commands/debug.ts | 4 ++-- lib/commands/deploy.ts | 4 ++-- lib/commands/install.ts | 6 +++--- lib/commands/prepare.ts | 4 ++-- lib/commands/run.ts | 8 ++++---- lib/commands/update.ts | 8 ++++---- .../test/unit-tests/services/net-service.ts | 6 +++--- lib/controllers/add-platform-controller.ts | 6 +++--- lib/controllers/build-controller.ts | 8 ++++---- lib/controllers/prepare-controller.ts | 4 ++-- lib/controllers/run-on-devices-controller.ts | 6 +++--- lib/definitions/platform.d.ts | 2 +- lib/definitions/plugins.d.ts | 2 +- lib/helpers/livesync-command-helper.ts | 8 ++++---- lib/providers/project-files-provider.ts | 4 ++-- lib/services/android-plugin-build-service.ts | 6 +++--- .../device/device-debug-app-service.ts | 2 +- .../device/device-install-app-service.ts | 6 +++--- lib/services/init-service.ts | 8 ++++---- .../android-device-livesync-service-base.ts | 4 ++-- .../android-device-livesync-service.ts | 4 ++-- .../android-device-livesync-sockets-service.ts | 6 +++--- .../livesync/android-livesync-service.ts | 4 ++-- .../livesync/device-livesync-service-base.ts | 4 ++-- .../livesync/ios-device-livesync-service.ts | 4 ++-- lib/services/livesync/ios-livesync-service.ts | 6 +++--- .../livesync/platform-livesync-service-base.ts | 10 +++++----- .../playground/preview-app-files-service.ts | 4 ++-- .../platform/platform-commands-service.ts | 18 +++++++++--------- .../platform/platform-validation-service.ts | 14 +++++++------- .../platforms-data-service.ts} | 13 +++++++------ lib/services/plugins-service.ts | 16 ++++++++-------- test/ios-entitlements-service.ts | 2 +- test/platform-commands.ts | 4 ++-- test/plugins-service.ts | 18 +++++++++--------- test/project-changes-service.ts | 10 +++++----- test/project-files-provider.ts | 2 +- .../android-device-livesync-service-base.ts | 4 ++-- test/services/platform/add-platform-service.ts | 14 +++++++------- .../playground/preview-app-files-service.ts | 4 ++-- .../playground/preview-app-livesync-service.ts | 2 +- test/stubs.ts | 4 ++-- test/tns-appstore-upload.ts | 2 +- test/update.ts | 2 +- 48 files changed, 152 insertions(+), 151 deletions(-) rename lib/{platforms-data.ts => services/platforms-data-service.ts} (58%) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 4841ff1fdf..91c113c782 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -32,7 +32,7 @@ $injector.require("projectTemplatesService", "./services/project-templates-servi $injector.require("projectNameService", "./services/project-name-service"); $injector.require("tnsModulesService", "./services/tns-modules-service"); -$injector.require("platformsData", "./platforms-data"); +$injector.require("platformsDataService", "./services/platforms-data-service"); $injector.require("addPlatformService", "./services/platform/add-platform-service"); $injector.require("buildInfoFileService", "./services/build-info-file-service"); $injector.require("prepareNativePlatformService", "./services/platform/prepare-native-platform-service"); diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts index 16fe4d13ab..baa670c541 100644 --- a/lib/commands/add-platform.ts +++ b/lib/commands/add-platform.ts @@ -7,9 +7,9 @@ export class AddPlatformCommand extends ValidatePlatformCommandBase implements I private $platformCommandsService: IPlatformCommandsService, $platformValidationService: IPlatformValidationService, $projectData: IProjectData, - $platformsData: IPlatformsData, + $platformsDataService: IPlatformsDataService, private $errors: IErrors) { - super($options, $platformsData, $platformValidationService, $projectData); + super($options, $platformsDataService, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } diff --git a/lib/commands/build.ts b/lib/commands/build.ts index a90d2d192d..2d0e7672a9 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -7,14 +7,14 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { constructor($options: IOptions, protected $errors: IErrors, $projectData: IProjectData, - $platformsData: IPlatformsData, + $platformsDataService: IPlatformsDataService, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $buildController: BuildController, $platformValidationService: IPlatformValidationService, private $bundleValidatorHelper: IBundleValidatorHelper, private $buildDataService: BuildDataService, protected $logger: ILogger) { - super($options, $platformsData, $platformValidationService, $projectData); + super($options, $platformsDataService, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } @@ -62,18 +62,18 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { constructor(protected $options: IOptions, $errors: IErrors, $projectData: IProjectData, - $platformsData: IPlatformsData, + $platformsDataService: IPlatformsDataService, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $buildController: BuildController, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, $logger: ILogger, $buildDataService: BuildDataService) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); + super($options, $errors, $projectData, $platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); } public async execute(args: string[]): Promise { - await this.executeCore([this.$platformsData.availablePlatforms.iOS]); + await this.executeCore([this.$platformsDataService.availablePlatforms.iOS]); } public async canExecute(args: string[]): Promise { @@ -98,7 +98,7 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { constructor(protected $options: IOptions, protected $errors: IErrors, $projectData: IProjectData, - $platformsData: IPlatformsData, + $platformsDataService: IPlatformsDataService, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $buildController: BuildController, $platformValidationService: IPlatformValidationService, @@ -106,11 +106,11 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, $buildDataService: BuildDataService, protected $logger: ILogger) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); + super($options, $errors, $projectData, $platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); } public async execute(args: string[]): Promise { - await this.executeCore([this.$platformsData.availablePlatforms.Android]); + await this.executeCore([this.$platformsDataService.availablePlatforms.Android]); if (this.$options.aab) { this.$logger.info(AndroidAppBundleMessages.ANDROID_APP_BUNDLE_DOCS_MESSAGE); diff --git a/lib/commands/command-base.ts b/lib/commands/command-base.ts index 27eb6a78fb..7621f92a69 100644 --- a/lib/commands/command-base.ts +++ b/lib/commands/command-base.ts @@ -1,6 +1,6 @@ export abstract class ValidatePlatformCommandBase { constructor(protected $options: IOptions, - protected $platformsData: IPlatformsData, + protected $platformsDataService: IPlatformsDataService, protected $platformValidationService: IPlatformValidationService, protected $projectData: IProjectData) { } @@ -22,7 +22,7 @@ export abstract class ValidatePlatformCommandBase { } private async validatePlatformBase(platform: string, notConfiguredEnvOptions: INotConfiguredEnvOptions): Promise { - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, this.$projectData); const platformProjectService = platformData.platformProjectService; const result = await platformProjectService.validate(this.$projectData, this.$options, notConfiguredEnvOptions); return result; diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 0ee43d9069..fab91e8499 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -13,14 +13,14 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements $platformValidationService: IPlatformValidationService, $projectData: IProjectData, $options: IOptions, - $platformsData: IPlatformsData, + $platformsDataService: IPlatformsDataService, protected $logger: ILogger, protected $errors: IErrors, private $debugDataService: IDebugDataService, private $deviceDebugAppService: DeviceDebugAppService, private $liveSyncCommandHelper: ILiveSyncCommandHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { - super($options, $platformsData, $platformValidationService, $projectData); + super($options, $platformsDataService, $platformValidationService, $projectData); } public async execute(args: string[]): Promise { diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index 66a672007c..bedfa975be 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -11,11 +11,11 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement $projectData: IProjectData, private $errors: IErrors, private $mobileHelper: Mobile.IMobileHelper, - $platformsData: IPlatformsData, + $platformsDataService: IPlatformsDataService, private $bundleValidatorHelper: IBundleValidatorHelper, private $deployCommandHelper: DeployCommandHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { - super($options, $platformsData, $platformValidationService, $projectData); + super($options, $platformsDataService, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } diff --git a/lib/commands/install.ts b/lib/commands/install.ts index bdbfd2921b..82f22366fa 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -5,7 +5,7 @@ export class InstallCommand implements ICommand { public allowedParameters: ICommandParameter[] = [this.$stringParameter]; constructor(private $options: IOptions, - private $platformsData: IPlatformsData, + private $platformsDataService: IPlatformsDataService, private $platformCommandsService: IPlatformCommandsService, private $projectData: IProjectData, private $projectDataService: IProjectDataService, @@ -26,8 +26,8 @@ export class InstallCommand implements ICommand { await this.$pluginsService.ensureAllDependenciesAreInstalled(this.$projectData); - for (const platform of this.$platformsData.platformsNames) { - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + for (const platform of this.$platformsDataService.platformsNames) { + const platformData = this.$platformsDataService.getPlatformData(platform, this.$projectData); const frameworkPackageData = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName); if (frameworkPackageData && frameworkPackageData.version) { try { diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 966a42f9ef..7eea86487d 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -14,9 +14,9 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm $platformValidationService: IPlatformValidationService, $projectData: IProjectData, private $platformCommandParameter: ICommandParameter, - $platformsData: IPlatformsData, + $platformsDataService: IPlatformsDataService, private $prepareDataService: PrepareDataService) { - super($options, $platformsData, $platformValidationService, $projectData); + super($options, $platformsDataService, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } diff --git a/lib/commands/run.ts b/lib/commands/run.ts index a283a5b03f..780f28fb37 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -67,7 +67,7 @@ export class RunIosCommand implements ICommand { private $errors: IErrors, private $injector: IInjector, private $options: IOptions, - private $platformsData: IPlatformsData, + private $platformsDataService: IPlatformsDataService, private $platformValidationService: IPlatformValidationService, private $projectDataService: IProjectDataService, ) { @@ -84,7 +84,7 @@ export class RunIosCommand implements ICommand { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - const result = await this.runCommand.canExecute(args) && await this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, projectData, this.$platformsData.availablePlatforms.iOS); + const result = await this.runCommand.canExecute(args) && await this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, projectData, this.$platformsDataService.availablePlatforms.iOS); return result; } } @@ -110,7 +110,7 @@ export class RunAndroidCommand implements ICommand { private $errors: IErrors, private $injector: IInjector, private $options: IOptions, - private $platformsData: IPlatformsData, + private $platformsDataService: IPlatformsDataService, private $platformValidationService: IPlatformValidationService, private $projectData: IProjectData, ) { } @@ -130,7 +130,7 @@ export class RunAndroidCommand implements ICommand { this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } - return this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); + return this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsDataService.availablePlatforms.Android); } } diff --git a/lib/commands/update.ts b/lib/commands/update.ts index be4f45f27c..8fa6d63206 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -10,13 +10,13 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma private $logger: ILogger, $options: IOptions, private $platformCommandsService: IPlatformCommandsService, - $platformsData: IPlatformsData, + $platformsDataService: IPlatformsDataService, $platformValidationService: IPlatformValidationService, private $pluginsService: IPluginsService, $projectData: IProjectData, private $projectDataService: IProjectDataService, ) { - super($options, $platformsData, $platformValidationService, $projectData); + super($options, $platformsDataService, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } @@ -81,7 +81,7 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma const platforms = this.getPlatforms(); for (const platform of _.xor(platforms.installed, platforms.packagePlatforms)) { - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, this.$projectData); this.$projectDataService.removeNSProperty(this.$projectData.projectDir, platformData.frameworkPackageName); } @@ -115,7 +115,7 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma const packagePlatforms: string[] = []; for (const platform of availablePlatforms) { - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, this.$projectData); const platformVersion = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName); if (platformVersion) { packagePlatforms.push(platform); diff --git a/lib/common/test/unit-tests/services/net-service.ts b/lib/common/test/unit-tests/services/net-service.ts index 38b0ff434b..daee64ed79 100644 --- a/lib/common/test/unit-tests/services/net-service.ts +++ b/lib/common/test/unit-tests/services/net-service.ts @@ -27,7 +27,7 @@ describe("net", () => { const childProcess = testInjector.resolve("childProcess"); childProcess.exec = async (command: string, options?: any, execOptions?: IExecOptions): Promise => { - const platformsData: IDictionary = { + const platformsDataService: IDictionary = { linux: { data: `Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State @@ -67,10 +67,10 @@ Active Connections execCalledCount++; - let data = platformsData[platform].data; + let data = platformsDataService[platform].data; if (port) { - data += `${EOL}${platformsData[platform].portData}`; + data += `${EOL}${platformsDataService[platform].portData}`; } if (iteration) { diff --git a/lib/controllers/add-platform-controller.ts b/lib/controllers/add-platform-controller.ts index a4e8190373..d7a5255cc4 100644 --- a/lib/controllers/add-platform-controller.ts +++ b/lib/controllers/add-platform-controller.ts @@ -11,14 +11,14 @@ export class AddPlatformController { private $logger: ILogger, private $packageInstallationManager: IPackageInstallationManager, private $projectDataService: IProjectDataService, - private $platformsData: IPlatformsData, + private $platformsDataService: IPlatformsDataService, private $projectChangesService: IProjectChangesService, ) { } public async addPlatform(addPlatformData: AddPlatformData): Promise { const [ platform, version ] = addPlatformData.platform.toLowerCase().split("@"); const projectData = this.$projectDataService.getProjectData(addPlatformData.projectDir); - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); this.$logger.trace(`Creating NativeScript project for the ${platform} platform`); this.$logger.trace(`Path: ${platformData.projectRoot}`); @@ -38,7 +38,7 @@ export class AddPlatformController { public async addPlatformIfNeeded(addPlatformData: AddPlatformData): Promise { const [ platform ] = addPlatformData.platform.toLowerCase().split("@"); const projectData = this.$projectDataService.getProjectData(addPlatformData.projectDir); - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const shouldAddPlatform = this.shouldAddPlatform(platformData, projectData, addPlatformData.nativePrepare); if (shouldAddPlatform) { diff --git a/lib/controllers/build-controller.ts b/lib/controllers/build-controller.ts index dd2229689f..263062064f 100644 --- a/lib/controllers/build-controller.ts +++ b/lib/controllers/build-controller.ts @@ -21,8 +21,8 @@ export class BuildController extends EventEmitter { private $prepareController: PrepareController, ) { super(); } - private get $platformsData(): IPlatformsData { - return this.$injector.resolve("platformsData"); + private get $platformsDataService(): IPlatformsDataService { + return this.$injector.resolve("platformsDataService"); } public async prepareAndBuildPlatform(buildData: BuildData): Promise { @@ -37,7 +37,7 @@ export class BuildController extends EventEmitter { const platform = buildData.platform.toLowerCase(); const projectData = this.$projectDataService.getProjectData(buildData.projectDir); - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const action = constants.TrackActionNames.Build; const isForDevice = this.$mobileHelper.isAndroidPlatform(platform) ? null : buildData && buildData.buildForDevice; @@ -81,7 +81,7 @@ export class BuildController extends EventEmitter { const platform = buildData.platform.toLowerCase(); const projectData = this.$projectDataService.getProjectData(buildData.projectDir); - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const outputPath = buildData.outputPath || platformData.getBuildOutputPath(buildData); const shouldBuildPlatform = await this.shouldBuildPlatform(buildData, platformData, outputPath); diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 62d121c03a..2edc33eff6 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -25,7 +25,7 @@ export class PrepareController extends EventEmitter { private $addPlatformController: AddPlatformController, public $hooksService: HooksService, private $logger: ILogger, - private $platformsData: IPlatformsData, + private $platformsDataService: IPlatformsDataService, private $prepareNativePlatformService: PrepareNativePlatformService, private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService, @@ -41,7 +41,7 @@ export class PrepareController extends EventEmitter { let result = null; const projectData = this.$projectDataService.getProjectData(prepareData.projectDir); - const platformData = this.$platformsData.getPlatformData(prepareData.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(prepareData.platform, projectData); if (prepareData.watch) { result = await this.startWatchersWithPrepare(platformData, projectData, prepareData); diff --git a/lib/controllers/run-on-devices-controller.ts b/lib/controllers/run-on-devices-controller.ts index 1d3c173c5f..a57b3f77aa 100644 --- a/lib/controllers/run-on-devices-controller.ts +++ b/lib/controllers/run-on-devices-controller.ts @@ -29,7 +29,7 @@ export class RunOnDevicesController extends EventEmitter { private $liveSyncServiceResolver: LiveSyncServiceResolver, private $logger: ILogger, private $pluginsService: IPluginsService, - private $platformsData: IPlatformsData, + private $platformsDataService: IPlatformsDataService, private $prepareNativePlatformService: PrepareNativePlatformService, private $prepareController: PrepareController, private $prepareDataService: PrepareDataService, @@ -165,7 +165,7 @@ export class RunOnDevicesController extends EventEmitter { private async syncInitialDataOnDevices(data: IPrepareOutputData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - const platformData = this.$platformsData.getPlatformData(data.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(data.platform, projectData); const buildData = this.$buildDataService.getBuildData(projectData.projectDir, data.platform, { ...liveSyncInfo, outputPath: deviceDescriptor.outputPath }); try { @@ -206,7 +206,7 @@ export class RunOnDevicesController extends EventEmitter { private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - const platformData = this.$platformsData.getPlatformData(data.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(data.platform, projectData); const prepareData = this.$prepareDataService.getPrepareData(projectData.projectDir, data.platform, { ...liveSyncInfo, watch: !liveSyncInfo.skipWatcher }); const buildData = this.$buildDataService.getBuildData(projectData.projectDir, data.platform, { ...liveSyncInfo, outputPath: deviceDescriptor.outputPath }); diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index d6714c247b..218c429d2d 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -42,7 +42,7 @@ interface IBuildOutputOptions extends Partial, IRelease, IHasAn outputPath?: string; } -interface IPlatformsData { +interface IPlatformsDataService { availablePlatforms: any; platformsNames: string[]; getPlatformData(platform: string, projectData: IProjectData): IPlatformData; diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 481f56057b..dd59b45d32 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -26,7 +26,7 @@ interface IBasePluginData { } interface IPluginData extends INodeModuleData { - platformsData: IPluginPlatformsData; + platformsDataService: IPluginPlatformsData; /* Gets all plugin variables from plugin */ pluginVariables: IDictionary; pluginPlatformsFolderPath(platform: string): string; diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 8347b99ec7..fb8656f451 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -20,8 +20,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $cleanupService: ICleanupService ) { } - private get $platformsData(): IPlatformsData { - return this.$injector.resolve("platformsData"); + private get $platformsDataService(): IPlatformsDataService { + return this.$injector.resolve("platformsDataService"); } public async getDeviceInstances(platform?: string): Promise { @@ -120,7 +120,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } public getPlatformsForOperation(platform: string): string[] { - const availablePlatforms = platform ? [platform] : _.values(this.$platformsData.availablePlatforms); + const availablePlatforms = platform ? [platform] : _.values(this.$platformsDataService.availablePlatforms); return availablePlatforms; } @@ -173,7 +173,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const availablePlatforms = this.getPlatformsForOperation(platform); for (const availablePlatform of availablePlatforms) { - const platformData = this.$platformsData.getPlatformData(availablePlatform, this.$projectData); + const platformData = this.$platformsDataService.getPlatformData(availablePlatform, this.$projectData); const platformProjectService = platformData.platformProjectService; const validateOutput = await platformProjectService.validate(this.$projectData, this.$options); result[availablePlatform.toLowerCase()] = validateOutput; diff --git a/lib/providers/project-files-provider.ts b/lib/providers/project-files-provider.ts index 7451b075ac..d72cf7bd42 100644 --- a/lib/providers/project-files-provider.ts +++ b/lib/providers/project-files-provider.ts @@ -4,7 +4,7 @@ import * as path from "path"; import { ProjectFilesProviderBase } from "../common/services/project-files-provider-base"; export class ProjectFilesProvider extends ProjectFilesProviderBase { - constructor(private $platformsData: IPlatformsData, + constructor(private $platformsDataService: IPlatformsDataService, $mobileHelper: Mobile.IMobileHelper, $options: IOptions) { super($mobileHelper, $options); @@ -13,7 +13,7 @@ export class ProjectFilesProvider extends ProjectFilesProviderBase { private static INTERNAL_NONPROJECT_FILES = ["**/*.ts"]; public mapFilePath(filePath: string, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): string { - const platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); + const platformData = this.$platformsDataService.getPlatformData(platform.toLowerCase(), projectData); const parsedFilePath = this.getPreparedFilePath(filePath, projectFilesConfig); let mappedFilePath = ""; let relativePath; diff --git a/lib/services/android-plugin-build-service.ts b/lib/services/android-plugin-build-service.ts index 1f53d8c345..64ea547406 100644 --- a/lib/services/android-plugin-build-service.ts +++ b/lib/services/android-plugin-build-service.ts @@ -4,8 +4,8 @@ import { getShortPluginName, hook } from "../common/helpers"; import { Builder, parseString } from "xml2js"; export class AndroidPluginBuildService implements IAndroidPluginBuildService { - private get $platformsData(): IPlatformsData { - return this.$injector.resolve("platformsData"); + private get $platformsDataService(): IPlatformsDataService { + return this.$injector.resolve("platformsDataService"); } constructor( @@ -293,7 +293,7 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { let runtimeGradleVersions: IRuntimeGradleVersions = null; if (projectDir) { const projectData = this.$projectDataService.getProjectData(projectDir); - const platformData = this.$platformsData.getPlatformData(this.$devicePlatformsConstants.Android, projectData); + const platformData = this.$platformsDataService.getPlatformData(this.$devicePlatformsConstants.Android, projectData); const projectRuntimeVersion = platformData.platformProjectService.getFrameworkVersion(projectData); this.$logger.trace(`Got gradle versions ${JSON.stringify(runtimeGradleVersions)} from runtime v${projectRuntimeVersion}`); } diff --git a/lib/services/device/device-debug-app-service.ts b/lib/services/device/device-debug-app-service.ts index aac9393130..147a6dbc78 100644 --- a/lib/services/device/device-debug-app-service.ts +++ b/lib/services/device/device-debug-app-service.ts @@ -43,7 +43,7 @@ export class DeviceDebugAppService { const projectData = this.$projectDataService.getProjectData(settings.projectDir); const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); - // const platformData = this.$platformsData.getPlatformData(settings.platform, projectData); + // 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. diff --git a/lib/services/device/device-install-app-service.ts b/lib/services/device/device-install-app-service.ts index cf5a6521f0..e243d188dd 100644 --- a/lib/services/device/device-install-app-service.ts +++ b/lib/services/device/device-install-app-service.ts @@ -18,14 +18,14 @@ export class DeviceInstallAppService { private $mobileHelper: MobileHelper, private $buildInfoFileService: BuildInfoFileService, private $projectDataService: IProjectDataService, - private $platformsData: IPlatformsData + private $platformsDataService: IPlatformsDataService ) { } public async installOnDevice(device: Mobile.IDevice, buildData: BuildData, packageFile?: string): Promise { this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); const projectData = this.$projectDataService.getProjectData(buildData.projectDir); - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); await this.$analyticsService.trackEventActionInGoogleAnalytics({ action: TrackActionNames.Deploy, @@ -98,7 +98,7 @@ export class DeviceInstallAppService { private async shouldInstall(device: Mobile.IDevice, buildData: BuildData, outputPath?: string): Promise { const projectData = this.$projectDataService.getProjectData(buildData.projectDir); - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); const platform = device.deviceInfo.platform; if (!(await device.applicationManager.isApplicationInstalled(projectData.projectIdentifiers[platform.toLowerCase()]))) { return true; diff --git a/lib/services/init-service.ts b/lib/services/init-service.ts index 8a35b0af0f..c19b30503d 100644 --- a/lib/services/init-service.ts +++ b/lib/services/init-service.ts @@ -35,7 +35,7 @@ export class InitService implements IInitService { if (!projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]) { projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE] = {}; - this.$fs.writeJson(this.projectFilePath, projectData); // We need to create package.json file here in order to prevent "No project found at or above and neither was a --path specified." when resolving platformsData + this.$fs.writeJson(this.projectFilePath, projectData); // We need to create package.json file here in order to prevent "No project found at or above and neither was a --path specified." when resolving platformsDataService } try { @@ -46,11 +46,11 @@ export class InitService implements IInitService { projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][this.$options.frameworkName] = _.extend(currentPlatformData, this.buildVersionData(this.$options.frameworkVersion)); } else { - const $platformsData = this.$injector.resolve("platformsData"); + const $platformsDataService = this.$injector.resolve("platformsDataService"); const $projectData = this.$injector.resolve("projectData"); $projectData.initializeProjectData(path.dirname(this.projectFilePath)); - for (const platform of $platformsData.platformsNames) { - const platformData: IPlatformData = $platformsData.getPlatformData(platform, $projectData); + for (const platform of $platformsDataService.platformsNames) { + const platformData: IPlatformData = $platformsDataService.getPlatformData(platform, $projectData); if (!platformData.targetedOS || (platformData.targetedOS && _.includes(platformData.targetedOS, process.platform))) { const currentPlatformData = projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][platformData.frameworkPackageName] || {}; diff --git a/lib/services/livesync/android-device-livesync-service-base.ts b/lib/services/livesync/android-device-livesync-service-base.ts index d8aa61f351..2459d625dd 100644 --- a/lib/services/livesync/android-device-livesync-service-base.ts +++ b/lib/services/livesync/android-device-livesync-service-base.ts @@ -2,11 +2,11 @@ import { DeviceLiveSyncServiceBase } from './device-livesync-service-base'; export abstract class AndroidDeviceLiveSyncServiceBase extends DeviceLiveSyncServiceBase { constructor(protected $injector: IInjector, - protected $platformsData: IPlatformsData, + protected $platformsDataService: IPlatformsDataService, protected $filesHashService: IFilesHashService, protected $logger: ILogger, protected device: Mobile.IAndroidDevice) { - super($platformsData, device); + super($platformsDataService, device); } public abstract async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise; diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index b67fcc3614..3203c6e6c3 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -12,11 +12,11 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa private $devicePathProvider: IDevicePathProvider, $injector: IInjector, private $androidProcessService: Mobile.IAndroidProcessService, - protected $platformsData: IPlatformsData, + protected $platformsDataService: IPlatformsDataService, protected device: Mobile.IAndroidDevice, $filesHashService: IFilesHashService, $logger: ILogger) { - super($injector, $platformsData, $filesHashService, $logger, device); + super($injector, $platformsDataService, $filesHashService, $logger, device); } public async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index f9efc9d11a..7e41efb4f6 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -14,7 +14,7 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe constructor( private data: IProjectData, $injector: IInjector, - protected $platformsData: IPlatformsData, + protected $platformsDataService: IPlatformsDataService, protected $staticConfig: Config.IStaticConfig, $logger: ILogger, protected device: Mobile.IAndroidDevice, @@ -23,7 +23,7 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe private $fs: IFileSystem, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $filesHashService: IFilesHashService) { - super($injector, $platformsData, $filesHashService, $logger, device); + super($injector, $platformsDataService, $filesHashService, $logger, device); this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool); } @@ -147,7 +147,7 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe } private async connectLivesyncTool(appIdentifier: string, connectTimeout?: number) { - const platformData = this.$platformsData.getPlatformData(this.$devicePlatformsConstants.Android, this.data); + const platformData = this.$platformsDataService.getPlatformData(this.$devicePlatformsConstants.Android, this.data); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); if (!this.livesyncTool.hasConnection()) { await this.livesyncTool.connect({ diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index 4c776fa564..d3abc27b2e 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -6,13 +6,13 @@ import * as semver from "semver"; export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { private static MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION = "4.2.0-2018-07-20-02"; - constructor(protected $platformsData: IPlatformsData, + constructor(protected $platformsDataService: IPlatformsDataService, protected $projectFilesManager: IProjectFilesManager, private $injector: IInjector, $devicePathProvider: IDevicePathProvider, $fs: IFileSystem, $logger: ILogger) { - super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider); + super($fs, $logger, $platformsDataService, $projectFilesManager, $devicePathProvider); } protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir, frameworkVersion: string): INativeScriptDeviceLiveSyncService { diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts index 62f9a8aad5..ecff896d1a 100644 --- a/lib/services/livesync/device-livesync-service-base.ts +++ b/lib/services/livesync/device-livesync-service-base.ts @@ -6,7 +6,7 @@ export abstract class DeviceLiveSyncServiceBase { private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; constructor( - protected $platformsData: IPlatformsData, + protected $platformsDataService: IPlatformsDataService, protected device: Mobile.IDevice ) { } @@ -23,7 +23,7 @@ export abstract class DeviceLiveSyncServiceBase { @cache() private getFastLiveSyncFileExtensions(platform: string, projectData: IProjectData): string[] { - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const fastSyncFileExtensions = DeviceLiveSyncServiceBase.FAST_SYNC_FILE_EXTENSIONS.concat(platformData.fastLivesyncFileExtensions); return fastSyncFileExtensions; } diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index af027caffd..82b4851fd9 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -11,9 +11,9 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen constructor( private $logger: ILogger, - protected $platformsData: IPlatformsData, + protected $platformsDataService: IPlatformsDataService, protected device: Mobile.IiOSDevice) { - super($platformsData, device); + super($platformsDataService, device); } private async setupSocketIfNeeded(projectData: IProjectData): Promise { diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index b24ffc93eb..89b367616e 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -8,12 +8,12 @@ import { performanceLog } from "../../common/decorators"; export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { constructor(protected $fs: IFileSystem, - protected $platformsData: IPlatformsData, + protected $platformsDataService: IPlatformsDataService, protected $projectFilesManager: IProjectFilesManager, private $injector: IInjector, $devicePathProvider: IDevicePathProvider, $logger: ILogger) { - super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider); + super($fs, $logger, $platformsDataService, $projectFilesManager, $devicePathProvider); } @performanceLog() @@ -24,7 +24,7 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I return super.fullSync(syncInfo); } const projectData = syncInfo.projectData; - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); const deviceAppData = await this.getAppData(syncInfo); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index c32e9beed7..e3e3b6e615 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -8,13 +8,13 @@ export abstract class PlatformLiveSyncServiceBase { constructor(protected $fs: IFileSystem, protected $logger: ILogger, - protected $platformsData: IPlatformsData, + protected $platformsDataService: IPlatformsDataService, protected $projectFilesManager: IProjectFilesManager, private $devicePathProvider: IDevicePathProvider) { } public getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService { const platform = device.deviceInfo.platform.toLowerCase(); - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); const frameworkVersion = platformData.platformProjectService.getFrameworkVersion(projectData); const key = getHash(`${device.deviceInfo.identifier}${projectData.projectIdentifiers[platform]}${projectData.projectDir}${frameworkVersion}`); if (!this._deviceLiveSyncServicesCache[key]) { @@ -53,7 +53,7 @@ export abstract class PlatformLiveSyncServiceBase { const projectData = syncInfo.projectData; const device = syncInfo.device; const deviceLiveSyncService = this.getDeviceLiveSyncService(device, syncInfo.projectData); - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); const deviceAppData = await this.getAppData(syncInfo); if (deviceLiveSyncService.beforeLiveSyncAction) { @@ -96,7 +96,7 @@ export abstract class PlatformLiveSyncServiceBase { } if (existingFiles.length) { - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, existingFiles, []); @@ -107,7 +107,7 @@ export abstract class PlatformLiveSyncServiceBase { if (liveSyncInfo.filesToRemove.length) { const filePaths = liveSyncInfo.filesToRemove; - const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); const mappedFiles = _(filePaths) // .map(filePath => this.$projectFilesProvider.mapFilePath(filePath, device.deviceInfo.platform, projectData)) diff --git a/lib/services/livesync/playground/preview-app-files-service.ts b/lib/services/livesync/playground/preview-app-files-service.ts index b3026bd49d..5237790b66 100644 --- a/lib/services/livesync/playground/preview-app-files-service.ts +++ b/lib/services/livesync/playground/preview-app-files-service.ts @@ -11,7 +11,7 @@ export class PreviewAppFilesService implements IPreviewAppFilesService { constructor( private $fs: IFileSystem, private $logger: ILogger, - private $platformsData: IPlatformsData, + private $platformsDataService: IPlatformsDataService, private $projectDataService: IProjectDataService, private $projectFilesManager: IProjectFilesManager, private $projectFilesProvider: IProjectFilesProvider @@ -84,7 +84,7 @@ export class PreviewAppFilesService implements IPreviewAppFilesService { private getRootFilesDir(data: IPreviewAppLiveSyncData, platform: string): string { const projectData = this.$projectDataService.getProjectData(data.projectDir); - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const rootFilesDir = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); return rootFilesDir; diff --git a/lib/services/platform/platform-commands-service.ts b/lib/services/platform/platform-commands-service.ts index 31a414ddc3..558485d86c 100644 --- a/lib/services/platform/platform-commands-service.ts +++ b/lib/services/platform/platform-commands-service.ts @@ -13,7 +13,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { private $logger: ILogger, private $packageInstallationManager: IPackageInstallationManager, private $pacoteService: IPacoteService, - private $platformsData: IPlatformsData, + private $platformsDataService: IPlatformsDataService, private $platformValidationService: PlatformValidationService, private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService @@ -53,7 +53,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { public async removePlatforms(platforms: string[], projectData: IProjectData): Promise { for (const platform of platforms) { this.$platformValidationService.validatePlatformInstalled(platform, projectData); - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); let errorMessage; try { @@ -102,22 +102,22 @@ export class PlatformCommandsService implements IPlatformCommandsService { } const subDirs = this.$fs.readDirectory(projectData.platformsDir); - return _.filter(subDirs, p => this.$platformsData.platformsNames.indexOf(p) > -1); + return _.filter(subDirs, p => this.$platformsDataService.platformsNames.indexOf(p) > -1); } public getAvailablePlatforms(projectData: IProjectData): string[] { const installedPlatforms = this.getInstalledPlatforms(projectData); - return _.filter(this.$platformsData.platformsNames, p => { + return _.filter(this.$platformsDataService.platformsNames, p => { return installedPlatforms.indexOf(p) < 0 && this.$platformValidationService.isPlatformSupportedForOS(p, projectData); // Only those not already installed }); } public getPreparedPlatforms(projectData: IProjectData): string[] { - return _.filter(this.$platformsData.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); + return _.filter(this.$platformsDataService.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); } public getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const currentPlatformData: any = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); const version = currentPlatformData && currentPlatformData.version; @@ -129,7 +129,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { return false; } - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); if (!prepareInfo) { return true; @@ -139,7 +139,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { } private async updatePlatform(platform: string, version: string, projectData: IProjectData): Promise { - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const data = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); const currentVersion = data && data.version ? data.version : "0.2.0"; @@ -182,7 +182,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { } private isPlatformPrepared(platform: string, projectData: IProjectData): boolean { - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); return platformData.platformProjectService.isPlatformPrepared(platformData.projectRoot, projectData); } } diff --git a/lib/services/platform/platform-validation-service.ts b/lib/services/platform/platform-validation-service.ts index 05c100ecde..8490a73d22 100644 --- a/lib/services/platform/platform-validation-service.ts +++ b/lib/services/platform/platform-validation-service.ts @@ -8,7 +8,7 @@ export class PlatformValidationService implements IPlatformValidationService { private $fs: IFileSystem, private $logger: ILogger, private $mobileHelper: Mobile.IMobileHelper, - private $platformsData: IPlatformsData + private $platformsDataService: IPlatformsDataService ) { } public validatePlatform(platform: string, projectData: IProjectData): void { @@ -18,8 +18,8 @@ export class PlatformValidationService implements IPlatformValidationService { platform = platform.split("@")[0].toLowerCase(); - if (!this.$platformsData.getPlatformData(platform, projectData)) { - const platformNames = helpers.formatListOfNames(this.$platformsData.platformsNames); + if (!this.$platformsDataService.getPlatformData(platform, projectData)) { + const platformNames = helpers.formatListOfNames(this.$platformsDataService.platformsNames); this.$errors.fail(`Invalid platform ${platform}. Valid platforms are ${platformNames}.`); } } @@ -41,7 +41,7 @@ export class PlatformValidationService implements IPlatformValidationService { if (platform) { platform = this.$mobileHelper.normalizePlatformName(platform); this.$logger.trace("Validate options for platform: " + platform); - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const result = await platformData.platformProjectService.validateOptions( projectData.projectIdentifiers[platform.toLowerCase()], @@ -52,9 +52,9 @@ export class PlatformValidationService implements IPlatformValidationService { return result; } else { let valid = true; - for (const availablePlatform in this.$platformsData.availablePlatforms) { + for (const availablePlatform in this.$platformsDataService.availablePlatforms) { this.$logger.trace("Validate options for platform: " + availablePlatform); - const platformData = this.$platformsData.getPlatformData(availablePlatform, projectData); + const platformData = this.$platformsDataService.getPlatformData(availablePlatform, projectData); valid = valid && await platformData.platformProjectService.validateOptions( projectData.projectIdentifiers[availablePlatform.toLowerCase()], provision, @@ -67,7 +67,7 @@ export class PlatformValidationService implements IPlatformValidationService { } public isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean { - const targetedOS = this.$platformsData.getPlatformData(platform, projectData).targetedOS; + const targetedOS = this.$platformsDataService.getPlatformData(platform, projectData).targetedOS; const res = !targetedOS || targetedOS.indexOf("*") >= 0 || targetedOS.indexOf(process.platform) >= 0; return res; } diff --git a/lib/platforms-data.ts b/lib/services/platforms-data-service.ts similarity index 58% rename from lib/platforms-data.ts rename to lib/services/platforms-data-service.ts index 7af6a383f1..e4aec9733e 100644 --- a/lib/platforms-data.ts +++ b/lib/services/platforms-data-service.ts @@ -1,24 +1,25 @@ -export class PlatformsData implements IPlatformsData { - private platformsData: { [index: string]: any } = {}; + +export class PlatformsDataService implements IPlatformsDataService { + private platformsDataService: { [index: string]: any } = {}; constructor($androidProjectService: IPlatformProjectService, $iOSProjectService: IPlatformProjectService) { - this.platformsData = { + this.platformsDataService = { ios: $iOSProjectService, android: $androidProjectService }; } public get platformsNames() { - return Object.keys(this.platformsData); + return Object.keys(this.platformsDataService); } public getPlatformData(platform: string, projectData: IProjectData): IPlatformData { const platformKey = platform && _.first(platform.toLowerCase().split("@")); let platformData: IPlatformData; if (platformKey) { - platformData = this.platformsData[platformKey] && this.platformsData[platformKey].getPlatformData(projectData); + platformData = this.platformsDataService[platformKey] && this.platformsDataService[platformKey].getPlatformData(projectData); } return platformData; @@ -31,4 +32,4 @@ export class PlatformsData implements IPlatformsData { }; } } -$injector.register("platformsData", PlatformsData); +$injector.register("platformsDataService", PlatformsDataService); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index ef5881c741..16dd554ce2 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -9,8 +9,8 @@ export class PluginsService implements IPluginsService { private static NPM_CONFIG = { save: true }; - private get $platformsData(): IPlatformsData { - return this.$injector.resolve("platformsData"); + private get $platformsDataService(): IPlatformsDataService { + return this.$injector.resolve("platformsDataService"); } private get $projectDataService(): IProjectDataService { return this.$injector.resolve("projectDataService"); @@ -88,7 +88,7 @@ export class PluginsService implements IPluginsService { } public async preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise { - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); pluginData.pluginPlatformsFolderPath = (_platform: string) => path.join(pluginData.fullPath, "platforms", _platform.toLowerCase()); const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(platform); @@ -208,7 +208,7 @@ export class PluginsService implements IPluginsService { const data = cacheData.nativescript || cacheData.moduleInfo; if (pluginData.isPlugin) { - pluginData.platformsData = data.platforms; + pluginData.platformsDataService = data.platforms; pluginData.pluginVariables = data.variables; } @@ -242,11 +242,11 @@ export class PluginsService implements IPluginsService { } private async executeForAllInstalledPlatforms(action: (_pluginDestinationPath: string, pl: string, _platformData: IPlatformData) => Promise, projectData: IProjectData): Promise { - const availablePlatforms = _.keys(this.$platformsData.availablePlatforms); + const availablePlatforms = _.keys(this.$platformsDataService.availablePlatforms); for (const platform of availablePlatforms) { const isPlatformInstalled = this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase())); if (isPlatformInstalled) { - const platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData); + const platformData = this.$platformsDataService.getPlatformData(platform.toLowerCase(), projectData); const pluginDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); await action(pluginDestinationPath, platform.toLowerCase(), platformData); } @@ -254,7 +254,7 @@ export class PluginsService implements IPluginsService { } private getInstalledFrameworkVersion(platform: string, projectData: IProjectData): string { - const platformData = this.$platformsData.getPlatformData(platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); const frameworkData = this.$projectDataService.getNSValue(projectData.projectDir, platformData.frameworkPackageName); return frameworkData.version; } @@ -263,7 +263,7 @@ export class PluginsService implements IPluginsService { let isValid = true; const installedFrameworkVersion = this.getInstalledFrameworkVersion(platform, projectData); - const pluginPlatformsData = pluginData.platformsData; + const pluginPlatformsData = pluginData.platformsDataService; if (pluginPlatformsData) { const pluginVersion = (pluginPlatformsData)[platform]; if (!pluginVersion) { diff --git a/test/ios-entitlements-service.ts b/test/ios-entitlements-service.ts index 95d7c17b6e..16caaa4185 100644 --- a/test/ios-entitlements-service.ts +++ b/test/ios-entitlements-service.ts @@ -17,7 +17,7 @@ describe("IOSEntitlements Service Tests", () => { const createTestInjector = (): IInjector => { const testInjector = new yok.Yok(); - testInjector.register('platformsData', stubs.PlatformsDataStub); + testInjector.register('platformsDataService', stubs.PlatformsDataStub); testInjector.register('projectData', stubs.ProjectDataStub); testInjector.register("logger", stubs.LoggerStub); testInjector.register('iOSEntitlementsService', IOSEntitlementsService); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 1e4cb367cd..af58a3b572 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -80,7 +80,7 @@ class ErrorsNoFailStub implements IErrors { validateYargsArguments(parsed: any, knownOpts: any, shorthands: any, clientName?: string): void { /* intentionally left blank */ } } -class PlatformsData implements IPlatformsData { +class PlatformsDataService implements IPlatformsDataService { platformsNames = ["android", "ios"]; getPlatformData(platform: string): IPlatformData { if (_.includes(this.platformsNames, platform)) { @@ -108,7 +108,7 @@ function createTestInjector() { testInjector.register('logger', stubs.LoggerStub); testInjector.register('packageInstallationManager', stubs.PackageInstallationManagerStub); testInjector.register('projectData', stubs.ProjectDataStub); - testInjector.register('platformsData', PlatformsData); + testInjector.register('platformsDataService', PlatformsDataService); testInjector.register('devicesService', {}); testInjector.register('projectDataService', stubs.ProjectDataService); testInjector.register('prompter', {}); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index f8c04fb8fe..2ddee59a97 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -13,7 +13,7 @@ import { StaticConfig } from "../lib/config"; import { HostInfo } from "../lib/common/host-info"; import { Errors } from "../lib/common/errors"; import { ProjectHelper } from "../lib/common/project-helper"; -import { PlatformsData } from "../lib/platforms-data"; +import { PlatformsDataService } from "../lib/services/platforms-data-service"; import { ProjectDataService } from "../lib/services/project-data-service"; import { ProjectFilesManager } from "../lib/common/services/project-files-manager"; import { ResourceLoader } from "../lib/common/resource-loader"; @@ -56,7 +56,7 @@ function createTestInjector() { testInjector.register("projectData", ProjectData); testInjector.register("platforsmData", stubs.PlatformsDataStub); testInjector.register("childProcess", ChildProcess); - testInjector.register("platformsData", PlatformsData); + testInjector.register("platformsDataService", PlatformsDataService); testInjector.register("androidEmulatorServices", {}); testInjector.register("androidToolsInfo", AndroidToolsInfo); testInjector.register("sysInfo", {}); @@ -320,9 +320,9 @@ describe("Plugins service", () => { return [{ name: "" }]; }; - // Mock platformsData - const platformsData = testInjector.resolve("platformsData"); - platformsData.getPlatformData = (platform: string) => { + // Mock platformsDataService + const platformsDataService = testInjector.resolve("platformsDataService"); + platformsDataService.getPlatformData = (platform: string) => { return { appDestinationDirectoryPath: path.join(projectFolder, "platforms", "android"), frameworkPackageName: "tns-android", @@ -544,9 +544,9 @@ describe("Plugins service", () => { const appDestinationDirectoryPath = path.join(projectFolder, "platforms", "android"); - // Mock platformsData - const platformsData = testInjector.resolve("platformsData"); - platformsData.getPlatformData = (platform: string) => { + // Mock platformsDataService + const platformsDataService = testInjector.resolve("platformsDataService"); + platformsDataService.getPlatformData = (platform: string) => { return { appDestinationDirectoryPath: appDestinationDirectoryPath, frameworkPackageName: "tns-android", @@ -590,7 +590,7 @@ describe("Plugins service", () => { }; const unitTestsInjector = new Yok(); - unitTestsInjector.register("platformsData", { + unitTestsInjector.register("platformsDataService", { getPlatformData: (_platform: string, pData: IProjectData) => ({ projectRoot: "projectRoot", platformProjectService: { diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index e9d30c2dd3..74be242d0b 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -2,7 +2,7 @@ import * as path from "path"; import { BaseServiceTest } from "./base-service-test"; import temp = require("temp"); import { assert } from "chai"; -import { PlatformsData } from "../lib/platforms-data"; +import { PlatformsDataService } from "../lib/services/platforms-data-service"; import { ProjectChangesService } from "../lib/services/project-changes-service"; import * as Constants from "../lib/constants"; import { FileSystem } from "../lib/common/file-system"; @@ -24,7 +24,7 @@ class ProjectChangesServiceTest extends BaseServiceTest { projectDir: this.projectDir }); - this.injector.register("platformsData", PlatformsData); + this.injector.register("platformsDataService", PlatformsDataService); this.injector.register("androidProjectService", {}); this.injector.register("iOSProjectService", {}); this.injector.register("fs", FileSystem); @@ -54,8 +54,8 @@ class ProjectChangesServiceTest extends BaseServiceTest { return this.injector.resolve("projectData"); } - get platformsData(): any { - return this.injector.resolve("platformsData"); + get platformsDataService(): any { + return this.injector.resolve("platformsDataService"); } getPlatformData(platform: string): IPlatformData { @@ -80,7 +80,7 @@ describe("Project Changes Service Tests", () => { Constants.PLATFORMS_DIR_NAME ); - serviceTest.platformsData.getPlatformData = + serviceTest.platformsDataService.getPlatformData = (platform: string) => { if (platform.toLowerCase() === "ios") { return { diff --git a/test/project-files-provider.ts b/test/project-files-provider.ts index 799d8141e7..00e4857236 100644 --- a/test/project-files-provider.ts +++ b/test/project-files-provider.ts @@ -17,7 +17,7 @@ function createTestInjector(): IInjector { testInjector.register('projectData', stubs.ProjectDataStub); - testInjector.register("platformsData", { + testInjector.register("platformsDataService", { getPlatformData: (platform: string) => { return { appDestinationDirectoryPath: appDestinationDirectoryPath, diff --git a/test/services/livesync/android-device-livesync-service-base.ts b/test/services/livesync/android-device-livesync-service-base.ts index b8e41cd7c3..f79a831af4 100644 --- a/test/services/livesync/android-device-livesync-service-base.ts +++ b/test/services/livesync/android-device-livesync-service-base.ts @@ -32,11 +32,11 @@ const appIdentifier = "testAppIdentifier"; class AndroidDeviceLiveSyncServiceBaseMock extends AndroidDeviceLiveSyncServiceBase { constructor($injector: IInjector, - $platformsData: any, + $platformsDataService: any, $filesHashService: any, $logger: ILogger, device: Mobile.IAndroidDevice) { - super($injector, $platformsData, $filesHashService, $logger, device); + super($injector, $platformsDataService, $filesHashService, $logger, device); } public async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { diff --git a/test/services/platform/add-platform-service.ts b/test/services/platform/add-platform-service.ts index 4da5880c47..7390b5b292 100644 --- a/test/services/platform/add-platform-service.ts +++ b/test/services/platform/add-platform-service.ts @@ -42,7 +42,7 @@ describe("AddPlatformService", () => { const pacoteService: PacoteService = injector.resolve("pacoteService"); pacoteService.extractPackage = async (): Promise => { throw new Error(errorMessage); }; - const platformData = injector.resolve("platformsData").getPlatformData(platform, projectData); + const platformData = injector.resolve("platformsDataService").getPlatformData(platform, projectData); await assert.isRejected(addPlatformService.addPlatformSafe(projectData, platformData, "somePackage", nativePrepare), errorMessage); }); @@ -51,10 +51,10 @@ describe("AddPlatformService", () => { projectDataService.getNSValue = () => ({ version: "4.2.0" }); let isCreateNativeProjectCalled = false; - const platformsData = injector.resolve("platformsData"); - const platformData = platformsData.getPlatformData(platform, injector.resolve("projectData")); + const platformsDataService = injector.resolve("platformsDataService"); + const platformData = platformsDataService.getPlatformData(platform, injector.resolve("projectData")); platformData.platformProjectService.createProject = () => isCreateNativeProjectCalled = true; - platformsData.getPlatformData = () => platformData; + platformsDataService.getPlatformData = () => platformData; await addPlatformService.addPlatformSafe(projectData, platformData, platform, { skipNativePrepare: true } ); assert.isFalse(isCreateNativeProjectCalled); @@ -64,10 +64,10 @@ describe("AddPlatformService", () => { projectDataService.getNSValue = () => ({ version: "4.2.0" }); let isCreateNativeProjectCalled = false; - const platformsData = injector.resolve("platformsData"); - const platformData = platformsData.getPlatformData(platform, injector.resolve("projectData")); + const platformsDataService = injector.resolve("platformsDataService"); + const platformData = platformsDataService.getPlatformData(platform, injector.resolve("projectData")); platformData.platformProjectService.createProject = () => isCreateNativeProjectCalled = true; - platformsData.getPlatformData = () => platformData; + platformsDataService.getPlatformData = () => platformData; await addPlatformService.addPlatformSafe(projectData, platformData, platform, nativePrepare); assert.isTrue(isCreateNativeProjectCalled); diff --git a/test/services/playground/preview-app-files-service.ts b/test/services/playground/preview-app-files-service.ts index 5ed2e97593..2317cf8c13 100644 --- a/test/services/playground/preview-app-files-service.ts +++ b/test/services/playground/preview-app-files-service.ts @@ -31,7 +31,7 @@ function createTestInjector(data?: { files: string[] }) { injector.register("previewAppFilesService", PreviewAppFilesService); injector.register("fs", FileSystemStub); injector.register("logger", LoggerStub); - injector.register("platformsData", PlatformsDataMock); + injector.register("platformsDataService", PlatformsDataMock); injector.register("projectDataService", ProjectDataServiceMock); injector.register("projectFilesManager", { getProjectFiles: () => data ? data.files : [] @@ -48,7 +48,7 @@ function createTestInjector(data?: { files: string[] }) { } function getExpectedResult(data: IPreviewAppLiveSyncData, injector: IInjector, expectedFiles: string[], platform: string): FilesPayload { - const platformData = injector.resolve("platformsData").getPlatformData(platform); + const platformData = injector.resolve("platformsDataService").getPlatformData(platform); const files = _.map(expectedFiles, expectedFile => { return { event: 'change', diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index 9baea238cc..e6c644356c 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -121,7 +121,7 @@ function createTestInjector(options?: { attachToHmrStatusEvent: () => ({}) }); injector.register("errors", ErrorsStub); - injector.register("platformsData", { + injector.register("platformsDataService", { getPlatformData: () => ({ appDestinationDirectoryPath: platformsDirPath, normalizedPlatformName diff --git a/test/stubs.ts b/test/stubs.ts index 90dc1863ed..2a958e8f0e 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -473,7 +473,7 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor } } -export class PlatformsDataStub extends EventEmitter implements IPlatformsData { +export class PlatformsDataStub extends EventEmitter implements IPlatformsDataService { public platformsNames: string[]; public getPlatformData(platform: string, projectData: IProjectData): IPlatformData { @@ -861,7 +861,7 @@ export class InjectorStub extends Yok implements IInjector { this.register('childProcess', ChildProcessStub); this.register("liveSyncService", LiveSyncServiceStub); this.register("prompter", PrompterStub); - this.register('platformsData', PlatformsDataStub); + this.register('platformsDataService', PlatformsDataStub); this.register("androidPluginBuildService", AndroidPluginBuildServiceStub); this.register('projectData', ProjectDataStub); this.register('packageInstallationManager', PackageInstallationManagerStub); diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index e80a4ff0e5..071c2cefcf 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -65,7 +65,7 @@ class AppStore { return "/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"; } }, - "platformsData": { + "platformsDataService": { getPlatformData: (platform: string) => { chai.assert.equal(platform, "iOS"); return this.iOSPlatformData; diff --git a/test/update.ts b/test/update.ts index 53516bd288..52912bb512 100644 --- a/test/update.ts +++ b/test/update.ts @@ -58,7 +58,7 @@ function createTestInjector( addPlatforms: async (): Promise => undefined, }); testInjector.register("platformValidationService", {}); - testInjector.register("platformsData", { + testInjector.register("platformsDataService", { availablePlatforms: { Android: "Android", iOS: "iOS" From 297814ef7b9a9b3d8f37095ee5b79c19c61e7628 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 17 May 2019 09:52:32 +0300 Subject: [PATCH 30/30] fix: fix PR comments --- PublicAPI.md | 5 - lib/bootstrap.ts | 14 +- lib/commands/add-platform.ts | 4 +- lib/commands/appstore-upload.ts | 4 +- lib/commands/build.ts | 24 ++-- lib/commands/install.ts | 7 +- lib/commands/list-platforms.ts | 8 +- lib/commands/platform-clean.ts | 8 +- lib/commands/prepare.ts | 2 +- lib/commands/remove-platform.ts | 4 +- lib/commands/run.ts | 6 +- lib/commands/update-platform.ts | 4 +- lib/commands/update.ts | 12 +- lib/controllers/build-controller.ts | 42 +++--- lib/controllers/deploy-controller.ts | 22 +++ .../deploy-on-devices-controller.ts | 27 ---- ...m-controller.ts => platform-controller.ts} | 12 +- lib/controllers/prepare-controller.ts | 23 ++-- lib/controllers/preview-app-controller.ts | 4 +- ...evices-controller.ts => run-controller.ts} | 130 ++++++++++-------- lib/data/build-data.ts | 4 +- .../{data-base.ts => controller-data-base.ts} | 2 +- lib/data/debug-data.ts | 3 + ...{add-platform-data.ts => platform-data.ts} | 4 +- lib/data/prepare-data.ts | 4 +- lib/data/run-data.ts | 5 + lib/data/run-on-devices-data.ts | 3 - lib/declarations.d.ts | 9 +- lib/definitions/build.d.ts | 47 +++++++ lib/definitions/data.d.ts | 5 + lib/definitions/debug.d.ts | 5 - lib/definitions/deploy.d.ts | 0 lib/definitions/livesync.d.ts | 2 +- lib/definitions/platform.d.ts | 15 +- lib/definitions/prepare.d.ts | 37 +++++ lib/definitions/run.d.ts | 37 +++++ lib/{ => emitters}/preview-app-emitter.ts | 2 +- .../run-emitter.ts} | 22 +-- lib/helpers/deploy-command-helper.ts | 8 +- lib/helpers/livesync-command-helper.ts | 62 ++++++--- .../platform-command-helper.ts} | 25 ++-- lib/services/build-artefacts-service.ts | 2 +- lib/services/build-data-service.ts | 2 +- lib/services/build-info-file-service.ts | 8 +- lib/services/debug-service.ts | 2 +- .../device/device-debug-app-service.ts | 7 +- .../device/device-install-app-service.ts | 42 +++--- .../device/device-refresh-app-service.ts | 12 +- lib/services/init-service.ts | 3 +- lib/services/ios-project-service.ts | 4 + .../android-device-livesync-service.ts | 4 +- ...android-device-livesync-sockets-service.ts | 6 +- .../livesync/device-livesync-service-base.ts | 4 +- .../livesync/ios-device-livesync-service.ts | 4 +- .../preview-app-livesync-service.ts | 2 +- lib/services/platform/add-platform-service.ts | 2 +- .../platform/platform-validation-service.ts | 5 +- .../prepare-native-platform-service.ts | 5 +- lib/services/platforms-data-service.ts | 12 -- lib/services/plugins-service.ts | 5 +- lib/services/prepare-data-service.ts | 2 +- lib/services/run-on-devices-data-service.ts | 34 ----- lib/services/test-execution-service.ts | 6 +- .../webpack/webpack-compiler-service.ts | 4 +- lib/services/webpack/webpack.d.ts | 11 +- package.json | 2 +- test/controllers/add-platform-controller.ts | 22 +-- test/controllers/prepare-controller.ts | 8 +- ...evices-controller.ts => run-controller.ts} | 51 ++++--- .../platform-command-helper.ts} | 32 +++-- test/ios-entitlements-service.ts | 2 +- test/platform-commands.ts | 19 +-- test/plugins-service.ts | 4 +- test/project-changes-service.ts | 4 +- .../services/platform/add-platform-service.ts | 4 +- .../playground/preview-app-files-service.ts | 4 +- .../preview-app-livesync-service.ts | 4 +- test/services/test-execution-service.ts | 2 +- test/stubs.ts | 6 +- test/tns-appstore-upload.ts | 6 +- test/update.ts | 32 ++--- 81 files changed, 575 insertions(+), 478 deletions(-) create mode 100644 lib/controllers/deploy-controller.ts delete mode 100644 lib/controllers/deploy-on-devices-controller.ts rename lib/controllers/{add-platform-controller.ts => platform-controller.ts} (88%) rename lib/controllers/{run-on-devices-controller.ts => run-controller.ts} (69%) rename lib/data/{data-base.ts => controller-data-base.ts} (71%) create mode 100644 lib/data/debug-data.ts rename lib/data/{add-platform-data.ts => platform-data.ts} (62%) create mode 100644 lib/data/run-data.ts delete mode 100644 lib/data/run-on-devices-data.ts create mode 100644 lib/definitions/build.d.ts create mode 100644 lib/definitions/data.d.ts create mode 100644 lib/definitions/deploy.d.ts create mode 100644 lib/definitions/prepare.d.ts create mode 100644 lib/definitions/run.d.ts rename lib/{ => emitters}/preview-app-emitter.ts (80%) rename lib/{run-on-devices-emitter.ts => emitters/run-emitter.ts} (76%) rename lib/{services/platform/platform-commands-service.ts => helpers/platform-command-helper.ts} (89%) delete mode 100644 lib/services/run-on-devices-data-service.ts rename test/controllers/{run-on-devices-controller.ts => run-controller.ts} (82%) rename test/{services/platform/platform-commands-service.ts => helpers/platform-command-helper.ts} (61%) diff --git a/PublicAPI.md b/PublicAPI.md index 066640eb0d..1dd590f933 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -656,11 +656,6 @@ interface IDebugData { */ applicationIdentifier: string; - /** - * Path to .app built for iOS Simulator. - */ - pathToAppPackage?: string; - /** * The name of the application, for example `MyProject`. */ diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 91c113c782..6e24271e22 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -37,7 +37,6 @@ $injector.require("addPlatformService", "./services/platform/add-platform-servic $injector.require("buildInfoFileService", "./services/build-info-file-service"); $injector.require("prepareNativePlatformService", "./services/platform/prepare-native-platform-service"); $injector.require("platformValidationService", "./services/platform/platform-validation-service"); -$injector.require("platformCommandsService", "./services/platform/platform-commands-service"); $injector.require("buildArtefactsService", "./services/build-artefacts-service"); @@ -45,16 +44,14 @@ $injector.require("deviceDebugAppService", "./services/device/device-debug-app-s $injector.require("deviceInstallAppService", "./services/device/device-install-app-service"); $injector.require("deviceRefreshAppService", "./services/device/device-refresh-app-service"); -$injector.require("runOnDevicesDataService", "./services/run-on-devices-data-service"); +$injector.require("runEmitter", "./emitters/run-emitter"); +$injector.require("previewAppEmitter", "./emitters/preview-app-emitter"); -$injector.require("runOnDevicesEmitter", "./run-on-devices-emitter"); -$injector.require("previewAppEmitter", "./preview-app-emitter"); - -$injector.require("addPlatformController", "./controllers/add-platform-controller"); +$injector.require("platformController", "./controllers/platform-controller"); $injector.require("prepareController", "./controllers/prepare-controller"); $injector.require("buildController", "./controllers/build-controller"); -$injector.require("deployOnDevicesController", "./controllers/deploy-on-devices-controller"); -$injector.require("runOnDevicesController", "./controllers/run-on-devices-controller"); +$injector.require("deployController", "./controllers/deploy-controller"); +$injector.require("runController", "./controllers/run-controller"); $injector.require("previewAppController", "./controllers/preview-app-controller"); $injector.require("prepareDataService", "./services/prepare-data-service"); @@ -155,6 +152,7 @@ $injector.require("bundleValidatorHelper", "./helpers/bundle-validator-helper"); $injector.require("androidBundleValidatorHelper", "./helpers/android-bundle-validator-helper"); $injector.require("liveSyncCommandHelper", "./helpers/livesync-command-helper"); $injector.require("deployCommandHelper", "./helpers/deploy-command-helper"); +$injector.require("platformCommandHelper", "./helpers/platform-command-helper"); $injector.require("optionsTracker", "./helpers/options-track-helper"); $injector.requirePublicClass("localBuildService", "./services/local-build-service"); diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts index baa670c541..b21878006d 100644 --- a/lib/commands/add-platform.ts +++ b/lib/commands/add-platform.ts @@ -4,7 +4,7 @@ export class AddPlatformCommand extends ValidatePlatformCommandBase implements I public allowedParameters: ICommandParameter[] = []; constructor($options: IOptions, - private $platformCommandsService: IPlatformCommandsService, + private $platformCommandHelper: IPlatformCommandHelper, $platformValidationService: IPlatformValidationService, $projectData: IProjectData, $platformsDataService: IPlatformsDataService, @@ -14,7 +14,7 @@ export class AddPlatformCommand extends ValidatePlatformCommandBase implements I } public async execute(args: string[]): Promise { - await this.$platformCommandsService.addPlatforms(args, this.$projectData, this.$options.frameworkPath); + await this.$platformCommandHelper.addPlatforms(args, this.$projectData, this.$options.frameworkPath); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 503b66abd0..a34019cc93 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -57,11 +57,11 @@ export class PublishIOS implements ICommand { this.$options.forDevice = true; const buildData = new IOSBuildData(this.$projectData.projectDir, platform, this.$options); - ipaFilePath = await this.$buildController.prepareAndBuildPlatform(buildData); + ipaFilePath = await this.$buildController.prepareAndBuild(buildData); } else { this.$logger.info("No .ipa, mobile provision or certificate set. Perfect! Now we'll build .xcarchive and let Xcode pick the distribution certificate and provisioning profile for you when exporting .ipa for AppStore submission."); const buildData = new IOSBuildData(this.$projectData.projectDir, platform, { ...this.$options, buildForAppStore: true }); - ipaFilePath = await this.$buildController.prepareAndBuildPlatform(buildData); + ipaFilePath = await this.$buildController.prepareAndBuild(buildData); this.$logger.info(`Export at: ${ipaFilePath}`); } } diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 2d0e7672a9..11b02b7e0b 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -1,7 +1,5 @@ import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE, AndroidAppBundleMessages } from "../constants"; import { ValidatePlatformCommandBase } from "./command-base"; -import { BuildController } from "../controllers/build-controller"; -import { BuildDataService } from "../services/build-data-service"; export abstract class BuildCommandBase extends ValidatePlatformCommandBase { constructor($options: IOptions, @@ -9,10 +7,10 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { $projectData: IProjectData, $platformsDataService: IPlatformsDataService, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - protected $buildController: BuildController, + protected $buildController: IBuildController, $platformValidationService: IPlatformValidationService, private $bundleValidatorHelper: IBundleValidatorHelper, - private $buildDataService: BuildDataService, + private $buildDataService: IBuildDataService, protected $logger: ILogger) { super($options, $platformsDataService, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); @@ -25,7 +23,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { public async executeCore(args: string[]): Promise { const platform = args[0].toLowerCase(); const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, platform, this.$options); - const outputPath = await this.$buildController.prepareAndBuildPlatform(buildData); + const outputPath = await this.$buildController.prepareAndBuild(buildData); return outputPath; } @@ -64,16 +62,16 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $projectData: IProjectData, $platformsDataService: IPlatformsDataService, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $buildController: BuildController, + $buildController: IBuildController, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, $logger: ILogger, - $buildDataService: BuildDataService) { + $buildDataService: IBuildDataService) { super($options, $errors, $projectData, $platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); } public async execute(args: string[]): Promise { - await this.executeCore([this.$platformsDataService.availablePlatforms.iOS]); + await this.executeCore([this.$devicePlatformsConstants.iOS.toLowerCase()]); } public async canExecute(args: string[]): Promise { @@ -98,19 +96,19 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { constructor(protected $options: IOptions, protected $errors: IErrors, $projectData: IProjectData, - $platformsDataService: IPlatformsDataService, + platformsDataService: IPlatformsDataService, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $buildController: BuildController, + $buildController: IBuildController, $platformValidationService: IPlatformValidationService, $bundleValidatorHelper: IBundleValidatorHelper, protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, - $buildDataService: BuildDataService, + $buildDataService: IBuildDataService, protected $logger: ILogger) { - super($options, $errors, $projectData, $platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); + super($options, $errors, $projectData, platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); } public async execute(args: string[]): Promise { - await this.executeCore([this.$platformsDataService.availablePlatforms.Android]); + await this.executeCore([this.$devicePlatformsConstants.Android.toLowerCase()]); if (this.$options.aab) { this.$logger.info(AndroidAppBundleMessages.ANDROID_APP_BUNDLE_DOCS_MESSAGE); diff --git a/lib/commands/install.ts b/lib/commands/install.ts index 82f22366fa..7e200c4b92 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -5,8 +5,9 @@ export class InstallCommand implements ICommand { public allowedParameters: ICommandParameter[] = [this.$stringParameter]; constructor(private $options: IOptions, + private $mobileHelper: Mobile.IMobileHelper, private $platformsDataService: IPlatformsDataService, - private $platformCommandsService: IPlatformCommandsService, + private $platformCommandHelper: IPlatformCommandHelper, private $projectData: IProjectData, private $projectDataService: IProjectDataService, private $pluginsService: IPluginsService, @@ -26,7 +27,7 @@ export class InstallCommand implements ICommand { await this.$pluginsService.ensureAllDependenciesAreInstalled(this.$projectData); - for (const platform of this.$platformsDataService.platformsNames) { + for (const platform of this.$mobileHelper.platformNames) { const platformData = this.$platformsDataService.getPlatformData(platform, this.$projectData); const frameworkPackageData = this.$projectDataService.getNSValue(this.$projectData.projectDir, platformData.frameworkPackageName); if (frameworkPackageData && frameworkPackageData.version) { @@ -34,7 +35,7 @@ export class InstallCommand implements ICommand { const platformProjectService = platformData.platformProjectService; await platformProjectService.validate(this.$projectData, this.$options); - await this.$platformCommandsService.addPlatforms([`${platform}@${frameworkPackageData.version}`], this.$projectData, this.$options.frameworkPath); + await this.$platformCommandHelper.addPlatforms([`${platform}@${frameworkPackageData.version}`], this.$projectData, this.$options.frameworkPath); } catch (err) { error = `${error}${EOL}${err}`; } diff --git a/lib/commands/list-platforms.ts b/lib/commands/list-platforms.ts index d47b1cf21d..f21f067ee0 100644 --- a/lib/commands/list-platforms.ts +++ b/lib/commands/list-platforms.ts @@ -3,17 +3,17 @@ import * as helpers from "../common/helpers"; export class ListPlatformsCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $platformCommandsService: IPlatformCommandsService, + constructor(private $platformCommandHelper: IPlatformCommandHelper, private $projectData: IProjectData, private $logger: ILogger) { this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - const installedPlatforms = this.$platformCommandsService.getInstalledPlatforms(this.$projectData); + const installedPlatforms = this.$platformCommandHelper.getInstalledPlatforms(this.$projectData); if (installedPlatforms.length > 0) { - const preparedPlatforms = this.$platformCommandsService.getPreparedPlatforms(this.$projectData); + const preparedPlatforms = this.$platformCommandHelper.getPreparedPlatforms(this.$projectData); if (preparedPlatforms.length > 0) { this.$logger.out("The project is prepared for: ", helpers.formatListOfNames(preparedPlatforms, "and")); } else { @@ -22,7 +22,7 @@ export class ListPlatformsCommand implements ICommand { this.$logger.out("Installed platforms: ", helpers.formatListOfNames(installedPlatforms, "and")); } else { - const formattedPlatformsList = helpers.formatListOfNames(this.$platformCommandsService.getAvailablePlatforms(this.$projectData), "and"); + const formattedPlatformsList = helpers.formatListOfNames(this.$platformCommandHelper.getAvailablePlatforms(this.$projectData), "and"); this.$logger.out("Available platforms for this OS: ", formattedPlatformsList); this.$logger.out("No installed platforms found. Use $ tns platform add"); } diff --git a/lib/commands/platform-clean.ts b/lib/commands/platform-clean.ts index 86c34c2bd9..4d1bb7e39b 100644 --- a/lib/commands/platform-clean.ts +++ b/lib/commands/platform-clean.ts @@ -1,12 +1,10 @@ -import { PlatformCommandsService } from "../services/platform/platform-commands-service"; - export class CleanCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor( private $errors: IErrors, private $options: IOptions, - private $platformCommandsService: PlatformCommandsService, + private $platformCommandHelper: IPlatformCommandHelper, private $platformValidationService: IPlatformValidationService, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $projectData: IProjectData @@ -15,7 +13,7 @@ export class CleanCommand implements ICommand { } public async execute(args: string[]): Promise { - await this.$platformCommandsService.cleanPlatforms(args, this.$projectData, this.$options.frameworkPath); + await this.$platformCommandHelper.cleanPlatforms(args, this.$projectData, this.$options.frameworkPath); } public async canExecute(args: string[]): Promise { @@ -30,7 +28,7 @@ export class CleanCommand implements ICommand { for (const platform of args) { this.$platformValidationService.validatePlatformInstalled(platform, this.$projectData); - const currentRuntimeVersion = this.$platformCommandsService.getCurrentPlatformVersion(platform, this.$projectData); + const currentRuntimeVersion = this.$platformCommandHelper.getCurrentPlatformVersion(platform, this.$projectData); await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ platform, projectDir: this.$projectData.projectDir, diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 7eea86487d..d047cc1d3a 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -24,7 +24,7 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm const platform = args[0]; const prepareData = this.$prepareDataService.getPrepareData(this.$projectData.projectDir, platform, this.$options); - await this.$prepareController.preparePlatform(prepareData); + await this.$prepareController.prepare(prepareData); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/remove-platform.ts b/lib/commands/remove-platform.ts index 97805861dd..92fbd50f5f 100644 --- a/lib/commands/remove-platform.ts +++ b/lib/commands/remove-platform.ts @@ -3,7 +3,7 @@ export class RemovePlatformCommand implements ICommand { constructor( private $errors: IErrors, - private $platformCommandsService: IPlatformCommandsService, + private $platformCommandHelper: IPlatformCommandHelper, private $platformValidationService: IPlatformValidationService, private $projectData: IProjectData ) { @@ -11,7 +11,7 @@ export class RemovePlatformCommand implements ICommand { } public execute(args: string[]): Promise { - return this.$platformCommandsService.removePlatforms(args, this.$projectData); + return this.$platformCommandHelper.removePlatforms(args, this.$projectData); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 780f28fb37..b5465736b7 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -67,7 +67,6 @@ export class RunIosCommand implements ICommand { private $errors: IErrors, private $injector: IInjector, private $options: IOptions, - private $platformsDataService: IPlatformsDataService, private $platformValidationService: IPlatformValidationService, private $projectDataService: IProjectDataService, ) { @@ -84,7 +83,7 @@ export class RunIosCommand implements ICommand { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } - const result = await this.runCommand.canExecute(args) && await this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, projectData, this.$platformsDataService.availablePlatforms.iOS); + const result = await this.runCommand.canExecute(args) && await this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, projectData, this.$devicePlatformsConstants.iOS.toLowerCase()); return result; } } @@ -110,7 +109,6 @@ export class RunAndroidCommand implements ICommand { private $errors: IErrors, private $injector: IInjector, private $options: IOptions, - private $platformsDataService: IPlatformsDataService, private $platformValidationService: IPlatformValidationService, private $projectData: IProjectData, ) { } @@ -130,7 +128,7 @@ export class RunAndroidCommand implements ICommand { this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } - return this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsDataService.availablePlatforms.Android); + return this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$devicePlatformsConstants.Android.toLowerCase()); } } diff --git a/lib/commands/update-platform.ts b/lib/commands/update-platform.ts index 51431949d4..b6a6f98f8d 100644 --- a/lib/commands/update-platform.ts +++ b/lib/commands/update-platform.ts @@ -5,7 +5,7 @@ export class UpdatePlatformCommand implements ICommand { private $errors: IErrors, private $options: IOptions, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, - private $platformCommandsService: IPlatformCommandsService, + private $platformCommandHelper: IPlatformCommandHelper, private $platformValidationService: IPlatformValidationService, private $projectData: IProjectData, ) { @@ -13,7 +13,7 @@ export class UpdatePlatformCommand implements ICommand { } public async execute(args: string[]): Promise { - await this.$platformCommandsService.updatePlatforms(args, this.$projectData); + await this.$platformCommandHelper.updatePlatforms(args, this.$projectData); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/update.ts b/lib/commands/update.ts index 8fa6d63206..50b3c00d7b 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -9,7 +9,7 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma private $fs: IFileSystem, private $logger: ILogger, $options: IOptions, - private $platformCommandsService: IPlatformCommandsService, + private $platformCommandHelper: IPlatformCommandHelper, $platformsDataService: IPlatformsDataService, $platformValidationService: IPlatformValidationService, private $pluginsService: IPluginsService, @@ -85,7 +85,7 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma this.$projectDataService.removeNSProperty(this.$projectData.projectDir, platformData.frameworkPackageName); } - await this.$platformCommandsService.removePlatforms(platforms.installed, this.$projectData); + await this.$platformCommandHelper.removePlatforms(platforms.installed, this.$projectData); await this.$pluginsService.remove(constants.TNS_CORE_MODULES_NAME, this.$projectData); if (!!this.$projectData.dependencies[constants.TNS_CORE_MODULES_WIDGETS_NAME]) { await this.$pluginsService.remove(constants.TNS_CORE_MODULES_WIDGETS_NAME, this.$projectData); @@ -97,12 +97,12 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma if (args.length === 1) { for (const platform of platforms.packagePlatforms) { - await this.$platformCommandsService.addPlatforms([platform + "@" + args[0]], this.$projectData, this.$options.frameworkPath); + await this.$platformCommandHelper.addPlatforms([platform + "@" + args[0]], this.$projectData, this.$options.frameworkPath); } await this.$pluginsService.add(`${constants.TNS_CORE_MODULES_NAME}@${args[0]}`, this.$projectData); } else { - await this.$platformCommandsService.addPlatforms(platforms.packagePlatforms, this.$projectData, this.$options.frameworkPath); + await this.$platformCommandHelper.addPlatforms(platforms.packagePlatforms, this.$projectData, this.$options.frameworkPath); await this.$pluginsService.add(constants.TNS_CORE_MODULES_NAME, this.$projectData); } @@ -110,8 +110,8 @@ export class UpdateCommand extends ValidatePlatformCommandBase implements IComma } private getPlatforms(): { installed: string[], packagePlatforms: string[] } { - const installedPlatforms = this.$platformCommandsService.getInstalledPlatforms(this.$projectData); - const availablePlatforms = this.$platformCommandsService.getAvailablePlatforms(this.$projectData); + const installedPlatforms = this.$platformCommandHelper.getInstalledPlatforms(this.$projectData); + const availablePlatforms = this.$platformCommandHelper.getAvailablePlatforms(this.$projectData); const packagePlatforms: string[] = []; for (const platform of availablePlatforms) { diff --git a/lib/controllers/build-controller.ts b/lib/controllers/build-controller.ts index 263062064f..a0acfd296c 100644 --- a/lib/controllers/build-controller.ts +++ b/lib/controllers/build-controller.ts @@ -1,38 +1,34 @@ -import { PrepareController } from "./prepare-controller"; -import { BuildData } from "../data/build-data"; import * as constants from "../constants"; -import { BuildArtefactsService } from "../services/build-artefacts-service"; import { Configurations } from "../common/constants"; import { EventEmitter } from "events"; import { attachAwaitDetach } from "../common/helpers"; -import { BuildInfoFileService } from "../services/build-info-file-service"; -export class BuildController extends EventEmitter { +export class BuildController extends EventEmitter implements IBuildController { constructor( private $analyticsService: IAnalyticsService, - private $buildArtefactsService: BuildArtefactsService, - private $buildInfoFileService: BuildInfoFileService, + private $buildArtefactsService: IBuildArtefactsService, + private $buildInfoFileService: IBuildInfoFileService, private $fs: IFileSystem, private $logger: ILogger, private $injector: IInjector, private $mobileHelper: Mobile.IMobileHelper, private $projectDataService: IProjectDataService, private $projectChangesService: IProjectChangesService, - private $prepareController: PrepareController, + private $prepareController: IPrepareController, ) { super(); } private get $platformsDataService(): IPlatformsDataService { return this.$injector.resolve("platformsDataService"); } - public async prepareAndBuildPlatform(buildData: BuildData): Promise { - await this.$prepareController.preparePlatform(buildData); - const result = await this.buildPlatform(buildData); + public async prepareAndBuild(buildData: IBuildData): Promise { + await this.$prepareController.prepare(buildData); + const result = await this.build(buildData); return result; } - public async buildPlatform(buildData: BuildData) { + public async build(buildData: IBuildData): Promise { this.$logger.out("Building project..."); const platform = buildData.platform.toLowerCase(); @@ -76,28 +72,28 @@ export class BuildController extends EventEmitter { return result; } - public async buildPlatformIfNeeded(buildData: BuildData): Promise { + public async buildIfNeeded(buildData: IBuildData): Promise { let result = null; - const platform = buildData.platform.toLowerCase(); - const projectData = this.$projectDataService.getProjectData(buildData.projectDir); - const platformData = this.$platformsDataService.getPlatformData(platform, projectData); - - const outputPath = buildData.outputPath || platformData.getBuildOutputPath(buildData); - const shouldBuildPlatform = await this.shouldBuildPlatform(buildData, platformData, outputPath); + const shouldBuildPlatform = await this.shouldBuild(buildData); if (shouldBuildPlatform) { - result = await this.buildPlatform(buildData); + result = await this.build(buildData); } return result; } - private async shouldBuildPlatform(buildData: BuildData, platformData: IPlatformData, outputPath: string): Promise { + public async shouldBuild(buildData: IBuildData): Promise { + const projectData = this.$projectDataService.getProjectData(buildData.projectDir); + const platformData = this.$platformsDataService.getPlatformData(buildData.platform, projectData); + const outputPath = buildData.outputPath || platformData.getBuildOutputPath(buildData); + if (buildData.release && this.$projectChangesService.currentChanges.hasChanges) { return true; } - if (this.$projectChangesService.currentChanges.changesRequireBuild) { + const changesInfo = this.$projectChangesService.currentChanges || await this.$projectChangesService.checkForChanges(platformData, projectData, buildData); + if (changesInfo.changesRequireBuild) { return true; } @@ -112,7 +108,7 @@ export class BuildController extends EventEmitter { } const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); - const buildInfo = this.$buildInfoFileService.getBuildInfoFromFile(platformData, buildData, outputPath); + const buildInfo = this.$buildInfoFileService.getBuildInfoFromFile(platformData, buildData); if (!prepareInfo || !buildInfo) { return true; } diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts new file mode 100644 index 0000000000..5f43a8d08f --- /dev/null +++ b/lib/controllers/deploy-controller.ts @@ -0,0 +1,22 @@ +export class DeployController { + + constructor( + private $buildDataService: IBuildDataService, + private $buildController: IBuildController, + private $deviceInstallAppService: IDeviceInstallAppService, + private $devicesService: Mobile.IDevicesService + ) { } + + public async deploy(data: IRunData): Promise { + const { projectDir, liveSyncInfo, deviceDescriptors } = data; + + const executeAction = async (device: Mobile.IDevice) => { + const buildData = this.$buildDataService.getBuildData(projectDir, device.deviceInfo.platform, liveSyncInfo); + await this.$buildController.prepareAndBuild(buildData); + await this.$deviceInstallAppService.installOnDevice(device, buildData); + }; + + await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); + } +} +$injector.register("deployController", DeployController); diff --git a/lib/controllers/deploy-on-devices-controller.ts b/lib/controllers/deploy-on-devices-controller.ts deleted file mode 100644 index 8dae51a6f7..0000000000 --- a/lib/controllers/deploy-on-devices-controller.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DeviceInstallAppService } from "../services/device/device-install-app-service"; -import { RunOnDevicesData } from "../data/run-on-devices-data"; -import { BuildController } from "./build-controller"; -import { BuildDataService } from "../services/build-data-service"; - -export class DeployOnDevicesController { - - constructor( - private $buildDataService: BuildDataService, - private $buildController: BuildController, - private $deviceInstallAppService: DeviceInstallAppService, - private $devicesService: Mobile.IDevicesService - ) { } - - public async deployOnDevices(data: RunOnDevicesData): Promise { - const { projectDir, liveSyncInfo, deviceDescriptors } = data; - - const executeAction = async (device: Mobile.IDevice) => { - const buildData = this.$buildDataService.getBuildData(projectDir, device.deviceInfo.platform, liveSyncInfo); - await this.$buildController.prepareAndBuildPlatform(buildData); - await this.$deviceInstallAppService.installOnDevice(device, buildData); - }; - - await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); - } -} -$injector.register("deployOnDevicesController", DeployOnDevicesController); diff --git a/lib/controllers/add-platform-controller.ts b/lib/controllers/platform-controller.ts similarity index 88% rename from lib/controllers/add-platform-controller.ts rename to lib/controllers/platform-controller.ts index d7a5255cc4..fb7142d109 100644 --- a/lib/controllers/add-platform-controller.ts +++ b/lib/controllers/platform-controller.ts @@ -1,11 +1,9 @@ -import { AddPlatformData } from "../data/add-platform-data"; -import { AddPlatformService } from "../services/platform/add-platform-service"; import { NativePlatformStatus } from "../constants"; import * as path from "path"; -export class AddPlatformController { +export class PlatformController implements IPlatformController { constructor( - private $addPlatformService: AddPlatformService, + private $addPlatformService: IAddPlatformService, private $errors: IErrors, private $fs: IFileSystem, private $logger: ILogger, @@ -15,7 +13,7 @@ export class AddPlatformController { private $projectChangesService: IProjectChangesService, ) { } - public async addPlatform(addPlatformData: AddPlatformData): Promise { + public async addPlatform(addPlatformData: IAddPlatformData): Promise { const [ platform, version ] = addPlatformData.platform.toLowerCase().split("@"); const projectData = this.$projectDataService.getProjectData(addPlatformData.projectDir); const platformData = this.$platformsDataService.getPlatformData(platform, projectData); @@ -35,7 +33,7 @@ export class AddPlatformController { this.$logger.out(`Platform ${platform} successfully added. v${installedPlatformVersion}`); } - public async addPlatformIfNeeded(addPlatformData: AddPlatformData): Promise { + public async addPlatformIfNeeded(addPlatformData: IAddPlatformData): Promise { const [ platform ] = addPlatformData.platform.toLowerCase().split("@"); const projectData = this.$projectDataService.getProjectData(addPlatformData.projectDir); const platformData = this.$platformsDataService.getPlatformData(platform, projectData); @@ -77,4 +75,4 @@ export class AddPlatformController { return !!result; } } -$injector.register("addPlatformController", AddPlatformController); +$injector.register("platformController", PlatformController); diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 2edc33eff6..aea42ebccb 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -1,15 +1,10 @@ import * as child_process from "child_process"; import * as choki from "chokidar"; import { hook } from "../common/helpers"; -import { PrepareNativePlatformService } from "../services/platform/prepare-native-platform-service"; import { performanceLog } from "../common/decorators"; import { EventEmitter } from "events"; import * as path from "path"; -import { WebpackCompilerService } from "../services/webpack/webpack-compiler-service"; import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE } from "../constants"; -import { HooksService } from "../common/services/hooks-service"; -import { AddPlatformController } from "./add-platform-controller"; -import { PrepareData } from "../data/prepare-data"; interface IPlatformWatcherData { webpackCompilerProcess: child_process.ChildProcess; @@ -22,20 +17,20 @@ export class PrepareController extends EventEmitter { private persistedData: IFilesChangeEventData[] = []; constructor( - private $addPlatformController: AddPlatformController, - public $hooksService: HooksService, + private $platformController: IPlatformController, + public $hooksService: IHooksService, private $logger: ILogger, private $platformsDataService: IPlatformsDataService, - private $prepareNativePlatformService: PrepareNativePlatformService, + private $prepareNativePlatformService: IPrepareNativePlatformService, private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService, - private $webpackCompilerService: WebpackCompilerService + private $webpackCompilerService: IWebpackCompilerService ) { super(); } @performanceLog() @hook("prepare") - public async preparePlatform(prepareData: PrepareData): Promise { - await this.$addPlatformController.addPlatformIfNeeded(prepareData); + public async prepare(prepareData: IPrepareData): Promise { + await this.$platformController.addPlatformIfNeeded(prepareData); this.$logger.out("Preparing project..."); let result = null; @@ -72,7 +67,7 @@ export class PrepareController extends EventEmitter { } @hook("watch") - private async startWatchersWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { + private async startWatchersWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { if (!this.watchersData[projectData.projectDir]) { this.watchersData[projectData.projectDir] = {}; } @@ -113,7 +108,7 @@ export class PrepareController extends EventEmitter { } } - private async startNativeWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { + private async startNativeWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { if ((prepareData.nativePrepare && prepareData.nativePrepare.skipNativePrepare) || this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher) { return false; } @@ -134,7 +129,7 @@ export class PrepareController extends EventEmitter { const watcher = choki.watch(patterns, watcherOptions) .on("all", async (event: string, filePath: string) => { filePath = path.join(projectData.projectDir, filePath); - this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); + this.$logger.info(`Chokidar raised event ${event} for ${filePath}.`); this.emitPrepareEvent({ files: [], hmrData: null, hasNativeChanges: true, platform: platformData.platformNameLowerCase }); }); diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts index 366c4e79a0..9dd961d5e1 100644 --- a/lib/controllers/preview-app-controller.ts +++ b/lib/controllers/preview-app-controller.ts @@ -5,7 +5,7 @@ import { performanceLog } from "../common/decorators"; import { stringify } from "../common/helpers"; import { HmrConstants } from "../common/constants"; import { EventEmitter } from "events"; -import { PreviewAppEmitter } from "../preview-app-emitter"; +import { PreviewAppEmitter } from "../emitters/preview-app-emitter"; import { PrepareDataService } from "../services/prepare-data-service"; export class PreviewAppController extends EventEmitter { @@ -60,7 +60,7 @@ export class PreviewAppController extends EventEmitter { data.env.externals = this.$previewAppPluginsService.getExternalPlugins(device); const prepareData = this.$prepareDataService.getPrepareData(data.projectDir, device.platform.toLowerCase(), { ...data, skipNativePrepare: true } ); - await this.$prepareController.preparePlatform(prepareData); + await this.$prepareController.prepare(prepareData); this.deviceInitializationPromise[device.id] = this.getInitialFilesForPlatformSafe(data, device.platform); diff --git a/lib/controllers/run-on-devices-controller.ts b/lib/controllers/run-controller.ts similarity index 69% rename from lib/controllers/run-on-devices-controller.ts rename to lib/controllers/run-controller.ts index a57b3f77aa..686183f846 100644 --- a/lib/controllers/run-on-devices-controller.ts +++ b/lib/controllers/run-controller.ts @@ -1,45 +1,36 @@ -import { DeviceDebugAppService } from "../services/device/device-debug-app-service"; -import { DeviceInstallAppService } from "../services/device/device-install-app-service"; -import { DeviceRefreshAppService } from "../services/device/device-refresh-app-service"; import { EventEmitter } from "events"; import { LiveSyncServiceResolver } from "../resolvers/livesync-service-resolver"; -import { RunOnDevicesDataService } from "../services/run-on-devices-data-service"; -import { RunOnDevicesEmitter } from "../run-on-devices-emitter"; import { HmrConstants, DeviceDiscoveryEventNames } from "../common/constants"; -import { PrepareNativePlatformService } from "../services/platform/prepare-native-platform-service"; -import { PrepareController } from "./prepare-controller"; -import { PREPARE_READY_EVENT_NAME } from "../constants"; +import { PREPARE_READY_EVENT_NAME, TrackActionNames } from "../constants"; import { cache } from "../common/decorators"; -import { RunOnDevicesData } from "../data/run-on-devices-data"; -import { PrepareDataService } from "../services/prepare-data-service"; -import { BuildController } from "./build-controller"; -import { BuildDataService } from "../services/build-data-service"; -export class RunOnDevicesController extends EventEmitter { +export class RunController extends EventEmitter { + private processesInfo: IDictionary = {}; + constructor( - private $buildDataService: BuildDataService, - private $buildController: BuildController, - private $deviceDebugAppService: DeviceDebugAppService, - private $deviceInstallAppService: DeviceInstallAppService, - private $deviceRefreshAppService: DeviceRefreshAppService, + private $analyticsService: IAnalyticsService, + private $buildDataService: IBuildDataService, + private $buildController: IBuildController, + private $deviceDebugAppService: IDeviceDebugAppService, + private $deviceInstallAppService: IDeviceInstallAppService, + private $deviceRefreshAppService: IDeviceRefreshAppService, private $devicesService: Mobile.IDevicesService, private $errors: IErrors, private $hmrStatusService: IHmrStatusService, public $hooksService: IHooksService, private $liveSyncServiceResolver: LiveSyncServiceResolver, private $logger: ILogger, - private $pluginsService: IPluginsService, private $platformsDataService: IPlatformsDataService, - private $prepareNativePlatformService: PrepareNativePlatformService, - private $prepareController: PrepareController, - private $prepareDataService: PrepareDataService, + private $pluginsService: IPluginsService, + private $prepareController: IPrepareController, + private $prepareDataService: IPrepareDataService, + private $prepareNativePlatformService: IPrepareNativePlatformService, private $projectDataService: IProjectDataService, - private $runOnDevicesDataService: RunOnDevicesDataService, - private $runOnDevicesEmitter: RunOnDevicesEmitter + private $runEmitter: IRunEmitter ) { super(); } - public async runOnDevices(runOnDevicesData: RunOnDevicesData): Promise { - const { projectDir, liveSyncInfo, deviceDescriptors } = runOnDevicesData; + public async run(runData: IRunData): Promise { + const { projectDir, liveSyncInfo, deviceDescriptors } = runData; const projectData = this.$projectDataService.getProjectData(projectDir); await this.initializeSetup(projectData); @@ -47,9 +38,9 @@ export class RunOnDevicesController extends EventEmitter { const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); const deviceDescriptorsForInitialSync = this.getDeviceDescriptorsForInitialSync(projectDir, deviceDescriptors); - this.$runOnDevicesDataService.persistData(projectDir, deviceDescriptors, platforms); + this.persistData(projectDir, deviceDescriptors, platforms); - const shouldStartWatcher = !liveSyncInfo.skipWatcher && this.$runOnDevicesDataService.hasDeviceDescriptors(projectData.projectDir); + const shouldStartWatcher = !liveSyncInfo.skipWatcher && !!this.processesInfo[projectDir].deviceDescriptors.length; if (shouldStartWatcher && liveSyncInfo.useHotModuleReload) { this.$hmrStatusService.attachToHmrStatusEvent(); } @@ -58,17 +49,13 @@ export class RunOnDevicesController extends EventEmitter { await this.syncChangedDataOnDevices(data, projectData, liveSyncInfo, deviceDescriptors); }); - for (const platform of platforms) { - const prepareData = this.$prepareDataService.getPrepareData(projectDir, platform, { ...liveSyncInfo, watch: !liveSyncInfo.skipWatcher }); - const prepareResult = await this.$prepareController.preparePlatform(prepareData); - await this.syncInitialDataOnDevices(prepareResult, projectData, liveSyncInfo, deviceDescriptorsForInitialSync); - } + await this.syncInitialDataOnDevices(projectData, liveSyncInfo, deviceDescriptorsForInitialSync); this.attachDeviceLostHandler(); } - public async stopRunOnDevices(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { - const liveSyncProcessInfo = this.$runOnDevicesDataService.getDataForProject(projectDir); + public async stop(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { + const liveSyncProcessInfo = this.processesInfo[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. @@ -117,20 +104,22 @@ export class RunOnDevicesController extends EventEmitter { // Emit RunOnDevice stopped when we've really stopped. _.each(removedDeviceIdentifiers, deviceIdentifier => { - this.$runOnDevicesEmitter.emitRunOnDeviceStoppedEvent(projectDir, deviceIdentifier); + this.$runEmitter.emitRunStoppedEvent(projectDir, deviceIdentifier); }); } } - public getRunOnDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { - return this.$runOnDevicesDataService.getDeviceDescriptors(projectDir); + public getDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + const liveSyncProcessesInfo = this.processesInfo[projectDir] || {}; + const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; + return currentDescriptors || []; } private getDeviceDescriptorsForInitialSync(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]) { - const currentRunOnDevicesData = this.$runOnDevicesDataService.getDataForProject(projectDir); - const isAlreadyLiveSyncing = currentRunOnDevicesData && !currentRunOnDevicesData.isStopped; + const currentRunData = this.processesInfo[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, currentRunOnDevicesData.deviceDescriptors, "identifier") : deviceDescriptors; + const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentRunData.deviceDescriptors, "identifier") : deviceDescriptors; return deviceDescriptorsForInitialSync; } @@ -149,11 +138,11 @@ export class RunOnDevicesController extends EventEmitter { 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.$runOnDevicesDataService.getAllData()) { + for (const projectDir in this.processesInfo) { try { - const deviceDescriptors = this.$runOnDevicesDataService.getDeviceDescriptors(projectDir); + const deviceDescriptors = this.getDeviceDescriptors(projectDir); if (_.find(deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { - await this.stopRunOnDevices(projectDir, [device.deviceInfo.identifier]); + await this.stop(projectDir, [device.deviceInfo.identifier]); } } catch (err) { this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); @@ -162,16 +151,26 @@ export class RunOnDevicesController extends EventEmitter { }); } - private async syncInitialDataOnDevices(data: IPrepareOutputData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + private async syncInitialDataOnDevices(projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): 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); - const buildData = this.$buildDataService.getBuildData(projectData.projectDir, data.platform, { ...liveSyncInfo, outputPath: deviceDescriptor.outputPath }); + const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); + const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, device.deviceInfo.platform, { ...liveSyncInfo, watch: !liveSyncInfo.skipWatcher, nativePrepare: { skipNativePrepare: !!deviceDescriptor.skipNativePrepare } }); + const buildData = this.$buildDataService.getBuildData(projectData.projectDir, device.deviceInfo.platform, { ...liveSyncInfo, outputPath: deviceDescriptor.outputPath }); + const prepareResultData = await this.$prepareController.prepare(prepareData); try { - const packageFilePath = data.hasNativeChanges ? - await this.$buildController.prepareAndBuildPlatform(buildData) : - await this.$buildController.buildPlatformIfNeeded(buildData); + let packageFilePath: string = null; + const shouldBuild = prepareResultData.hasNativeChanges || await this.$buildController.shouldBuild(buildData); + if (shouldBuild) { + packageFilePath = await deviceDescriptor.buildAction(); + } else { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.LiveSync, + device, + projectDir: projectData.projectDir + }); + } await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, buildData, packageFilePath); @@ -181,7 +180,7 @@ export class RunOnDevicesController extends EventEmitter { const refreshInfo = await this.$deviceRefreshAppService.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); - this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, device, { + this.$runEmitter.emitRunExecutedEvent(projectData, device, { syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); @@ -192,15 +191,15 @@ export class RunOnDevicesController extends EventEmitter { this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - this.$runOnDevicesEmitter.emitRunOnDeviceStartedEvent(projectData, device); + this.$runEmitter.emitRunStartedEvent(projectData, device); } catch (err) { this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); - this.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, err); + this.$runEmitter.emitRunErrorEvent(projectData, device, err); } }; - await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === data.platform.toLowerCase() && _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); + await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { @@ -213,7 +212,7 @@ export class RunOnDevicesController extends EventEmitter { try { if (data.hasNativeChanges) { await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); - await this.$buildController.prepareAndBuildPlatform(buildData); + await this.$buildController.prepareAndBuild(buildData); } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; @@ -255,14 +254,14 @@ export class RunOnDevicesController extends EventEmitter { 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.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, deviceError); + this.$runEmitter.emitRunErrorEvent(projectData, device, deviceError); } } } }; await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => { - const liveSyncProcessInfo = this.$runOnDevicesDataService.getDataForProject(projectData.projectDir); + const liveSyncProcessInfo = this.processesInfo[projectData.projectDir]; return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); })); } @@ -270,7 +269,7 @@ export class RunOnDevicesController extends EventEmitter { private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo) { const refreshInfo = await this.$deviceRefreshAppService.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); - this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + this.$runEmitter.emitRunExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); @@ -281,7 +280,7 @@ export class RunOnDevicesController extends EventEmitter { } private async addActionToChain(projectDir: string, action: () => Promise): Promise { - const liveSyncInfo = this.$runOnDevicesDataService.getDataForProject(projectDir); + const liveSyncInfo = this.processesInfo[projectDir]; if (liveSyncInfo) { liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { if (!liveSyncInfo.isStopped) { @@ -295,5 +294,16 @@ export class RunOnDevicesController extends EventEmitter { return result; } } + + 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"); + } } -$injector.register("runOnDevicesController", RunOnDevicesController); +$injector.register("runController", RunController); diff --git a/lib/data/build-data.ts b/lib/data/build-data.ts index 04e967b03c..9ab809739b 100644 --- a/lib/data/build-data.ts +++ b/lib/data/build-data.ts @@ -1,6 +1,6 @@ import { PrepareData } from "./prepare-data"; -export class BuildData extends PrepareData { +export class BuildData extends PrepareData implements IBuildData { public device?: string; public emulator?: boolean; public clean: boolean; @@ -22,7 +22,7 @@ export class BuildData extends PrepareData { } } -export class IOSBuildData extends BuildData { +export class IOSBuildData extends BuildData implements IiOSBuildData { public teamId: string; public provision: string; public mobileProvisionData: any; diff --git a/lib/data/data-base.ts b/lib/data/controller-data-base.ts similarity index 71% rename from lib/data/data-base.ts rename to lib/data/controller-data-base.ts index 4d630c022f..c5dfaad64f 100644 --- a/lib/data/data-base.ts +++ b/lib/data/controller-data-base.ts @@ -1,4 +1,4 @@ -export class DataBase { +export class ControllerDataBase implements IControllerDataBase { public nativePrepare?: INativePrepare; constructor(public projectDir: string, public platform: string, data: any) { diff --git a/lib/data/debug-data.ts b/lib/data/debug-data.ts new file mode 100644 index 0000000000..c80d2885d0 --- /dev/null +++ b/lib/data/debug-data.ts @@ -0,0 +1,3 @@ +export class DebugData { + // +} diff --git a/lib/data/add-platform-data.ts b/lib/data/platform-data.ts similarity index 62% rename from lib/data/add-platform-data.ts rename to lib/data/platform-data.ts index c43aeef36f..89ff99a77c 100644 --- a/lib/data/add-platform-data.ts +++ b/lib/data/platform-data.ts @@ -1,6 +1,6 @@ -import { DataBase } from "./data-base"; +import { ControllerDataBase } from "./controller-data-base"; -export class AddPlatformData extends DataBase { +export class AddPlatformData extends ControllerDataBase { public frameworkPath?: string; constructor(public projectDir: string, public platform: string, data: any) { diff --git a/lib/data/prepare-data.ts b/lib/data/prepare-data.ts index 8a7d06de13..c24f6fcc0f 100644 --- a/lib/data/prepare-data.ts +++ b/lib/data/prepare-data.ts @@ -1,6 +1,6 @@ -import { DataBase } from "./data-base"; +import { ControllerDataBase } from "./controller-data-base"; -export class PrepareData extends DataBase { +export class PrepareData extends ControllerDataBase { public release: boolean; public hmr: boolean; public env: any; diff --git a/lib/data/run-data.ts b/lib/data/run-data.ts new file mode 100644 index 0000000000..4d67d8043d --- /dev/null +++ b/lib/data/run-data.ts @@ -0,0 +1,5 @@ +export class RunData { + constructor(public projectDir: string, + public liveSyncInfo: ILiveSyncInfo, + public deviceDescriptors: ILiveSyncDeviceInfo[]) { } +} diff --git a/lib/data/run-on-devices-data.ts b/lib/data/run-on-devices-data.ts deleted file mode 100644 index 1ceb7b5515..0000000000 --- a/lib/data/run-on-devices-data.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class RunOnDevicesData { - constructor(public projectDir: string, public liveSyncInfo: ILiveSyncInfo, public deviceDescriptors: ILiveSyncDeviceInfo[]) { } -} diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 77a1f6d9b4..604cac288a 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -1029,7 +1029,7 @@ interface IPlatformValidationService { isPlatformSupportedForOS(platform: string, projectData: IProjectData): boolean; } -interface IPlatformCommandsService { +interface IPlatformCommandHelper { addPlatforms(platforms: string[], projectData: IProjectData, frameworkPath: string): Promise; cleanPlatforms(platforms: string[], projectData: IProjectData, framworkPath: string): Promise; removePlatforms(platforms: string[], projectData: IProjectData): Promise; @@ -1037,10 +1037,5 @@ interface IPlatformCommandsService { getInstalledPlatforms(projectData: IProjectData): string[]; getAvailablePlatforms(projectData: IProjectData): string[]; getPreparedPlatforms(projectData: IProjectData): string[]; -} - -interface IAddPlatformData { - platformParam: string; - frameworkPath?: string; - nativePrepare?: INativePrepare; + getCurrentPlatformVersion(platform: string, projectData: IProjectData): string; } \ No newline at end of file diff --git a/lib/definitions/build.d.ts b/lib/definitions/build.d.ts new file mode 100644 index 0000000000..fffc6fa53e --- /dev/null +++ b/lib/definitions/build.d.ts @@ -0,0 +1,47 @@ +interface IBuildData extends IPrepareData { + device?: string; + emulator?: boolean; + clean: boolean; + buildForDevice?: boolean; + buildOutputStdio?: string; + outputPath?: string; + copyTo?: string; +} + +interface IiOSBuildData extends IBuildData { + teamId: string; + provision: string; + mobileProvisionData: any; + buildForAppStore: boolean; + iCloudContainerEnvironment: string; +} + +interface IAndroidBuildData extends IBuildData { + keyStoreAlias: string; + keyStorePath: string; + keyStoreAliasPassword: string; + keyStorePassword: string; + androidBundle: boolean; +} + +interface IBuildController { + prepareAndBuild(buildData: IBuildData): Promise; + build(buildData: IBuildData): Promise; + buildIfNeeded(buildData: IBuildData): Promise; + shouldBuild(buildData: IBuildData): Promise; +} + +interface IBuildDataService { + getBuildData(projectDir: string, platform: string, data: any): IBuildData; +} + +interface IBuildArtefactsService { + getLatestApplicationPackagePath(platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): Promise; + getAllApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[]; + copyLastOutput(targetPath: string, platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): void; +} + +interface IBuildInfoFileService { + saveBuildInfoFile(platformData: IPlatformData, buildInfoFileDirname: string): void; + getBuildInfoFromFile(platformData: IPlatformData, buildData: IBuildData): IBuildInfo; +} \ No newline at end of file diff --git a/lib/definitions/data.d.ts b/lib/definitions/data.d.ts new file mode 100644 index 0000000000..d15afc8cc2 --- /dev/null +++ b/lib/definitions/data.d.ts @@ -0,0 +1,5 @@ +interface IControllerDataBase { + projectDir: string; + platform: string; + nativePrepare?: INativePrepare; +} \ No newline at end of file diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index f05682ac38..469ddacd9e 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -13,11 +13,6 @@ interface IAppDebugData extends IProjectDir { */ applicationIdentifier: string; - /** - * Path to .app built for iOS Simulator. - */ - pathToAppPackage?: string; - /** * The name of the application, for example `MyProject`. */ diff --git a/lib/definitions/deploy.d.ts b/lib/definitions/deploy.d.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index a6e4dfb120..38ca7a93ba 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -1,7 +1,7 @@ import { EventEmitter } from "events"; declare global { - interface ILiveSyncProcessInfo { + interface IRunOnDeviceProcessInfo { timer: NodeJS.Timer; actionsChain: Promise; isStopped: boolean; diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 218c429d2d..46406ade21 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -43,8 +43,6 @@ interface IBuildOutputOptions extends Partial, IRelease, IHasAn } interface IPlatformsDataService { - availablePlatforms: any; - platformsNames: string[]; getPlatformData(platform: string, projectData: IProjectData): IPlatformData; } @@ -84,3 +82,16 @@ interface ICheckEnvironmentRequirementsOutput { canExecute: boolean; selectedOption: string; } + +interface IAddPlatformData extends IControllerDataBase { + frameworkPath?: string; +} + +interface IPlatformController { + addPlatform(addPlatformData: IAddPlatformData): Promise; + addPlatformIfNeeded(addPlatformData: IAddPlatformData): Promise; +} + +interface IAddPlatformService { + addPlatformSafe(projectData: IProjectData, platformData: IPlatformData, packageToInstall: string, nativePrepare: INativePrepare): Promise; +} diff --git a/lib/definitions/prepare.d.ts b/lib/definitions/prepare.d.ts new file mode 100644 index 0000000000..15c6c981b9 --- /dev/null +++ b/lib/definitions/prepare.d.ts @@ -0,0 +1,37 @@ +import { EventEmitter } from "events"; + +declare global { + + interface IPrepareData extends IControllerDataBase { + release: boolean; + hmr: boolean; + env: any; + watch?: boolean; + } + + interface IiOSPrepareData extends IPrepareData { + teamId: string; + provision: string; + mobileProvisionData: any; + } + + interface IAndroidPrepareData extends IPrepareData { } + + interface IPrepareDataService { + getPrepareData(projectDir: string, platform: string, data: any): IPrepareData; + } + + interface IPrepareController extends EventEmitter { + prepare(prepareData: IPrepareData): Promise; + stopWatchers(projectDir: string, platform: string): void; + } + + interface IPrepareResultData { + platform: string; + hasNativeChanges: boolean; + } + + interface IPrepareNativePlatformService { + prepareNativePlatform(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise; + } +} \ No newline at end of file diff --git a/lib/definitions/run.d.ts b/lib/definitions/run.d.ts new file mode 100644 index 0000000000..5ef09b0ab1 --- /dev/null +++ b/lib/definitions/run.d.ts @@ -0,0 +1,37 @@ +interface IRunData { + projectDir: string; + liveSyncInfo: ILiveSyncInfo; + deviceDescriptors: ILiveSyncDeviceInfo[]; +} + +interface IRunController { + +} + +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 IDeviceInstallAppService { + installOnDevice(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; + installOnDeviceIfNeeded(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; + getDeviceBuildInfoFilePath(device: Mobile.IDevice, projectData: IProjectData): Promise; + shouldInstall(device: Mobile.IDevice, buildData: IBuildData): Promise; +} + +interface IDeviceRefreshAppService { + refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, settings?: IRefreshApplicationSettings): Promise; +} + +interface IDeviceDebugAppService { + enableDebugging(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise; + attachDebugger(settings: IAttachDebuggerOptions): Promise; + printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean): IDebugInformation; +} \ No newline at end of file diff --git a/lib/preview-app-emitter.ts b/lib/emitters/preview-app-emitter.ts similarity index 80% rename from lib/preview-app-emitter.ts rename to lib/emitters/preview-app-emitter.ts index 40c05a555c..99923fbef2 100644 --- a/lib/preview-app-emitter.ts +++ b/lib/emitters/preview-app-emitter.ts @@ -1,5 +1,5 @@ import { EventEmitter } from "events"; -import { PreviewAppLiveSyncEvents } from "./services/livesync/playground/preview-app-constants"; +import { PreviewAppLiveSyncEvents } from "../services/livesync/playground/preview-app-constants"; export class PreviewAppEmitter extends EventEmitter { public emitPreviewAppLiveSyncError(data: IPreviewAppLiveSyncData, deviceId: string, error: Error, platform?: string) { diff --git a/lib/run-on-devices-emitter.ts b/lib/emitters/run-emitter.ts similarity index 76% rename from lib/run-on-devices-emitter.ts rename to lib/emitters/run-emitter.ts index 55ed8e3ed1..947222292a 100644 --- a/lib/run-on-devices-emitter.ts +++ b/lib/emitters/run-emitter.ts @@ -1,12 +1,12 @@ import { EventEmitter } from "events"; -import { RunOnDeviceEvents, DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "./constants"; +import { RunOnDeviceEvents, DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "../constants"; -export class RunOnDevicesEmitter extends EventEmitter { +export class RunEmitter extends EventEmitter implements IRunEmitter { constructor( private $logger: ILogger ) { super(); } - public emitRunOnDeviceStartedEvent(projectData: IProjectData, device: Mobile.IDevice) { + public emitRunStartedEvent(projectData: IProjectData, device: Mobile.IDevice): void { this.emitCore(RunOnDeviceEvents.runOnDeviceStarted, { projectDir: projectData.projectDir, deviceIdentifier: device.deviceInfo.identifier, @@ -14,7 +14,7 @@ export class RunOnDevicesEmitter extends EventEmitter { }); } - public emitRunOnDeviceNotificationEvent(projectData: IProjectData, device: Mobile.IDevice, notification: string) { + public emitRunNotificationEvent(projectData: IProjectData, device: Mobile.IDevice, notification: string): void { this.emitCore(RunOnDeviceEvents.runOnDeviceNotification, { projectDir: projectData.projectDir, deviceIdentifier: device.deviceInfo.identifier, @@ -23,7 +23,7 @@ export class RunOnDevicesEmitter extends EventEmitter { }); } - public emitRunOnDeviceErrorEvent(projectData: IProjectData, device: Mobile.IDevice, error: Error) { + public emitRunErrorEvent(projectData: IProjectData, device: Mobile.IDevice, error: Error): void { this.emitCore(RunOnDeviceEvents.runOnDeviceError, { projectDir: projectData.projectDir, deviceIdentifier: device.deviceInfo.identifier, @@ -32,7 +32,7 @@ export class RunOnDevicesEmitter extends EventEmitter { }); } - public emitRunOnDeviceExecutedEvent(projectData: IProjectData, device: Mobile.IDevice, options: { syncedFiles: string[], isFullSync: boolean }) { + public emitRunExecutedEvent(projectData: IProjectData, device: Mobile.IDevice, options: { syncedFiles: string[], isFullSync: boolean }): void { this.emitCore(RunOnDeviceEvents.runOnDeviceExecuted, { projectDir: projectData.projectDir, deviceIdentifier: device.deviceInfo.identifier, @@ -42,23 +42,23 @@ export class RunOnDevicesEmitter extends EventEmitter { }); } - public emitRunOnDeviceStoppedEvent(projectDir: string, deviceIdentifier: string) { + public emitRunStoppedEvent(projectDir: string, deviceIdentifier: string): void { this.emitCore(RunOnDeviceEvents.runOnDeviceStopped, { projectDir, deviceIdentifier }); } - public emitDebuggerAttachedEvent(debugInformation: IDebugInformation) { + public emitDebuggerAttachedEvent(debugInformation: IDebugInformation): void { this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); } - public emitDebuggerDetachedEvent(device: Mobile.IDevice) { + 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) { + public emitUserInteractionNeededEvent(projectData: IProjectData, device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo): void { const deviceIdentifier = device.deviceInfo.identifier; const attachDebuggerOptions: IAttachDebuggerOptions = { platform: device.deviceInfo.platform, @@ -76,4 +76,4 @@ export class RunOnDevicesEmitter extends EventEmitter { this.emit(event, data); } } -$injector.register("runOnDevicesEmitter", RunOnDevicesEmitter); +$injector.register("runEmitter", RunEmitter); diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index b609fa4ea6..d037ad435d 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -1,11 +1,11 @@ -import { DeployOnDevicesController } from "../controllers/deploy-on-devices-controller"; +import { DeployController } from "../controllers/deploy-controller"; import { BuildController } from "../controllers/build-controller"; export class DeployCommandHelper { constructor( private $buildController: BuildController, private $devicesService: Mobile.IDevicesService, - private $deployOnDevicesController: DeployOnDevicesController, + private $deployController: DeployController, private $options: IOptions, private $projectData: IProjectData ) { } @@ -42,7 +42,7 @@ export class DeployCommandHelper { const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : - this.$buildController.prepareAndBuildPlatform.bind(this.$buildController, d.deviceInfo.platform, buildConfig, this.$projectData); + this.$buildController.prepareAndBuild.bind(this.$buildController, d.deviceInfo.platform, buildConfig, this.$projectData); const outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ platform: d.deviceInfo.platform, @@ -74,7 +74,7 @@ export class DeployCommandHelper { emulator: this.$options.emulator }; - await this.$deployOnDevicesController.deployOnDevices({ + 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 fb8656f451..9c6a1dc7fd 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,13 +1,20 @@ -import { RunOnDevicesController } from "../controllers/run-on-devices-controller"; +import { RunController } from "../controllers/run-controller"; import { BuildController } from "../controllers/build-controller"; +import { BuildDataService } from "../services/build-data-service"; +import { DeployController } from "../controllers/deploy-controller"; +import { RunOnDeviceEvents } from "../constants"; +import { RunEmitter } from "../emitters/run-emitter"; export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; constructor( + private $buildDataService: BuildDataService, private $projectData: IProjectData, private $options: IOptions, - private $runOnDevicesController: RunOnDevicesController, + private $runController: RunController, + private $runEmitter: RunEmitter, + private $deployController: DeployController, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $devicesService: Mobile.IDevicesService, @@ -94,9 +101,11 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { keyStorePassword: this.$options.keyStorePassword }; + const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, buildConfig); + const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : - this.$buildController.prepareAndBuildPlatform.bind(this.$buildController, d.deviceInfo.platform, buildConfig, this.$projectData); + this.$buildController.prepareAndBuild.bind(this.$buildController, buildData); const outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ platform: d.deviceInfo.platform, @@ -120,7 +129,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } public getPlatformsForOperation(platform: string): string[] { - const availablePlatforms = platform ? [platform] : _.values(this.$platformsDataService.availablePlatforms); + const availablePlatforms = platform ? [platform] : _.values(this.$mobileHelper.platformNames.map(p => p.toLowerCase())); return availablePlatforms; } @@ -144,28 +153,43 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { emulator: this.$options.emulator }; - // if (this.$options.release) { - // liveSyncInfo.skipWatcher = true; - // await this.$bundleWorkflowService.deployPlatform(this.$projectData.projectDir, deviceDescriptors, liveSyncInfo); - // return; - // } + if (this.$options.release) { + await this.$deployController.deploy({ + projectDir: this.$projectData.projectDir, + liveSyncInfo: { ...liveSyncInfo, clean: true, skipWatcher: true }, + deviceDescriptors + }); + + await this.$devicesService.initialize({ + platform, + deviceId: this.$options.device, + emulator: this.$options.emulator, + skipInferPlatform: !platform, + sdk: this.$options.sdk + }); + + for (const deviceDescriptor of deviceDescriptors) { + const device = this.$devicesService.getDeviceByIdentifier(deviceDescriptor.identifier); + await device.applicationManager.startApplication({ appId: this.$projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], projectName: this.$projectData.projectName }); + } - await this.$runOnDevicesController.runOnDevices({ + return; + } + + await this.$runController.run({ projectDir: this.$projectData.projectDir, liveSyncInfo, deviceDescriptors }); - // const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); - // this.$liveSyncService.on(LiveSyncEvents.liveSyncStopped, (data: { projectDir: string, deviceIdentifier: string }) => { - // _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); - - // if (remainingDevicesToSync.length === 0) { - // process.exit(ErrorCodes.ALL_DEVICES_DISCONNECTED); - // } - // }); + const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); + this.$runEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: { projectDir: string, deviceIdentifier: string }) => { + _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); - // await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + if (remainingDevicesToSync.length === 0) { + process.exit(ErrorCodes.ALL_DEVICES_DISCONNECTED); + } + }); } public async validatePlatform(platform: string): Promise> { diff --git a/lib/services/platform/platform-commands-service.ts b/lib/helpers/platform-command-helper.ts similarity index 89% rename from lib/services/platform/platform-commands-service.ts rename to lib/helpers/platform-command-helper.ts index 558485d86c..6b76864bf4 100644 --- a/lib/services/platform/platform-commands-service.ts +++ b/lib/helpers/platform-command-helper.ts @@ -1,16 +1,17 @@ import * as path from "path"; import * as semver from "semver"; import * as temp from "temp"; -import * as constants from "../../constants"; -import { PlatformValidationService } from "./platform-validation-service"; -import { AddPlatformController } from "../../controllers/add-platform-controller"; +import * as constants from "../constants"; +import { PlatformController } from "../controllers/platform-controller"; +import { PlatformValidationService } from "../services/platform/platform-validation-service"; -export class PlatformCommandsService implements IPlatformCommandsService { +export class PlatformCommandHelper implements IPlatformCommandHelper { constructor( - private $addPlatformController: AddPlatformController, + private $platformController: PlatformController, private $fs: IFileSystem, private $errors: IErrors, private $logger: ILogger, + private $mobileHelper: Mobile.IMobileHelper, private $packageInstallationManager: IPackageInstallationManager, private $pacoteService: IPacoteService, private $platformsDataService: IPlatformsDataService, @@ -32,7 +33,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { this.$errors.failWithoutHelp(`Platform ${platform} already added`); } - await this.$addPlatformController.addPlatform({ + await this.$platformController.addPlatform({ projectDir: projectData.projectDir, platform, frameworkPath, @@ -88,7 +89,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { if (hasPlatformDirectory) { await this.updatePlatform(platform, version, projectData); } else { - await this.$addPlatformController.addPlatform({ + await this.$platformController.addPlatform({ projectDir: projectData.projectDir, platform: platformParam, }); @@ -102,18 +103,18 @@ export class PlatformCommandsService implements IPlatformCommandsService { } const subDirs = this.$fs.readDirectory(projectData.platformsDir); - return _.filter(subDirs, p => this.$platformsDataService.platformsNames.indexOf(p) > -1); + return _.filter(subDirs, p => this.$mobileHelper.platformNames.indexOf(p) > -1); } public getAvailablePlatforms(projectData: IProjectData): string[] { const installedPlatforms = this.getInstalledPlatforms(projectData); - return _.filter(this.$platformsDataService.platformsNames, p => { + return _.filter(this.$mobileHelper.platformNames, p => { return installedPlatforms.indexOf(p) < 0 && this.$platformValidationService.isPlatformSupportedForOS(p, projectData); // Only those not already installed }); } public getPreparedPlatforms(projectData: IProjectData): string[] { - return _.filter(this.$platformsDataService.platformsNames, p => { return this.isPlatformPrepared(p, projectData); }); + return _.filter(this.$mobileHelper.platformNames, p => { return this.isPlatformPrepared(p, projectData); }); } public getCurrentPlatformVersion(platform: string, projectData: IProjectData): string { @@ -174,7 +175,7 @@ export class PlatformCommandsService implements IPlatformCommandsService { let packageName = platformData.normalizedPlatformName.toLowerCase(); await this.removePlatforms([packageName], projectData); packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; - await this.$addPlatformController.addPlatform({ + await this.$platformController.addPlatform({ projectDir: projectData.projectDir, platform: packageName }); @@ -186,4 +187,4 @@ export class PlatformCommandsService implements IPlatformCommandsService { return platformData.platformProjectService.isPlatformPrepared(platformData.projectRoot, projectData); } } -$injector.register("platformCommandsService", PlatformCommandsService); +$injector.register("platformCommandHelper", PlatformCommandHelper); diff --git a/lib/services/build-artefacts-service.ts b/lib/services/build-artefacts-service.ts index c30d8612bf..5a62f83c76 100644 --- a/lib/services/build-artefacts-service.ts +++ b/lib/services/build-artefacts-service.ts @@ -1,6 +1,6 @@ import * as path from "path"; -export class BuildArtefactsService { +export class BuildArtefactsService implements IBuildArtefactsService { constructor( private $errors: IErrors, private $fs: IFileSystem, diff --git a/lib/services/build-data-service.ts b/lib/services/build-data-service.ts index 1934aa3307..2cb3ea930f 100644 --- a/lib/services/build-data-service.ts +++ b/lib/services/build-data-service.ts @@ -1,6 +1,6 @@ import { AndroidBuildData, IOSBuildData } from "../data/build-data"; -export class BuildDataService { +export class BuildDataService implements IBuildDataService { constructor(private $mobileHelper: Mobile.IMobileHelper) { } public getBuildData(projectDir: string, platform: string, data: any) { diff --git a/lib/services/build-info-file-service.ts b/lib/services/build-info-file-service.ts index cf6b78651a..f23679b195 100644 --- a/lib/services/build-info-file-service.ts +++ b/lib/services/build-info-file-service.ts @@ -2,7 +2,7 @@ import * as path from "path"; const buildInfoFileName = ".nsbuildinfo"; -export class BuildInfoFileService { +export class BuildInfoFileService implements IBuildInfoFileService { constructor( private $fs: IFileSystem, private $projectChangesService: IProjectChangesService @@ -20,9 +20,9 @@ export class BuildInfoFileService { this.$fs.writeJson(buildInfoFile, buildInfo); } - public getBuildInfoFromFile(platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions, buildOutputPath?: string): IBuildInfo { - buildOutputPath = buildOutputPath || platformData.getBuildOutputPath(buildOutputOptions); - const buildInfoFile = path.join(buildOutputPath, buildInfoFileName); + public getBuildInfoFromFile(platformData: IPlatformData, buildData: IBuildData): IBuildInfo { + const outputPath = buildData.outputPath || platformData.getBuildOutputPath(buildData); + const buildInfoFile = path.join(outputPath, buildInfoFileName); if (this.$fs.exists(buildInfoFile)) { try { const buildInfo = this.$fs.readJson(buildInfoFile); diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index 3958df4f79..a52cecce2a 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -49,7 +49,7 @@ export class DebugService extends EventEmitter implements IDebugService { // TODO: Consider to move this code to ios-device-debug-service if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { - if (device.isEmulator && !debugData.pathToAppPackage && debugOptions.debugBrk) { + if (device.isEmulator && debugOptions.debugBrk) { this.$errors.failWithoutHelp("To debug on iOS simulator you need to provide path to the app package."); } diff --git a/lib/services/device/device-debug-app-service.ts b/lib/services/device/device-debug-app-service.ts index 147a6dbc78..2b1f767f14 100644 --- a/lib/services/device/device-debug-app-service.ts +++ b/lib/services/device/device-debug-app-service.ts @@ -1,5 +1,5 @@ import { performanceLog } from "../../common/decorators"; -import { RunOnDevicesEmitter } from "../../run-on-devices-emitter"; +import { RunEmitter } from "../../emitters/run-emitter"; import { EOL } from "os"; export class DeviceDebugAppService { @@ -10,7 +10,7 @@ export class DeviceDebugAppService { private $errors: IErrors, private $logger: ILogger, private $projectDataService: IProjectDataService, - private $runOnDevicesEmitter: RunOnDevicesEmitter + private $runEmitter: RunEmitter ) { } @performanceLog() @@ -47,7 +47,6 @@ export class DeviceDebugAppService { // 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. - // debugData.pathToAppPackage = this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildConfig, settings.outputPath); const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); const result = this.printDebugInformation(debugInfo, settings.debugOptions.forceDebuggerAttachedEvent); return result; @@ -56,7 +55,7 @@ export class DeviceDebugAppService { public printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean = true): IDebugInformation { if (!!debugInformation.url) { if (fireDebuggerAttachedEvent) { - this.$runOnDevicesEmitter.emitDebuggerAttachedEvent(debugInformation); + this.$runEmitter.emitDebuggerAttachedEvent(debugInformation); } this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan); diff --git a/lib/services/device/device-install-app-service.ts b/lib/services/device/device-install-app-service.ts index e243d188dd..3cd6853e9b 100644 --- a/lib/services/device/device-install-app-service.ts +++ b/lib/services/device/device-install-app-service.ts @@ -1,27 +1,23 @@ import { TrackActionNames, HASHES_FILE_NAME } from "../../constants"; -import { BuildArtefactsService } from "../build-artefacts-service"; -import { BuildInfoFileService } from "../build-info-file-service"; -import { MobileHelper } from "../../common/mobile/mobile-helper"; import * as helpers from "../../common/helpers"; import * as path from "path"; -import { BuildData } from "../../data/build-data"; const buildInfoFileName = ".nsbuildinfo"; export class DeviceInstallAppService { constructor( private $analyticsService: IAnalyticsService, - private $buildArtefactsService: BuildArtefactsService, + private $buildArtefactsService: IBuildArtefactsService, private $devicePathProvider: IDevicePathProvider, private $fs: IFileSystem, private $logger: ILogger, - private $mobileHelper: MobileHelper, - private $buildInfoFileService: BuildInfoFileService, + private $mobileHelper: Mobile.IMobileHelper, + private $buildInfoFileService: IBuildInfoFileService, private $projectDataService: IProjectDataService, private $platformsDataService: IPlatformsDataService ) { } - public async installOnDevice(device: Mobile.IDevice, buildData: BuildData, packageFile?: string): Promise { + public async installOnDevice(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise { this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); const projectData = this.$projectDataService.getProjectData(buildData.projectDir); @@ -64,7 +60,7 @@ export class DeviceInstallAppService { this.$logger.out(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); } - public async installOnDeviceIfNeeded(device: Mobile.IDevice, buildData: BuildData, packageFile?: string): Promise { + public async installOnDeviceIfNeeded(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise { const shouldInstall = await this.shouldInstall(device, buildData); if (shouldInstall) { await this.installOnDevice(device, buildData, packageFile); @@ -80,6 +76,20 @@ export class DeviceInstallAppService { return helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, buildInfoFileName)); } + public async shouldInstall(device: Mobile.IDevice, buildData: IBuildData): Promise { + const projectData = this.$projectDataService.getProjectData(buildData.projectDir); + const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); + const platform = device.deviceInfo.platform; + if (!(await device.applicationManager.isApplicationInstalled(projectData.projectIdentifiers[platform.toLowerCase()]))) { + return true; + } + + const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); + const localBuildInfo = this.$buildInfoFileService.getBuildInfoFromFile(platformData, { ...buildData, buildForDevice: !device.isEmulator }); + + return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; + } + private async updateHashesOnDevice(data: { device: Mobile.IDevice, appIdentifier: string, outputFilePath: string, platformData: IPlatformData }): Promise { const { device, appIdentifier, platformData, outputFilePath } = data; @@ -96,20 +106,6 @@ export class DeviceInstallAppService { await device.fileSystem.updateHashesOnDevice(hashes, appIdentifier); } - private async shouldInstall(device: Mobile.IDevice, buildData: BuildData, outputPath?: string): Promise { - const projectData = this.$projectDataService.getProjectData(buildData.projectDir); - const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); - const platform = device.deviceInfo.platform; - if (!(await device.applicationManager.isApplicationInstalled(projectData.projectIdentifiers[platform.toLowerCase()]))) { - return true; - } - - const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); - const localBuildInfo = this.$buildInfoFileService.getBuildInfoFromFile(platformData, { buildForDevice: !device.isEmulator, release: buildData.release }, outputPath); - - return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; - } - private async getDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData): Promise { const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); try { diff --git a/lib/services/device/device-refresh-app-service.ts b/lib/services/device/device-refresh-app-service.ts index e5db853823..db2656f3a9 100644 --- a/lib/services/device/device-refresh-app-service.ts +++ b/lib/services/device/device-refresh-app-service.ts @@ -1,13 +1,13 @@ import { performanceLog } from "../../common/decorators"; -import { RunOnDevicesEmitter } from "../../run-on-devices-emitter"; +import { RunEmitter } from "../../emitters/run-emitter"; import { LiveSyncServiceResolver } from "../../resolvers/livesync-service-resolver"; -export class DeviceRefreshAppService { +export class DeviceRefreshAppService implements IDeviceRefreshAppService { constructor( private $liveSyncServiceResolver: LiveSyncServiceResolver, private $logger: ILogger, - private $runOnDevicesEmitter: RunOnDevicesEmitter + private $runEmitter: RunEmitter ) { } @performanceLog() @@ -28,7 +28,7 @@ export class DeviceRefreshAppService { } if (shouldRestart) { - this.$runOnDevicesEmitter.emitDebuggerDetachedEvent(liveSyncResultInfo.deviceAppData.device); + this.$runEmitter.emitDebuggerDetachedEvent(liveSyncResultInfo.deviceAppData.device); await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); result.didRestart = true; } @@ -37,11 +37,11 @@ export class DeviceRefreshAppService { const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; this.$logger.warn(msg); if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { - this.$runOnDevicesEmitter.emitRunOnDeviceNotificationEvent(projectData, liveSyncResultInfo.deviceAppData.device, msg); + this.$runEmitter.emitRunNotificationEvent(projectData, liveSyncResultInfo.deviceAppData.device, msg); } if (settings && settings.shouldCheckDeveloperDiscImage && (err.message || err) === "Could not find developer disk image") { - this.$runOnDevicesEmitter.emitUserInteractionNeededEvent(projectData, liveSyncResultInfo.deviceAppData.device, deviceDescriptor); + this.$runEmitter.emitUserInteractionNeededEvent(projectData, liveSyncResultInfo.deviceAppData.device, deviceDescriptor); } } diff --git a/lib/services/init-service.ts b/lib/services/init-service.ts index c19b30503d..b902b8df17 100644 --- a/lib/services/init-service.ts +++ b/lib/services/init-service.ts @@ -46,10 +46,11 @@ export class InitService implements IInitService { projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][this.$options.frameworkName] = _.extend(currentPlatformData, this.buildVersionData(this.$options.frameworkVersion)); } else { + const $mobileHelper: Mobile.IMobileHelper = this.$injector.resolve("mobileHelper"); const $platformsDataService = this.$injector.resolve("platformsDataService"); const $projectData = this.$injector.resolve("projectData"); $projectData.initializeProjectData(path.dirname(this.projectFilePath)); - for (const platform of $platformsDataService.platformsNames) { + for (const platform of $mobileHelper.platformNames) { const platformData: IPlatformData = $platformsDataService.getPlatformData(platform, $projectData); if (!platformData.targetedOS || (platformData.targetedOS && _.includes(platformData.targetedOS, process.platform))) { const currentPlatformData = projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][platformData.frameworkPackageName] || {}; diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 3c6cfc85e7..32afea3cdb 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -179,6 +179,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return undefined; } + public async cleanProject(projectRoot: string, projectData: IProjectData): Promise { + return null; + } + public afterCreateProject(projectRoot: string, projectData: IProjectData): void { this.$fs.rename(path.join(projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER), path.join(projectRoot, projectData.projectName)); diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index 3203c6e6c3..b4cc66b453 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -12,11 +12,11 @@ export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBa private $devicePathProvider: IDevicePathProvider, $injector: IInjector, private $androidProcessService: Mobile.IAndroidProcessService, - protected $platformsDataService: IPlatformsDataService, + protected platformsDataService: IPlatformsDataService, protected device: Mobile.IAndroidDevice, $filesHashService: IFilesHashService, $logger: ILogger) { - super($injector, $platformsDataService, $filesHashService, $logger, device); + super($injector, platformsDataService, $filesHashService, $logger, device); } public async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 7e41efb4f6..06f9bd5f40 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -14,7 +14,7 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe constructor( private data: IProjectData, $injector: IInjector, - protected $platformsDataService: IPlatformsDataService, + protected platformsDataService: IPlatformsDataService, protected $staticConfig: Config.IStaticConfig, $logger: ILogger, protected device: Mobile.IAndroidDevice, @@ -23,7 +23,7 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe private $fs: IFileSystem, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $filesHashService: IFilesHashService) { - super($injector, $platformsDataService, $filesHashService, $logger, device); + super($injector, platformsDataService, $filesHashService, $logger, device); this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool); } @@ -147,7 +147,7 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe } private async connectLivesyncTool(appIdentifier: string, connectTimeout?: number) { - const platformData = this.$platformsDataService.getPlatformData(this.$devicePlatformsConstants.Android, this.data); + const platformData = this.platformsDataService.getPlatformData(this.$devicePlatformsConstants.Android, this.data); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); if (!this.livesyncTool.hasConnection()) { await this.livesyncTool.connect({ diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts index ecff896d1a..c10c23fd7d 100644 --- a/lib/services/livesync/device-livesync-service-base.ts +++ b/lib/services/livesync/device-livesync-service-base.ts @@ -6,7 +6,7 @@ export abstract class DeviceLiveSyncServiceBase { private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; constructor( - protected $platformsDataService: IPlatformsDataService, + protected platformsDataService: IPlatformsDataService, protected device: Mobile.IDevice ) { } @@ -23,7 +23,7 @@ export abstract class DeviceLiveSyncServiceBase { @cache() private getFastLiveSyncFileExtensions(platform: string, projectData: IProjectData): string[] { - const platformData = this.$platformsDataService.getPlatformData(platform, projectData); + const platformData = this.platformsDataService.getPlatformData(platform, projectData); const fastSyncFileExtensions = DeviceLiveSyncServiceBase.FAST_SYNC_FILE_EXTENSIONS.concat(platformData.fastLivesyncFileExtensions); return fastSyncFileExtensions; } diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 82b4851fd9..497bb66988 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -11,9 +11,9 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen constructor( private $logger: ILogger, - protected $platformsDataService: IPlatformsDataService, + protected platformsDataService: IPlatformsDataService, protected device: Mobile.IiOSDevice) { - super($platformsDataService, device); + super(platformsDataService, device); } private async setupSocketIfNeeded(projectData: IProjectData): Promise { diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts index 5772d5074e..66e0e1ac84 100644 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -1,7 +1,7 @@ import { APP_RESOURCES_FOLDER_NAME } from "../../../constants"; import { EventEmitter } from "events"; import { performanceLog } from "../../../common/decorators"; -import { PreviewAppEmitter } from "../../../preview-app-emitter"; +import { PreviewAppEmitter } from "../../../emitters/preview-app-emitter"; export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewAppLiveSyncService { diff --git a/lib/services/platform/add-platform-service.ts b/lib/services/platform/add-platform-service.ts index af412b5c35..115fe3d1b7 100644 --- a/lib/services/platform/add-platform-service.ts +++ b/lib/services/platform/add-platform-service.ts @@ -3,7 +3,7 @@ import * as temp from "temp"; import { PROJECT_FRAMEWORK_FOLDER_NAME, NativePlatformStatus } from "../../constants"; import { performanceLog } from "../../common/decorators"; -export class AddPlatformService { +export class AddPlatformService implements IAddPlatformService { constructor( private $fs: IFileSystem, private $pacoteService: IPacoteService, diff --git a/lib/services/platform/platform-validation-service.ts b/lib/services/platform/platform-validation-service.ts index 8490a73d22..6648803b51 100644 --- a/lib/services/platform/platform-validation-service.ts +++ b/lib/services/platform/platform-validation-service.ts @@ -19,7 +19,7 @@ export class PlatformValidationService implements IPlatformValidationService { platform = platform.split("@")[0].toLowerCase(); if (!this.$platformsDataService.getPlatformData(platform, projectData)) { - const platformNames = helpers.formatListOfNames(this.$platformsDataService.platformsNames); + const platformNames = helpers.formatListOfNames(this.$mobileHelper.platformNames); this.$errors.fail(`Invalid platform ${platform}. Valid platforms are ${platformNames}.`); } } @@ -52,7 +52,8 @@ export class PlatformValidationService implements IPlatformValidationService { return result; } else { let valid = true; - for (const availablePlatform in this.$platformsDataService.availablePlatforms) { + const platforms = this.$mobileHelper.platformNames.map(p => p.toLowerCase()); + for (const availablePlatform of platforms) { this.$logger.trace("Validate options for platform: " + availablePlatform); const platformData = this.$platformsDataService.getPlatformData(availablePlatform, projectData); valid = valid && await platformData.platformProjectService.validateOptions( diff --git a/lib/services/platform/prepare-native-platform-service.ts b/lib/services/platform/prepare-native-platform-service.ts index 713ea8059b..b017932d70 100644 --- a/lib/services/platform/prepare-native-platform-service.ts +++ b/lib/services/platform/prepare-native-platform-service.ts @@ -3,9 +3,8 @@ import { hook } from "../../common/helpers"; import { performanceLog } from "../../common/decorators"; import * as path from "path"; import { NativePlatformStatus, APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME } from "../../constants"; -import { PrepareData } from "../../data/prepare-data"; -export class PrepareNativePlatformService { +export class PrepareNativePlatformService implements IPrepareNativePlatformService { constructor( private $androidResourcesMigrationService: IAndroidResourcesMigrationService, @@ -17,7 +16,7 @@ export class PrepareNativePlatformService { @performanceLog() @hook('prepareNativeApp') - public async prepareNativePlatform(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { + public async prepareNativePlatform(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { const { nativePrepare, release } = prepareData; if (nativePrepare && nativePrepare.skipNativePrepare) { return false; diff --git a/lib/services/platforms-data-service.ts b/lib/services/platforms-data-service.ts index e4aec9733e..6512d9daf0 100644 --- a/lib/services/platforms-data-service.ts +++ b/lib/services/platforms-data-service.ts @@ -1,4 +1,3 @@ - export class PlatformsDataService implements IPlatformsDataService { private platformsDataService: { [index: string]: any } = {}; @@ -11,10 +10,6 @@ export class PlatformsDataService implements IPlatformsDataService { }; } - public get platformsNames() { - return Object.keys(this.platformsDataService); - } - public getPlatformData(platform: string, projectData: IProjectData): IPlatformData { const platformKey = platform && _.first(platform.toLowerCase().split("@")); let platformData: IPlatformData; @@ -24,12 +19,5 @@ export class PlatformsDataService implements IPlatformsDataService { return platformData; } - - public get availablePlatforms(): any { - return { - iOS: "ios", - Android: "android" - }; - } } $injector.register("platformsDataService", PlatformsDataService); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 16dd554ce2..b9ff50db08 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -31,7 +31,8 @@ export class PluginsService implements IPluginsService { private $logger: ILogger, private $errors: IErrors, private $filesHashService: IFilesHashService, - private $injector: IInjector) { } + private $injector: IInjector, + private $mobileHelper: Mobile.IMobileHelper) { } public async add(plugin: string, projectData: IProjectData): Promise { await this.ensure(projectData); @@ -242,7 +243,7 @@ export class PluginsService implements IPluginsService { } private async executeForAllInstalledPlatforms(action: (_pluginDestinationPath: string, pl: string, _platformData: IPlatformData) => Promise, projectData: IProjectData): Promise { - const availablePlatforms = _.keys(this.$platformsDataService.availablePlatforms); + const availablePlatforms = this.$mobileHelper.platformNames.map(p => p.toLowerCase()); for (const platform of availablePlatforms) { const isPlatformInstalled = this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase())); if (isPlatformInstalled) { diff --git a/lib/services/prepare-data-service.ts b/lib/services/prepare-data-service.ts index 07b2170396..b13a00d44f 100644 --- a/lib/services/prepare-data-service.ts +++ b/lib/services/prepare-data-service.ts @@ -1,6 +1,6 @@ import { IOSPrepareData, AndroidPrepareData } from "../data/prepare-data"; -export class PrepareDataService { +export class PrepareDataService implements IPrepareDataService { constructor(private $mobileHelper: Mobile.IMobileHelper) { } public getPrepareData(projectDir: string, platform: string, data: any) { diff --git a/lib/services/run-on-devices-data-service.ts b/lib/services/run-on-devices-data-service.ts deleted file mode 100644 index f116f4c600..0000000000 --- a/lib/services/run-on-devices-data-service.ts +++ /dev/null @@ -1,34 +0,0 @@ -export class RunOnDevicesDataService { - // TODO: Rename liveSyncProcessesInfo - private liveSyncProcessesInfo: IDictionary = {}; - - public getDataForProject(projectDir: string): ILiveSyncProcessInfo { - return this.liveSyncProcessesInfo[projectDir]; - } - - public getAllData(): IDictionary { - return this.liveSyncProcessesInfo; - } - - public getDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { - const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; - const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; - return currentDescriptors || []; - } - - public hasDeviceDescriptors(projectDir: string): boolean { - return !!this.liveSyncProcessesInfo[projectDir].deviceDescriptors.length; - } - - public persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], platforms: string[]): void { - this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); - this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); - this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; - this.liveSyncProcessesInfo[projectDir].isStopped = false; - this.liveSyncProcessesInfo[projectDir].platforms = platforms; - - const currentDeviceDescriptors = this.getDeviceDescriptors(projectDir); - this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); - } -} -$injector.register("runOnDevicesDataService", RunOnDevicesDataService); diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 27cb6eebfc..c4a91cb97e 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -1,7 +1,7 @@ import * as constants from "../constants"; import * as path from 'path'; import * as os from 'os'; -import { RunOnDevicesController } from "../controllers/run-on-devices-controller"; +import { RunController } from "../controllers/run-controller"; interface IKarmaConfigOptions { debugBrk: boolean; @@ -13,7 +13,7 @@ export class TestExecutionService implements ITestExecutionService { private static SOCKETIO_JS_FILE_NAME = `node_modules/${constants.TEST_RUNNER_NAME}/socket.io.js`; constructor( - private $runOnDevicesController: RunOnDevicesController, + private $runController: RunController, private $httpClient: Server.IHttpClient, private $config: IConfiguration, private $logger: ILogger, @@ -56,7 +56,7 @@ export class TestExecutionService implements ITestExecutionService { // Prepare the project AFTER the TestExecutionService.CONFIG_FILE_NAME file is created in node_modules // so it will be sent to device. - await this.$runOnDevicesController.runOnDevices({ + 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 1c106b9562..c657583381 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -58,7 +58,6 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp childProcess.on("close", (arg: any) => { const exitCode = typeof arg === "number" ? arg : arg && arg.code; - console.log("=========== WEBPACK EXIT WITH CODE ========== ", exitCode); if (exitCode === 0) { resolve(childProcess); } else { @@ -80,7 +79,6 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const childProcess = await this.startWebpackProcess(platformData, projectData, config); childProcess.on("close", (arg: any) => { const exitCode = typeof arg === "number" ? arg : arg && arg.code; - console.log("=========== WEBPACK EXIT WITH CODE ========== ", exitCode); if (exitCode === 0) { resolve(); } else { @@ -92,7 +90,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp }); } - public stopWebpackCompiler(platform: string) { + public stopWebpackCompiler(platform: string): void { if (platform) { this.stopWebpackForPlatform(platform); } else { diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index a6b771b64f..d58c7978ac 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -6,6 +6,7 @@ declare global { interface IWebpackCompilerService extends EventEmitter { compileWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; compileWithoutWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; + stopWebpackCompiler(platform: string): void; } interface IWebpackCompilerConfig { @@ -14,11 +15,12 @@ declare global { } interface IWebpackEnvOptions { - + sourceMap?: boolean; + uglify?: boolean; } interface IProjectChangesService { - checkForChanges(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise; + checkForChanges(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise; getPrepareInfoFilePath(platformData: IPlatformData): string; getPrepareInfo(platformData: IPlatformData): IPrepareInfo; savePrepareInfo(platformData: IPlatformData): void; @@ -33,11 +35,6 @@ declare global { hasNativeChanges: boolean; } - interface IPrepareOutputData { - platform: string; - hasNativeChanges: boolean; - } - interface IDeviceRestartApplicationService { restartOnDevice(deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, platformLiveSyncService: IPlatformLiveSyncService): Promise; } diff --git a/package.json b/package.json index ad975a9576..b0f3dee87b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "5.4.0", + "version": "6.0.0", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { diff --git a/test/controllers/add-platform-controller.ts b/test/controllers/add-platform-controller.ts index ab119fade9..6ab114fa5a 100644 --- a/test/controllers/add-platform-controller.ts +++ b/test/controllers/add-platform-controller.ts @@ -1,5 +1,5 @@ import { InjectorStub, PacoteServiceStub } from "../stubs"; -import { AddPlatformController } from "../../lib/controllers/add-platform-controller"; +import { PlatformController } from "../../lib/controllers/platform-controller"; import { AddPlatformService } from "../../lib/services/platform/add-platform-service"; import { assert } from "chai"; import { format } from "util"; @@ -13,7 +13,7 @@ function createInjector(data?: { latestFrameworkVersion: string }) { const version = (data && data.latestFrameworkVersion) || latestFrameworkVersion; const injector = new InjectorStub(); - injector.register("addPlatformController", AddPlatformController); + injector.register("platformController", PlatformController); injector.register("addPlatformService", AddPlatformService); injector.register("pacoteService", PacoteServiceStub); @@ -35,7 +35,7 @@ function createInjector(data?: { latestFrameworkVersion: string }) { const projectDir = "/my/test/dir"; -describe("AddPlatformController", () => { +describe("PlatformController", () => { const testCases = [ { name: "should add the platform (tns platform add @4.2.1)", @@ -63,8 +63,8 @@ describe("AddPlatformController", () => { const injector = createInjector({ latestFrameworkVersion: testCase.latestFrameworkVersion }); const platformParam = testCase.getPlatformParam ? testCase.getPlatformParam(platform) : platform; - const addPlatformController: AddPlatformController = injector.resolve("addPlatformController"); - await addPlatformController.addPlatform({ projectDir, platform: platformParam, frameworkPath: testCase.frameworkPath }); + const platformController: PlatformController = injector.resolve("platformController"); + await platformController.addPlatform({ projectDir, platform: platformParam, frameworkPath: testCase.frameworkPath }); const expectedMessage = `Platform ${platform} successfully added. v${testCase.latestFrameworkVersion}`; assert.deepEqual(actualMessage, expectedMessage); @@ -81,9 +81,9 @@ describe("AddPlatformController", () => { const fs = injector.resolve("fs"); fs.exists = (filePath: string) => filePath !== frameworkPath; - const addPlatformController: AddPlatformController = injector.resolve("addPlatformController"); + const platformController: PlatformController = injector.resolve("platformController"); - await assert.isRejected(addPlatformController.addPlatform({ projectDir, platform, frameworkPath }), errorMessage); + await assert.isRejected(platformController.addPlatform({ projectDir, platform, frameworkPath }), errorMessage); }); it(`should respect platform version in package.json's nativescript key for ${platform}`, async () => { const version = "2.5.0"; @@ -93,8 +93,8 @@ describe("AddPlatformController", () => { const projectDataService = injector.resolve("projectDataService"); projectDataService.getNSValue = () => ({ version }); - const addPlatformController: AddPlatformController = injector.resolve("addPlatformController"); - await addPlatformController.addPlatform({ projectDir, platform }); + const platformController: PlatformController = injector.resolve("platformController"); + await platformController.addPlatform({ projectDir, platform }); const expectedPackageToAdd = `tns-${platform}@${version}`; assert.deepEqual(extractedPackageFromPacote, expectedPackageToAdd); @@ -105,8 +105,8 @@ describe("AddPlatformController", () => { const projectDataService = injector.resolve("projectDataService"); projectDataService.getNSValue = () => null; - const addPlatformController: AddPlatformController = injector.resolve("addPlatformController"); - await addPlatformController.addPlatform({ projectDir, platform }); + const platformController: PlatformController = injector.resolve("platformController"); + await platformController.addPlatform({ projectDir, platform }); const expectedPackageToAdd = `tns-${platform}@${latestFrameworkVersion}`; assert.deepEqual(extractedPackageFromPacote, expectedPackageToAdd); diff --git a/test/controllers/prepare-controller.ts b/test/controllers/prepare-controller.ts index c42ca3c7a9..604a07c3cc 100644 --- a/test/controllers/prepare-controller.ts +++ b/test/controllers/prepare-controller.ts @@ -21,7 +21,7 @@ let emittedEventData: any[] = []; function createTestInjector(data: { hasNativeChanges: boolean }): IInjector { const injector = new InjectorStub(); - injector.register("addPlatformController", { + injector.register("platformController", { addPlatformIfNeeded: () => ({}) }); @@ -75,7 +75,7 @@ describe("prepareController", () => { const injector = createTestInjector({ hasNativeChanges }); const prepareController: PrepareController = injector.resolve("prepareController"); - await prepareController.preparePlatform({ ...prepareData, platform }); + await prepareController.prepare({ ...prepareData, platform }); assert.isTrue(isCompileWithWatchCalled); assert.isTrue(isNativePrepareCalled); @@ -94,7 +94,7 @@ describe("prepareController", () => { return false; }; - await prepareController.preparePlatform({ ...prepareData, platform }); + await prepareController.prepare({ ...prepareData, platform }); assert.lengthOf(emittedEventNames, 1); assert.lengthOf(emittedEventData, 1); @@ -110,7 +110,7 @@ describe("prepareController", () => { const injector = createTestInjector({ hasNativeChanges: false }); const prepareController: PrepareController = injector.resolve("prepareController"); - await prepareController.preparePlatform({ ...prepareData, watch: false, platform }); + await prepareController.prepare({ ...prepareData, watch: false, platform }); assert.isTrue(isNativePrepareCalled); assert.isTrue(isCompileWithoutWatchCalled); diff --git a/test/controllers/run-on-devices-controller.ts b/test/controllers/run-controller.ts similarity index 82% rename from test/controllers/run-on-devices-controller.ts rename to test/controllers/run-controller.ts index 7dfb5ecb31..6c4c4eeda0 100644 --- a/test/controllers/run-on-devices-controller.ts +++ b/test/controllers/run-controller.ts @@ -1,17 +1,17 @@ -import { RunOnDevicesController } from "../../lib/controllers/run-on-devices-controller"; +import { RunController } from "../../lib/controllers/run-controller"; 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 { RunOnDevicesDataService } from "../../lib/services/run-on-devices-data-service"; -import { RunOnDevicesEmitter } from "../../lib/run-on-devices-emitter"; +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"; let isAttachToHmrStatusCalled = false; -let prepareData: PrepareData = null; +let prepareData: IPrepareData = null; const appIdentifier = "org.nativescript.myCoolApp"; const projectDir = "/path/to/my/projecDir"; @@ -98,7 +98,7 @@ function createTestInjector() { injector.register("mobileHelper", MobileHelper); injector.register("prepareController", { stopWatchers: () => ({}), - preparePlatform: async (currentPrepareData: PrepareData) => { + prepare: async (currentPrepareData: PrepareData) => { prepareData = currentPrepareData; return { platform: prepareData.platform, hasNativeChanges: false }; }, @@ -106,11 +106,11 @@ function createTestInjector() { }); injector.register("prepareNativePlatformService", {}); injector.register("projectChangesService", {}); - injector.register("runOnDevicesController", RunOnDevicesController); - injector.register("runOnDevicesDataService", RunOnDevicesDataService); - injector.register("runOnDevicesEmitter", RunOnDevicesEmitter); + injector.register("runController", RunController); + injector.register("runEmitter", RunEmitter); injector.register("prepareDataService", PrepareDataService); injector.register("buildDataService", BuildDataService); + injector.register("analyticsService", ({})); const devicesService = injector.resolve("devicesService"); devicesService.getDevicesForPlatform = () => [{ identifier: "myTestDeviceId1" }]; @@ -120,20 +120,18 @@ function createTestInjector() { return injector; } -describe("RunOnDevicesController", () => { +describe("RunController", () => { let injector: IInjector = null; - let runOnDevicesController: RunOnDevicesController = null; - let runOnDevicesDataService: RunOnDevicesDataService = null; - let runOnDevicesEmitter: RunOnDevicesEmitter = null; + let runController: RunController = null; + let runEmitter: RunEmitter = null; beforeEach(() => { isAttachToHmrStatusCalled = false; prepareData = null; injector = createTestInjector(); - runOnDevicesController = injector.resolve("runOnDevicesController"); - runOnDevicesDataService = injector.resolve("runOnDevicesDataService"); - runOnDevicesEmitter = injector.resolve("runOnDevicesEmitter"); + runController = injector.resolve("runController"); + runEmitter = injector.resolve("runEmitter"); }); describe("runOnDevices", () => { @@ -141,7 +139,7 @@ describe("RunOnDevicesController", () => { it("shouldn't start the watcher when skipWatcher flag is provided", async () => { mockDevicesService(injector, [iOSDevice]); - await runOnDevicesController.runOnDevices({ + await runController.run({ projectDir, liveSyncInfo: { ...liveSyncInfo, skipWatcher: true }, deviceDescriptors: [iOSDeviceDescriptor] @@ -152,7 +150,7 @@ describe("RunOnDevicesController", () => { it("shouldn't attach to hmr status when skipWatcher flag is provided", async () => { mockDevicesService(injector, [iOSDevice]); - await runOnDevicesController.runOnDevices({ + await runController.run({ projectDir, liveSyncInfo: { ...liveSyncInfo, skipWatcher: true, useHotModuleReload: true }, deviceDescriptors: [iOSDeviceDescriptor] @@ -162,9 +160,8 @@ describe("RunOnDevicesController", () => { }); it("shouldn't attach to hmr status when useHotModuleReload is false", async () => { mockDevicesService(injector, [iOSDevice]); - runOnDevicesDataService.hasDeviceDescriptors = () => true; - await runOnDevicesController.runOnDevices({ + await runController.run({ projectDir, liveSyncInfo, deviceDescriptors: [iOSDeviceDescriptor] @@ -173,7 +170,9 @@ describe("RunOnDevicesController", () => { assert.isFalse(isAttachToHmrStatusCalled); }); it("shouldn't attach to hmr status when no deviceDescriptors are provided", async () => { - await runOnDevicesController.runOnDevices({ + mockDevicesService(injector, [iOSDevice]); + + await runController.run({ projectDir, liveSyncInfo, deviceDescriptors: [] @@ -206,13 +205,13 @@ describe("RunOnDevicesController", () => { mockDevicesService(injector, testCase.connectedDevices.map(d => map[d.identifier].device)); const preparedPlatforms: string[] = []; - const prepareController = injector.resolve("prepareController"); - prepareController.preparePlatform = (currentPrepareData: PrepareData) => { + const prepareController: PrepareController = injector.resolve("prepareController"); + prepareController.prepare = async (currentPrepareData: PrepareData) => { preparedPlatforms.push(currentPrepareData.platform); return { platform: currentPrepareData.platform, hasNativeChanges: false }; }; - await runOnDevicesController.runOnDevices({ + await runController.run({ projectDir, liveSyncInfo, deviceDescriptors: testCase.connectedDevices @@ -258,16 +257,16 @@ describe("RunOnDevicesController", () => { for (const testCase of testCases) { it(testCase.name, async () => { - runOnDevicesDataService.persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier })), ["ios"]); + (runController).persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier })), ["ios"]); const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; - runOnDevicesEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { + runEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { assert.equal(data.projectDir, projectDir); emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); }); - await runOnDevicesController.stopRunOnDevices(projectDir, testCase.deviceIdentifiersToBeStopped); + await runController.stop(projectDir, testCase.deviceIdentifiersToBeStopped); assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); }); diff --git a/test/services/platform/platform-commands-service.ts b/test/helpers/platform-command-helper.ts similarity index 61% rename from test/services/platform/platform-commands-service.ts rename to test/helpers/platform-command-helper.ts index 185afb81b5..0f71c8399b 100644 --- a/test/services/platform/platform-commands-service.ts +++ b/test/helpers/platform-command-helper.ts @@ -1,6 +1,9 @@ -import { PlatformCommandsService } from "../../../lib/services/platform/platform-commands-service"; + import { assert } from "chai"; -import { InjectorStub } from "../../stubs"; +import { InjectorStub } from "../stubs"; +import { MobileHelper } from "../../lib/common/mobile/mobile-helper"; +import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants"; +import { PlatformCommandHelper } from "../../lib/helpers/platform-command-helper"; let isAddPlatformCalled = false; @@ -16,7 +19,7 @@ function createTestInjector() { addPlatform: () => ({}) }); - injector.register("addPlatformController", { + injector.register("platformController", { addPlatform: () => isAddPlatformCalled = true }); @@ -28,17 +31,20 @@ function createTestInjector() { validatePlatformInstalled: () => ({}) }); - injector.register("platformCommandsService", PlatformCommandsService); + injector.register("platformCommandHelper", PlatformCommandHelper); + + injector.register("mobileHelper", MobileHelper); + injector.register("devicePlatformsConstants", DevicePlatformsConstants); return injector; } -describe("PlatformCommandsService", () => { +describe("PlatformCommandHelper", () => { let injector: IInjector = null; - let platformCommandsService: PlatformCommandsService = null; + let platformCommandHelper: IPlatformCommandHelper = null; beforeEach(() => { injector = createTestInjector(); - platformCommandsService = injector.resolve("platformCommandsService"); + platformCommandHelper = injector.resolve("platformCommandHelper"); }); describe("add platforms unit tests", () => { @@ -51,16 +57,16 @@ describe("PlatformCommandsService", () => { const fs = injector.resolve("fs"); fs.exists = () => false; - await platformCommandsService.addPlatforms([platform], projectData, null); + await platformCommandHelper.addPlatforms([platform], projectData, null); assert.isTrue(isAddPlatformCalled); }); }); _.each(["ios", "android"], platform => { it(`should fail if ${platform} platform is already installed`, async () => { - (platformCommandsService).isPlatformAdded = () => true; + (platformCommandHelper).isPlatformAdded = () => true; - await assert.isRejected(platformCommandsService.addPlatforms([platform], projectData, ""), `Platform ${platform} already added`); + await assert.isRejected(platformCommandHelper.addPlatforms([platform], projectData, ""), `Platform ${platform} already added`); }); }); }); @@ -73,9 +79,9 @@ describe("PlatformCommandsService", () => { projectDataService.getNSValue = () => versionData; projectDataService.removeNSProperty = () => { versionData = null; }; - (platformCommandsService).isPlatformAdded = () => false; + (platformCommandHelper).isPlatformAdded = () => false; - await platformCommandsService.cleanPlatforms([platform], injector.resolve("projectData"), ""); + await platformCommandHelper.cleanPlatforms([platform], injector.resolve("projectData"), ""); }); }); }); @@ -84,7 +90,7 @@ describe("PlatformCommandsService", () => { const packageInstallationManager: IPackageInstallationManager = injector.resolve("packageInstallationManager"); packageInstallationManager.getLatestVersion = async () => "0.2.0"; - await assert.isRejected(platformCommandsService.updatePlatforms(["android"], projectData), "Native Platform cannot be updated."); + await assert.isRejected(platformCommandHelper.updatePlatforms(["android"], projectData), "Native Platform cannot be updated."); }); }); }); diff --git a/test/ios-entitlements-service.ts b/test/ios-entitlements-service.ts index 16caaa4185..41e5101c14 100644 --- a/test/ios-entitlements-service.ts +++ b/test/ios-entitlements-service.ts @@ -17,7 +17,7 @@ describe("IOSEntitlements Service Tests", () => { const createTestInjector = (): IInjector => { const testInjector = new yok.Yok(); - testInjector.register('platformsDataService', stubs.PlatformsDataStub); + testInjector.register('platformsDataService', stubs.NativeProjectDataStub); testInjector.register('projectData', stubs.ProjectDataStub); testInjector.register("logger", stubs.LoggerStub); testInjector.register('iOSEntitlementsService', IOSEntitlementsService); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index af58a3b572..0a3808baa5 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -19,8 +19,8 @@ import * as ChildProcessLib from "../lib/common/child-process"; import ProjectChangesLib = require("../lib/services/project-changes-service"); import { Messages } from "../lib/common/messages/messages"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; -import { PlatformCommandsService } from "../lib/services/platform/platform-commands-service"; import { PlatformValidationService } from "../lib/services/platform/platform-validation-service"; +import { PlatformCommandHelper } from "../lib/helpers/platform-command-helper"; let isCommandExecuted = true; @@ -81,9 +81,9 @@ class ErrorsNoFailStub implements IErrors { } class PlatformsDataService implements IPlatformsDataService { - platformsNames = ["android", "ios"]; + platformNames = ["android", "ios"]; getPlatformData(platform: string): IPlatformData { - if (_.includes(this.platformsNames, platform)) { + if (_.includes(this.platformNames, platform)) { return new PlatformData(); } @@ -102,7 +102,7 @@ function createTestInjector() { testInjector.register("hooksService", stubs.HooksServiceStub); testInjector.register("staticConfig", StaticConfigLib.StaticConfig); testInjector.register("nodeModulesDependenciesBuilder", {}); - testInjector.register('platformCommandsService', PlatformCommandsService); + testInjector.register('platformCommandHelper', PlatformCommandHelper); testInjector.register('platformValidationService', PlatformValidationService); testInjector.register('errors', ErrorsNoFailStub); testInjector.register('logger', stubs.LoggerStub); @@ -185,20 +185,21 @@ function createTestInjector() { setShouldDispose: (shouldDispose: boolean): void => undefined }); testInjector.register("addPlatformService", {}); - testInjector.register("addPlatformController", {}); + testInjector.register("platformController", {}); + testInjector.register("platformCommandHelper", PlatformCommandHelper); return testInjector; } describe('Platform Service Tests', () => { - let platformCommandsService: IPlatformCommandsService, testInjector: IInjector; + let platformCommandHelper: IPlatformCommandHelper, testInjector: IInjector; let commandsService: ICommandsService; let fs: IFileSystem; beforeEach(() => { testInjector = createTestInjector(); testInjector.register("fs", stubs.FileSystemStub); commandsService = testInjector.resolve("commands-service"); - platformCommandsService = testInjector.resolve("platformCommandsService"); + platformCommandHelper = testInjector.resolve("platformCommandHelper"); fs = testInjector.resolve("fs"); }); @@ -480,11 +481,11 @@ describe('Platform Service Tests', () => { const platformActions: { action: string, platforms: string[] }[] = []; const cleanCommand = testInjector.resolveCommand("platform|clean"); - platformCommandsService.removePlatforms = async (platforms: string[]) => { + platformCommandHelper.removePlatforms = async (platforms: string[]) => { platformActions.push({ action: "removePlatforms", platforms }); }; - platformCommandsService.addPlatforms = async (platforms: string[]) => { + platformCommandHelper.addPlatforms = async (platforms: string[]) => { platformActions.push({ action: "addPlatforms", platforms }); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 2ddee59a97..700be07c6c 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -54,7 +54,7 @@ function createTestInjector() { testInjector.register("adb", {}); testInjector.register("androidDebugBridgeResultHandler", {}); testInjector.register("projectData", ProjectData); - testInjector.register("platforsmData", stubs.PlatformsDataStub); + testInjector.register("platforsmData", stubs.NativeProjectDataStub); testInjector.register("childProcess", ChildProcess); testInjector.register("platformsDataService", PlatformsDataService); testInjector.register("androidEmulatorServices", {}); @@ -636,6 +636,8 @@ describe("Plugins service", () => { unitTestsInjector.register("logger", {}); unitTestsInjector.register("errors", {}); unitTestsInjector.register("injector", unitTestsInjector); + unitTestsInjector.register("mobileHelper", MobileHelper); + unitTestsInjector.register("devicePlatformsConstants", DevicePlatformsConstants); const pluginsService: PluginsService = unitTestsInjector.resolve(PluginsService); testData.pluginsService = pluginsService; diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index 74be242d0b..a5383ae4ee 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -54,7 +54,7 @@ class ProjectChangesServiceTest extends BaseServiceTest { return this.injector.resolve("projectData"); } - get platformsDataService(): any { + get getNativeProjectDataService(): any { return this.injector.resolve("platformsDataService"); } @@ -80,7 +80,7 @@ describe("Project Changes Service Tests", () => { Constants.PLATFORMS_DIR_NAME ); - serviceTest.platformsDataService.getPlatformData = + serviceTest.getNativeProjectDataService.getPlatformData = (platform: string) => { if (platform.toLowerCase() === "ios") { return { diff --git a/test/services/platform/add-platform-service.ts b/test/services/platform/add-platform-service.ts index 7390b5b292..8a2dcc9810 100644 --- a/test/services/platform/add-platform-service.ts +++ b/test/services/platform/add-platform-service.ts @@ -42,9 +42,9 @@ describe("AddPlatformService", () => { const pacoteService: PacoteService = injector.resolve("pacoteService"); pacoteService.extractPackage = async (): Promise => { throw new Error(errorMessage); }; - const platformData = injector.resolve("platformsDataService").getPlatformData(platform, projectData); + const platformsDataService = injector.resolve("platformsDataService").getPlatformData(platform, projectData); - await assert.isRejected(addPlatformService.addPlatformSafe(projectData, platformData, "somePackage", nativePrepare), errorMessage); + await assert.isRejected(addPlatformService.addPlatformSafe(projectData, platformsDataService, "somePackage", nativePrepare), errorMessage); }); it(`shouldn't add native platform when skipNativePrepare is provided for ${platform}`, async () => { const projectDataService = injector.resolve("projectDataService"); diff --git a/test/services/playground/preview-app-files-service.ts b/test/services/playground/preview-app-files-service.ts index 2317cf8c13..b32855fcb0 100644 --- a/test/services/playground/preview-app-files-service.ts +++ b/test/services/playground/preview-app-files-service.ts @@ -17,7 +17,7 @@ class ProjectDataServiceMock { } } -class PlatformsDataMock { +class NativeProjectDataServiceMock { public getPlatformData(platform: string) { const appDestinationDirectoryPath = path.join(projectDir, "platforms", platform, "app"); return { @@ -31,7 +31,7 @@ function createTestInjector(data?: { files: string[] }) { injector.register("previewAppFilesService", PreviewAppFilesService); injector.register("fs", FileSystemStub); injector.register("logger", LoggerStub); - injector.register("platformsDataService", PlatformsDataMock); + injector.register("platformsDataService", NativeProjectDataServiceMock); injector.register("projectDataService", ProjectDataServiceMock); injector.register("projectFilesManager", { getProjectFiles: () => data ? data.files : [] diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index e6c644356c..fea024d29d 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -10,7 +10,7 @@ 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/preview-app-emitter"; +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"; @@ -104,7 +104,7 @@ class LoggerMock extends LoggerStub { } class PrepareControllerMock extends EventEmitter { - public preparePlatform(prepareData: PrepareData) { + public prepare(prepareData: PrepareData) { isHMRPassedToEnv = prepareData.env.hmr; this.emit(PREPARE_READY_EVENT_NAME, { hmrData: {}, files: [] }); } diff --git a/test/services/test-execution-service.ts b/test/services/test-execution-service.ts index c8c541aad1..b533da0358 100644 --- a/test/services/test-execution-service.ts +++ b/test/services/test-execution-service.ts @@ -8,7 +8,7 @@ const unitTestsPluginName = "nativescript-unit-test-runner"; function getTestExecutionService(): ITestExecutionService { const injector = new InjectorStub(); injector.register("testExecutionService", TestExecutionService); - injector.register("runOnDevicesController", {}); + injector.register("runController", {}); return injector.resolve("testExecutionService"); } diff --git a/test/stubs.ts b/test/stubs.ts index 2a958e8f0e..947a60dd1b 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -473,8 +473,8 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor } } -export class PlatformsDataStub extends EventEmitter implements IPlatformsDataService { - public platformsNames: string[]; +export class NativeProjectDataStub extends EventEmitter implements IPlatformsDataService { + public platformNames: string[]; public getPlatformData(platform: string, projectData: IProjectData): IPlatformData { return { @@ -861,7 +861,7 @@ export class InjectorStub extends Yok implements IInjector { this.register('childProcess', ChildProcessStub); this.register("liveSyncService", LiveSyncServiceStub); this.register("prompter", PrompterStub); - this.register('platformsDataService', PlatformsDataStub); + this.register('platformsDataService', NativeProjectDataStub); this.register("androidPluginBuildService", AndroidPluginBuildServiceStub); this.register('projectData', ProjectDataStub); this.register('packageInstallationManager', PackageInstallationManagerStub); diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index 071c2cefcf..053b784c1b 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -20,7 +20,7 @@ class AppStore { projectData: ProjectDataStub; buildController: BuildController; prepareNativePlatformService: PrepareNativePlatformService; - platformCommandsService: any; + platformCommandHelper: any; platformValidationService: any; iOSPlatformData: any; iOSProjectService: any; @@ -57,7 +57,7 @@ class AppStore { "iOS": "iOS" }, "prepareNativePlatformService": this.prepareNativePlatformService = {}, - "platformCommandsService": this.platformCommandsService = {}, + "platformCommandHelper": this.platformCommandHelper = {}, "platformValidationService": this.platformValidationService = {}, "buildController": this.buildController = { buildPlatform: async () => { @@ -107,7 +107,7 @@ class AppStore { expectArchive() { this.expectedArchiveCalls = 1; - this.buildController.prepareAndBuildPlatform = (iOSBuildData: IOSBuildData) => { + this.buildController.prepareAndBuild = (iOSBuildData: IOSBuildData) => { this.archiveCalls++; chai.assert.equal(iOSBuildData.projectDir, "/Users/person/git/MyProject"); chai.assert.isTrue(iOSBuildData.buildForAppStore); diff --git a/test/update.ts b/test/update.ts index 52912bb512..2577812e38 100644 --- a/test/update.ts +++ b/test/update.ts @@ -47,7 +47,7 @@ function createTestInjector( return "1.0.0"; } }); - testInjector.register("platformCommandsService", { + testInjector.register("platformCommandHelper", { getInstalledPlatforms: function(): string[] { return installedPlatforms; }, @@ -142,17 +142,17 @@ describe("update command method tests", () => { const testInjector = createTestInjector(installedPlatforms); const fs = testInjector.resolve("fs"); const deleteDirectory: sinon.SinonStub = sandbox.stub(fs, "deleteDirectory"); - const platformCommandsService = testInjector.resolve("platformCommandsService"); + const platformCommandHelper = testInjector.resolve("platformCommandHelper"); sandbox.stub(fs, "copyFile").throws(); - sandbox.spy(platformCommandsService, "addPlatforms"); - sandbox.spy(platformCommandsService, "removePlatforms"); + sandbox.spy(platformCommandHelper, "addPlatforms"); + sandbox.spy(platformCommandHelper, "removePlatforms"); const updateCommand = testInjector.resolve(UpdateCommand); await updateCommand.execute(["3.3.0"]); assert.isTrue(deleteDirectory.calledWith(path.join(projectFolder, UpdateCommand.tempFolder))); - assert.isFalse(platformCommandsService.removePlatforms.calledWith(installedPlatforms)); - assert.isFalse(platformCommandsService.addPlatforms.calledWith(installedPlatforms)); + assert.isFalse(platformCommandHelper.removePlatforms.calledWith(installedPlatforms)); + assert.isFalse(platformCommandHelper.addPlatforms.calledWith(installedPlatforms)); }); it("calls copy to temp for package.json and folders(backup)", async () => { @@ -171,7 +171,7 @@ describe("update command method tests", () => { it("calls copy from temp for package.json and folders to project folder(restore)", async () => { const testInjector = createTestInjector(); - testInjector.resolve("platformCommandsService").removePlatforms = () => { + testInjector.resolve("platformCommandHelper").removePlatforms = () => { throw new Error(); }; const fs = testInjector.resolve("fs"); @@ -205,29 +205,29 @@ describe("update command method tests", () => { it("calls remove platforms and add platforms", async () => { const installedPlatforms: string[] = ["android"]; const testInjector = createTestInjector(installedPlatforms); - const platformCommandsService = testInjector.resolve("platformCommandsService"); - sandbox.spy(platformCommandsService, "addPlatforms"); - sandbox.spy(platformCommandsService, "removePlatforms"); + const platformCommandHelper = testInjector.resolve("platformCommandHelper"); + sandbox.spy(platformCommandHelper, "addPlatforms"); + sandbox.spy(platformCommandHelper, "removePlatforms"); const updateCommand = testInjector.resolve(UpdateCommand); await updateCommand.execute([]); - assert(platformCommandsService.removePlatforms.calledWith(installedPlatforms)); - assert(platformCommandsService.addPlatforms.calledWith(installedPlatforms)); + assert(platformCommandHelper.removePlatforms.calledWith(installedPlatforms)); + assert(platformCommandHelper.addPlatforms.calledWith(installedPlatforms)); }); it("call add platforms with specific verison", async () => { const version = "3.3.0"; const installedPlatforms: string[] = ["android"]; const testInjector = createTestInjector(installedPlatforms); - const platformCommandsService = testInjector.resolve("platformCommandsService"); - sandbox.spy(platformCommandsService, "addPlatforms"); - sandbox.spy(platformCommandsService, "removePlatforms"); + const platformCommandHelper = testInjector.resolve("platformCommandHelper"); + sandbox.spy(platformCommandHelper, "addPlatforms"); + sandbox.spy(platformCommandHelper, "removePlatforms"); const updateCommand = testInjector.resolve(UpdateCommand); await updateCommand.execute([version]); - assert(platformCommandsService.addPlatforms.calledWith([`${installedPlatforms}@${version}`])); + assert(platformCommandHelper.addPlatforms.calledWith([`${installedPlatforms}@${version}`])); }); it("calls remove and add of core modules and widgets", async () => {