From 2f8a11ce6302976017e04c548a2bcae36859051a Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 10 Apr 2019 11:01:00 +0300 Subject: [PATCH 001/102] chore: remove the code related to js prepare as webpack overrides prepareJS method with own logic --- lib/definitions/platform.d.ts | 1 - lib/definitions/plugins.d.ts | 2 - lib/services/plugins-service.ts | 35 - lib/services/prepare-platform-js-service.ts | 53 +- .../node-modules/node-modules-builder.ts | 40 +- .../node-modules/node-modules-dest-copy.ts | 173 +-- test/platform-service.ts | 1107 ++++++++--------- test/plugin-prepare.ts | 59 - test/plugins-service.ts | 2 +- 9 files changed, 535 insertions(+), 937 deletions(-) delete mode 100644 test/plugin-prepare.ts diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 6a02997efc..3462e437e1 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -283,7 +283,6 @@ interface INodeModulesBuilderData { interface INodeModulesBuilder { prepareNodeModules(opts: INodeModulesBuilderData): Promise; - prepareJSNodeModules(opts: INodeModulesBuilderData): Promise; } interface INodeModulesDependenciesBuilder { diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 2f0d775666..8fce2600ba 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -1,10 +1,8 @@ interface IPluginsService { add(plugin: string, projectData: IProjectData): Promise; // adds plugin by name, github url, local path and et. remove(pluginName: string, projectData: IProjectData): Promise; // removes plugin only by name - prepare(pluginData: IDependencyData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; getAllInstalledPlugins(projectData: IProjectData): Promise; ensureAllDependenciesAreInstalled(projectData: IProjectData): Promise; - preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): void /** * Returns all dependencies and devDependencies from pacakge.json file. diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index f18a9e8e7f..143da2d6d8 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -18,9 +18,6 @@ export class PluginsService implements IPluginsService { private get $projectDataService(): IProjectDataService { return this.$injector.resolve("projectDataService"); } - private get $projectFilesManager(): IProjectFilesManager { - return this.$injector.resolve("projectFilesManager"); - } private get npmInstallOptions(): INodePackageManagerInstallOptions { return _.merge({ @@ -108,38 +105,6 @@ export class PluginsService implements IPluginsService { return await platformData.platformProjectService.validatePlugins(projectData); } - public async prepare(dependencyData: IDependencyData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { - platform = platform.toLowerCase(); - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const pluginData = this.convertToPluginData(dependencyData, projectData.projectDir); - - const appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); - if (appFolderExists) { - this.preparePluginScripts(pluginData, platform, projectData, projectFilesConfig); - await this.preparePluginNativeCode(pluginData, platform, projectData); - - // Show message - this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); - } - } - - public preparePluginScripts(pluginData: IPluginData, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): void { - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const pluginScriptsDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules"); - const scriptsDestinationExists = this.$fs.exists(pluginScriptsDestinationPath); - if (!scriptsDestinationExists) { - //tns_modules/ doesn't exist. Assuming we're running a bundled prepare. - return; - } - - if (!this.isPluginDataValidForPlatform(pluginData, platform, projectData)) { - return; - } - - //prepare platform speciffic files, .map and .ts files - this.$projectFilesManager.processPlatformSpecificFiles(pluginScriptsDestinationPath, platform, projectFilesConfig); - } - public async preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise { const platformData = this.$platformsData.getPlatformData(platform, projectData); pluginData.pluginPlatformsFolderPath = (_platform: string) => path.join(pluginData.fullPath, "platforms", _platform.toLowerCase()); diff --git a/lib/services/prepare-platform-js-service.ts b/lib/services/prepare-platform-js-service.ts index 6b74a4e262..f4675cbf93 100644 --- a/lib/services/prepare-platform-js-service.ts +++ b/lib/services/prepare-platform-js-service.ts @@ -1,6 +1,5 @@ import * as constants from "../constants"; import * as path from "path"; -import * as shell from "shelljs"; import * as temp from "temp"; import { hook } from "../common/helpers"; import { PreparePlatformService } from "./prepare-platform-service"; @@ -16,7 +15,6 @@ export class PreparePlatformJSService extends PreparePlatformService implements private $errors: IErrors, private $logger: ILogger, private $projectDataService: IProjectDataService, - private $nodeModulesBuilder: INodeModulesBuilder, private $packageManager: INodePackageManager) { super($fs, $hooksService, $xmlValidator); } @@ -36,23 +34,7 @@ export class PreparePlatformJSService extends PreparePlatformService implements @performanceLog() @hook('prepareJSApp') public async preparePlatform(config: IPreparePlatformJSInfo): Promise { - if (!config.changesInfo || config.changesInfo.appFilesChanged || config.changesInfo.changesRequirePrepare) { - await this.copyAppFiles(config); - this.copyAppResourcesFiles(config); - } - - if (config.changesInfo && !config.changesInfo.changesRequirePrepare) { - // remove the App_Resources folder from the app/assets as here we're applying other files changes. - const appDestinationDirectoryPath = path.join(config.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - const appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, path.basename(config.projectData.appResourcesDirectoryPath)); - if (this.$fs.exists(appResourcesDirectoryPath)) { - this.$fs.deleteDirectory(appResourcesDirectoryPath); - } - } - - if (!config.changesInfo || config.changesInfo.modulesChanged) { - await this.copyTnsModules(config.platform, config.platformData, config.projectData, config.appFilesUpdaterOptions, config.projectFilesConfig); - } + // intentionally left blank, keep the support for before-prepareJSApp and after-prepareJSApp hooks } private async getPathToPlatformTemplate(selectedTemplate: string, frameworkPackageName: string, projectDir: string): Promise<{ selectedTemplate: string, pathToTemplate: string }> { @@ -82,39 +64,6 @@ export class PreparePlatformJSService extends PreparePlatformService implements return null; } - - private async copyTnsModules(platform: string, platformData: IPlatformData, projectData: IProjectData, appFilesUpdaterOptions: IAppFilesUpdaterOptions, projectFilesConfig?: IProjectFilesConfig): Promise { - const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - const lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; - - try { - const absoluteOutputPath = path.join(appDestinationDirectoryPath, constants.TNS_MODULES_FOLDER_NAME); - // Process node_modules folder - await this.$nodeModulesBuilder.prepareJSNodeModules({ - nodeModulesData: { - absoluteOutputPath, - platform, - lastModifiedTime, - projectData, - appFilesUpdaterOptions, - projectFilesConfig - }, - release: appFilesUpdaterOptions.release, - copyNodeModules: true - }); - } catch (error) { - this.$logger.debug(error); - shell.rm("-rf", appDestinationDirectoryPath); - this.$errors.failWithoutHelp(`Processing node_modules failed. ${error}`); - } - } - - private copyAppResourcesFiles(config: IPreparePlatformJSInfo): void { - const appDestinationDirectoryPath = path.join(config.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); - const appResourcesSourcePath = config.projectData.appResourcesDirectoryPath; - - shell.cp("-Rf", appResourcesSourcePath, path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME)); - } } $injector.register("preparePlatformJSService", PreparePlatformJSService); diff --git a/lib/tools/node-modules/node-modules-builder.ts b/lib/tools/node-modules/node-modules-builder.ts index 17f1c27317..c9af8361fd 100644 --- a/lib/tools/node-modules/node-modules-builder.ts +++ b/lib/tools/node-modules/node-modules-builder.ts @@ -1,47 +1,15 @@ -import { TnsModulesCopy, NpmPluginPrepare } from "./node-modules-dest-copy"; +import { NpmPluginPrepare } from "./node-modules-dest-copy"; export class NodeModulesBuilder implements INodeModulesBuilder { - constructor(private $fs: IFileSystem, + constructor( private $injector: IInjector, private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder ) { } public async prepareNodeModules(opts: INodeModulesBuilderData): Promise { - const productionDependencies = this.intialPrepareNodeModulesIfRequired(opts); + 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, opts.nodeModulesData.projectFilesConfig); - } - - public async prepareJSNodeModules(opts: INodeModulesBuilderData): Promise { - const productionDependencies = this.intialPrepareNodeModulesIfRequired(opts); - const npmPluginPrepare: NpmPluginPrepare = this.$injector.resolve(NpmPluginPrepare); - await npmPluginPrepare.prepareJSPlugins(productionDependencies, opts.nodeModulesData.platform, opts.nodeModulesData.projectData, opts.nodeModulesData.projectFilesConfig); - } - - private intialPrepareNodeModulesIfRequired(opts: INodeModulesBuilderData): IDependencyData[] { - const { nodeModulesData } = opts; - const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(nodeModulesData.projectData.projectDir); - - if (opts.copyNodeModules && !nodeModulesData.appFilesUpdaterOptions.bundle) { - this.initialPrepareNodeModules(opts, productionDependencies); - } - - return productionDependencies; - } - - private initialPrepareNodeModules(opts: INodeModulesBuilderData, productionDependencies: IDependencyData[]): void { - const { nodeModulesData, release } = opts; - - if (!this.$fs.exists(nodeModulesData.absoluteOutputPath)) { - // Force copying if the destination doesn't exist. - nodeModulesData.lastModifiedTime = null; - } - - const tnsModulesCopy: TnsModulesCopy = this.$injector.resolve(TnsModulesCopy, { - outputRoot: nodeModulesData.absoluteOutputPath - }); - - tnsModulesCopy.copyModules({ dependencies: productionDependencies, release }); + await npmPluginPrepare.preparePlugins(productionDependencies, opts.nodeModulesData.platform, opts.nodeModulesData.projectData); } } diff --git a/lib/tools/node-modules/node-modules-dest-copy.ts b/lib/tools/node-modules/node-modules-dest-copy.ts index 55bcaaedc4..45af036501 100644 --- a/lib/tools/node-modules/node-modules-dest-copy.ts +++ b/lib/tools/node-modules/node-modules-dest-copy.ts @@ -1,155 +1,10 @@ -import * as path from "path"; -import * as shelljs from "shelljs"; -import * as constants from "../../constants"; -import * as minimatch from "minimatch"; - -export interface ILocalDependencyData extends IDependencyData { - directory: string; -} - -export class TnsModulesCopy { - constructor( - private outputRoot: string, - private $fs: IFileSystem, - private $pluginsService: IPluginsService - ) { - } - - public copyModules(opts: { dependencies: IDependencyData[], release: boolean }): void { - const filePatternsToDelete = opts.release ? "**/*.ts" : "**/*.d.ts"; - for (const entry in opts.dependencies) { - const dependency = opts.dependencies[entry]; - - this.copyDependencyDir(dependency, filePatternsToDelete); - } - } - - private copyDependencyDir(dependency: IDependencyData, filePatternsToDelete: string): void { - if (dependency.depth === 0) { - const targetPackageDir = path.join(this.outputRoot, dependency.name); - - shelljs.mkdir("-p", targetPackageDir); - - const isScoped = dependency.name.indexOf("@") === 0; - const destinationPath = isScoped ? path.join(this.outputRoot, dependency.name.substring(0, dependency.name.indexOf("/"))) : this.outputRoot; - shelljs.cp("-RfL", dependency.directory, destinationPath); - - // remove platform-specific files (processed separately by plugin services) - shelljs.rm("-rf", path.join(targetPackageDir, "platforms")); - - this.removeNonProductionDependencies(dependency, targetPackageDir); - this.removeDependenciesPlatformsDirs(targetPackageDir); - const allFiles = this.$fs.enumerateFilesInDirectorySync(targetPackageDir); - allFiles.filter(file => minimatch(file, filePatternsToDelete, { nocase: true })).map(file => this.$fs.deleteFile(file)); - } - } - - private removeDependenciesPlatformsDirs(dependencyDir: string): void { - const dependenciesFolder = path.join(dependencyDir, constants.NODE_MODULES_FOLDER_NAME); - - if (this.$fs.exists(dependenciesFolder)) { - const dependencies = this.getDependencies(dependenciesFolder); - - dependencies - .forEach(d => { - const pathToDependency = path.join(dependenciesFolder, d); - const pathToPackageJson = path.join(pathToDependency, constants.PACKAGE_JSON_FILE_NAME); - - if (this.$pluginsService.isNativeScriptPlugin(pathToPackageJson)) { - this.$fs.deleteDirectory(path.join(pathToDependency, constants.PLATFORMS_DIR_NAME)); - } - - this.removeDependenciesPlatformsDirs(pathToDependency); - }); - } - } - - private removeNonProductionDependencies(dependency: IDependencyData, targetPackageDir: string): void { - const packageJsonFilePath = path.join(dependency.directory, constants.PACKAGE_JSON_FILE_NAME); - if (!this.$fs.exists(packageJsonFilePath)) { - return; - } - - const packageJsonContent = this.$fs.readJson(packageJsonFilePath); - const productionDependencies = packageJsonContent.dependencies; - - const dependenciesFolder = path.join(targetPackageDir, constants.NODE_MODULES_FOLDER_NAME); - if (this.$fs.exists(dependenciesFolder)) { - const dependencies = this.getDependencies(dependenciesFolder); - - dependencies.filter(dir => !productionDependencies || !productionDependencies.hasOwnProperty(dir)) - .forEach(dir => shelljs.rm("-rf", path.join(dependenciesFolder, dir))); - } - } - - private getDependencies(dependenciesFolder: string): string[] { - const dependencies = _.flatten(this.$fs.readDirectory(dependenciesFolder) - .map(dir => { - if (_.startsWith(dir, "@")) { - const pathToDir = path.join(dependenciesFolder, dir); - const contents = this.$fs.readDirectory(pathToDir); - return _.map(contents, subDir => `${dir}/${subDir}`); - } - - return dir; - })); - - return dependencies; - } -} - export class NpmPluginPrepare { constructor( - private $fs: IFileSystem, private $pluginsService: IPluginsService, private $platformsData: IPlatformsData, - private $logger: ILogger - ) { - } - - protected async afterPrepare(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { - const prepareData: IDictionary = {}; - _.each(dependencies, d => { - prepareData[d.name] = true; - }); - this.$fs.createDirectory(this.preparedPlatformsDir(platform, projectData)); - this.$fs.writeJson(this.preparedPlatformsFile(platform, projectData), prepareData, " ", "utf8"); - } - - private preparedPlatformsDir(platform: string, projectData: IProjectData): string { - const platformRoot = this.$platformsData.getPlatformData(platform, projectData).projectRoot; - if (/android/i.test(platform)) { - return path.join(platformRoot, "build", "intermediates"); - } else if (/ios/i.test(platform)) { - return path.join(platformRoot, "build"); - } else { - throw new Error("Invalid platform: " + platform); - } - } - - private preparedPlatformsFile(platform: string, projectData: IProjectData): string { - return path.join(this.preparedPlatformsDir(platform, projectData), "prepared-platforms.json"); - } - - protected getPreviouslyPreparedDependencies(platform: string, projectData: IProjectData): IDictionary { - if (!this.$fs.exists(this.preparedPlatformsFile(platform, projectData))) { - return {}; - } - return this.$fs.readJson(this.preparedPlatformsFile(platform, projectData), "utf8"); - } + ) { } - private allPrepared(dependencies: IDependencyData[], platform: string, projectData: IProjectData): boolean { - let result = true; - const previouslyPrepared = this.getPreviouslyPreparedDependencies(platform, projectData); - _.each(dependencies, d => { - if (!previouslyPrepared[d.name]) { - result = false; - } - }); - return result; - } - - public async preparePlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { + public async preparePlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData): Promise { if (_.isEmpty(dependencies)) { return; } @@ -164,29 +19,5 @@ export class NpmPluginPrepare { await this.$pluginsService.preparePluginNativeCode(pluginData, platform, projectData); } } - - await this.afterPrepare(dependencies, platform, projectData); - } - - public async prepareJSPlugins(dependencies: IDependencyData[], platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise { - if (_.isEmpty(dependencies) || this.allPrepared(dependencies, platform, projectData)) { - return; - } - - for (const dependencyKey in dependencies) { - const dependency = dependencies[dependencyKey]; - const isPlugin = !!dependency.nativescript; - if (isPlugin) { - platform = platform.toLowerCase(); - const pluginData = this.$pluginsService.convertToPluginData(dependency, projectData.projectDir); - const platformData = this.$platformsData.getPlatformData(platform, projectData); - const appFolderExists = this.$fs.exists(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)); - if (appFolderExists) { - this.$pluginsService.preparePluginScripts(pluginData, platform, projectData, projectFilesConfig); - // Show message - this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform}.`); - } - } - } } } diff --git a/test/platform-service.ts b/test/platform-service.ts index 8503a71a06..47c578fb85 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -21,7 +21,6 @@ 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 { INFO_PLIST_FILE_NAME, MANIFEST_FILE_NAME } from "../lib/constants"; import { mkdir } from "shelljs"; import * as constants from "../lib/constants"; @@ -125,59 +124,6 @@ function createTestInjector() { return testInjector; } -class CreatedTestData { - files: string[]; - - resources: { - ios: string[], - android: string[] - }; - - testDirData: { - tempFolder: string, - appFolderPath: string, - app1FolderPath: string, - appDestFolderPath: string, - appResourcesFolderPath: string - }; - - constructor() { - this.files = []; - this.resources = { - ios: [], - android: [] - }; - - this.testDirData = { - tempFolder: "", - appFolderPath: "", - app1FolderPath: "", - appDestFolderPath: "", - appResourcesFolderPath: "" - }; - } -} - -class DestinationFolderVerifier { - static verify(data: any, fs: IFileSystem) { - _.forOwn(data, (folder, folderRoot) => { - _.each(folder.filesWithContent || [], (file) => { - const filePath = path.join(folderRoot, file.name); - assert.isTrue(fs.exists(filePath), `Expected file ${filePath} to be present.`); - assert.equal(fs.readFile(filePath).toString(), file.content, `File content for ${filePath} doesn't match.`); - }); - - _.each(folder.missingFiles || [], (file) => { - assert.isFalse(fs.exists(path.join(folderRoot, file)), `Expected file ${file} to be missing.`); - }); - - _.each(folder.presentFiles || [], (file) => { - assert.isTrue(fs.exists(path.join(folderRoot, file)), `Expected file ${file} to be present.`); - }); - }); - } -} - describe('Platform Service Tests', () => { let platformService: IPlatformService, testInjector: IInjector; const config: IPlatformOptions = { @@ -425,548 +371,549 @@ describe('Platform Service Tests', () => { }); }); - 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, - config: { 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 - } - ] - }; + // TODO: check this tests with QAs + // describe("prepare platform unit tests", () => { + // let fs: IFileSystem; - 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 - } - ] - }; + // beforeEach(() => { + // testInjector = createTestInjector(); + // testInjector.register("fs", fsLib.FileSystem); + // fs = testInjector.resolve("fs"); + // testInjector.resolve("projectData").initializeProjectData(); + // }); - return modifications; - }; - await testChangesApplied("Android", applyChangesFn); - }); + // 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, + // config: { 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 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 - } - ] - }; + // it("should process only files in app folder when preparing for Android platform", async () => { + // await testPreparePlatform("Android"); + // }); - return modifications; - }; - await testChangesApplied("iOS", applyChangesFn); - }); + // it("should process only files in app folder when preparing for iOS platform", async () => { + // await testPreparePlatform("iOS", true); + // }); - 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 - } - ] - }; + // it("should process only files in app folder when preparing for Android platform", async () => { + // await testPreparePlatform("Android", true); + // }); - return modifications; - }; - await testChangesApplied("Android", applyChangesFn); - }); + // 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 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 - } - ] - }; + // 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); + // }); - return modifications; - }; - await testChangesApplied("iOS", 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("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 - } - ] - }; + // 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); + // }); - 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("invalid xml is caught", async () => { - require("colors"); - const testDirData = prepareDirStructure(); + // 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); + // }); - // generate invalid xml - const fileFullPath = path.join(testDirData.appFolderPath, "file.xml"); - fs.writeFile(fileFullPath, ""); + // 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); + // }); - 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" - }; - }; + // 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); + // }); - const projectData = testInjector.resolve("projectData"); - projectData.projectDir = testDirData.tempFolder; - projectData.appDirectoryPath = projectData.getAppDirectoryPath(); - projectData.appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); + // 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); + // }); - 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, - config: { provision: null, teamId: null, sdk: null, frameworkPath: null, ignoreScripts: false }, - env: {} - }); - } finally { - testInjector.resolve("$logger").warn = oldLoggerWarner; - } + // 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); + // }); - // Asserts that prepare has caught invalid xml - assert.isFalse(warnings.indexOf("has errors") !== -1); - }); - }); + // 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, + // config: { 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 { diff --git a/test/plugin-prepare.ts b/test/plugin-prepare.ts deleted file mode 100644 index 00dd7b04b1..0000000000 --- a/test/plugin-prepare.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { assert } from "chai"; -import { NpmPluginPrepare } from "../lib/tools/node-modules/node-modules-dest-copy"; - -require("should"); - -class TestNpmPluginPrepare extends NpmPluginPrepare { - public preparedDependencies: IDictionary = {}; - - constructor(private previouslyPrepared: IDictionary) { - super(null, null, { - getPlatformData: () => { - return { - platformProjectService: { - beforePrepareAllPlugins: () => Promise.resolve() - } - }; - } - }, null); - } - - protected getPreviouslyPreparedDependencies(platform: string): IDictionary { - return this.previouslyPrepared; - } - - protected async afterPrepare(dependencies: IDependencyData[], platform: string): Promise { - _.each(dependencies, d => { - this.preparedDependencies[d.name] = true; - }); - } -} - -describe("Plugin preparation", () => { - it("skips prepare if no plugins", async () => { - const pluginPrepare = new TestNpmPluginPrepare({}); - await pluginPrepare.preparePlugins([], "android", null, {}); - assert.deepEqual({}, pluginPrepare.preparedDependencies); - }); - - it("saves prepared plugins after preparation", async () => { - const pluginPrepare = new TestNpmPluginPrepare({ "tns-core-modules-widgets": true }); - const testDependencies: IDependencyData[] = [ - { - name: "tns-core-modules-widgets", - depth: 0, - directory: "some dir", - nativescript: null, - }, - { - name: "nativescript-calendar", - depth: 0, - directory: "some dir", - nativescript: null, - } - ]; - await pluginPrepare.preparePlugins(testDependencies, "android", null, {}); - const prepareData = { "tns-core-modules-widgets": true, "nativescript-calendar": true }; - assert.deepEqual(prepareData, pluginPrepare.preparedDependencies); - }); -}); diff --git a/test/plugins-service.ts b/test/plugins-service.ts index f2530b2e73..c4b0f9f874 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -569,7 +569,7 @@ describe("Plugins service", () => { `\n@#[line:1,col:39].` + `\n@#[line:1,col:39].`; mockBeginCommand(testInjector, expectedErrorMessage); - await pluginsService.prepare(pluginJsonData, "android", projectData, {}); + await pluginsService.preparePluginNativeCode(pluginsService.convertToPluginData(pluginJsonData, projectData.projectDir), "android", projectData); }); }); From 60548665d66fc779eded0cc9069b773638a23c04 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 10 Apr 2019 13:06:08 +0300 Subject: [PATCH 002/102] chore: remove platformTemplate logic --- lib/commands/add-platform.ts | 2 +- lib/commands/appstore-upload.ts | 1 - lib/commands/build.ts | 1 - lib/commands/clean-app.ts | 1 - lib/commands/install.ts | 2 +- lib/commands/platform-clean.ts | 2 +- lib/commands/prepare.ts | 1 - lib/commands/update-platform.ts | 2 +- lib/commands/update.ts | 4 +- lib/declarations.d.ts | 11 +--- lib/definitions/platform.d.ts | 9 ++- lib/definitions/project.d.ts | 3 +- lib/helpers/deploy-command-helper.ts | 1 - lib/options.ts | 1 - lib/services/livesync/livesync-service.ts | 1 - lib/services/local-build-service.ts | 3 +- lib/services/platform-service.ts | 48 +++++++-------- lib/services/prepare-platform-js-service.ts | 42 +------------ test/platform-service.ts | 67 ++++++++++----------- 19 files changed, 72 insertions(+), 130 deletions(-) diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts index 20d42e5ef1..a75cff15bd 100644 --- a/lib/commands/add-platform.ts +++ b/lib/commands/add-platform.ts @@ -13,7 +13,7 @@ export class AddPlatformCommand extends ValidatePlatformCommandBase implements I } public async execute(args: string[]): Promise { - await this.$platformService.addPlatforms(args, this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); + await this.$platformService.addPlatforms(args, this.$projectData, this.$options, this.$options.frameworkPath); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 03168cde65..50dd6b43b2 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -64,7 +64,6 @@ export class PublishIOS implements ICommand { const platformInfo: IPreparePlatformInfo = { platform, appFilesUpdaterOptions, - platformTemplate: this.$options.platformTemplate, projectData: this.$projectData, config: this.$options, env: this.$options.env diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 0c4f091f19..4d77ce696e 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -24,7 +24,6 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { const platformInfo: IPreparePlatformInfo = { platform, appFilesUpdaterOptions, - platformTemplate: this.$options.platformTemplate, projectData: this.$projectData, config: this.$options, env: this.$options.env diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index e22c1a4c64..b825b7e071 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -25,7 +25,6 @@ export class CleanAppCommandBase extends ValidatePlatformCommandBase implements appFilesUpdaterOptions, platform: this.platform.toLowerCase(), config: this.$options, - platformTemplate: this.$options.platformTemplate, projectData: this.$projectData, env: this.$options.env }; diff --git a/lib/commands/install.ts b/lib/commands/install.ts index a92ee7a6ab..655097d6a8 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -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.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); + await this.$platformService.addPlatforms([`${platform}@${frameworkPackageData.version}`], this.$projectData, this.$options, this.$options.frameworkPath); } catch (err) { error = `${error}${EOL}${err}`; } diff --git a/lib/commands/platform-clean.ts b/lib/commands/platform-clean.ts index d7e6a3a1c4..9837902c8e 100644 --- a/lib/commands/platform-clean.ts +++ b/lib/commands/platform-clean.ts @@ -10,7 +10,7 @@ export class CleanCommand implements ICommand { } public async execute(args: string[]): Promise { - await this.$platformService.cleanPlatforms(args, this.$options.platformTemplate, this.$projectData, this.$options); + await this.$platformService.cleanPlatforms(args, this.$projectData, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 409ddf16c6..afa0387614 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -21,7 +21,6 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm const platformInfo: IPreparePlatformInfo = { platform: args[0], appFilesUpdaterOptions, - platformTemplate: this.$options.platformTemplate, projectData: this.$projectData, config: this.$options, env: this.$options.env diff --git a/lib/commands/update-platform.ts b/lib/commands/update-platform.ts index df40f4f8a5..61d62f779c 100644 --- a/lib/commands/update-platform.ts +++ b/lib/commands/update-platform.ts @@ -10,7 +10,7 @@ export class UpdatePlatformCommand implements ICommand { } public async execute(args: string[]): Promise { - await this.$platformService.updatePlatforms(args, this.$options.platformTemplate, this.$projectData, this.$options); + await this.$platformService.updatePlatforms(args, this.$projectData, this.$options); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/update.ts b/lib/commands/update.ts index 1649284c41..afbd5f7c0a 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -94,12 +94,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.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); + await this.$platformService.addPlatforms([platform + "@" + args[0]], this.$projectData, this.$options, this.$options.frameworkPath); } await this.$pluginsService.add(`${constants.TNS_CORE_MODULES_NAME}@${args[0]}`, this.$projectData); } else { - await this.$platformService.addPlatforms(platforms.packagePlatforms, this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); + await this.$platformService.addPlatforms(platforms.packagePlatforms, this.$projectData, this.$options, this.$options.frameworkPath); await this.$pluginsService.add(constants.TNS_CORE_MODULES_NAME, this.$projectData); } diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index e7c4a4456d..208ca92bad 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -441,11 +441,6 @@ interface IBundleString { bundle: string; } -interface IPlatformTemplate { - platformTemplate: string; -} - - interface IClean { clean: boolean; } @@ -499,7 +494,7 @@ interface IAndroidBundleOptions { aab: boolean; } -interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvailableDevices, IProfileDir, IHasEmulatorOption, IBundleString, IPlatformTemplate, IHasEmulatorOption, IClean, IProvision, ITeamIdentifier, IAndroidReleaseOptions, IAndroidBundleOptions, INpmInstallConfigurationOptions, IPort, IEnvOptions, IPluginSeedOptions, IGenerateOptions { +interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvailableDevices, IProfileDir, IHasEmulatorOption, IBundleString, IHasEmulatorOption, IClean, IProvision, ITeamIdentifier, IAndroidReleaseOptions, IAndroidBundleOptions, INpmInstallConfigurationOptions, IPort, IEnvOptions, IPluginSeedOptions, IGenerateOptions { argv: IYargArgv; validateOptions(commandSpecificDashedOptions?: IDictionary): void; options: IDictionary; @@ -589,11 +584,11 @@ interface IDeviceEmulator extends IHasEmulatorOption, IDeviceIdentifier { } interface IRunPlatformOptions extends IJustLaunch, IDeviceEmulator { } -interface IDeployPlatformOptions extends IAndroidReleaseOptions, IPlatformTemplate, IRelease, IClean, IDeviceEmulator, IProvision, ITeamIdentifier, IProjectDir { +interface IDeployPlatformOptions extends IAndroidReleaseOptions, IRelease, IClean, IDeviceEmulator, IProvision, ITeamIdentifier, IProjectDir { forceInstall?: boolean; } -interface IUpdatePlatformOptions extends IPlatformTemplate { +interface IUpdatePlatformOptions { currentVersion: string; newVersion: string; canUpdate: boolean; diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 3462e437e1..650017d684 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -15,9 +15,9 @@ interface IBuildPlatformAction { } interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { - cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, framework?: string): Promise; + cleanPlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions, framework?: string): Promise; - addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string): Promise; + addPlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string): Promise; /** * Gets list of all installed platforms (the ones for which /platforms/ exists). @@ -48,7 +48,7 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { */ removePlatforms(platforms: string[], projectData: IProjectData): Promise; - updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise; + updatePlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions): Promise; /** * Ensures that the specified platform and its dependencies are installed. @@ -316,7 +316,6 @@ interface IAddPlatformInfo extends IProjectDataComposition, IPlatformDataComposi frameworkDir: string; installedVersion: string; config: IPlatformOptions; - platformTemplate?: string; } interface IPreparePlatformJSInfo extends IPreparePlatformCoreInfo, ICopyAppFilesData { @@ -335,7 +334,7 @@ interface IPreparePlatformCoreInfo extends IPreparePlatformInfoBase, IOptionalPr platformSpecificData: IPlatformSpecificData; } -interface IPreparePlatformInfo extends IPreparePlatformInfoBase, IPlatformConfig, IPlatformTemplate, ISkipNativeCheckOptional { } +interface IPreparePlatformInfo extends IPreparePlatformInfoBase, IPlatformConfig, ISkipNativeCheckOptional { } interface IPlatformConfig { config: IPlatformOptions; diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 05d67d21ab..fa5f35b915 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -336,10 +336,9 @@ interface ILocalBuildService { * Builds a project locally. * @param {string} platform Platform for which to build. * @param {IPlatformBuildData} platformBuildOptions Additional options for controlling the build. - * @param {string} platformTemplate The name of the template. * @return {Promise} Path to the build output. */ - build(platform: string, platformBuildOptions: IPlatformBuildData, platformTemplate?: string): Promise; + build(platform: string, platformBuildOptions: IPlatformBuildData): Promise; /** * Removes build artifacts specific to the platform * @param {ICleanNativeAppData} data Data describing the clean app process diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index de2d02f31a..a5a0e9d652 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -17,7 +17,6 @@ export class DeployCommandHelper implements IDeployCommandHelper { device: this.$options.device, projectDir: this.$projectData.projectDir, emulator: this.$options.emulator, - platformTemplate: this.$options.platformTemplate, release: this.$options.release, forceInstall: true, provision: this.$options.provision, diff --git a/lib/options.ts b/lib/options.ts index 79931e43e7..09fdcdcec8 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -90,7 +90,6 @@ export class Options { compileSdk: { type: OptionType.Number, hasSensitiveValue: false }, port: { type: OptionType.Number, hasSensitiveValue: false }, copyTo: { type: OptionType.String, hasSensitiveValue: true }, - platformTemplate: { type: OptionType.String, hasSensitiveValue: true }, js: { type: OptionType.Boolean, hasSensitiveValue: false }, javascript: { type: OptionType.Boolean, hasSensitiveValue: false }, ng: { type: OptionType.Boolean, hasSensitiveValue: false }, diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index fa1eb1502b..878f4437f2 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -429,7 +429,6 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi nativePrepare: nativePrepare, filesToSync: options.filesToSync, filesToRemove: options.filesToRemove, - platformTemplate: null, skipModulesNativeCheck: options.skipModulesNativeCheck, config: platformSpecificOptions }; diff --git a/lib/services/local-build-service.ts b/lib/services/local-build-service.ts index b33f49051d..ae50dfb717 100644 --- a/lib/services/local-build-service.ts +++ b/lib/services/local-build-service.ts @@ -12,7 +12,7 @@ export class LocalBuildService extends EventEmitter implements ILocalBuildServic super(); } - public async build(platform: string, platformBuildOptions: IPlatformBuildData, platformTemplate?: string): Promise { + 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); } @@ -21,7 +21,6 @@ export class LocalBuildService extends EventEmitter implements ILocalBuildServic const prepareInfo: IPreparePlatformInfo = { platform, appFilesUpdaterOptions: platformBuildOptions, - platformTemplate, projectData: this.$projectData, env: platformBuildOptions.env, config: { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index e481600407..a670852b95 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -41,7 +41,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { super(); } - public async cleanPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, framworkPath?: string): Promise { + public async cleanPlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions, framworkPath?: string): Promise { for (const platform of platforms) { const version: string = this.getCurrentPlatformVersion(platform, projectData); @@ -51,11 +51,11 @@ export class PlatformService extends EventEmitter implements IPlatformService { } await this.removePlatforms([platform], projectData); - await this.addPlatforms([platformWithVersion], platformTemplate, projectData, config); + await this.addPlatforms([platformWithVersion], projectData, config); } } - public async addPlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string): Promise { + public async addPlatforms(platforms: string[], projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string): Promise { const platformsDir = projectData.platformsDir; this.$fs.ensureDirectoryExists(platformsDir); @@ -68,7 +68,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$errors.failWithoutHelp(`Platform ${platform} already added`); } - await this.addPlatform(platform.toLowerCase(), platformTemplate, projectData, config, frameworkPath); + await this.addPlatform(platform.toLowerCase(), projectData, config, frameworkPath); } } @@ -83,7 +83,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return version; } - private async addPlatform(platformParam: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, frameworkPath?: string, nativePrepare?: INativePrepare): Promise { + 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]; @@ -126,7 +126,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { let frameworkDir = path.join(downloadedPackagePath, constants.PROJECT_FRAMEWORK_FOLDER_NAME); frameworkDir = path.resolve(frameworkDir); installedPlatformVersion = - await this.addPlatformCore(platformData, frameworkDir, platformTemplate, projectData, config, nativePrepare); + await this.addPlatformCore(platformData, frameworkDir, projectData, config, nativePrepare); } catch (err) { this.$fs.deleteDirectory(platformPath); throw err; @@ -138,7 +138,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out(`Platform ${platform} successfully added. v${installedPlatformVersion}`); } - private async addPlatformCore(platformData: IPlatformData, frameworkDir: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, nativePrepare?: INativePrepare): Promise { + 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; @@ -147,8 +147,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { frameworkDir, installedVersion, projectData, - config, - platformTemplate + config }); if (!nativePrepare || !nativePrepare.skipNativePrepare) { @@ -287,7 +286,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } private async initialPrepare(preparePlatformInfo: IPreparePlatformInfo) { - const { platform, appFilesUpdaterOptions, platformTemplate, projectData, config, nativePrepare } = preparePlatformInfo; + const { platform, appFilesUpdaterOptions, projectData, config, nativePrepare } = preparePlatformInfo; this.validatePlatform(platform, projectData); // We need dev-dependencies here, so before-prepare hooks will be executed correctly. @@ -298,7 +297,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); } - await this.ensurePlatformInstalled(platform, platformTemplate, projectData, config, appFilesUpdaterOptions, nativePrepare); + 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.*/ @@ -535,7 +534,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.preparePlatform({ platform: deployInfo.platform, appFilesUpdaterOptions: deployInfo.appFilesUpdaterOptions, - platformTemplate: deployInfo.deployOptions.platformTemplate, projectData: deployInfo.projectData, config: deployInfo.config, nativePrepare: deployInfo.nativePrepare, @@ -649,7 +647,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { @helpers.hook('cleanApp') public async cleanDestinationApp(platformInfo: IPreparePlatformInfo): Promise { - await this.ensurePlatformInstalled(platformInfo.platform, platformInfo.platformTemplate, platformInfo.projectData, platformInfo.config, platformInfo.appFilesUpdaterOptions, platformInfo.nativePrepare); + 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); @@ -716,16 +714,16 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async updatePlatforms(platforms: string[], platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise { + 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, platformTemplate, projectData, config); + await this.updatePlatform(platform, version, projectData, config); } else { - await this.addPlatform(platformParam, platformTemplate, projectData, config); + await this.addPlatform(platformParam, projectData, config); } } } @@ -770,7 +768,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } } - public async ensurePlatformInstalled(platform: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, appFilesUpdaterOptions: IAppFilesUpdaterOptions, nativePrepare?: INativePrepare): Promise { + public async ensurePlatformInstalled(platform: string, projectData: IProjectData, config: IPlatformOptions, appFilesUpdaterOptions: IAppFilesUpdaterOptions, nativePrepare?: INativePrepare): Promise { let requiresNativePlatformAdd = false; const platformData = this.$platformsData.getPlatformData(platform, projectData); @@ -780,7 +778,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { // 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, platformTemplate, projectData, config, platformData, nativePrepare); + await this.persistWebpackFiles(platform, projectData, config, platformData, nativePrepare); return; } @@ -790,10 +788,10 @@ export class PlatformService extends EventEmitter implements IPlatformService { // In case there's no prepare info, it means only platform add had been executed. So we've come from CLI and we do not need to prepare natively. requiresNativePlatformAdd = prepareInfo && prepareInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPlatformAdd; if (requiresNativePlatformAdd && shouldAddNativePlatform) { - await this.addPlatform(platform, platformTemplate, projectData, config, "", nativePrepare); + await this.addPlatform(platform, projectData, config, "", nativePrepare); } } else { - await this.addPlatform(platform, platformTemplate, projectData, config, "", nativePrepare); + await this.addPlatform(platform, projectData, config, "", nativePrepare); } } @@ -808,12 +806,12 @@ export class PlatformService extends EventEmitter implements IPlatformService { return result; } - private async persistWebpackFiles(platform: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions, platformData: IPlatformData, nativePrepare?: INativePrepare): Promise { + 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, platformTemplate, projectData, config, "", nativePrepare); + await this.addPlatform(platform, projectData, config, "", nativePrepare); this.$fs.copyFile(path.join(tmpDirectoryPath, "*"), platformData.appDestinationDirectoryPath); this.$fs.deleteDirectory(tmpDirectoryPath); } @@ -903,7 +901,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { return this.getLatestApplicationPackage(outputPath || platformData.getBuildOutputPath(buildConfig), platformData.getValidBuildOutputData(buildOutputOptions)); } - private async updatePlatform(platform: string, version: string, platformTemplate: string, projectData: IProjectData, config: IPlatformOptions): Promise { + 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); @@ -924,7 +922,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } if (!semver.gt(currentVersion, newVersion)) { - await this.updatePlatformCore(platformData, { currentVersion, newVersion, canUpdate, platformTemplate }, projectData, config); + 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 { @@ -939,7 +937,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { let packageName = platformData.normalizedPlatformName.toLowerCase(); await this.removePlatforms([packageName], projectData); packageName = updateOptions.newVersion ? `${packageName}@${updateOptions.newVersion}` : packageName; - await this.addPlatform(packageName, updateOptions.platformTemplate, projectData, config); + await this.addPlatform(packageName, projectData, config); this.$logger.out("Successfully updated to version ", updateOptions.newVersion); } diff --git a/lib/services/prepare-platform-js-service.ts b/lib/services/prepare-platform-js-service.ts index f4675cbf93..0ea772d0bb 100644 --- a/lib/services/prepare-platform-js-service.ts +++ b/lib/services/prepare-platform-js-service.ts @@ -1,5 +1,3 @@ -import * as constants from "../constants"; -import * as path from "path"; import * as temp from "temp"; import { hook } from "../common/helpers"; import { PreparePlatformService } from "./prepare-platform-service"; @@ -12,22 +10,12 @@ export class PreparePlatformJSService extends PreparePlatformService implements constructor($fs: IFileSystem, $xmlValidator: IXmlValidator, $hooksService: IHooksService, - private $errors: IErrors, - private $logger: ILogger, - private $projectDataService: IProjectDataService, - private $packageManager: INodePackageManager) { + private $projectDataService: IProjectDataService) { super($fs, $hooksService, $xmlValidator); } public async addPlatform(info: IAddPlatformInfo): Promise { - const customTemplateOptions = await this.getPathToPlatformTemplate(info.platformTemplate, info.platformData.frameworkPackageName, info.projectData.projectDir); - info.config.pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate; - const frameworkPackageNameData: any = { version: info.installedVersion }; - if (customTemplateOptions) { - frameworkPackageNameData.template = customTemplateOptions.selectedTemplate; - } - this.$projectDataService.setNSValue(info.projectData.projectDir, info.platformData.frameworkPackageName, frameworkPackageNameData); } @@ -36,34 +24,6 @@ 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 } - - private async getPathToPlatformTemplate(selectedTemplate: string, frameworkPackageName: string, projectDir: string): Promise<{ selectedTemplate: string, pathToTemplate: string }> { - if (!selectedTemplate) { - // read data from package.json's nativescript key - // check the nativescript.tns-.template value - const nativescriptPlatformData = this.$projectDataService.getNSValue(projectDir, frameworkPackageName); - selectedTemplate = nativescriptPlatformData && nativescriptPlatformData.template; - } - - if (selectedTemplate) { - const tempDir = temp.mkdirSync("platform-template"); - this.$fs.writeJson(path.join(tempDir, constants.PACKAGE_JSON_FILE_NAME), {}); - try { - const npmInstallResult = await this.$packageManager.install(selectedTemplate, tempDir, { - disableNpmInstall: false, - frameworkPath: null, - ignoreScripts: false - }); - const pathToTemplate = path.join(tempDir, constants.NODE_MODULES_FOLDER_NAME, npmInstallResult.name); - return { selectedTemplate, pathToTemplate }; - } catch (err) { - this.$logger.trace("Error while trying to install specified template: ", err); - this.$errors.failWithoutHelp(`Unable to install platform template ${selectedTemplate}. Make sure the specified value is valid.`); - } - } - - return null; - } } $injector.register("preparePlatformJSService", PreparePlatformJSService); diff --git a/test/platform-service.ts b/test/platform-service.ts index 47c578fb85..45211b1546 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -147,22 +147,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 platformService.addPlatforms(["Android"], projectData, config); + await platformService.addPlatforms(["ANDROID"], projectData, config); + await platformService.addPlatforms(["AnDrOiD"], projectData, config); + await platformService.addPlatforms(["androiD"], projectData, config); + + await platformService.addPlatforms(["iOS"], projectData, config); + await platformService.addPlatforms(["IOS"], projectData, config); + await platformService.addPlatforms(["IoS"], projectData, config); + await platformService.addPlatforms(["iOs"], projectData, config); }); it("should fail if platform is already installed", async () => { 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(platformService.addPlatforms(["android"], projectData, config), "Platform android already added"); + await assert.isRejected(platformService.addPlatforms(["ios"], projectData, config), "Platform ios already added"); }); it("should fail if unable to extract runtime package", async () => { @@ -176,7 +176,7 @@ describe('Platform Service Tests', () => { }; const projectData: IProjectData = testInjector.resolve("projectData"); - await assert.isRejected(platformService.addPlatforms(["android"], "", projectData, config), errorMessage); + await assert.isRejected(platformService.addPlatforms(["android"], projectData, config), errorMessage); }); it("fails when path passed to frameworkPath does not exist", async () => { @@ -186,7 +186,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(platformService.addPlatforms(["android"], projectData, config, frameworkPath), errorMessage); }); const assertCorrectDataIsPassedToPacoteService = async (versionString: string): Promise => { @@ -217,9 +217,9 @@ describe('Platform Service Tests', () => { }; const projectData: IProjectData = testInjector.resolve("projectData"); - await platformService.addPlatforms(["android"], "", projectData, config); + await platformService.addPlatforms(["android"], projectData, config); assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); - await platformService.addPlatforms(["ios"], "", projectData, config); + await platformService.addPlatforms(["ios"], projectData, config); assert.equal(packageNamePassedToPacoteService, `${packageName}@${versionString}`); }; it("should respect platform version in package.json's nativescript key", async () => { @@ -261,7 +261,7 @@ describe('Platform Service Tests', () => { const preparePlatformNativeService = testInjector.resolve("preparePlatformNativeService"); preparePlatformNativeService.addPlatform = async () => isNativePlatformAdded = true; - await platformService.addPlatforms(["android"], "", projectData, config); + await platformService.addPlatforms(["android"], projectData, config); assert.isTrue(isJsPlatformAdded); assert.isTrue(isNativePlatformAdded); @@ -275,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(platformService.addPlatforms(["android"], projectData, config), "Platform android already added"); }); // Workflow: tns run; tns platform add @@ -286,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(platformService.addPlatforms(["android"], projectData, config), "Platform android already added"); }); }); }); @@ -315,7 +315,7 @@ 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 platformService.addPlatforms(["android"], projectData, config); testInjector.resolve("fs").exists = () => true; await platformService.removePlatforms(["android"], projectData); @@ -345,10 +345,10 @@ describe('Platform Service Tests', () => { return Promise.resolve(); }; - await platformService.cleanPlatforms(["android"], "", projectData, config); + await platformService.cleanPlatforms(["android"], projectData, config); nsValueObject[VERSION_STRING] = versionString; - await platformService.cleanPlatforms(["ios"], "", projectData, config); + await platformService.cleanPlatforms(["ios"], projectData, config); }); }); @@ -366,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(platformService.updatePlatforms(["android"], projectData, null)); }); }); }); @@ -1001,7 +1001,6 @@ describe('Platform Service Tests', () => { describe("ensurePlatformInstalled", () => { const platform = "android"; - const platformTemplate = "testPlatformTemplate"; const appFilesUpdaterOptions = { bundle: true }; let areWebpackFilesPersisted = false; @@ -1079,7 +1078,7 @@ describe('Platform Service Tests', () => { usbLiveSyncService.isInitialized = testCase.isWebpackWatcherStarted === undefined ? true : testCase.isWebpackWatcherStarted; mockPrepareInfo(testCase.prepareInfo); - await (platformService).ensurePlatformInstalled(platform, platformTemplate, projectData, config, appFilesUpdaterOptions, testCase.nativePrepare); + await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, testCase.nativePrepare); assert.deepEqual(areWebpackFilesPersisted, testCase.areWebpackFilesPersisted); }); }); @@ -1087,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, platformTemplate, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); assert.isTrue(areWebpackFilesPersisted); // Second execution of `tns preview --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - await (platformService).ensurePlatformInstalled(platform, platformTemplate, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformService).ensurePlatformInstalled(platform, projectData, config, 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, platformTemplate, projectData, config, appFilesUpdaterOptions); + await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions); assert.isTrue(areWebpackFilesPersisted); // Second execution of `tns run --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - await (platformService).ensurePlatformInstalled(platform, platformTemplate, projectData, config, appFilesUpdaterOptions); + await (platformService).ensurePlatformInstalled(platform, projectData, config, 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, platformTemplate, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); assert.isTrue(areWebpackFilesPersisted); // Execution of `tns run --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - await (platformService).ensurePlatformInstalled(platform, platformTemplate, projectData, config, appFilesUpdaterOptions); + await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions); assert.isTrue(areWebpackFilesPersisted); // Execution of `tns preview --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - await (platformService).ensurePlatformInstalled(platform, platformTemplate, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformService).ensurePlatformInstalled(platform, projectData, config, 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, platformTemplate, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); + await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions, { skipNativePrepare: true }); assert.isTrue(areWebpackFilesPersisted); // Execution of `tns run --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.requiresPlatformAdd }); - await (platformService).ensurePlatformInstalled(platform, platformTemplate, projectData, config, appFilesUpdaterOptions); + await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions); assert.isTrue(areWebpackFilesPersisted); // Execution of `tns build --bundle` reset(); mockPrepareInfo({ nativePlatformStatus: constants.NativePlatformStatus.alreadyPrepared }); - await (platformService).ensurePlatformInstalled(platform, platformTemplate, projectData, config, appFilesUpdaterOptions); + await (platformService).ensurePlatformInstalled(platform, projectData, config, appFilesUpdaterOptions); assert.isFalse(areWebpackFilesPersisted); }); }); From 3a61cee129945691ab8a47511e1d57d601dc5bac Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 10 Apr 2019 14:10:24 +0300 Subject: [PATCH 003/102] chore: remove the legacy code from android-project-service --- lib/common/helpers.ts | 6 - lib/definitions/project.d.ts | 10 -- lib/services/android-project-service.ts | 221 ++---------------------- lib/services/ios-project-service.ts | 6 - lib/services/platform-service.ts | 6 +- test/debug.ts | 115 ------------ test/stubs.ts | 6 - 7 files changed, 17 insertions(+), 353 deletions(-) delete mode 100644 test/debug.ts diff --git a/lib/common/helpers.ts b/lib/common/helpers.ts index f3c22b4430..fd5b6044e4 100644 --- a/lib/common/helpers.ts +++ b/lib/common/helpers.ts @@ -97,12 +97,6 @@ export function regExpEscape(input: string): string { return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } -export function isRecommendedAarFile(foundAarFile: string, packageJsonPluginName: string): boolean { - const filename = foundAarFile.replace(/^.*[\\\/]/, ''); - packageJsonPluginName = getShortPluginName(packageJsonPluginName); - return `${packageJsonPluginName}.aar` === filename; -} - export function getShortPluginName(pluginName: string): string { return sanitizePluginName(pluginName).replace(/[\-]/g, "_"); } diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 05d67d21ab..4463c00e38 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -461,16 +461,6 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS */ checkForChanges(changeset: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): Promise; - /** - * Build native part of a nativescript plugins if necessary - */ - prebuildNativePlugin(buildOptions: IPluginBuildOptions): Promise; - - /** - * Traverse through the production dependencies and find plugins that need build/rebuild - */ - checkIfPluginsNeedBuild(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 diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 380ce5a24f..47e3a10a4b 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -4,7 +4,7 @@ import * as constants from "../constants"; import * as semver from "semver"; import * as projectServiceBaseLib from "./platform-project-service-base"; import { DeviceAndroidDebugBridge } from "../common/mobile/android/device-android-debug-bridge"; -import { attachAwaitDetach, isRecommendedAarFile } from "../common/helpers"; +import { attachAwaitDetach } from "../common/helpers"; import { Configurations, LiveSyncPaths } from "../common/constants"; import { SpawnOptions } from "child_process"; import { performanceLog } from ".././common/decorators"; @@ -14,7 +14,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private static VALUES_VERSION_DIRNAME_PREFIX = AndroidProjectService.VALUES_DIRNAME + "-v"; private static ANDROID_PLATFORM_NAME = "android"; private static MIN_RUNTIME_VERSION_WITH_GRADLE = "1.5.0"; - private static MIN_RUNTIME_VERSION_WITHOUT_DEPS = "4.2.0-2018-06-29-02"; private isAndroidStudioTemplate: boolean; @@ -26,9 +25,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $logger: ILogger, $projectDataService: IProjectDataService, private $injector: IInjector, - private $pluginVariablesService: IPluginVariablesService, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $packageManager: INodePackageManager, private $androidPluginBuildService: IAndroidPluginBuildService, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $androidResourcesMigrationService: IAndroidResourcesMigrationService, @@ -186,52 +183,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } this.cleanResValues(targetSdkVersion, projectData); - - if (semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITHOUT_DEPS)) { - await this.installRuntimeDeps(projectData, config); - } - } - - private async installRuntimeDeps(projectData: IProjectData, config: ICreateProjectOptions) { - const requiredDevDependencies = [ - { name: "babel-traverse", version: "^6.4.5" }, - { name: "babel-types", version: "^6.4.5" }, - { name: "babylon", version: "^6.4.5" }, - { name: "lazy", version: "^1.0.11" } - ]; - - const npmConfig: INodePackageManagerInstallOptions = { - save: true, - "save-dev": true, - "save-exact": true, - silent: true, - disableNpmInstall: false, - frameworkPath: config.frameworkPath, - ignoreScripts: config.ignoreScripts - }; - - const projectPackageJson: any = this.$fs.readJson(projectData.projectFilePath); - - for (const dependency of requiredDevDependencies) { - let dependencyVersionInProject = (projectPackageJson.dependencies && projectPackageJson.dependencies[dependency.name]) || - (projectPackageJson.devDependencies && projectPackageJson.devDependencies[dependency.name]); - - if (!dependencyVersionInProject) { - await this.$packageManager.install(`${dependency.name}@${dependency.version}`, projectData.projectDir, npmConfig); - } else { - const cleanedVersion = semver.clean(dependencyVersionInProject); - - // The plugin version is not valid. Check node_modules for the valid version. - if (!cleanedVersion) { - const pathToPluginPackageJson = path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME, dependency.name, constants.PACKAGE_JSON_FILE_NAME); - dependencyVersionInProject = this.$fs.exists(pathToPluginPackageJson) && this.$fs.readJson(pathToPluginPackageJson).version; - } - - if (!semver.satisfies(dependencyVersionInProject || cleanedVersion, dependency.version)) { - this.$errors.failWithoutHelp(`Your project have installed ${dependency.name} version ${cleanedVersion} but Android platform requires version ${dependency.version}.`); - } - } - } } private cleanResValues(targetSdkVersion: number, projectData: IProjectData): void { @@ -442,166 +393,37 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public async preparePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise { - if (!this.runtimeVersionIsGreaterThanOrEquals(projectData, "3.3.0")) { - const pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME); - await this.processResourcesFromPlugin(pluginData, pluginPlatformsFolderPath, projectData); - } else if (this.runtimeVersionIsGreaterThanOrEquals(projectData, "4.0.0")) { - // build Android plugins which contain AndroidManifest.xml and/or resources - const pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME); - if (this.$fs.exists(pluginPlatformsFolderPath)) { - const options: IPluginBuildOptions = { - projectDir: projectData.projectDir, - pluginName: pluginData.name, - platformsAndroidDirPath: pluginPlatformsFolderPath, - aarOutputDir: pluginPlatformsFolderPath, - tempPluginDirPath: path.join(projectData.platformsDir, "tempPlugin") - }; + // build Android plugins which contain AndroidManifest.xml and/or resources + const pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, AndroidProjectService.ANDROID_PLATFORM_NAME); + if (this.$fs.exists(pluginPlatformsFolderPath)) { + const options: IPluginBuildOptions = { + projectDir: projectData.projectDir, + pluginName: pluginData.name, + platformsAndroidDirPath: pluginPlatformsFolderPath, + aarOutputDir: pluginPlatformsFolderPath, + tempPluginDirPath: path.join(projectData.platformsDir, "tempPlugin") + }; - await this.prebuildNativePlugin(options); + if (await this.$androidPluginBuildService.buildAar(options)) { + this.$logger.info(`Built aar for ${options.pluginName}`); } - } - - // Do nothing, the Android Gradle script will configure itself based on the input dependencies.json - } - - public async checkIfPluginsNeedBuild(projectData: IProjectData): Promise> { - const detectedPlugins: Array<{ platformsAndroidDirPath: string, pluginName: string }> = []; - - const platformsAndroid = path.join(constants.PLATFORMS_DIR_NAME, "android"); - const pathToPlatformsAndroid = path.join(projectData.projectDir, platformsAndroid); - const dependenciesJson = await this.$fs.readJson(path.join(pathToPlatformsAndroid, constants.DEPENDENCIES_JSON_NAME)); - const productionDependencies = dependenciesJson.map((item: any) => { - return path.resolve(pathToPlatformsAndroid, item.directory); - }); - for (const dependency of productionDependencies) { - const jsonContent = this.$fs.readJson(path.join(dependency, constants.PACKAGE_JSON_FILE_NAME)); - const isPlugin = !!jsonContent.nativescript; - const pluginName = jsonContent.name; - if (isPlugin) { - const platformsAndroidDirPath = path.join(dependency, platformsAndroid); - if (this.$fs.exists(platformsAndroidDirPath)) { - let hasGeneratedAar = false; - let generatedAarPath = ""; - const nativeFiles = this.$fs.enumerateFilesInDirectorySync(platformsAndroidDirPath).filter((item) => { - if (isRecommendedAarFile(item, pluginName)) { - generatedAarPath = item; - hasGeneratedAar = true; - } - return this.isAllowedFile(item); - }); - - if (hasGeneratedAar) { - const aarStat = this.$fs.getFsStats(generatedAarPath); - nativeFiles.forEach((item) => { - const currentItemStat = this.$fs.getFsStats(item); - if (currentItemStat.mtime > aarStat.mtime) { - detectedPlugins.push({ - platformsAndroidDirPath, - pluginName - }); - } - }); - } else if (nativeFiles.length > 0) { - detectedPlugins.push({ - platformsAndroidDirPath, - pluginName - }); - } - } - } + this.$androidPluginBuildService.migrateIncludeGradle(options); } - return detectedPlugins; - } - - private isAllowedFile(item: string): boolean { - return item.endsWith(constants.MANIFEST_FILE_NAME) || item.endsWith(constants.RESOURCES_DIR); - } - - public async prebuildNativePlugin(options: IPluginBuildOptions): Promise { - if (await this.$androidPluginBuildService.buildAar(options)) { - this.$logger.info(`Built aar for ${options.pluginName}`); - } - - this.$androidPluginBuildService.migrateIncludeGradle(options); } public async processConfigurationFilesFromAppResources(): Promise { return; } - private async processResourcesFromPlugin(pluginData: IPluginData, pluginPlatformsFolderPath: string, projectData: IProjectData): Promise { - const configurationsDirectoryPath = path.join(this.getPlatformData(projectData).projectRoot, "configurations"); - this.$fs.ensureDirectoryExists(configurationsDirectoryPath); - - const pluginConfigurationDirectoryPath = path.join(configurationsDirectoryPath, pluginData.name); - if (this.$fs.exists(pluginPlatformsFolderPath)) { - this.$fs.ensureDirectoryExists(pluginConfigurationDirectoryPath); - - const isScoped = pluginData.name.indexOf("@") === 0; - const flattenedDependencyName = isScoped ? pluginData.name.replace("/", "_") : pluginData.name; - - // Copy all resources from plugin - const resourcesDestinationDirectoryPath = path.join(this.getPlatformData(projectData).projectRoot, constants.SRC_DIR, flattenedDependencyName); - this.$fs.ensureDirectoryExists(resourcesDestinationDirectoryPath); - shell.cp("-Rf", path.join(pluginPlatformsFolderPath, "*"), resourcesDestinationDirectoryPath); - - const filesForInterpolation = this.$fs.enumerateFilesInDirectorySync(resourcesDestinationDirectoryPath, file => this.$fs.getFsStats(file).isDirectory() || path.extname(file) === constants.XML_FILE_EXTENSION) || []; - for (const file of filesForInterpolation) { - this.$logger.trace(`Interpolate data for plugin file: ${file}`); - await this.$pluginVariablesService.interpolate(pluginData, file, projectData.projectDir, projectData.projectIdentifiers.android); - } - } - - // Copy include.gradle file - const includeGradleFilePath = path.join(pluginPlatformsFolderPath, constants.INCLUDE_GRADLE_NAME); - if (this.$fs.exists(includeGradleFilePath)) { - shell.cp("-f", includeGradleFilePath, pluginConfigurationDirectoryPath); - } - } - public async removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise { - try { - if (!this.runtimeVersionIsGreaterThanOrEquals(projectData, "3.3.0")) { - const pluginConfigDir = path.join(this.getPlatformData(projectData).projectRoot, "configurations", pluginData.name); - if (this.$fs.exists(pluginConfigDir)) { - await this.cleanProject(this.getPlatformData(projectData).projectRoot, projectData); - } - } - } catch (e) { - if (e.code === "ENOENT") { - this.$logger.debug("No native code jars found: " + e.message); - } else { - throw e; - } - } + // not implemented } public async beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise { - const shouldUseNewRoutine = this.runtimeVersionIsGreaterThanOrEquals(projectData, "3.3.0"); - if (dependencies) { dependencies = this.filterUniqueDependencies(dependencies); - if (shouldUseNewRoutine) { - this.provideDependenciesJson(projectData, dependencies); - } else { - const platformDir = path.join(projectData.platformsDir, AndroidProjectService.ANDROID_PLATFORM_NAME); - const buildDir = path.join(platformDir, "build-tools"); - const checkV8dependants = path.join(buildDir, "check-v8-dependants.js"); - if (this.$fs.exists(checkV8dependants)) { - const stringifiedDependencies = JSON.stringify(dependencies); - try { - await this.spawn('node', [checkV8dependants, stringifiedDependencies, projectData.platformsDir], { stdio: "inherit" }); - } catch (e) { - this.$logger.info("Checking for dependants on v8 public API failed. This is likely caused because of cyclic production dependencies. Error code: " + e.code + "\nMore information: https://github.com/NativeScript/nativescript-cli/issues/2561"); - } - } - } - } - - if (!shouldUseNewRoutine) { - const projectRoot = this.getPlatformData(projectData).projectRoot; - await this.cleanProject(projectRoot, projectData); + this.provideDependenciesJson(projectData, dependencies); } } @@ -763,17 +585,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return semver.gte(normalizedPlatformVersion, androidStudioCompatibleTemplate); } - private runtimeVersionIsGreaterThanOrEquals(projectData: IProjectData, versionString: string): boolean { - const platformVersion = this.getCurrentPlatformVersion(this.getPlatformData(projectData), projectData); - - if (platformVersion === constants.PackageVersion.NEXT) { - return true; - } - - const normalizedPlatformVersion = `${semver.major(platformVersion)}.${semver.minor(platformVersion)}.0`; - return semver.gte(normalizedPlatformVersion, versionString); - } - private getLegacyAppResourcesDestinationDirPath(projectData: IProjectData): string { const resourcePath: string[] = [constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR]; if (this.isAndroidStudioTemplate) { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 85960e4ec2..65156eb9cc 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -1038,12 +1038,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } } - public async prebuildNativePlugin(options: IPluginBuildOptions): Promise { /** */ } - - public async checkIfPluginsNeedBuild(projectData: IProjectData): Promise> { - return []; - } - public getDeploymentTarget(projectData: IProjectData): semver.SemVer { const target = this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "IPHONEOS_DEPLOYMENT_TARGET"); if (!target) { diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index e481600407..591c03214e 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -757,7 +757,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { platform = platform.split("@")[0].toLowerCase(); - if (!this.isValidPlatform(platform, projectData)) { + if (!this.$platformsData.getPlatformData(platform, projectData)) { this.$errors.fail("Invalid platform %s. Valid platforms are %s.", platform, helpers.formatListOfNames(this.$platformsData.platformsNames)); } } @@ -822,10 +822,6 @@ export class PlatformService extends EventEmitter implements IPlatformService { return this.$fs.exists(path.join(projectData.platformsDir, platform.toLowerCase())); } - private isValidPlatform(platform: string, projectData: IProjectData) { - return this.$platformsData.getPlatformData(platform, projectData); - } - 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; diff --git a/test/debug.ts b/test/debug.ts deleted file mode 100644 index a3de9409d4..0000000000 --- a/test/debug.ts +++ /dev/null @@ -1,115 +0,0 @@ -import * as stubs from "./stubs"; -import * as yok from "../lib/common/yok"; -import { DebugAndroidCommand } from "../lib/commands/debug"; -import { assert } from "chai"; -import { BundleValidatorHelper } from "../lib/helpers/bundle-validator-helper"; -import { Configuration, StaticConfig } from "../lib/config"; -import { Options } from "../lib/options"; -import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; -import { FileSystem } from "../lib/common/file-system"; -import { AndroidProjectService } from "../lib/services/android-project-service"; -import { AndroidDebugBridge } from "../lib/common/mobile/android/android-debug-bridge"; -import { AndroidDebugBridgeResultHandler } from "../lib/common/mobile/android/android-debug-bridge-result-handler"; -import { SettingsService } from "../lib/common/test/unit-tests/stubs"; - -function createTestInjector(): IInjector { - const testInjector: IInjector = new yok.Yok(); - - testInjector.register("debug|android", DebugAndroidCommand); - testInjector.register("config", Configuration); - testInjector.register("staticConfig", StaticConfig); - testInjector.register("logger", stubs.LoggerStub); - testInjector.register("options", Options); - testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); - testInjector.register('childProcess', stubs.ChildProcessStub); - testInjector.register('fs', FileSystem); - testInjector.register('errors', stubs.ErrorsStub); - testInjector.register('hostInfo', {}); - testInjector.register("androidBundleValidatorHelper", stubs.AndroidBundleValidatorHelper); - testInjector.register("bundleValidatorHelper", BundleValidatorHelper); - testInjector.register("analyticsService", { - trackException: async (): Promise => undefined, - checkConsent: async (): Promise => undefined, - trackFeature: async (): Promise => undefined - }); - testInjector.register('devicesService', { - initialize: async () => { /* Intentionally left blank */ }, - getDeviceInstances: (): any[] => { return []; }, - execute: async (): Promise => ({}) - }); - testInjector.register("liveSyncService", stubs.LiveSyncServiceStub); - testInjector.register("androidProjectService", AndroidProjectService); - testInjector.register("androidToolsInfo", stubs.AndroidToolsInfoStub); - testInjector.register("hostInfo", {}); - testInjector.register("projectData", { platformsDir: "test", initializeProjectData: () => { /* empty */ } }); - testInjector.register("projectDataService", {}); - testInjector.register("sysInfo", {}); - testInjector.register("mobileHelper", {}); - testInjector.register("pluginVariablesService", {}); - testInjector.register("projectTemplatesService", {}); - testInjector.register("debugService", {}); - testInjector.register("xmlValidator", {}); - testInjector.register("packageManager", {}); - testInjector.register("debugDataService", { - createDebugData: () => ({}) - }); - testInjector.register("androidEmulatorServices", {}); - testInjector.register("adb", AndroidDebugBridge); - testInjector.register("androidDebugBridgeResultHandler", AndroidDebugBridgeResultHandler); - testInjector.register("platformService", stubs.PlatformServiceStub); - testInjector.register("platformsData", { - availablePlatforms: { - Android: "Android", - iOS: "iOS" - } - }); - - testInjector.register("prompter", {}); - testInjector.registerCommand("debug|android", DebugAndroidCommand); - testInjector.register("liveSyncCommandHelper", { - executeLiveSyncOperation: async (): Promise => { - return null; - } - }); - testInjector.register("settingsService", SettingsService); - testInjector.register("androidPluginBuildService", stubs.AndroidPluginBuildServiceStub); - testInjector.register("platformEnvironmentRequirements", {}); - testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub); - testInjector.register("filesHashService", {}); - - return testInjector; -} - -describe("debug command tests", () => { - describe("Debugger tests", () => { - let testInjector: IInjector; - - beforeEach(() => { - testInjector = createTestInjector(); - }); - - it("Ensures that beforePrepareAllPlugins will call gradle with clean option when *NOT* livesyncing", async () => { - const platformData = testInjector.resolve("platformsData"); - platformData.frameworkPackageName = "tns-android"; - - // only test that 'clean' is performed on android <=3.2. See https://github.com/NativeScript/nativescript-cli/pull/3032 - const projectDataService: IProjectDataService = testInjector.resolve("projectDataService"); - projectDataService.getNSValue = (projectDir: string, propertyName: string) => { - return { version: "3.2.0" }; - }; - - const childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); - const androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); - androidProjectService.getPlatformData = (_projectData: IProjectData): IPlatformData => { - return platformData; - }; - const projectData: IProjectData = testInjector.resolve("projectData"); - const spawnFromEventCount = childProcess.spawnFromEventCount; - await androidProjectService.beforePrepareAllPlugins(projectData); - assert.isTrue(childProcess.lastCommand.indexOf("gradle") !== -1); - assert.isTrue(childProcess.lastCommandArgs[0] === "clean"); - assert.isTrue(spawnFromEventCount === 0); - assert.isTrue(spawnFromEventCount + 1 === childProcess.spawnFromEventCount); - }); - }); -}); diff --git a/test/stubs.ts b/test/stubs.ts index 7746d4b4b8..cd20ee835c 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -388,13 +388,7 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor fastLivesyncFileExtensions: [] }; } - prebuildNativePlugin(options: IPluginBuildOptions): Promise { - return Promise.resolve(); - } - checkIfPluginsNeedBuild(projectData: IProjectData): Promise> { - return Promise.resolve([]); - } getAppResourcesDestinationDirectoryPath(): string { return ""; } From f918e28fed18b953ed2b65fc48c219e254d08ae0 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 10 Apr 2019 14:51:42 +0300 Subject: [PATCH 004/102] chore: delete plugin variables service --- lib/common/declarations.d.ts | 5 - lib/common/plugin-variables-helper.ts | 90 ------ lib/definitions/plugins.d.ts | 47 --- lib/services/ios-project-service.ts | 6 - lib/services/plugin-variables-service.ts | 104 ------- lib/services/plugins-service.ts | 14 - test/ios-project-service.ts | 4 - test/plugin-variables-service.ts | 372 ----------------------- test/update.ts | 1 - 9 files changed, 643 deletions(-) delete mode 100644 lib/common/plugin-variables-helper.ts delete mode 100644 lib/services/plugin-variables-service.ts delete mode 100644 test/plugin-variables-service.ts diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index 4b27870564..252dd0bfd7 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -1242,11 +1242,6 @@ interface IResourceLoader { readJson(path: string): any; } -interface IPluginVariablesHelper { - getPluginVariableFromVarOption(variableName: string, configuration?: string): any; - simplifyYargsObject(obj: any, configuration?: string): any; -} - /** * Used for getting strings for informational/error messages. */ diff --git a/lib/common/plugin-variables-helper.ts b/lib/common/plugin-variables-helper.ts deleted file mode 100644 index 45800e10f2..0000000000 --- a/lib/common/plugin-variables-helper.ts +++ /dev/null @@ -1,90 +0,0 @@ -export class PluginVariablesHelper implements IPluginVariablesHelper { - constructor(private $options: IOptions) { } - - /** - * Checks if the specified pluginVariable exists in the --var option specified by user. - * The variable can be added to --var option for configuration or globally, for ex.: - * `--var.APP_ID myAppIdentifier` or `--var.debug.APP_ID myAppIdentifier`. - * NOTE: If the variable is added for specific configuration and globally, - * the value for the specified configuration will be used as it has higher priority. For ex.: - * `--var.APP_ID myAppIdentifier1 --var.debug.APP_ID myAppIdentifier2` will return myAppIdentifier2 for debug configuration - * and myAppIdentifier for release configuration. - * @param {string} variableName The name of the plugin variable. - * @param {string} configuration The configuration for which the variable will be used. - * @returns {any} The value of the plugin variable specified in --var or undefined. - */ - public getPluginVariableFromVarOption(variableName: string, configuration?: string): any { - let varOption = this.$options.var; - configuration = configuration ? configuration.toLowerCase() : undefined; - const lowerCasedVariableName = variableName.toLowerCase(); - if (varOption) { - let configVariableValue: string; - let generalVariableValue: string; - if (variableName.indexOf(".") !== -1) { - varOption = this.simplifyYargsObject(varOption, configuration); - } - _.each(varOption, (propValue: any, propKey: string) => { - if (propKey.toLowerCase() === configuration) { - _.each(propValue, (configPropValue: string, configPropKey: string) => { - if (configPropKey.toLowerCase() === lowerCasedVariableName) { - configVariableValue = configPropValue; - return false; - } - }); - } else if (propKey.toLowerCase() === lowerCasedVariableName) { - generalVariableValue = propValue; - } - }); - - const value = configVariableValue || generalVariableValue; - if (value) { - const obj = Object.create(null); - obj[variableName] = value.toString(); - return obj; - } - } - - return undefined; - } - - /** - * Converts complicated yargs object with many subobjects, to simplified one. - * Use it when the plugin variable contains dots ("."). In this case yargs treats them as inner object instead of propery name. - * For ex. '--var.debug.DATA.APP.ID testId' will be converted to {debug: {DATA: {APP: {ID: testId}}}}, while we need {debug: {DATA.APP.ID: testId}} - * '--var.DATA.APP.ID testId' will be converted to DATA: {APP: {ID: testId}}}, while we need {DATA.APP.ID: testId} - * @param {any} obj varObject created by yargs - * @param {string} configuration The configuration for which the plugin variable will be used. - * @return {any} Converted object if the obj paramater is of type object, otherwise - the object itself. - */ - public simplifyYargsObject(obj: any, configuration?: string): any { - if (obj && typeof (obj) === "object") { - const convertedObject: any = Object.create({}); - - _.each(obj, (propValue: any, propKey: string) => { - if (typeof (propValue) !== "object") { - convertedObject[propKey] = propValue; - return false; - } - - configuration = configuration ? configuration.toLowerCase() : undefined; - const innerObj = this.simplifyYargsObject(propValue, configuration); - - if (propKey.toLowerCase() === configuration) { - // for --var.debug.DATA.APP.ID testId - convertedObject[propKey] = innerObj; - } else { - // for --var.DATA.APP.ID testId - _.each(innerObj, (innerPropValue: any, innerPropKey: string) => { - convertedObject[`${propKey}.${innerPropKey}`] = innerPropValue; - }); - } - - }); - - return convertedObject; - } - - return obj; - } -} -$injector.register("pluginVariablesHelper", PluginVariablesHelper); diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 8fce2600ba..46759de0dc 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -44,53 +44,6 @@ interface IPluginPlatformsData { android: string; } -interface IPluginVariablesService { - /** - * Saves plugin variables in project package.json file. - * @param {IPluginData} pluginData for the plugin. - * @param {string} projectDir: Specifies the directory of the project. - * @return {Promise} - */ - savePluginVariablesInProjectFile(pluginData: IPluginData, projectDir: string): Promise; - - /** - * Removes plugin variables from project package.json file. - * @param {string} pluginName Name of the plugin. - * @param {string} projectDir: Specifies the directory of the project. - * @return {void} - */ - removePluginVariablesFromProjectFile(pluginName: string, projectDir: string): void; - - /** - * Replaces all plugin variables with their corresponding values. - * @param {IPluginData} pluginData for the plugin. - * @param {pluginConfigurationFilePath} pluginConfigurationFilePath for the plugin. - * @param {string} projectDir: Specifies the directory of the project. - * @return {Promise} - */ - interpolatePluginVariables(pluginData: IPluginData, pluginConfigurationFilePath: string, projectDir: string): Promise; - - /** - * Replaces {nativescript.id} expression with the application identifier from package.json. - * @param {pluginConfigurationFilePath} pluginConfigurationFilePath for the plugin. - * @return {void} - */ - interpolateAppIdentifier(pluginConfigurationFilePath: string, projectIdentifier: string): void; - - /** - * Replaces both plugin variables and appIdentifier - */ - interpolate(pluginData: IPluginData, pluginConfigurationFilePath: string, projectDir: string, projectIdentifier: string): Promise; - - /** - * Returns the - * @param {string} pluginName for the plugin. - * @return {Promise} returns the changed plugin configuration file content. - */ - getPluginVariablePropertyName(pluginName: string): string; - -} - interface IPluginVariableData { defaultValue?: string; name?: string; diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 65156eb9cc..13fbf48e38 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -43,7 +43,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $devicesService: Mobile.IDevicesService, private $mobileHelper: Mobile.IMobileHelper, private $hostInfo: IHostInfo, - private $pluginVariablesService: IPluginVariablesService, private $xcprojService: IXcprojService, private $iOSProvisionService: IOSProvisionService, private $pbxprojDomXcode: IPbxprojDomXcode, @@ -800,11 +799,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f await this.mergeInfoPlists(projectData, opts); await this.$iOSEntitlementsService.merge(projectData); await this.mergeProjectXcconfigFiles(projectData, opts); - for (const pluginData of await this.getAllInstalledPlugins(projectData)) { - await this.$pluginVariablesService.interpolatePluginVariables(pluginData, this.getPlatformData(projectData).configurationFilePath, projectData.projectDir); - } - - this.$pluginVariablesService.interpolateAppIdentifier(this.getPlatformData(projectData).configurationFilePath, projectData.projectIdentifiers.ios); } private getInfoPlistPath(projectData: IProjectData): string { diff --git a/lib/services/plugin-variables-service.ts b/lib/services/plugin-variables-service.ts deleted file mode 100644 index d73ed0a56d..0000000000 --- a/lib/services/plugin-variables-service.ts +++ /dev/null @@ -1,104 +0,0 @@ -import * as helpers from "./../common/helpers"; - -export class PluginVariablesService implements IPluginVariablesService { - private static PLUGIN_VARIABLES_KEY = "variables"; - - constructor(private $errors: IErrors, - private $pluginVariablesHelper: IPluginVariablesHelper, - private $projectDataService: IProjectDataService, - private $prompter: IPrompter, - private $fs: IFileSystem) { } - - public getPluginVariablePropertyName(pluginName: string): string { - return `${pluginName}-${PluginVariablesService.PLUGIN_VARIABLES_KEY}`; - } - - public async savePluginVariablesInProjectFile(pluginData: IPluginData, projectDir: string): Promise { - const values = Object.create(null); - await this.executeForAllPluginVariables(pluginData, async (pluginVariableData: IPluginVariableData) => { - const pluginVariableValue = await this.getPluginVariableValue(pluginVariableData); - this.ensurePluginVariableValue(pluginVariableValue, `Unable to find value for ${pluginVariableData.name} plugin variable from ${pluginData.name} plugin. Ensure the --var option is specified or the plugin variable has default value.`); - values[pluginVariableData.name] = pluginVariableValue; - }, projectDir); - - if (!_.isEmpty(values)) { - this.$projectDataService.setNSValue(projectDir, this.getPluginVariablePropertyName(pluginData.name), values); - } - } - - public removePluginVariablesFromProjectFile(pluginName: string, projectDir: string): void { - this.$projectDataService.removeNSProperty(projectDir, this.getPluginVariablePropertyName(pluginName)); - } - - public async interpolatePluginVariables(pluginData: IPluginData, pluginConfigurationFilePath: string, projectDir: string): Promise { - let pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath); - await this.executeForAllPluginVariables(pluginData, async (pluginVariableData: IPluginVariableData) => { - this.ensurePluginVariableValue(pluginVariableData.value, `Unable to find the value for ${pluginVariableData.name} plugin variable into project package.json file. Verify that your package.json file is correct and try again.`); - pluginConfigurationFileContent = this.interpolateCore(pluginVariableData.name, pluginVariableData.value, pluginConfigurationFileContent); - }, projectDir); - - this.$fs.writeFile(pluginConfigurationFilePath, pluginConfigurationFileContent); - } - - public interpolateAppIdentifier(pluginConfigurationFilePath: string, projectIdentifier: string): void { - const pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath); - const newContent = this.interpolateCore("nativescript.id", projectIdentifier, pluginConfigurationFileContent); - this.$fs.writeFile(pluginConfigurationFilePath, newContent); - } - - public async interpolate(pluginData: IPluginData, pluginConfigurationFilePath: string, projectDir: string, projectIdentifier: string): Promise { - await this.interpolatePluginVariables(pluginData, pluginConfigurationFilePath, projectDir); - this.interpolateAppIdentifier(pluginConfigurationFilePath, projectIdentifier); - } - - private interpolateCore(name: string, value: string, content: string): string { - return content.replace(new RegExp(`{${name}}`, "gi"), value); - } - - private ensurePluginVariableValue(pluginVariableValue: string, errorMessage: string): void { - if (!pluginVariableValue) { - this.$errors.failWithoutHelp(errorMessage); - } - } - - private async getPluginVariableValue(pluginVariableData: IPluginVariableData): Promise { - const pluginVariableName = pluginVariableData.name; - let value = this.$pluginVariablesHelper.getPluginVariableFromVarOption(pluginVariableName); - if (value) { - value = value[pluginVariableName]; - } else { - value = pluginVariableData.defaultValue; - if (!value && helpers.isInteractive()) { - const promptSchema = { - name: pluginVariableName, - type: "input", - message: `Enter value for ${pluginVariableName} variable:`, - validate: (val: string) => !!val ? true : 'Please enter a value!' - }; - const promptData = await this.$prompter.get([promptSchema]); - value = promptData[pluginVariableName]; - } - } - - return value; - } - - private async executeForAllPluginVariables(pluginData: IPluginData, action: (pluginVariableData: IPluginVariableData) => Promise, projectDir: string): Promise { - const pluginVariables = pluginData.pluginVariables; - const pluginVariablesNames = _.keys(pluginVariables); - await Promise.all(_.map(pluginVariablesNames, pluginVariableName => action(this.createPluginVariableData(pluginData, pluginVariableName, projectDir)))); - } - - private createPluginVariableData(pluginData: IPluginData, pluginVariableName: string, projectDir: string): IPluginVariableData { - const variableData = pluginData.pluginVariables[pluginVariableName]; - - variableData.name = pluginVariableName; - - const pluginVariableValues = this.$projectDataService.getNSValue(projectDir, this.getPluginVariablePropertyName(pluginData.name)); - variableData.value = pluginVariableValues ? pluginVariableValues[pluginVariableName] : undefined; - - return variableData; - } -} - -$injector.register("pluginVariablesService", PluginVariablesService); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 143da2d6d8..7fbf435a71 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -12,9 +12,6 @@ export class PluginsService implements IPluginsService { private get $platformsData(): IPlatformsData { return this.$injector.resolve("platformsData"); } - private get $pluginVariablesService(): IPluginVariablesService { - return this.$injector.resolve("pluginVariablesService"); - } private get $projectDataService(): IProjectDataService { return this.$injector.resolve("projectDataService"); } @@ -57,16 +54,6 @@ export class PluginsService implements IPluginsService { await this.executeForAllInstalledPlatforms(action, projectData); - try { - await this.$pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData.projectDir); - } catch (err) { - // Revert package.json - this.$projectDataService.removeNSProperty(projectData.projectDir, this.$pluginVariablesService.getPluginVariablePropertyName(pluginData.name)); - await this.$packageManager.uninstall(plugin, PluginsService.NPM_CONFIG, projectData.projectDir); - - throw err; - } - this.$logger.out(`Successfully installed plugin ${realNpmPackageJson.name}.`); } else { await this.$packageManager.uninstall(realNpmPackageJson.name, { save: true }, projectData.projectDir); @@ -81,7 +68,6 @@ export class PluginsService implements IPluginsService { await platformData.platformProjectService.removePluginNativeCode(pluginData, projectData); }; - this.$pluginVariablesService.removePluginVariablesFromProjectFile(pluginName.toLowerCase(), projectData.projectDir); await this.executeForAllInstalledPlatforms(removePluginNativeCodeAction, projectData); await this.executeNpmCommand(PluginsService.UNINSTALL_COMMAND_NAME, pluginName, projectData); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 74468e6e6b..2efac35129 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -21,8 +21,6 @@ import { LoggingLevels } from "../lib/common/mobile/logging-levels"; import { DeviceDiscovery } from "../lib/common/mobile/mobile-core/device-discovery"; import { IOSDeviceDiscovery } from "../lib/common/mobile/mobile-core/ios-device-discovery"; import { AndroidDeviceDiscovery } from "../lib/common/mobile/mobile-core/android-device-discovery"; -import { PluginVariablesService } from "../lib/services/plugin-variables-service"; -import { PluginVariablesHelper } from "../lib/common/plugin-variables-helper"; import { Utils } from "../lib/common/utils"; import { CocoaPodsService } from "../lib/services/cocoapods-service"; import { PackageManager } from "../lib/package-manager"; @@ -113,8 +111,6 @@ function createTestInjector(projectPath: string, projectName: string, xCode?: IX checkIfXcodeprojIsRequired: () => ({}) }); testInjector.register("iosDeviceOperations", {}); - testInjector.register("pluginVariablesService", PluginVariablesService); - testInjector.register("pluginVariablesHelper", PluginVariablesHelper); testInjector.register("pluginsService", { getAllInstalledPlugins: (): string[] => [] }); diff --git a/test/plugin-variables-service.ts b/test/plugin-variables-service.ts deleted file mode 100644 index 86657459e3..0000000000 --- a/test/plugin-variables-service.ts +++ /dev/null @@ -1,372 +0,0 @@ -import { assert } from "chai"; -import { Errors } from "../lib/common/errors"; -import { FileSystem } from "../lib/common/file-system"; -import { HostInfo } from "../lib/common/host-info"; -import { Options } from "../lib/options"; -import { PluginVariablesHelper } from "../lib/common/plugin-variables-helper"; -import { PluginVariablesService } from "../lib/services/plugin-variables-service"; -import { ProjectData } from "../lib/project-data"; -import { ProjectDataService } from "../lib/services/project-data-service"; -import { ProjectHelper } from "../lib/common/project-helper"; -import { StaticConfig } from "../lib/config"; -import { MessagesService } from "../lib/common/services/messages-service"; -import { Yok } from '../lib/common/yok'; -import { SettingsService } from "../lib/common/test/unit-tests/stubs"; -import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; -import * as stubs from './stubs'; -import * as path from "path"; -import * as temp from "temp"; -temp.track(); - -function createTestInjector(): IInjector { - const testInjector = new Yok(); - - testInjector.register("messagesService", MessagesService); - testInjector.register("errors", Errors); - testInjector.register("fs", FileSystem); - testInjector.register("hostInfo", HostInfo); - testInjector.register("logger", stubs.LoggerStub); - testInjector.register("options", Options); - testInjector.register("pluginVariablesHelper", PluginVariablesHelper); - testInjector.register("pluginVariablesService", PluginVariablesService); - testInjector.register("projectData", ProjectData); - testInjector.register("projectDataService", ProjectDataService); - testInjector.register("projectHelper", ProjectHelper); - testInjector.register("prompter", { - get: () => { - const errors: IErrors = testInjector.resolve("errors"); - errors.fail("$prompter.get function shouldn't be called!"); - } - }); - testInjector.register("staticConfig", StaticConfig); - testInjector.register("settingsService", SettingsService); - testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); - testInjector.register("androidResourcesMigrationService", { - hasMigrated: () => true - }); - - return testInjector; -} - -async function createProjectFile(testInjector: IInjector): Promise { - const tempFolder = temp.mkdirSync("pluginVariablesService"); - - const options = testInjector.resolve("options"); - options.path = tempFolder; - - const projectData = { - "name": "myProject", - "nativescript": { - id: { android: "", ios: ""} - } - }; - testInjector.resolve("fs").writeJson(path.join(tempFolder, "package.json"), projectData); - - return tempFolder; -} - -function createPluginData(pluginVariables: any): IPluginData { - const pluginData = { - name: "myTestPlugin", - version: "", - fullPath: "", - isPlugin: true, - moduleInfo: "", - platformsData: { - ios: "", - android: "" - }, - pluginVariables: pluginVariables, - pluginPlatformsFolderPath: (platform: string) => "" - }; - - return pluginData; -} - -describe("Plugin Variables service", () => { - let testInjector: IInjector; - beforeEach(() => { - testInjector = createTestInjector(); - }); - - describe("plugin add when the console is non interactive", () => { - beforeEach(() => { - const helpers = require("./../lib/common/helpers"); - helpers.isInteractive = () => false; - }); - it("fails when no --var option and no default value are specified", async () => { - await createProjectFile(testInjector); - - const pluginVariables = { "MY_TEST_PLUGIN_VARIABLE": {} }; - const pluginData = createPluginData(pluginVariables); - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const projectData: IProjectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - - const expectedError = `Unable to find value for MY_TEST_PLUGIN_VARIABLE plugin variable from ${pluginData.name} plugin. Ensure the --var option is specified or the plugin variable has default value.`; - let actualError: string = null; - - try { - await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData.projectDir); - } catch (err) { - actualError = err.message; - } - - assert.equal(expectedError, actualError); - }); - it("does not fail when --var option is specified", async () => { - await createProjectFile(testInjector); - - const pluginVariableValue = "myAppId"; - testInjector.resolve("options").var = { - "MY_APP_ID": pluginVariableValue - }; - - const pluginVariables = { "MY_APP_ID": {} }; - const pluginData = createPluginData(pluginVariables); - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const projectData: IProjectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData.projectDir); - - const fs = testInjector.resolve("fs"); - const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - - const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); - assert.equal(pluginVariableValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["MY_APP_ID"]); - }); - it("does not fail when default value is specified", async () => { - await createProjectFile(testInjector); - - const defaultPluginValue = "myDefaultValue"; - const pluginVariables = { "MY_TEST_PLUGIN_VARIABLE": { defaultValue: defaultPluginValue } }; - const pluginData = createPluginData(pluginVariables); - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const projectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - - await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData.projectDir); - - const fs = testInjector.resolve("fs"); - const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - - const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); - assert.equal(defaultPluginValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["MY_TEST_PLUGIN_VARIABLE"]); - }); - }); - - describe("plugin add when the console is interactive", () => { - beforeEach(() => { - const helpers = require("./../lib/common/helpers"); - helpers.isInteractive = () => true; - }); - it("prompt for plugin variable value when no --var option and no default value are specified", async () => { - await createProjectFile(testInjector); - - const pluginVariableValue = "testAppURL"; - const prompter = testInjector.resolve("prompter"); - prompter.get = async () => ({ "APP_URL": pluginVariableValue }); - - const pluginVariables = { "APP_URL": {} }; - const pluginData = createPluginData(pluginVariables); - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const projectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData.projectDir); - - const fs = testInjector.resolve("fs"); - const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - - const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); - assert.equal(pluginVariableValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["APP_URL"]); - }); - it("does not prompt for plugin variable value when default value is specified", async () => { - await createProjectFile(testInjector); - - const defaultPluginValue = "myAppNAme"; - const pluginVariables = { "APP_NAME": { defaultValue: defaultPluginValue } }; - const pluginData = createPluginData(pluginVariables); - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const projectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - - await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData.projectDir); - - const fs = testInjector.resolve("fs"); - const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - - const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); - assert.equal(defaultPluginValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["APP_NAME"]); - }); - it("does not prompt for plugin variable value when --var option is specified", async () => { - await createProjectFile(testInjector); - - const pluginVariableValue = "pencho.goshko"; - testInjector.resolve("options").var = { - "USERNAME": pluginVariableValue - }; - - const pluginVariables = { "USERNAME": {} }; - const pluginData = createPluginData(pluginVariables); - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const projectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - await pluginVariablesService.savePluginVariablesInProjectFile(pluginData, projectData.projectDir); - - const fs = testInjector.resolve("fs"); - const staticConfig: IStaticConfig = testInjector.resolve("staticConfig"); - - const projectFileContent = fs.readJson(path.join(projectData.projectDir, "package.json")); - assert.equal(pluginVariableValue, projectFileContent[staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][`${pluginData.name}-variables`]["USERNAME"]); - }); - }); - - describe("plugin interpolation", () => { - it("fails when the plugin value is undefined", async () => { - const tempFolder = await createProjectFile(testInjector); - - const pluginVariables = { "MY_VAR": {} }; - const pluginData = createPluginData(pluginVariables); - - const fs: IFileSystem = testInjector.resolve("fs"); - const filePath = path.join(tempFolder, "myfile"); - fs.writeFile(filePath, ""); - - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const projectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - - const expectedError = "Unable to find the value for MY_VAR plugin variable into project package.json file. Verify that your package.json file is correct and try again."; - let error: string = null; - try { - await pluginVariablesService.interpolatePluginVariables(pluginData, filePath, projectData.projectDir); - } catch (err) { - error = err.message; - } - - assert.equal(error, expectedError); - }); - - it("interpolates correctly plugin variable value", async () => { - const tempFolder = await createProjectFile(testInjector); - - const projectData: IProjectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - const fs: IFileSystem = testInjector.resolve("fs"); - - // Write plugin variables values to package.json file - const packageJsonFilePath = path.join(projectData.projectDir, "package.json"); - const data = fs.readJson(packageJsonFilePath); - data["nativescript"]["myTestPlugin-variables"] = { - "FB_APP_NAME": "myFacebookAppName" - }; - fs.writeJson(packageJsonFilePath, data); - - const pluginVariables = { "FB_APP_NAME": {} }; - const pluginData = createPluginData(pluginVariables); - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const pluginConfigurationFileContent = '' + - '' + - '' + - '' + - '' + - ''; - const filePath = path.join(tempFolder, "myfile"); - fs.writeFile(filePath, pluginConfigurationFileContent); - - await pluginVariablesService.interpolatePluginVariables(pluginData, filePath, projectData.projectDir); - - const result = fs.readText(filePath); - const expectedResult = '' + - '' + - '' + - '' + - '' + - ''; - - assert.equal(result, expectedResult); - }); - - it("interpolates correctly case sensive plugin variable value", async () => { - const tempFolder = await createProjectFile(testInjector); - - const projectData: IProjectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - const fs: IFileSystem = testInjector.resolve("fs"); - - // Write plugin variables values to package.json file - const packageJsonFilePath = path.join(projectData.projectDir, "package.json"); - const data = fs.readJson(packageJsonFilePath); - data["nativescript"]["myTestPlugin-variables"] = { - "FB_APP_NAME": "myFacebookAppName" - }; - fs.writeJson(packageJsonFilePath, data); - - const pluginVariables = { "FB_APP_NAME": {} }; - const pluginData = createPluginData(pluginVariables); - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const pluginConfigurationFileContent = '' + - '' + - '' + - '' + - '' + - ''; - const filePath = path.join(tempFolder, "myfile"); - fs.writeFile(filePath, pluginConfigurationFileContent); - - await pluginVariablesService.interpolatePluginVariables(pluginData, filePath, projectData.projectDir); - - const result = fs.readText(filePath); - const expectedResult = '' + - '' + - '' + - '' + - '' + - ''; - - assert.equal(result, expectedResult); - }); - - it("interpolates correctly more than one plugin variables values", async () => { - const tempFolder = await createProjectFile(testInjector); - - const projectData: IProjectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - const fs: IFileSystem = testInjector.resolve("fs"); - - const packageJsonFilePath = path.join(projectData.projectDir, "package.json"); - const data = fs.readJson(packageJsonFilePath); - data["nativescript"]["myTestPlugin-variables"] = { - "FB_APP_NAME": "myFacebookAppName", - "FB_APP_URL": "myFacebookAppURl" - }; - fs.writeJson(packageJsonFilePath, data); - - const pluginVariables = { "FB_APP_NAME": {}, "FB_APP_URL": {} }; - const pluginData = createPluginData(pluginVariables); - const pluginVariablesService: IPluginVariablesService = testInjector.resolve("pluginVariablesService"); - const pluginConfigurationFileContent = '' + - '' + - '' + - '' + - '' + - '' + - ''; - const filePath = path.join(tempFolder, "myfile"); - fs.writeFile(filePath, pluginConfigurationFileContent); - - await pluginVariablesService.interpolatePluginVariables(pluginData, filePath, projectData.projectDir); - - const result = fs.readText(filePath); - const expectedResult = '' + - '' + - '' + - '' + - '' + - '' + - ''; - - assert.equal(result, expectedResult); - }); - }); -}); diff --git a/test/update.ts b/test/update.ts index 65b162c48a..80eb387f02 100644 --- a/test/update.ts +++ b/test/update.ts @@ -47,7 +47,6 @@ function createTestInjector( return "1.0.0"; } }); - testInjector.register("pluginVariablesService", {}); testInjector.register("platformService", { getInstalledPlatforms: function(): string[] { return installedPlatforms; From 4eee60419369554a47b74aa6d999791f3fdb7f37 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 11 Apr 2019 15:57:05 +0300 Subject: [PATCH 005/102] chore: remove legacy code from ios-project-service --- lib/definitions/plugins.d.ts | 1 - lib/definitions/project.d.ts | 2 - lib/services/android-project-service.ts | 2 - lib/services/ios-project-service.ts | 152 +----------------- lib/services/plugins-service.ts | 4 - .../prepare-platform-native-service.ts | 3 - test/stubs.ts | 3 - 7 files changed, 3 insertions(+), 164 deletions(-) diff --git a/lib/definitions/plugins.d.ts b/lib/definitions/plugins.d.ts index 46759de0dc..481f56057b 100644 --- a/lib/definitions/plugins.d.ts +++ b/lib/definitions/plugins.d.ts @@ -10,7 +10,6 @@ interface IPluginsService { * @returns {IPackageJsonDepedenciesResult} */ getDependenciesFromPackageJson(projectDir: string): IPackageJsonDepedenciesResult; - validate(platformData: IPlatformData, projectData: IProjectData): Promise; preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise; convertToPluginData(cacheData: any, projectDir: string): IPluginData; isNativeScriptPlugin(pluginPackageJsonPath: string): boolean; diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 3f20ac098f..f2d7bb8905 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -372,8 +372,6 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS */ validateOptions(projectId?: string, provision?: true | string, teamId?: true | string): Promise; - validatePlugins(projectData: IProjectData): Promise; - buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise; /** diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 47e3a10a4b..77f48e7878 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -145,8 +145,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject }; } - public async validatePlugins(): Promise { /* */ } - public async createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise { if (semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) { this.$errors.failWithoutHelp(`The NativeScript CLI requires Android runtime ${AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE} or later to work properly.`); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 13fbf48e38..3e40303a9e 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -50,7 +50,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $iOSEntitlementsService: IOSEntitlementsService, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $plistParser: IPlistParser, - private $sysInfo: ISysInfo, private $xcconfigService: IXcconfigService, private $iOSExtensionsService: IIOSExtensionsService) { super($fs, $projectDataService); @@ -110,25 +109,17 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ if (provision === true) { await this.$iOSProvisionService.listProvisions(projectId); this.$errors.failWithoutHelp("Please provide provisioning profile uuid or name with the --provision option."); - return false; } if (teamId === true) { await this.$iOSProvisionService.listTeams(); this.$errors.failWithoutHelp("Please provide team id or team name with the --teamId options."); - return false; } return true; } public getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string { - const frameworkVersion = this.getFrameworkVersion(projectData); - - if (semver.lt(frameworkVersion, "1.3.0")) { - return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName, "Resources", "icons"); - } - return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName, "Resources"); } @@ -342,12 +333,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ 'SHARED_PRECOMPS_DIR=' + path.join(projectRoot, 'build', 'sharedpch') ]; - // Starting from tns-ios 1.4 the xcconfig file is referenced in the project template - const frameworkVersion = this.getFrameworkVersion(projectData); - if (semver.lt(frameworkVersion, "1.4.0")) { - basicArgs.push("-xcconfig", path.join(projectRoot, projectData.projectName, BUILD_XCCONFIG_FILE_NAME)); - } - const handler = (data: any) => { this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); }; @@ -367,17 +352,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.validateApplicationIdentifier(projectData); } - public async validatePlugins(projectData: IProjectData): Promise { - const installedPlugins = await (this.$injector.resolve("pluginsService")).getAllInstalledPlugins(projectData); - for (const pluginData of installedPlugins) { - const pluginsFolderExists = this.$fs.exists(path.join(pluginData.pluginPlatformsFolderPath(this.$devicePlatformsConstants.iOS.toLowerCase()), "Podfile")); - const cocoaPodVersion = await this.$sysInfo.getCocoaPodsVersion(); - if (pluginsFolderExists && !cocoaPodVersion) { - this.$errors.failWithoutHelp(`${pluginData.name} has Podfile and you don't have Cocoapods installed or it is not configured correctly. Please verify Cocoapods can work on your machine.`); - } - } - } - private async buildForDevice(projectRoot: string, args: string[], buildConfig: IBuildConfig, projectData: IProjectData): Promise { if (!buildConfig.release && !buildConfig.architectures) { await this.$devicesService.initialize({ @@ -406,24 +380,15 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR) ]); - const xcodeBuildVersion = await this.getXcodeVersion(); - if (helpers.versionCompare(xcodeBuildVersion, "8.0") >= 0) { - await this.setupSigningForDevice(projectRoot, buildConfig, projectData); - } + await this.setupSigningForDevice(projectRoot, buildConfig, projectData); await this.createIpa(projectRoot, projectData, buildConfig, args); } private async xcodebuild(args: string[], cwd: string, stdio: any = "inherit"): Promise { const localArgs = [...args]; - const xcodeBuildVersion = await this.getXcodeVersion(); - try { - if (helpers.versionCompare(xcodeBuildVersion, "9.0") >= 0) { - localArgs.push("-allowProvisioningUpdates"); - } - } catch (e) { - this.$logger.warn("Failed to detect whether -allowProvisioningUpdates can be used with your xcodebuild version due to error: " + e); - } + localArgs.push("-allowProvisioningUpdates"); + if (this.$logger.getLevel() === "INFO") { localArgs.push("-quiet"); this.$logger.info("Xcode build..."); @@ -650,87 +615,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return contentIsTheSame; } - /** - * Patch **LaunchScreen.xib** so we can be backward compatible for eternity. - * The **xcodeproj** template proior v**2.1.0** had blank white screen launch screen. - * We extended that by adding **app/AppResources/iOS/LaunchScreen.storyboard** - * However for projects created prior **2.1.0** to keep working without the obsolete **LaunchScreen.xib** - * we must still provide it on prepare. - * Here we check if **UILaunchStoryboardName** is set to **LaunchScreen** in the **platform/ios//-Info.plist**. - * If it is, and no **LaunchScreen.storyboard** nor **.xib** is found in the project, we will create one. - */ - private provideLaunchScreenIfMissing(projectData: IProjectData): void { - try { - this.$logger.trace("Checking if we need to provide compatability LaunchScreen.xib"); - const platformData = this.getPlatformData(projectData); - const projectPath = path.join(platformData.projectRoot, projectData.projectName); - const projectPlist = this.getInfoPlistPath(projectData); - const plistContent = plist.parse(this.$fs.readText(projectPlist)); - const storyName = plistContent["UILaunchStoryboardName"]; - this.$logger.trace(`Examining ${projectPlist} UILaunchStoryboardName: "${storyName}".`); - if (storyName !== "LaunchScreen") { - this.$logger.trace("The project has its UILaunchStoryboardName set to " + storyName + " which is not the pre v2.1.0 default LaunchScreen, probably the project is migrated so we are good to go."); - return; - } - - const expectedStoryPath = path.join(projectPath, "Resources", "LaunchScreen.storyboard"); - if (this.$fs.exists(expectedStoryPath)) { - // Found a LaunchScreen on expected path - this.$logger.trace("LaunchScreen.storyboard was found. Project is up to date."); - return; - } - this.$logger.trace("LaunchScreen file not found at: " + expectedStoryPath); - - const expectedXibPath = path.join(projectPath, "en.lproj", "LaunchScreen.xib"); - if (this.$fs.exists(expectedXibPath)) { - this.$logger.trace("Obsolete LaunchScreen.xib was found. It'k OK, we are probably running with iOS runtime from pre v2.1.0."); - return; - } - this.$logger.trace("LaunchScreen file not found at: " + expectedXibPath); - - const isTheLaunchScreenFile = (fileName: string) => fileName === "LaunchScreen.xib" || fileName === "LaunchScreen.storyboard"; - const matches = this.$fs.enumerateFilesInDirectorySync(projectPath, isTheLaunchScreenFile, { enumerateDirectories: false }); - if (matches.length > 0) { - this.$logger.trace("Found LaunchScreen by slowly traversing all files here: " + matches + "\nConsider moving the LaunchScreen so it could be found at: " + expectedStoryPath); - return; - } - - const compatabilityXibPath = path.join(projectPath, "Resources", "LaunchScreen.xib"); - this.$logger.warn(`Failed to find LaunchScreen.storyboard but it was specified in the Info.plist. -Consider updating the resources in app/App_Resources/iOS/. -A good starting point would be to create a new project and diff the changes with your current one. -Also the following repo may be helpful: https://github.com/NativeScript/template-hello-world/tree/master/App_Resources/iOS -We will now place an empty obsolete compatability white screen LauncScreen.xib for you in ${path.relative(projectData.projectDir, compatabilityXibPath)} so your app may appear as it did in pre v2.1.0 versions of the ios runtime.`); - - const content = ` - - - - - - - - - - - - - - - - -`; - try { - this.$fs.createDirectory(path.dirname(compatabilityXibPath)); - this.$fs.writeFile(compatabilityXibPath, content); - } catch (e) { - this.$logger.warn("We have failed to add compatability LaunchScreen.xib due to: " + e); - } - } catch (e) { - this.$logger.warn("We have failed to check if we need to add a compatability LaunchScreen.xib due to: " + e); - } - } - public async prepareProject(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { const projectRoot = path.join(projectData.platformsDir, "ios"); @@ -745,8 +629,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f const project = this.createPbxProj(projectData); - this.provideLaunchScreenIfMissing(projectData); - const resources = project.pbxGroupByName("Resources"); if (resources) { @@ -801,14 +683,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f await this.mergeProjectXcconfigFiles(projectData, opts); } - private getInfoPlistPath(projectData: IProjectData): string { - return path.join( - projectData.appResourcesDirectoryPath, - this.getPlatformData(projectData).normalizedPlatformName, - this.getPlatformData(projectData).configurationFileName - ); - } - public ensureConfigurationFileInAppResources(): void { return null; } @@ -826,11 +700,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f const infoPlistPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName); this.ensureConfigurationFileInAppResources(); - if (!this.$fs.exists(infoPlistPath)) { - this.$logger.trace("Info.plist: No app/App_Resources/iOS/Info.plist found, falling back to pre-1.6.0 Info.plist behavior."); - return; - } - const reporterTraceMessage = "Info.plist:"; const reporter: Reporter = { log: (txt: string) => this.$logger.trace(`${reporterTraceMessage} ${txt}`), @@ -1244,21 +1113,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f } } - private async getXcodeVersion(): Promise { - let xcodeBuildVersion = ""; - - try { - xcodeBuildVersion = await this.$sysInfo.getXcodeVersion(); - } catch (error) { - this.$errors.fail("xcodebuild execution failed. Make sure that you have latest Xcode and tools installed."); - } - - const splitedXcodeBuildVersion = xcodeBuildVersion.split("."); - xcodeBuildVersion = `${splitedXcodeBuildVersion[0] || 0}.${splitedXcodeBuildVersion[1] || 0}`; - - return xcodeBuildVersion; - } - private getBuildXCConfigFilePath(projectData: IProjectData): string { const buildXCConfig = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, BUILD_XCCONFIG_FILE_NAME); diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 7fbf435a71..ef5881c741 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -87,10 +87,6 @@ export class PluginsService implements IPluginsService { } } - public async validate(platformData: IPlatformData, projectData: IProjectData): Promise { - return await platformData.platformProjectService.validatePlugins(projectData); - } - public async preparePluginNativeCode(pluginData: IPluginData, platform: string, projectData: IProjectData): Promise { const platformData = this.$platformsData.getPlatformData(platform, projectData); pluginData.pluginPlatformsFolderPath = (_platform: string) => path.join(pluginData.fullPath, "platforms", _platform.toLowerCase()); diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/prepare-platform-native-service.ts index 4de09e99a7..5afbbb5dc6 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/prepare-platform-native-service.ts @@ -9,7 +9,6 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme $xmlValidator: IXmlValidator, $hooksService: IHooksService, private $nodeModulesBuilder: INodeModulesBuilder, - private $pluginsService: IPluginsService, private $projectChangesService: IProjectChangesService, private $androidResourcesMigrationService: IAndroidResourcesMigrationService) { super($fs, $hooksService, $xmlValidator); @@ -46,8 +45,6 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme const hasConfigChange = !config.changesInfo || config.changesInfo.configChanged; if (hasModulesChange) { - await this.$pluginsService.validate(config.platformData, config.projectData); - const appDestinationDirectoryPath = path.join(config.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); const lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath) ? this.$fs.getFsStats(appDestinationDirectoryPath).mtime : null; diff --git a/test/stubs.ts b/test/stubs.ts index cd20ee835c..38a5b31750 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -398,9 +398,6 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor validate(): Promise { return Promise.resolve({}); } - validatePlugins(projectData: IProjectData) { - return Promise.resolve(); - } async createProject(projectRoot: string, frameworkDir: string): Promise { return Promise.resolve(); } From 99a5ec844dcbcfe6f415d2d6e6ecdf77b6120339 Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 13 Apr 2019 20:58:01 +0300 Subject: [PATCH 006/102] refactor: refactor gradle related part of android-project-service * remove the support for targetSdk * extract the logic to separate services --- lib/bootstrap.ts | 3 + lib/definitions/gradle.d.ts | 24 +++ lib/definitions/project.d.ts | 4 +- lib/services/android-project-service.ts | 146 ++---------------- .../android/gradle-build-args-service.ts | 65 ++++++++ lib/services/android/gradle-build-service.ts | 30 ++++ .../android/gradle-command-service.ts | 31 ++++ lib/services/platform-service.ts | 10 +- 8 files changed, 174 insertions(+), 139 deletions(-) create mode 100644 lib/definitions/gradle.d.ts create mode 100644 lib/services/android/gradle-build-args-service.ts create mode 100644 lib/services/android/gradle-build-service.ts create mode 100644 lib/services/android/gradle-command-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index cc08b41de5..77f76187b3 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -11,6 +11,9 @@ $injector.require("performanceService", "./services/performance-service"); $injector.requirePublic("projectService", "./services/project-service"); $injector.require("androidProjectService", "./services/android-project-service"); $injector.require("androidPluginBuildService", "./services/android-plugin-build-service"); +$injector.require("gradleCommandService", "./services/android/gradle-command-service"); +$injector.require("gradleBuildService", "./services/android/gradle-build-service"); +$injector.require("gradleBuildArgsService", "./services/android/gradle-build-args-service"); $injector.require("iOSEntitlementsService", "./services/ios-entitlements-service"); $injector.require("iOSExtensionsService", "./services/ios-extensions-service"); $injector.require("iOSProjectService", "./services/ios-project-service"); diff --git a/lib/definitions/gradle.d.ts b/lib/definitions/gradle.d.ts new file mode 100644 index 0000000000..630dd568b9 --- /dev/null +++ b/lib/definitions/gradle.d.ts @@ -0,0 +1,24 @@ +interface IGradleCommandService { + executeCommand(gradleArgs: string[], options: IGradleCommandOptions): Promise; +} + +interface IGradleCommandOptions { + message?: string; + cwd: string; + stdio?: string; + spawnOptions?: any; +} + +interface IAndroidBuildConfig extends IRelease, IAndroidReleaseOptions, IHasAndroidBundle { + buildOutputStdio?: string; +} + +interface IGradleBuildService { + buildProject(projectRoot: string, buildConfig: IAndroidBuildConfig): Promise; + cleanProject(projectRoot: string, buildConfig: IAndroidBuildConfig): Promise; +} + +interface IGradleBuildArgsService { + getBuildTaskArgs(buildConfig: IAndroidBuildConfig): string[]; + getCleanTaskArgs(buildConfig: IAndroidBuildConfig): string[]; +} \ No newline at end of file diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index f2d7bb8905..48ac391674 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -439,10 +439,10 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS /** * Stops all running processes that might hold a lock on the filesystem. * Android: Gradle daemon processes are terminated. - * @param {string} projectRoot The root directory of the native project. + * @param {IPlatformData} platformData The data for the specified platform. * @returns {void} */ - stopServices(projectRoot: string): Promise; + stopServices(platformData: IPlatformData): Promise; /** * Removes build artifacts specific to the platform diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 77f48e7878..a247eec918 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -4,9 +4,7 @@ import * as constants from "../constants"; import * as semver from "semver"; import * as projectServiceBaseLib from "./platform-project-service-base"; import { DeviceAndroidDebugBridge } from "../common/mobile/android/device-android-debug-bridge"; -import { attachAwaitDetach } from "../common/helpers"; import { Configurations, LiveSyncPaths } from "../common/constants"; -import { SpawnOptions } from "child_process"; import { performanceLog } from ".././common/decorators"; export class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { @@ -18,10 +16,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private isAndroidStudioTemplate: boolean; constructor(private $androidToolsInfo: IAndroidToolsInfo, - private $childProcess: IChildProcess, private $errors: IErrors, $fs: IFileSystem, - private $hostInfo: IHostInfo, private $logger: ILogger, $projectDataService: IProjectDataService, private $injector: IInjector, @@ -29,7 +25,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $androidPluginBuildService: IAndroidPluginBuildService, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $androidResourcesMigrationService: IAndroidResourcesMigrationService, - private $filesHashService: IFilesHashService) { + private $filesHashService: IFilesHashService, + private $gradleCommandService: IGradleCommandService, + private $gradleBuildService: IGradleBuildService) { super($fs, $projectDataService); this.isAndroidStudioTemplate = false; } @@ -277,76 +275,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject @performanceLog() public async buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise { - let task; - const gradleArgs = this.getGradleBuildOptions(buildConfig, projectData); - const baseTask = buildConfig.androidBundle ? "bundle" : "assemble"; const platformData = this.getPlatformData(projectData); - const outputPath = buildConfig.androidBundle ? platformData.bundleBuildOutputPath : platformData.getBuildOutputPath(buildConfig); - if (this.$logger.getLevel() === "TRACE") { - gradleArgs.unshift("--stacktrace"); - gradleArgs.unshift("--debug"); - } - if (buildConfig.release) { - task = `${baseTask}Release`; - } else { - task = `${baseTask}Debug`; - } - - gradleArgs.unshift(task); - - const handler = (data: any) => { - this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data); - }; - - await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, - this.$childProcess, - handler, - this.executeCommand({ - projectRoot: this.getPlatformData(projectData).projectRoot, - gradleArgs, - childProcessOpts: { stdio: buildConfig.buildOutputStdio || "inherit" }, - spawnFromEventOptions: { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }, - message: "Gradle build..." - }) - ); + await this.$gradleBuildService.buildProject(platformData.projectRoot, buildConfig); + const outputPath = buildConfig.androidBundle ? platformData.bundleBuildOutputPath : platformData.getBuildOutputPath(buildConfig); await this.$filesHashService.saveHashesForProject(this._platformData, outputPath); } - private getGradleBuildOptions(settings: IAndroidBuildOptionsSettings, projectData: IProjectData): Array { - const configurationFilePath = this.getPlatformData(projectData).configurationFilePath; - - const buildOptions: Array = this.getBuildOptions(configurationFilePath); - - if (settings.release) { - buildOptions.push("-Prelease"); - buildOptions.push(`-PksPath=${path.resolve(settings.keyStorePath)}`); - buildOptions.push(`-Palias=${settings.keyStoreAlias}`); - buildOptions.push(`-Ppassword=${settings.keyStoreAliasPassword}`); - buildOptions.push(`-PksPassword=${settings.keyStorePassword}`); - } - - return buildOptions; - } - - private getBuildOptions(configurationFilePath?: string): Array { - this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, validateTargetSdk: true }); - - const androidToolsInfo = this.$androidToolsInfo.getToolsInfo(); - const compileSdk = androidToolsInfo.compileSdkVersion; - const targetSdk = this.getTargetFromAndroidManifest(configurationFilePath) || compileSdk; - const buildToolsVersion = androidToolsInfo.buildToolsVersion; - const generateTypings = androidToolsInfo.generateTypings; - const buildOptions = [ - `-PcompileSdk=android-${compileSdk}`, - `-PtargetSdk=${targetSdk}`, - `-PbuildToolsVersion=${buildToolsVersion}`, - `-PgenerateTypings=${generateTypings}` - ]; - - return buildOptions; - } - public async buildForDeploy(projectRoot: string, projectData: IProjectData, buildConfig?: IBuildConfig): Promise { return this.buildProject(projectRoot, projectData, buildConfig); } @@ -456,25 +391,19 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return nativescript && (nativescript.android || (nativescript.platforms && nativescript.platforms.android)); } - public stopServices(projectRoot: string): Promise { - return this.executeCommand({ - projectRoot, - gradleArgs: ["--stop", "--quiet"], - childProcessOpts: { stdio: "pipe" }, - message: "Gradle stop services..." + public async stopServices(platformData: IPlatformData): Promise { + const result = await this.$gradleCommandService.executeCommand(["--stop", "--quiet"], { + cwd: platformData.projectRoot, + message: "Gradle stop services...", + stdio: "pipe" }); + + return result; } public async cleanProject(projectRoot: string, projectData: IProjectData): Promise { - if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { - const gradleArgs = this.getGradleBuildOptions({ release: false }, projectData); - gradleArgs.unshift("clean"); - await this.executeCommand({ - projectRoot, - gradleArgs, - message: "Gradle clean..." - }); - } + // TODO: Check why there isn't a clean release build and a clean release aab build + await this.$gradleBuildService.cleanProject(projectRoot, { release: false }); } public async cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise { @@ -494,10 +423,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject shell.cp(cpArg, paths, projectRoot); } - private async spawn(command: string, args: string[], opts?: any, spawnOpts?: ISpawnFromEventOptions): Promise { - return this.$childProcess.spawnFromEvent(command, args, "close", opts || { stdio: "inherit" }, spawnOpts); - } - private validatePackageName(packageName: string): void { //Make the package conform to Java package types //Enforce underscore limitation @@ -522,49 +447,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - private getTargetFromAndroidManifest(configurationFilePath: string): string { - let versionInManifest: string; - if (this.$fs.exists(configurationFilePath)) { - const targetFromAndroidManifest: string = this.$fs.readText(configurationFilePath); - if (targetFromAndroidManifest) { - const match = targetFromAndroidManifest.match(/.*?android:targetSdkVersion=\"(.*?)\"/); - if (match && match[1]) { - versionInManifest = match[1]; - } - } - } - - return versionInManifest; - } - - private async executeCommand(opts: { projectRoot: string, gradleArgs: any, childProcessOpts?: SpawnOptions, spawnFromEventOptions?: ISpawnFromEventOptions, message: string }): Promise { - if (this.$androidToolsInfo.getToolsInfo().androidHomeEnvVar) { - const { projectRoot, gradleArgs, message, spawnFromEventOptions } = opts; - const gradlew = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew"; - - if (this.$logger.getLevel() === "INFO") { - gradleArgs.push("--quiet"); - } - - this.$logger.info(message); - - const childProcessOpts = opts.childProcessOpts || {}; - childProcessOpts.cwd = childProcessOpts.cwd || projectRoot; - childProcessOpts.stdio = childProcessOpts.stdio || "inherit"; - let commandResult; - try { - commandResult = await this.spawn(gradlew, - gradleArgs, - childProcessOpts, - spawnFromEventOptions); - } catch (err) { - this.$errors.failWithoutHelp(err.message); - } - - return commandResult; - } - } - private isAndroidStudioCompatibleTemplate(projectData: IProjectData, frameworkVersion?: string): boolean { const currentPlatformData: IDictionary = this.$projectDataService.getNSValue(projectData.projectDir, constants.TNS_ANDROID_RUNTIME_NAME); const platformVersion = (currentPlatformData && currentPlatformData[constants.VERSION_STRING]) || frameworkVersion; diff --git a/lib/services/android/gradle-build-args-service.ts b/lib/services/android/gradle-build-args-service.ts new file mode 100644 index 0000000000..d178dd7437 --- /dev/null +++ b/lib/services/android/gradle-build-args-service.ts @@ -0,0 +1,65 @@ +import * as path from "path"; +import { Configurations } from "../../common/constants"; + +export class GradleBuildArgsService implements IGradleBuildArgsService { + constructor(private $androidToolsInfo: IAndroidToolsInfo, + private $logger: ILogger) { } + + public getBuildTaskArgs(buildConfig: IAndroidBuildConfig): string[] { + const args = this.getBaseTaskArgs(buildConfig); + args.unshift(this.getBuildTaskName(buildConfig)); + + return args; + } + + public getCleanTaskArgs(buildConfig: IAndroidBuildConfig): string[] { + const args = this.getBaseTaskArgs(buildConfig); + args.unshift("clean"); + + return args; + } + + private getBaseTaskArgs(buildConfig: IAndroidBuildConfig): string[] { + const args = this.getBuildLoggingArgs(); + + const toolsInfo = this.$androidToolsInfo.getToolsInfo(); + args.push( + `-PcompileSdk=android-${toolsInfo.compileSdkVersion}`, + `-PbuildToolsVersion=${toolsInfo.buildToolsVersion}`, + `-PgenerateTypings=${toolsInfo.generateTypings}` + ); + + if (buildConfig.release) { + args.push( + "-Prelease", + `-PksPath=${path.resolve(buildConfig.keyStorePath)}`, + `-Palias=${buildConfig.keyStoreAlias}`, + `-Ppassword=${buildConfig.keyStoreAliasPassword}`, + `-PksPassword=${buildConfig.keyStorePassword}` + ); + } + + return args; + } + + private getBuildLoggingArgs(): string[] { + const args = []; + + const logLevel = this.$logger.getLevel(); + if (logLevel === "TRACE") { + args.push("--stacktrace", "--debug"); + } else if (logLevel === "INFO") { + args.push("--quiet"); + } + + return args; + } + + private getBuildTaskName(buildConfig: IAndroidBuildConfig): string { + const baseTaskName = buildConfig.androidBundle ? "bundle" : "assemble"; + const buildTaskName = buildConfig.release ? `${baseTaskName}${Configurations.Release}` : `${baseTaskName}${Configurations.Debug}`; + + return buildTaskName; + } +} +$injector.register("gradleBuildArgsService", GradleBuildArgsService); diff --git a/lib/services/android/gradle-build-service.ts b/lib/services/android/gradle-build-service.ts new file mode 100644 index 0000000000..499d5bd9ab --- /dev/null +++ b/lib/services/android/gradle-build-service.ts @@ -0,0 +1,30 @@ +import { attachAwaitDetach } from "../../common/helpers"; +import * as constants from "../../constants"; +import { EventEmitter } from "events"; + +export class GradleBuildService extends EventEmitter implements IGradleBuildService { + constructor( + private $childProcess: IChildProcess, + private $gradleBuildArgsService: IGradleBuildArgsService, + private $gradleCommandService: IGradleCommandService, + ) { super(); } + + public async buildProject(projectRoot: string, buildConfig: IAndroidBuildConfig): Promise { + const buildTaskArgs = this.$gradleBuildArgsService.getBuildTaskArgs(buildConfig); + const spawnOptions = { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }; + const gradleCommandOptions = { cwd: projectRoot, message: "Gradle build...", stdio: buildConfig.buildOutputStdio, spawnOptions }; + + await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, + this.$childProcess, + (data: any) => this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data), + this.$gradleCommandService.executeCommand(buildTaskArgs, gradleCommandOptions) + ); + } + + public async cleanProject(projectRoot: string, buildConfig: IAndroidBuildConfig): Promise { + const cleanTaskArgs = this.$gradleBuildArgsService.getCleanTaskArgs(buildConfig); + const gradleCommandOptions = { cwd: projectRoot, message: "Gradle clean..." }; + await this.$gradleCommandService.executeCommand(cleanTaskArgs, gradleCommandOptions); + } +} +$injector.register("gradleBuildService", GradleBuildService); diff --git a/lib/services/android/gradle-command-service.ts b/lib/services/android/gradle-command-service.ts new file mode 100644 index 0000000000..4cddc0c759 --- /dev/null +++ b/lib/services/android/gradle-command-service.ts @@ -0,0 +1,31 @@ +export class GradleCommandService implements IGradleCommandService { + constructor( + private $childProcess: IChildProcess, + private $errors: IErrors, + private $hostInfo: IHostInfo, + private $logger: ILogger + ) { } + + public async executeCommand(gradleArgs: string[], options: IGradleCommandOptions): Promise { + const { message, cwd, stdio = "inherit", spawnOptions } = options; + this.$logger.info(message); + + const childProcessOptions = { cwd, stdio }; + const gradleExecutable = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew"; + + const result = await this.executeCommandSafe(gradleExecutable, gradleArgs, childProcessOptions, spawnOptions); + + return result; + } + + private async executeCommandSafe(gradleExecutable: string, gradleArgs: string[], childProcessOptions: any, spawnOptions: any) { + try { + const result = await this.$childProcess.spawnFromEvent(gradleExecutable, gradleArgs, "close", childProcessOptions, spawnOptions); + + return result; + } catch (err) { + this.$errors.failWithoutHelp(err.message); + } + } +} +$injector.register("gradleCommandService", GradleCommandService); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 3d63d94ec2..88f14dcaaa 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -690,12 +690,12 @@ export class PlatformService extends EventEmitter implements IPlatformService { for (const platform of platforms) { this.validatePlatformInstalled(platform, projectData); const platformData = this.$platformsData.getPlatformData(platform, projectData); - let gradleErrorMessage; + let errorMessage; try { - await platformData.platformProjectService.stopServices(platformData.projectRoot); + await platformData.platformProjectService.stopServices(platformData); } catch (err) { - gradleErrorMessage = err.message; + errorMessage = err.message; } try { @@ -706,8 +706,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { this.$logger.out(`Platform ${platform} successfully removed.`); } catch (err) { this.$logger.error(`Failed to remove ${platform} platform with errors:`); - if (gradleErrorMessage) { - this.$logger.error(gradleErrorMessage); + if (errorMessage) { + this.$logger.error(errorMessage); } this.$errors.failWithoutHelp(err.message); } From 599eb024fe0fced4d4425a3cd0afef77b4c8b2c1 Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 13 Apr 2019 20:58:49 +0300 Subject: [PATCH 007/102] test: add tests and fix existing ones --- test/plugins-service.ts | 7 + test/services/android-project-service.ts | 6 + .../android/gradle-build-args-service.ts | 161 ++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 test/services/android/gradle-build-args-service.ts diff --git a/test/plugins-service.ts b/test/plugins-service.ts index c4b0f9f874..c1a8ebef54 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -35,6 +35,9 @@ import StaticConfigLib = require("../lib/config"); import * as path from "path"; import * as temp from "temp"; 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'; temp.track(); let isErrorThrown = false; @@ -150,6 +153,10 @@ function createTestInjector() { }, extractPackage: async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => undefined }); + testInjector.register("gradleCommandService", GradleCommandService); + testInjector.register("gradleBuildService", GradleBuildService); + testInjector.register("gradleBuildArgsService", GradleBuildArgsService); + return testInjector; } diff --git a/test/services/android-project-service.ts b/test/services/android-project-service.ts index aad86d5916..c7b1640444 100644 --- a/test/services/android-project-service.ts +++ b/test/services/android-project-service.ts @@ -3,6 +3,9 @@ import { Yok } from "../../lib/common/yok"; import * as stubs from "../stubs"; import { assert } from "chai"; import * as sinon from "sinon"; +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"; const createTestInjector = (): IInjector => { const testInjector = new Yok(); @@ -35,6 +38,9 @@ const createTestInjector = (): IInjector => { return true; } }); + testInjector.register("gradleCommandService", GradleCommandService); + testInjector.register("gradleBuildService", GradleBuildService); + testInjector.register("gradleBuildArgsService", GradleBuildArgsService); return testInjector; }; diff --git a/test/services/android/gradle-build-args-service.ts b/test/services/android/gradle-build-args-service.ts new file mode 100644 index 0000000000..d112872936 --- /dev/null +++ b/test/services/android/gradle-build-args-service.ts @@ -0,0 +1,161 @@ +import { Yok } from "../../../lib/common/yok"; +import { GradleBuildArgsService } from "../../../lib/services/android/gradle-build-args-service"; +import { assert } from "chai"; + +function createTestInjector(): IInjector { + const injector = new Yok(); + injector.register("androidToolsInfo", { + getToolsInfo: () => ({ + compileSdkVersion: 28, + buildToolsVersion: "my-build-tools-version", + generateTypings: true + }) + }); + injector.register("logger", {}); + injector.register("gradleBuildArgsService", GradleBuildArgsService); + + return injector; +} + +function executeTests(testCases: any[], testFunction: (gradleBuildArgsService: IGradleBuildArgsService, buildConfig: IAndroidBuildConfig) => string[]) { + _.each(testCases, testCase => { + it(testCase.name, () => { + const injector = createTestInjector(); + if (testCase.logLevel) { + const logger = injector.resolve("logger"); + logger.getLevel = () => testCase.logLevel; + } + + const gradleBuildArgsService = injector.resolve("gradleBuildArgsService"); + const args = testFunction(gradleBuildArgsService, testCase.buildConfig); + + assert.deepEqual(args, testCase.expectedResult); + }); + }); +} + +const expectedInfoLoggingArgs = ["--quiet"]; +const expectedTraceLoggingArgs = ["--stacktrace", "--debug"]; +const expectedDebugBuildArgs = ["-PcompileSdk=android-28", "-PbuildToolsVersion=my-build-tools-version", "-PgenerateTypings=true"]; +const expectedReleaseBuildArgs = expectedDebugBuildArgs.concat(["-Prelease", "-PksPath=/Users/havaluova/Work/nativescript-cli/keyStorePath", + "-Palias=keyStoreAlias", "-Ppassword=keyStoreAliasPassword", "-PksPassword=keyStorePassword"]); + +const releaseBuildConfig = { + release: true, + keyStorePath: "keyStorePath", + keyStoreAlias: "keyStoreAlias", + keyStoreAliasPassword: "keyStoreAliasPassword", + keyStorePassword: "keyStorePassword" +}; + +describe("GradleBuildArgsService", () => { + describe("getBuildTaskArgs", () => { + const testCases = [ + { + name: "should return correct args for debug build with info log", + buildConfig: { release: false }, + logLevel: "INFO", + expectedResult: ["assembleDebug"].concat(expectedInfoLoggingArgs).concat(expectedDebugBuildArgs) + }, + { + name: "should return correct args for debug build with trace log", + buildConfig: { release: false }, + logLevel: "TRACE", + expectedResult: ["assembleDebug"].concat(expectedTraceLoggingArgs).concat(expectedDebugBuildArgs) + }, + { + name: "should return correct args for release build with info log", + buildConfig: releaseBuildConfig, + logLevel: "INFO", + expectedResult: ["assembleRelease"].concat(expectedInfoLoggingArgs).concat(expectedReleaseBuildArgs) + }, + { + name: "should return correct args for release build with trace log", + buildConfig: releaseBuildConfig, + logLevel: "TRACE", + expectedResult: ["assembleRelease"].concat(expectedTraceLoggingArgs).concat(expectedReleaseBuildArgs) + }, + { + name: "should return correct args for debug build with info log and android bundle", + buildConfig: { release: false, androidBundle: true }, + logLevel: "INFO", + expectedResult: ["bundleDebug"].concat(expectedInfoLoggingArgs).concat(expectedDebugBuildArgs) + }, + { + name: "should return correct args for debug build with trace log and android bundle", + buildConfig: { release: false, androidBundle: true }, + logLevel: "TRACE", + expectedResult: ["bundleDebug"].concat(expectedTraceLoggingArgs).concat(expectedDebugBuildArgs) + }, + { + name: "should return correct args for release build with info log and android bundle", + buildConfig: { ...releaseBuildConfig, androidBundle: true }, + logLevel: "INFO", + expectedResult: ["bundleRelease"].concat(expectedInfoLoggingArgs).concat(expectedReleaseBuildArgs) + }, + { + name: "should return correct args for release build with trace log and android bundle", + buildConfig: { ...releaseBuildConfig, androidBundle: true }, + logLevel: "TRACE", + expectedResult: ["bundleRelease"].concat(expectedTraceLoggingArgs).concat(expectedReleaseBuildArgs) + } + ]; + + executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildConfig: IAndroidBuildConfig) => gradleBuildArgsService.getBuildTaskArgs(buildConfig)); + }); + + describe("getCleanTaskArgs", () => { + const testCases = [ + { + name: "should return correct args for debug clean build with info log", + buildConfig: { release: false }, + logLevel: "INFO", + expectedResult: ["clean"].concat(expectedInfoLoggingArgs).concat(expectedDebugBuildArgs) + }, + { + name: "should return correct args for debug clean build with trace log", + buildConfig: { release: false }, + logLevel: "TRACE", + expectedResult: ["clean"].concat(expectedTraceLoggingArgs).concat(expectedDebugBuildArgs) + }, + { + name: "should return correct args for release clean build with info log", + buildConfig: releaseBuildConfig, + logLevel: "INFO", + expectedResult: ["clean"].concat(expectedInfoLoggingArgs).concat(expectedReleaseBuildArgs) + }, + { + name: "should return correct args for release clean build with trace log", + buildConfig: releaseBuildConfig, + logLevel: "TRACE", + expectedResult: ["clean"].concat(expectedTraceLoggingArgs).concat(expectedReleaseBuildArgs) + }, + { + name: "should return correct args for debug clean build with info log and android bundle", + buildConfig: { release: false, androidBundle: true }, + logLevel: "INFO", + expectedResult: ["clean"].concat(expectedInfoLoggingArgs).concat(expectedDebugBuildArgs) + }, + { + name: "should return correct args for debug clean build with trace log and android bundle", + buildConfig: { release: false, androidBundle: true }, + logLevel: "TRACE", + expectedResult: ["clean"].concat(expectedTraceLoggingArgs).concat(expectedDebugBuildArgs) + }, + { + name: "should return correct args for release clean build with info log and android bundle", + buildConfig: { ...releaseBuildConfig, androidBundle: true }, + logLevel: "INFO", + expectedResult: ["clean"].concat(expectedInfoLoggingArgs).concat(expectedReleaseBuildArgs) + }, + { + name: "should return correct args for release clean build with trace log and android bundle", + buildConfig: { ...releaseBuildConfig, androidBundle: true }, + logLevel: "TRACE", + expectedResult: ["clean"].concat(expectedTraceLoggingArgs).concat(expectedReleaseBuildArgs) + } + ]; + + executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildConfig: IAndroidBuildConfig) => gradleBuildArgsService.getCleanTaskArgs(buildConfig)); + }); +}); From 0f8e88e38d8a85d4c37484ba9e5ddbb01dbd485b Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 13 Apr 2019 21:06:24 +0300 Subject: [PATCH 008/102] chore: remove the support for android < 3.4.0 --- lib/services/android-project-service.ts | 79 ++----------------------- 1 file changed, 6 insertions(+), 73 deletions(-) diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index a247eec918..961ef2726a 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -13,8 +13,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private static ANDROID_PLATFORM_NAME = "android"; private static MIN_RUNTIME_VERSION_WITH_GRADLE = "1.5.0"; - private isAndroidStudioTemplate: boolean; - constructor(private $androidToolsInfo: IAndroidToolsInfo, private $errors: IErrors, $fs: IFileSystem, @@ -29,7 +27,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $gradleCommandService: IGradleCommandService, private $gradleBuildService: IGradleBuildService) { super($fs, $projectDataService); - this.isAndroidStudioTemplate = false; } private _platformData: IPlatformData = null; @@ -39,27 +36,10 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } if (projectData && projectData.platformsDir) { const projectRoot = path.join(projectData.platformsDir, AndroidProjectService.ANDROID_PLATFORM_NAME); - if (this.isAndroidStudioCompatibleTemplate(projectData)) { - this.isAndroidStudioTemplate = true; - } - - const appDestinationDirectoryArr = [projectRoot]; - if (this.isAndroidStudioTemplate) { - appDestinationDirectoryArr.push(constants.APP_FOLDER_NAME); - } - appDestinationDirectoryArr.push(constants.SRC_DIR, constants.MAIN_DIR, constants.ASSETS_DIR); - - const configurationsDirectoryArr = [projectRoot]; - if (this.isAndroidStudioTemplate) { - configurationsDirectoryArr.push(constants.APP_FOLDER_NAME); - } - configurationsDirectoryArr.push(constants.SRC_DIR, constants.MAIN_DIR, constants.MANIFEST_FILE_NAME); - const deviceBuildOutputArr = [projectRoot]; - if (this.isAndroidStudioTemplate) { - deviceBuildOutputArr.push(constants.APP_FOLDER_NAME); - } - deviceBuildOutputArr.push(constants.BUILD_DIR, constants.OUTPUTS_DIR, constants.APK_DIR); + const appDestinationDirectoryArr = [projectRoot, constants.APP_FOLDER_NAME, constants.SRC_DIR, constants.MAIN_DIR, constants.ASSETS_DIR]; + const configurationsDirectoryArr = [projectRoot, constants.APP_FOLDER_NAME, constants.SRC_DIR, constants.MAIN_DIR, constants.MANIFEST_FILE_NAME]; + const deviceBuildOutputArr = [projectRoot, constants.APP_FOLDER_NAME, constants.BUILD_DIR, constants.OUTPUTS_DIR, constants.APK_DIR]; const packageName = this.getProjectNameFromId(projectData); @@ -153,30 +133,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject const targetSdkVersion = androidToolsInfo && androidToolsInfo.targetSdkVersion; this.$logger.trace(`Using Android SDK '${targetSdkVersion}'.`); - this.isAndroidStudioTemplate = this.isAndroidStudioCompatibleTemplate(projectData, frameworkVersion); - if (this.isAndroidStudioTemplate) { - this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "*", "-R"); - } else { - this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "libs", "-R"); - - if (config.pathToTemplate) { - const mainPath = path.join(this.getPlatformData(projectData).projectRoot, constants.SRC_DIR, constants.MAIN_DIR); - this.$fs.createDirectory(mainPath); - shell.cp("-R", path.join(path.resolve(config.pathToTemplate), "*"), mainPath); - } else { - this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, constants.SRC_DIR, "-R"); - } - this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "build.gradle settings.gradle build-tools", "-Rf"); - - try { - this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "gradle.properties", "-Rf"); - } catch (e) { - this.$logger.warn(`\n${e}\nIt's possible, the final .apk file will contain all architectures instead of the ones described in the abiFilters!\nYou can fix this by using the latest android platform.`); - } - - this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "gradle", "-R"); - this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "gradlew gradlew.bat", "-f"); - } + this.copy(this.getPlatformData(projectData).projectRoot, frameworkDir, "*", "-R"); this.cleanResValues(targetSdkVersion, projectData); } @@ -447,38 +404,14 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - private isAndroidStudioCompatibleTemplate(projectData: IProjectData, frameworkVersion?: string): boolean { - const currentPlatformData: IDictionary = this.$projectDataService.getNSValue(projectData.projectDir, constants.TNS_ANDROID_RUNTIME_NAME); - const platformVersion = (currentPlatformData && currentPlatformData[constants.VERSION_STRING]) || frameworkVersion; - - if (!platformVersion) { - return true; - } - - if (platformVersion === constants.PackageVersion.NEXT || platformVersion === constants.PackageVersion.LATEST || platformVersion === constants.PackageVersion.RC) { - return true; - } - - const androidStudioCompatibleTemplate = "3.4.0"; - const normalizedPlatformVersion = `${semver.major(platformVersion)}.${semver.minor(platformVersion)}.0`; - - return semver.gte(normalizedPlatformVersion, androidStudioCompatibleTemplate); - } - private getLegacyAppResourcesDestinationDirPath(projectData: IProjectData): string { - const resourcePath: string[] = [constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR]; - if (this.isAndroidStudioTemplate) { - resourcePath.unshift(constants.APP_FOLDER_NAME); - } + const resourcePath: string[] = [constants.APP_FOLDER_NAME, constants.SRC_DIR, constants.MAIN_DIR, constants.RESOURCES_DIR]; return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath); } private getUpdatedAppResourcesDestinationDirPath(projectData: IProjectData): string { - const resourcePath: string[] = [constants.SRC_DIR]; - if (this.isAndroidStudioTemplate) { - resourcePath.unshift(constants.APP_FOLDER_NAME); - } + const resourcePath: string[] = [constants.APP_FOLDER_NAME, constants.SRC_DIR]; return path.join(this.getPlatformData(projectData).projectRoot, ...resourcePath); } From ed0343af18e700b33e345ebec45e77b2f523d059 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 16 Apr 2019 04:00:28 +0300 Subject: [PATCH 009/102] chore: fix PR comments --- lib/definitions/gradle.d.ts | 6 +++--- lib/services/android-project-service.ts | 1 - lib/services/android/gradle-build-args-service.ts | 1 + lib/services/android/gradle-command-service.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/definitions/gradle.d.ts b/lib/definitions/gradle.d.ts index 630dd568b9..aa6bdf6ac8 100644 --- a/lib/definitions/gradle.d.ts +++ b/lib/definitions/gradle.d.ts @@ -3,10 +3,10 @@ interface IGradleCommandService { } interface IGradleCommandOptions { - message?: string; cwd: string; + message?: string; stdio?: string; - spawnOptions?: any; + spawnOptions?: ISpawnFromEventOptions; } interface IAndroidBuildConfig extends IRelease, IAndroidReleaseOptions, IHasAndroidBundle { @@ -21,4 +21,4 @@ interface IGradleBuildService { interface IGradleBuildArgsService { getBuildTaskArgs(buildConfig: IAndroidBuildConfig): string[]; getCleanTaskArgs(buildConfig: IAndroidBuildConfig): string[]; -} \ No newline at end of file +} diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 961ef2726a..97b3b9e58f 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -359,7 +359,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public async cleanProject(projectRoot: string, projectData: IProjectData): Promise { - // TODO: Check why there isn't a clean release build and a clean release aab build await this.$gradleBuildService.cleanProject(projectRoot, { release: false }); } diff --git a/lib/services/android/gradle-build-args-service.ts b/lib/services/android/gradle-build-args-service.ts index d178dd7437..e00f71d784 100644 --- a/lib/services/android/gradle-build-args-service.ts +++ b/lib/services/android/gradle-build-args-service.ts @@ -25,6 +25,7 @@ export class GradleBuildArgsService implements IGradleBuildArgsService { const toolsInfo = this.$androidToolsInfo.getToolsInfo(); args.push( `-PcompileSdk=android-${toolsInfo.compileSdkVersion}`, + `-PtargetSdk=${toolsInfo.targetSdkVersion}`, `-PbuildToolsVersion=${toolsInfo.buildToolsVersion}`, `-PgenerateTypings=${toolsInfo.generateTypings}` ); diff --git a/lib/services/android/gradle-command-service.ts b/lib/services/android/gradle-command-service.ts index 4cddc0c759..c829ac217f 100644 --- a/lib/services/android/gradle-command-service.ts +++ b/lib/services/android/gradle-command-service.ts @@ -18,7 +18,7 @@ export class GradleCommandService implements IGradleCommandService { return result; } - private async executeCommandSafe(gradleExecutable: string, gradleArgs: string[], childProcessOptions: any, spawnOptions: any) { + private async executeCommandSafe(gradleExecutable: string, gradleArgs: string[], childProcessOptions: { cwd: string, stdio: string }, spawnOptions: ISpawnFromEventOptions) { try { const result = await this.$childProcess.spawnFromEvent(gradleExecutable, gradleArgs, "close", childProcessOptions, spawnOptions); From 31d9f4c1995e8a45b90c92f819bb873e55dc24d0 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 19 Apr 2019 15:25:19 +0300 Subject: [PATCH 010/102] chore: automate publish of webpack tag --- .travis.yml | 1 + .travis/add-publishConfig.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 17da6134aa..f79e684e53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ branches: - master - release - release-patch + - feature/webpack-only env: global: - DATE=$(date +%Y-%m-%d) diff --git a/.travis/add-publishConfig.js b/.travis/add-publishConfig.js index 7d03f227f1..b338e5e1aa 100644 --- a/.travis/add-publishConfig.js +++ b/.travis/add-publishConfig.js @@ -26,6 +26,9 @@ switch (branch) { case "master": packageDef.publishConfig.tag = "next"; break; + case "feature/webpack-only": + packageDef.publishConfig.tag = "webpack"; + break; default: throw new Error(`Unable to publish as the branch ${branch} does not have corresponding tag. Supported branches are master (next tag), release (rc tag) and release-patch (patch tag)`); } From 535e296a2b59e94a49fb92c4035ae48df7531b85 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 22 Apr 2019 11:45:37 +0300 Subject: [PATCH 011/102] fix: fix the unit tests execution with istanbul --- lib/services/android/gradle-command-service.ts | 6 +++--- test/services/android/gradle-build-args-service.ts | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/services/android/gradle-command-service.ts b/lib/services/android/gradle-command-service.ts index c829ac217f..07e398450b 100644 --- a/lib/services/android/gradle-command-service.ts +++ b/lib/services/android/gradle-command-service.ts @@ -7,10 +7,10 @@ export class GradleCommandService implements IGradleCommandService { ) { } public async executeCommand(gradleArgs: string[], options: IGradleCommandOptions): Promise { - const { message, cwd, stdio = "inherit", spawnOptions } = options; + const { message, cwd, stdio, spawnOptions } = options; this.$logger.info(message); - const childProcessOptions = { cwd, stdio }; + const childProcessOptions = { cwd, stdio: stdio || "inherit" }; const gradleExecutable = this.$hostInfo.isWindows ? "gradlew.bat" : "./gradlew"; const result = await this.executeCommandSafe(gradleExecutable, gradleArgs, childProcessOptions, spawnOptions); @@ -18,7 +18,7 @@ export class GradleCommandService implements IGradleCommandService { return result; } - private async executeCommandSafe(gradleExecutable: string, gradleArgs: string[], childProcessOptions: { cwd: string, stdio: string }, spawnOptions: ISpawnFromEventOptions) { + private async executeCommandSafe(gradleExecutable: string, gradleArgs: string[], childProcessOptions: { cwd: string, stdio: string }, spawnOptions: ISpawnFromEventOptions): Promise { try { const result = await this.$childProcess.spawnFromEvent(gradleExecutable, gradleArgs, "close", childProcessOptions, spawnOptions); diff --git a/test/services/android/gradle-build-args-service.ts b/test/services/android/gradle-build-args-service.ts index d112872936..0a93c8ebdf 100644 --- a/test/services/android/gradle-build-args-service.ts +++ b/test/services/android/gradle-build-args-service.ts @@ -7,6 +7,7 @@ function createTestInjector(): IInjector { injector.register("androidToolsInfo", { getToolsInfo: () => ({ compileSdkVersion: 28, + targetSdkVersion: 26, buildToolsVersion: "my-build-tools-version", generateTypings: true }) @@ -36,13 +37,13 @@ function executeTests(testCases: any[], testFunction: (gradleBuildArgsService: I const expectedInfoLoggingArgs = ["--quiet"]; const expectedTraceLoggingArgs = ["--stacktrace", "--debug"]; -const expectedDebugBuildArgs = ["-PcompileSdk=android-28", "-PbuildToolsVersion=my-build-tools-version", "-PgenerateTypings=true"]; -const expectedReleaseBuildArgs = expectedDebugBuildArgs.concat(["-Prelease", "-PksPath=/Users/havaluova/Work/nativescript-cli/keyStorePath", +const expectedDebugBuildArgs = ["-PcompileSdk=android-28", "-PtargetSdk=26", "-PbuildToolsVersion=my-build-tools-version", "-PgenerateTypings=true"]; +const expectedReleaseBuildArgs = expectedDebugBuildArgs.concat(["-Prelease", "-PksPath=/my/key/store/path", "-Palias=keyStoreAlias", "-Ppassword=keyStoreAliasPassword", "-PksPassword=keyStorePassword"]); const releaseBuildConfig = { release: true, - keyStorePath: "keyStorePath", + keyStorePath: "/my/key/store/path", keyStoreAlias: "keyStoreAlias", keyStoreAliasPassword: "keyStoreAliasPassword", keyStorePassword: "keyStorePassword" From 3786e42e7fab8b0a6a4c807760785954d6c6a0c3 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 22 Apr 2019 01:05:42 +0300 Subject: [PATCH 012/102] refactor: refactor xcodebuild related part of ios-project-service --- lib/bootstrap.ts | 5 + lib/commands/appstore-upload.ts | 39 +- lib/constants.ts | 3 + lib/declarations.d.ts | 4 +- lib/definitions/project.d.ts | 6 +- lib/definitions/xcode.d.ts | 157 +++--- lib/services/ios-project-service.ts | 476 +----------------- .../ios/export-options-plist-service.ts | 98 ++++ lib/services/ios/ios-signing-service.ts | 194 +++++++ lib/services/ios/xcodebuild-args-service.ts | 102 ++++ .../ios/xcodebuild-command-service.ts | 29 ++ lib/services/ios/xcodebuild-service.ts | 58 +++ lib/services/xcproj-service.ts | 4 +- test/ios-project-service.ts | 432 +--------------- 14 files changed, 640 insertions(+), 967 deletions(-) create mode 100644 lib/services/ios/export-options-plist-service.ts create mode 100644 lib/services/ios/ios-signing-service.ts create mode 100644 lib/services/ios/xcodebuild-args-service.ts create mode 100644 lib/services/ios/xcodebuild-command-service.ts create mode 100644 lib/services/ios/xcodebuild-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 486751b637..a8eda19224 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -19,6 +19,11 @@ $injector.require("iOSExtensionsService", "./services/ios-extensions-service"); $injector.require("iOSProjectService", "./services/ios-project-service"); $injector.require("iOSProvisionService", "./services/ios-provision-service"); $injector.require("xcconfigService", "./services/xcconfig-service"); +$injector.require("iOSSigningService", "./services/ios/ios-signing-service"); +$injector.require("xcodebuildArgsService", "./services/ios/xcodebuild-args-service"); +$injector.require("xcodebuildCommandService", "./services/ios/xcodebuild-command-service"); +$injector.require("xcodebuildService", "./services/ios/xcodebuild-service"); +$injector.require("exportOptionsPlistService", "./services/ios/export-options-plist-service"); $injector.require("cocoapodsService", "./services/cocoapods-service"); $injector.require("cocoaPodsPlatformManager", "./services/cocoapods-platform-manager"); diff --git a/lib/commands/appstore-upload.ts b/lib/commands/appstore-upload.ts index 0ab77c5afc..c1055a2f92 100644 --- a/lib/commands/appstore-upload.ts +++ b/lib/commands/appstore-upload.ts @@ -1,6 +1,5 @@ -import { StringCommandParameter } from "../common/command-params"; import * as path from "path"; -import { IOSProjectService } from "../services/ios-project-service"; +import { StringCommandParameter } from "../common/command-params"; export class PublishIOS implements ICommand { public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector), new StringCommandParameter(this.$injector), @@ -13,7 +12,8 @@ export class PublishIOS implements ICommand { private $projectData: IProjectData, private $options: IOptions, private $prompter: IPrompter, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) { + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $xcodebuildService: IXcodebuildService) { this.$projectData.initializeProjectData(); } @@ -32,7 +32,6 @@ export class PublishIOS implements ICommand { let password = args[1]; const mobileProvisionIdentifier = args[2]; const codeSignIdentity = args[3]; - const teamID = this.$options.teamId; let ipaFilePath = this.$options.ipa ? path.resolve(this.$options.ipa) : null; if (!username) { @@ -68,35 +67,31 @@ export class PublishIOS implements ICommand { config: this.$options, 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 + }; if (mobileProvisionIdentifier || codeSignIdentity) { - const iOSBuildConfig: 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 - }; 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, iOSBuildConfig, this.$projectData); - ipaFilePath = this.$platformService.lastOutputPath(platform, iOSBuildConfig, this.$projectData); + await this.$platformService.buildPlatform(platform, buildConfig, this.$projectData); + 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 iOSProjectService = platformData.platformProjectService; - - const archivePath = await iOSProjectService.archive(this.$projectData); - this.$logger.info("Archive at: " + archivePath); - const exportPath = await iOSProjectService.exportArchive(this.$projectData, { archivePath, teamID, provision: mobileProvisionIdentifier || this.$options.provision }); + const exportPath = await this.$xcodebuildService.buildForAppStore(platformData, this.$projectData, buildConfig); this.$logger.info("Export at: " + exportPath); ipaFilePath = exportPath; diff --git a/lib/constants.ts b/lib/constants.ts index a1a8961018..ccd1ae5053 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -110,6 +110,9 @@ class ItunesConnectApplicationTypesClass implements IiTunesConnectApplicationTyp public Mac = "Mac OS X App"; } +export const iOSAppResourcesFolderName = "iOS"; +export const androidAppResourcesFolderName = "Android"; + export const ItunesConnectApplicationTypes = new ItunesConnectApplicationTypesClass(); export const VUE_NAME = "vue"; export const ANGULAR_NAME = "angular"; diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 0298f9ea02..1be467010f 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -841,10 +841,10 @@ interface IXcprojService { /** * Returns the path to the xcodeproj file * @param projectData Information about the project. - * @param platformData Information about the platform. + * @param projectRoot The root folder of native project. * @return {string} The full path to the xcodeproj */ - getXcodeprojPath(projectData: IProjectData, platformData: IPlatformData): string; + getXcodeprojPath(projectData: IProjectData, projectRoot: string): string; /** * Checks whether the system needs xcproj to execute ios builds successfully. * In case the system does need xcproj but does not have it, prints an error message. diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 44c3a9df40..f68e51b970 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -446,7 +446,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS * @param {IPlatformData} platformData The data for the specified platform. * @returns {void} */ - stopServices(platformData: IPlatformData): Promise; + stopServices?(projectRoot: string): Promise; /** * Removes build artifacts specific to the platform @@ -454,7 +454,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS * @param {IProjectData} projectData DTO with information about the project. * @returns {void} */ - cleanProject(projectRoot: string, projectData: IProjectData): Promise + cleanProject?(projectRoot: string, projectData: IProjectData): Promise /** * Check the current state of the project, and validate against the options. @@ -466,7 +466,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS * 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; + getDeploymentTarget?(projectData: IProjectData): any; } interface IValidatePlatformOutput { diff --git a/lib/definitions/xcode.d.ts b/lib/definitions/xcode.d.ts index 065fbe2067..d1d524d45e 100644 --- a/lib/definitions/xcode.d.ts +++ b/lib/definitions/xcode.d.ts @@ -1,61 +1,100 @@ declare module "nativescript-dev-xcode" { - interface Options { - [key: string]: any; - - customFramework?: boolean; - embed?: boolean; - relativePath?: string; - } - - class project { - constructor(filename: string); - - parse(callback: () => void): void; - parseSync(): void; - - writeSync(options: any): string; - - addFramework(filepath: string, options?: Options): void; - removeFramework(filePath: string, options?: Options): void; - - addPbxGroup(filePathsArray: any[], name: string, path: string, sourceTree: string): void; - - removePbxGroup(groupName: string, path: string): void; - - addToHeaderSearchPaths(options?: Options): void; - removeFromHeaderSearchPaths(options?: Options): void; - updateBuildProperty(key: string, value: any): void; - - pbxXCBuildConfigurationSection(): any; - - addTarget(targetName: string, targetType: string, targetPath?: string): target; - addBuildPhase(filePathsArray: string[], - buildPhaseType: string, - comment: string, - target?: string, - optionsOrFolderType?: Object|string, - subfolderPath?: string - ): any; - addToBuildSettings(buildSetting: string, value: any, targetUuid?: string): void; - addPbxGroup( - filePathsArray: string[], - name: string, - path: string, - sourceTree: string, - opt: {filesRelativeToProject?: boolean, target?: string, uuid?: string, isMain?: boolean } - ): group; - addBuildProperty(prop: string, value: any, build_name?: string, productName?: string): void; - addToHeaderSearchPaths(file: string|Object, productName?: string): void; - removeTargetsByProductType(targetType: string): void - } - - class target { - uuid: string; - pbxNativeTarget: {productName: string} - } - - class group { - uuid: string; - pbxGroup: Object; - } + interface Options { + [key: string]: any; + + customFramework?: boolean; + embed?: boolean; + relativePath?: string; + } + + class project { + constructor(filename: string); + + parse(callback: () => void): void; + parseSync(): void; + + writeSync(options: any): string; + + addFramework(filepath: string, options?: Options): void; + removeFramework(filePath: string, options?: Options): void; + + addPbxGroup(filePathsArray: any[], name: string, path: string, sourceTree: string): void; + + removePbxGroup(groupName: string, path: string): void; + + addToHeaderSearchPaths(options?: Options): void; + removeFromHeaderSearchPaths(options?: Options): void; + updateBuildProperty(key: string, value: any): void; + + pbxXCBuildConfigurationSection(): any; + + addTarget(targetName: string, targetType: string, targetPath?: string): target; + addBuildPhase(filePathsArray: string[], + buildPhaseType: string, + comment: string, + target?: string, + optionsOrFolderType?: Object|string, + subfolderPath?: string + ): any; + addToBuildSettings(buildSetting: string, value: any, targetUuid?: string): void; + addPbxGroup( + filePathsArray: string[], + name: string, + path: string, + sourceTree: string, + opt: {filesRelativeToProject?: boolean, target?: string, uuid?: string, isMain?: boolean } + ): group; + addBuildProperty(prop: string, value: any, build_name?: string, productName?: string): void; + addToHeaderSearchPaths(file: string|Object, productName?: string): void; + removeTargetsByProductType(targetType: string): void + } + + class target { + uuid: string; + pbxNativeTarget: {productName: string} + } + + class group { + 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/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 0dec1a1390..6ddf99e502 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -1,6 +1,5 @@ import * as path from "path"; import * as shell from "shelljs"; -import * as semver from "semver"; import * as constants from "../constants"; import { Configurations } from "../common/constants"; import * as helpers from "../common/helpers"; @@ -12,7 +11,6 @@ import * as temp from "temp"; import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; -import * as mobileProvisionFinder from "ios-mobileprovision-finder"; import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants } from "../constants"; interface INativeSourceCodeGroup { @@ -38,19 +36,18 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $logger: ILogger, private $injector: IInjector, $projectDataService: IProjectDataService, - private $prompter: IPrompter, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $devicesService: Mobile.IDevicesService, - private $mobileHelper: Mobile.IMobileHelper, private $hostInfo: IHostInfo, private $xcprojService: IXcprojService, private $iOSProvisionService: IOSProvisionService, + private $iOSSigningService: IiOSSigningService, private $pbxprojDomXcode: IPbxprojDomXcode, private $xcode: IXcode, private $iOSEntitlementsService: IOSEntitlementsService, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $plistParser: IPlistParser, private $xcconfigService: IXcconfigService, + private $xcodebuildService: IXcodebuildService, private $iOSExtensionsService: IIOSExtensionsService) { super($fs, $projectDataService); } @@ -194,376 +191,29 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ path.join(projectRoot, projectData.projectName)); } - /** - * Archive the Xcode project to .xcarchive. - * Returns the path to the .xcarchive. - */ - public async archive(projectData: IProjectData, buildConfig?: IBuildConfig, options?: { archivePath?: string, additionalArgs?: string[] }): Promise { - const platformData = this.getPlatformData(projectData); - const projectRoot = this.getPlatformData(projectData).projectRoot; - const archivePath = options && options.archivePath ? path.resolve(options.archivePath) : path.join(platformData.getBuildOutputPath(buildConfig), projectData.projectName + ".xcarchive"); - let args = ["archive", "-archivePath", archivePath, "-configuration", - getConfigurationName(!buildConfig || buildConfig.release)] - .concat(this.xcbuildProjectArgs(projectRoot, projectData, "scheme")); - - if (options && options.additionalArgs) { - args = args.concat(options.additionalArgs); - } - - await this.xcodebuild(args, projectRoot, buildConfig && buildConfig.buildOutputStdio); - return archivePath; - } - - /** - * Exports .xcarchive for AppStore distribution. - */ - public async exportArchive(projectData: IProjectData, options: { archivePath: string, exportDir?: string, teamID?: string, provision?: string }): Promise { - const projectRoot = this.getPlatformData(projectData).projectRoot; - const archivePath = options.archivePath; - // The xcodebuild exportPath expects directory and writes the .ipa at that directory. - const exportPath = path.resolve(options.exportDir || path.join(projectRoot, "/build/archive")); - const exportFile = path.join(exportPath, projectData.projectName + ".ipa"); - - // These are the options that you can set in the Xcode UI when exporting for AppStore deployment. - let plistTemplate = ` - - - -`; - if (options && options.teamID) { - plistTemplate += ` teamID - ${options.teamID} -`; - } - if (options && options.provision) { - plistTemplate += ` provisioningProfiles - - ${projectData.projectIdentifiers.ios} - ${options.provision} - `; - } - plistTemplate += ` method - app-store - uploadBitcode - - compileBitcode - - uploadSymbols - - -`; - - // Save the options... - temp.track(); - const exportOptionsPlist = temp.path({ prefix: "export-", suffix: ".plist" }); - this.$fs.writeFile(exportOptionsPlist, plistTemplate); - - await this.xcodebuild( - [ - "-exportArchive", - "-archivePath", archivePath, - "-exportPath", exportPath, - "-exportOptionsPlist", exportOptionsPlist - ], - projectRoot); - return exportFile; - } - - private iCloudContainerEnvironment(buildConfig: IBuildConfig): string { - return buildConfig && buildConfig.iCloudContainerEnvironment ? buildConfig.iCloudContainerEnvironment : null; - } - - /** - * Exports .xcarchive for a development device. - */ - private async exportDevelopmentArchive(projectData: IProjectData, buildConfig: IBuildConfig, options: { archivePath: string, provision?: string }): Promise { - const platformData = this.getPlatformData(projectData); - const projectRoot = platformData.projectRoot; - const archivePath = options.archivePath; - const exportOptionsMethod = await this.getExportOptionsMethod(projectData, archivePath); - const iCloudContainerEnvironment = this.iCloudContainerEnvironment(buildConfig); - let plistTemplate = ` - - - - method - ${exportOptionsMethod}`; - if (options && options.provision) { - plistTemplate += ` provisioningProfiles - - ${projectData.projectIdentifiers.ios} - ${options.provision} -`; - } - plistTemplate += ` - uploadBitcode - - compileBitcode - `; - if (iCloudContainerEnvironment) { - plistTemplate += ` - iCloudContainerEnvironment - ${iCloudContainerEnvironment}`; - } - plistTemplate += ` - -`; - - // Save the options... - temp.track(); - const exportOptionsPlist = temp.path({ prefix: "export-", suffix: ".plist" }); - this.$fs.writeFile(exportOptionsPlist, plistTemplate); - - // The xcodebuild exportPath expects directory and writes the .ipa at that directory. - const exportPath = path.resolve(path.dirname(archivePath)); - const exportFile = path.join(exportPath, projectData.projectName + ".ipa"); - - await this.xcodebuild( - [ - "-exportArchive", - "-archivePath", archivePath, - "-exportPath", exportPath, - "-exportOptionsPlist", exportOptionsPlist - ], - projectRoot, buildConfig.buildOutputStdio); - return exportFile; - } - - private xcbuildProjectArgs(projectRoot: string, projectData: IProjectData, product?: "scheme" | "target"): string[] { - const xcworkspacePath = path.join(projectRoot, projectData.projectName + ".xcworkspace"); - if (this.$fs.exists(xcworkspacePath)) { - return ["-workspace", xcworkspacePath, product ? "-" + product : "-scheme", projectData.projectName]; - } else { - const xcodeprojPath = path.join(projectRoot, projectData.projectName + ".xcodeproj"); - return ["-project", xcodeprojPath, product ? "-" + product : "-target", projectData.projectName]; - } - } - public async buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise { - const basicArgs = [ - 'SHARED_PRECOMPS_DIR=' + path.join(projectRoot, 'build', 'sharedpch') - ]; - + 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); await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, - this.buildForDevice(projectRoot, basicArgs, buildConfig, projectData)); + this.$xcodebuildService.buildForDevice(platformData, projectData, buildConfig)); } else { await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, - this.buildForSimulator(projectRoot, basicArgs, projectData, buildConfig)); + this.$xcodebuildService.buildForSimulator(platformData, projectData, buildConfig)); } + // TODO: Check if we need to validate the identifier here this.validateApplicationIdentifier(projectData); } - private async buildForDevice(projectRoot: string, args: string[], buildConfig: IBuildConfig, projectData: IProjectData): Promise { - if (!buildConfig.release && !buildConfig.architectures) { - await this.$devicesService.initialize({ - platform: this.$devicePlatformsConstants.iOS.toLowerCase(), deviceId: buildConfig.device, - skipEmulatorStart: true - }); - const instances = this.$devicesService.getDeviceInstances(); - const devicesArchitectures = _(instances) - .filter(d => this.$mobileHelper.isiOSPlatform(d.deviceInfo.platform) && !!d.deviceInfo.activeArchitecture) - .map(d => d.deviceInfo.activeArchitecture) - .uniq() - .value(); - if (devicesArchitectures.length > 0) { - const architectures = this.getBuildArchitectures(projectData, buildConfig, devicesArchitectures); - if (devicesArchitectures.length > 1) { - architectures.push('ONLY_ACTIVE_ARCH=NO'); - } - buildConfig.architectures = architectures; - } - } - - args = args.concat((buildConfig && buildConfig.architectures) || this.getBuildArchitectures(projectData, buildConfig, ["armv7", "arm64"])); - - args = args.concat([ - "-sdk", DevicePlatformSdkName, - "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR) - ]); - - await this.setupSigningForDevice(projectRoot, buildConfig, projectData); - - await this.createIpa(projectRoot, projectData, buildConfig, args); - } - - private async xcodebuild(args: string[], cwd: string, stdio: any = "inherit"): Promise { - const localArgs = [...args]; - localArgs.push("-allowProvisioningUpdates"); - - if (this.$logger.getLevel() === "INFO") { - localArgs.push("-quiet"); - this.$logger.info("Xcode build..."); - } - - let commandResult; - try { - commandResult = await this.$childProcess.spawnFromEvent("xcodebuild", - localArgs, - "exit", - { stdio: stdio || "inherit", cwd }, - { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }); - } catch (err) { - this.$errors.failWithoutHelp(err.message); - } - - return commandResult; - } - - private getBuildArchitectures(projectData: IProjectData, buildConfig: IBuildConfig, architectures: string[]): string[] { - let result: string[] = []; - - const frameworkVersion = this.getFrameworkVersion(projectData); - if (semver.valid(frameworkVersion) && semver.lt(semver.coerce(frameworkVersion), "5.1.0")) { - const target = this.getDeploymentTarget(projectData); - if (target && target.major >= 11) { - // We need to strip 32bit architectures as of deployment target >= 11 it is not allowed to have such - architectures = _.filter(architectures, arch => { - const is64BitArchitecture = arch === "x86_64" || arch === "arm64"; - if (!is64BitArchitecture) { - this.$logger.warn(`The architecture ${arch} will be stripped as it is not supported for deployment target ${target.version}.`); - } - return is64BitArchitecture; - }); - } - result = [`ARCHS=${architectures.join(" ")}`, `VALID_ARCHS=${architectures.join(" ")}`]; - } - - return result; - } - - private async setupSigningFromTeam(projectRoot: string, projectData: IProjectData, teamId: string) { - const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData)); - const signing = xcode.getSigning(projectData.projectName); - - let shouldUpdateXcode = false; - if (signing && signing.style === "Automatic") { - if (signing.team !== teamId) { - // Maybe the provided team is name such as "Telerik AD" and we need to convert it to CH******37 - const teamIdsForName = await this.$iOSProvisionService.getTeamIdsWithName(teamId); - if (!teamIdsForName.some(id => id === signing.team)) { - shouldUpdateXcode = true; - } - } - } else { - shouldUpdateXcode = true; - } - - if (shouldUpdateXcode) { - const teamIdsForName = await this.$iOSProvisionService.getTeamIdsWithName(teamId); - if (teamIdsForName.length > 0) { - this.$logger.trace(`Team id ${teamIdsForName[0]} will be used for team name "${teamId}".`); - teamId = teamIdsForName[0]; - } - - xcode.setAutomaticSigningStyle(projectData.projectName, teamId); - xcode.setAutomaticSigningStyleByTargetProductType("com.apple.product-type.app-extension", teamId); - xcode.save(); - - this.$logger.trace(`Set Automatic signing style and team id ${teamId}.`); - } else { - this.$logger.trace(`The specified ${teamId} is already set in the Xcode.`); - } - } - - private async setupSigningFromProvision(projectRoot: string, projectData: IProjectData, provision?: string, mobileProvisionData?: mobileProvisionFinder.provision.MobileProvision): Promise { - if (provision) { - const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData)); - const signing = xcode.getSigning(projectData.projectName); - - let shouldUpdateXcode = false; - if (signing && signing.style === "Manual") { - for (const config in signing.configurations) { - const options = signing.configurations[config]; - if (options.name !== provision && options.uuid !== provision) { - shouldUpdateXcode = true; - break; - } - } - } else { - shouldUpdateXcode = true; - } - - if (shouldUpdateXcode) { - const pickStart = Date.now(); - const mobileprovision = mobileProvisionData || await this.$iOSProvisionService.pick(provision, projectData.projectIdentifiers.ios); - const pickEnd = Date.now(); - this.$logger.trace("Searched and " + (mobileprovision ? "found" : "failed to find ") + " matching provisioning profile. (" + (pickEnd - pickStart) + "ms.)"); - if (!mobileprovision) { - this.$errors.failWithoutHelp("Failed to find mobile provision with UUID or Name: " + provision); - } - const configuration = { - team: mobileprovision.TeamIdentifier && mobileprovision.TeamIdentifier.length > 0 ? mobileprovision.TeamIdentifier[0] : undefined, - uuid: mobileprovision.UUID, - name: mobileprovision.Name, - identity: mobileprovision.Type === "Development" ? "iPhone Developer" : "iPhone Distribution" - }; - xcode.setManualSigningStyle(projectData.projectName, configuration); - xcode.setManualSigningStyleByTargetProductType("com.apple.product-type.app-extension", configuration); - xcode.save(); - - // this.cache(uuid); - this.$logger.trace(`Set Manual signing style and provisioning profile: ${mobileprovision.Name} (${mobileprovision.UUID})`); - } else { - this.$logger.trace(`The specified provisioning profile is already set in the Xcode: ${provision}`); - } - } else { - // read uuid from Xcode and cache... - } - } - - private async setupSigningForDevice(projectRoot: string, buildConfig: IiOSBuildConfig, projectData: IProjectData): Promise { - const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData)); - const signing = xcode.getSigning(projectData.projectName); - - const hasProvisioningProfileInXCConfig = - this.readXCConfigProvisioningProfileSpecifierForIPhoneOs(projectData) || - this.readXCConfigProvisioningProfileSpecifier(projectData) || - this.readXCConfigProvisioningProfileForIPhoneOs(projectData) || - this.readXCConfigProvisioningProfile(projectData); - - if (hasProvisioningProfileInXCConfig && (!signing || signing.style !== "Manual")) { - xcode.setManualSigningStyle(projectData.projectName); - xcode.save(); - } else if (!buildConfig.provision && !(signing && signing.style === "Manual" && !buildConfig.teamId)) { - const teamId = await this.getDevelopmentTeam(projectData, buildConfig.teamId); - await this.setupSigningFromTeam(projectRoot, projectData, teamId); - } - } - - private async buildForSimulator(projectRoot: string, args: string[], projectData: IProjectData, buildConfig?: IBuildConfig): Promise { - const architectures = this.getBuildArchitectures(projectData, buildConfig, ["i386", "x86_64"]); - - args = args - .concat(architectures) - .concat([ - "build", - "-configuration", getConfigurationName(buildConfig.release), - "-sdk", SimulatorPlatformSdkName, - "ONLY_ACTIVE_ARCH=NO", - "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR), - "CODE_SIGN_IDENTITY=", - ]) - .concat(this.xcbuildProjectArgs(projectRoot, projectData)); - - await this.xcodebuild(args, projectRoot, buildConfig.buildOutputStdio); - } - - private async createIpa(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig, args: string[]): Promise { - const archivePath = await this.archive(projectData, buildConfig, { additionalArgs: args }); - const exportFileIpa = await this.exportDevelopmentArchive(projectData, buildConfig, { archivePath, provision: buildConfig.provision || buildConfig.mobileProvisionIdentifier }); - return exportFileIpa; - } - public isPlatformPrepared(projectRoot: string, projectData: IProjectData): boolean { return this.$fs.exists(path.join(projectRoot, projectData.projectName, constants.APP_FOLDER_NAME)); } @@ -632,10 +282,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ const provision = platformSpecificData && platformSpecificData.provision; const teamId = platformSpecificData && platformSpecificData.teamId; if (provision) { - await this.setupSigningFromProvision(projectRoot, projectData, provision, platformSpecificData.mobileProvisionData); + await this.$iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision, platformSpecificData.mobileProvisionData); } if (teamId) { - await this.setupSigningFromTeam(projectRoot, projectData, teamId); + await this.$iOSSigningService.setupSigningFromTeam(projectRoot, projectData, teamId); } const project = this.createPbxProj(projectData); @@ -671,7 +321,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData); } - } public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void { @@ -698,14 +347,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return null; } - public async stopServices(): Promise { - return { stderr: "", stdout: "", exitCode: 0 }; - } - - public async cleanProject(projectRoot: string): Promise { - return Promise.resolve(); - } - private async mergeInfoPlists(projectData: IProjectData, buildOptions: IRelease): Promise { const projectDir = projectData.projectDir; const infoPlistPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName); @@ -802,7 +443,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private getPbxProjPath(projectData: IProjectData): string { - return path.join(this.$xcprojService.getXcodeprojPath(projectData, this.getPlatformData(projectData)), "project.pbxproj"); + return path.join(this.$xcprojService.getXcodeprojPath(projectData, this.getPlatformData(projectData).projectRoot), "project.pbxproj"); } private createPbxProj(projectData: IProjectData): any { @@ -849,7 +490,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(platformData.projectRoot); if (this.$fs.exists(projectPodfilePath)) { - await this.$cocoapodsService.executePodInstall(platformData.projectRoot, this.$xcprojService.getXcodeprojPath(projectData, platformData)); + await this.$cocoapodsService.executePodInstall(platformData.projectRoot, this.$xcprojService.getXcodeprojPath(projectData, platformData.projectRoot)); // The `pod install` command adds a new target to the .pbxproject. This target adds additional build phases to Xcode project. // Some of these phases relies on env variables (like PODS_PODFILE_DIR_PATH or PODS_ROOT). // These variables are produced from merge of pod's xcconfig file and project's xcconfig file. @@ -861,6 +502,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.$iOSExtensionsService.removeExtensions({ pbxProjPath }); await this.addExtensions(projectData); } + public beforePrepareAllPlugins(): Promise { return Promise.resolve(); } @@ -912,13 +554,9 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } } - public getDeploymentTarget(projectData: IProjectData): semver.SemVer { + public getDeploymentTarget(projectData: IProjectData): string { const target = this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "IPHONEOS_DEPLOYMENT_TARGET"); - if (!target) { - return null; - } - - return semver.coerce(target); + return target; } private getAllLibsForPluginWithFileExtension(pluginData: IPluginData, fileExtension: string): string[] { @@ -1130,80 +768,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return buildXCConfig; } - private readTeamId(projectData: IProjectData): string { - let teamId = this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "DEVELOPMENT_TEAM"); - - const fileName = path.join(this.getPlatformData(projectData).projectRoot, "teamid"); - if (this.$fs.exists(fileName)) { - teamId = this.$fs.readText(fileName); - } - - return teamId; - } - - private readXCConfigProvisioningProfile(projectData: IProjectData): string { - return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE"); - } - - private readXCConfigProvisioningProfileForIPhoneOs(projectData: IProjectData): string { - return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE[sdk=iphoneos*]"); - } - - private readXCConfigProvisioningProfileSpecifier(projectData: IProjectData): string { - return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER"); - } - - private readXCConfigProvisioningProfileSpecifierForIPhoneOs(projectData: IProjectData): string { - return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]"); - } - - private async getDevelopmentTeam(projectData: IProjectData, teamId?: string): Promise { - teamId = teamId || this.readTeamId(projectData); - - if (!teamId) { - const teams = await this.$iOSProvisionService.getDevelopmentTeams(); - this.$logger.warn("Xcode requires a team id to be specified when building for device."); - this.$logger.warn("You can specify the team id by setting the DEVELOPMENT_TEAM setting in build.xcconfig file located in App_Resources folder of your app, or by using the --teamId option when calling run, debug or livesync commands."); - if (teams.length === 1) { - teamId = teams[0].id; - this.$logger.warn("Found and using the following development team installed on your system: " + teams[0].name + " (" + teams[0].id + ")"); - } else if (teams.length > 0) { - if (!helpers.isInteractive()) { - this.$errors.failWithoutHelp(`Unable to determine default development team. Available development teams are: ${_.map(teams, team => team.id)}. Specify team in app/App_Resources/iOS/build.xcconfig file in the following way: DEVELOPMENT_TEAM = `); - } - - const choices: string[] = []; - for (const team of teams) { - choices.push(team.name + " (" + team.id + ")"); - } - const choice = await this.$prompter.promptForChoice('Found multiple development teams, select one:', choices); - teamId = teams[choices.indexOf(choice)].id; - - const choicesPersist = [ - "Yes, set the DEVELOPMENT_TEAM setting in build.xcconfig file.", - "Yes, persist the team id in platforms folder.", - "No, don't persist this setting." - ]; - const choicePersist = await this.$prompter.promptForChoice("Do you want to make teamId: " + teamId + " a persistent choice for your app?", choicesPersist); - switch (choicesPersist.indexOf(choicePersist)) { - case 0: - const xcconfigFile = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, BUILD_XCCONFIG_FILE_NAME); - this.$fs.appendFile(xcconfigFile, "\nDEVELOPMENT_TEAM = " + teamId + "\n"); - break; - case 1: - this.$fs.writeFile(path.join(this.getPlatformData(projectData).projectRoot, "teamid"), teamId); - break; - default: - break; - } - } - } - - this.$logger.trace(`Selected teamId is '${teamId}'.`); - - return teamId; - } - private validateApplicationIdentifier(projectData: IProjectData): void { const infoPlistPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName); const mergedPlistPath = this.getPlatformData(projectData).configurationFilePath; @@ -1219,18 +783,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.$logger.warnWithLabel("The CFBundleIdentifier key inside the 'Info.plist' will be overriden by the 'id' inside 'package.json'."); } } - - private getExportOptionsMethod(projectData: IProjectData, archivePath: string): string { - const embeddedMobileProvisionPath = path.join(archivePath, 'Products', 'Applications', `${projectData.projectName}.app`, "embedded.mobileprovision"); - const provision = mobileProvisionFinder.provision.readFromFile(embeddedMobileProvisionPath); - - return { - "Development": "development", - "AdHoc": "ad-hoc", - "Distribution": "app-store", - "Enterprise": "enterprise" - }[provision.Type]; - } } $injector.register("iOSProjectService", IOSProjectService); diff --git a/lib/services/ios/export-options-plist-service.ts b/lib/services/ios/export-options-plist-service.ts new file mode 100644 index 0000000000..b25507b011 --- /dev/null +++ b/lib/services/ios/export-options-plist-service.ts @@ -0,0 +1,98 @@ +import * as path from "path"; +import * as temp from "temp"; +import * as mobileProvisionFinder from "ios-mobileprovision-finder"; + +export class ExportOptionsPlistService implements IExportOptionsPlistService { + constructor(private $fs: IFileSystem) { } + + public createDevelopmentExportOptionsPlist(archivePath: string, projectData: IProjectData, buildConfig: IBuildConfig): IExportOptionsPlistOutput { + const exportOptionsMethod = this.getExportOptionsMethod(projectData, archivePath); + const provision = buildConfig.provision || buildConfig.mobileProvisionIdentifier; + let plistTemplate = ` + + + + method + ${exportOptionsMethod}`; + if (provision) { + plistTemplate += ` provisioningProfiles + + ${projectData.projectIdentifiers.ios} + ${provision} +`; + } + plistTemplate += ` + uploadBitcode + + compileBitcode + + +`; + + // Save the options... + temp.track(); + const exportOptionsPlistFilePath = temp.path({ prefix: "export-", suffix: ".plist" }); + this.$fs.writeFile(exportOptionsPlistFilePath, plistTemplate); + + // The xcodebuild exportPath expects directory and writes the .ipa at that directory. + const exportFileDir = path.resolve(path.dirname(archivePath)); + const exportFilePath = path.join(exportFileDir, projectData.projectName + ".ipa"); + + return { exportFileDir, exportFilePath, exportOptionsPlistFilePath }; + } + + public createDistributionExportOptionsPlist(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): IExportOptionsPlistOutput { + const provision = buildConfig.provision || buildConfig.mobileProvisionIdentifier; + const teamId = buildConfig.teamId; + let plistTemplate = ` + + + +`; + if (teamId) { + plistTemplate += ` teamID + ${teamId} +`; + } + if (provision) { + plistTemplate += ` provisioningProfiles + + ${projectData.projectIdentifiers.ios} + ${provision} + `; + } + plistTemplate += ` method + app-store + uploadBitcode + + compileBitcode + + uploadSymbols + + +`; + + // Save the options... + temp.track(); + const exportOptionsPlistFilePath = temp.path({ prefix: "export-", suffix: ".plist" }); + this.$fs.writeFile(exportOptionsPlistFilePath, plistTemplate); + + const exportFileDir = path.resolve(path.join(projectRoot, "/build/archive")); + const exportFilePath = path.join(exportFileDir, projectData.projectName + ".ipa"); + + return { exportFileDir, exportFilePath, exportOptionsPlistFilePath }; + } + + private getExportOptionsMethod(projectData: IProjectData, archivePath: string): string { + const embeddedMobileProvisionPath = path.join(archivePath, 'Products', 'Applications', `${projectData.projectName}.app`, "embedded.mobileprovision"); + const provision = mobileProvisionFinder.provision.readFromFile(embeddedMobileProvisionPath); + + return { + "Development": "development", + "AdHoc": "ad-hoc", + "Distribution": "app-store", + "Enterprise": "enterprise" + }[provision.Type]; + } +} +$injector.register("exportOptionsPlistService", ExportOptionsPlistService); diff --git a/lib/services/ios/ios-signing-service.ts b/lib/services/ios/ios-signing-service.ts new file mode 100644 index 0000000000..ae92d8a28d --- /dev/null +++ b/lib/services/ios/ios-signing-service.ts @@ -0,0 +1,194 @@ +import * as path from "path"; +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"; + +export class IOSSigningService implements IiOSSigningService { + constructor( + private $errors: IErrors, + private $fs: IFileSystem, + private $iOSProvisionService: IOSProvisionService, + private $logger: ILogger, + private $pbxprojDomXcode: IPbxprojDomXcode, + private $prompter: IPrompter, + private $xcconfigService: IXcconfigService, + private $xcprojService: IXcprojService + ) { } + + public async setupSigningForDevice(projectRoot: string, projectData: IProjectData, buildConfig: IiOSBuildConfig): Promise { + const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData, projectRoot)); + const signing = xcode.getSigning(projectData.projectName); + + const hasProvisioningProfileInXCConfig = + this.readXCConfigProvisioningProfileSpecifierForIPhoneOs(projectData) || + this.readXCConfigProvisioningProfileSpecifier(projectData) || + this.readXCConfigProvisioningProfileForIPhoneOs(projectData) || + this.readXCConfigProvisioningProfile(projectData); + + if (hasProvisioningProfileInXCConfig && (!signing || signing.style !== "Manual")) { + xcode.setManualSigningStyle(projectData.projectName); + xcode.save(); + } else if (!buildConfig.provision && !(signing && signing.style === "Manual" && !buildConfig.teamId)) { + const teamId = await this.getDevelopmentTeam(projectData, projectRoot, buildConfig.teamId); + await this.setupSigningFromTeam(projectRoot, projectData, teamId); + } + } + + public async setupSigningFromTeam(projectRoot: string, projectData: IProjectData, teamId: string): Promise { + const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData, projectRoot)); + const signing = xcode.getSigning(projectData.projectName); + + let shouldUpdateXcode = false; + if (signing && signing.style === "Automatic") { + if (signing.team !== teamId) { + // Maybe the provided team is name such as "Telerik AD" and we need to convert it to CH******37 + const teamIdsForName = await this.$iOSProvisionService.getTeamIdsWithName(teamId); + if (!teamIdsForName.some(id => id === signing.team)) { + shouldUpdateXcode = true; + } + } + } else { + shouldUpdateXcode = true; + } + + if (shouldUpdateXcode) { + const teamIdsForName = await this.$iOSProvisionService.getTeamIdsWithName(teamId); + if (teamIdsForName.length > 0) { + this.$logger.trace(`Team id ${teamIdsForName[0]} will be used for team name "${teamId}".`); + teamId = teamIdsForName[0]; + } + + xcode.setAutomaticSigningStyle(projectData.projectName, teamId); + xcode.setAutomaticSigningStyleByTargetProductType("com.apple.product-type.app-extension", teamId); + xcode.save(); + + this.$logger.trace(`Set Automatic signing style and team id ${teamId}.`); + } else { + this.$logger.trace(`The specified ${teamId} is already set in the Xcode.`); + } + } + + public async setupSigningFromProvision(projectRoot: string, projectData: IProjectData, provision?: string, mobileProvisionData?: mobileProvisionFinder.provision.MobileProvision): Promise { + if (!provision) { + // read uuid from Xcode an cache... + return; + } + + const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData, projectRoot)); + const signing = xcode.getSigning(projectData.projectName); + + let shouldUpdateXcode = false; + if (signing && signing.style === "Manual") { + for (const config in signing.configurations) { + const options = signing.configurations[config]; + if (options.name !== provision && options.uuid !== provision) { + shouldUpdateXcode = true; + break; + } + } + } else { + shouldUpdateXcode = true; + } + + if (shouldUpdateXcode) { + const pickStart = Date.now(); + const mobileprovision = mobileProvisionData || await this.$iOSProvisionService.pick(provision, projectData.projectIdentifiers.ios); + const pickEnd = Date.now(); + this.$logger.trace("Searched and " + (mobileprovision ? "found" : "failed to find ") + " matching provisioning profile. (" + (pickEnd - pickStart) + "ms.)"); + if (!mobileprovision) { + this.$errors.failWithoutHelp("Failed to find mobile provision with UUID or Name: " + provision); + } + const configuration = { + team: mobileprovision.TeamIdentifier && mobileprovision.TeamIdentifier.length > 0 ? mobileprovision.TeamIdentifier[0] : undefined, + uuid: mobileprovision.UUID, + name: mobileprovision.Name, + identity: mobileprovision.Type === "Development" ? "iPhone Developer" : "iPhone Distribution" + }; + xcode.setManualSigningStyle(projectData.projectName, configuration); + xcode.setManualSigningStyleByTargetProductType("com.apple.product-type.app-extension", configuration); + xcode.save(); + + // this.cache(uuid); + this.$logger.trace(`Set Manual signing style and provisioning profile: ${mobileprovision.Name} (${mobileprovision.UUID})`); + } else { + this.$logger.trace(`The specified provisioning profile is already set in the Xcode: ${provision}`); + } + } + + private getBuildXCConfigFilePath(projectData: IProjectData): string { + return path.join(projectData.appResourcesDirectoryPath, iOSAppResourcesFolderName, BUILD_XCCONFIG_FILE_NAME); + } + + private getPbxProjPath(projectData: IProjectData, projectRoot: string): string { + return path.join(this.$xcprojService.getXcodeprojPath(projectData, projectRoot), "project.pbxproj"); + } + + private async getDevelopmentTeam(projectData: IProjectData, projectRoot: string, teamId?: string): Promise { + teamId = teamId || this.readXCConfigDevelopmentTeam(projectData); + + if (!teamId) { + const teams = await this.$iOSProvisionService.getDevelopmentTeams(); + this.$logger.warn("Xcode requires a team id to be specified when building for device."); + this.$logger.warn("You can specify the team id by setting the DEVELOPMENT_TEAM setting in build.xcconfig file located in App_Resources folder of your app, or by using the --teamId option when calling run, debug or livesync commands."); + if (teams.length === 1) { + teamId = teams[0].id; + this.$logger.warn("Found and using the following development team installed on your system: " + teams[0].name + " (" + teams[0].id + ")"); + } else if (teams.length > 0) { + if (!helpers.isInteractive()) { + this.$errors.failWithoutHelp(`Unable to determine default development team. Available development teams are: ${_.map(teams, team => team.id)}. Specify team in app/App_Resources/iOS/build.xcconfig file in the following way: DEVELOPMENT_TEAM = `); + } + + const choices: string[] = []; + for (const team of teams) { + choices.push(team.name + " (" + team.id + ")"); + } + const choice = await this.$prompter.promptForChoice('Found multiple development teams, select one:', choices); + teamId = teams[choices.indexOf(choice)].id; + + const choicesPersist = [ + "Yes, set the DEVELOPMENT_TEAM setting in build.xcconfig file.", + "Yes, persist the team id in platforms folder.", + "No, don't persist this setting." + ]; + const choicePersist = await this.$prompter.promptForChoice("Do you want to make teamId: " + teamId + " a persistent choice for your app?", choicesPersist); + switch (choicesPersist.indexOf(choicePersist)) { + case 0: + const xcconfigFile = path.join(projectData.appResourcesDirectoryPath, "iOS", BUILD_XCCONFIG_FILE_NAME); + this.$fs.appendFile(xcconfigFile, "\nDEVELOPMENT_TEAM = " + teamId + "\n"); + break; + case 1: + this.$fs.writeFile(path.join(projectRoot, "teamid"), teamId); + break; + default: + break; + } + } + } + + this.$logger.trace(`Selected teamId is '${teamId}'.`); + + return teamId; + } + + private readXCConfigDevelopmentTeam(projectData: IProjectData): string { + return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "DEVELOPMENT_TEAM"); + } + + private readXCConfigProvisioningProfile(projectData: IProjectData): string { + return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE"); + } + + private readXCConfigProvisioningProfileForIPhoneOs(projectData: IProjectData): string { + return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE[sdk=iphoneos*]"); + } + + private readXCConfigProvisioningProfileSpecifier(projectData: IProjectData): string { + return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER"); + } + + private readXCConfigProvisioningProfileSpecifierForIPhoneOs(projectData: IProjectData): string { + return this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]"); + } +} +$injector.register("iOSSigningService", IOSSigningService); diff --git a/lib/services/ios/xcodebuild-args-service.ts b/lib/services/ios/xcodebuild-args-service.ts new file mode 100644 index 0000000000..5165534428 --- /dev/null +++ b/lib/services/ios/xcodebuild-args-service.ts @@ -0,0 +1,102 @@ +import * as path from "path"; +import * as constants from "../../constants"; +import { Configurations } from "../../common/constants"; + +const DevicePlatformSdkName = "iphoneos"; +const SimulatorPlatformSdkName = "iphonesimulator"; + +export class XcodebuildArgsService implements IXcodebuildArgsService { + + constructor( + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $devicesService: Mobile.IDevicesService, + private $fs: IFileSystem, + private $logger: ILogger + ) { } + + public async getBuildForSimulatorArgs(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + let args = await this.getArchitecturesArgs(buildConfig); + + args = args + .concat([ + "build", + "-configuration", buildConfig.release ? Configurations.Release : Configurations.Debug, + "CODE_SIGN_IDENTITY=" + ]) + .concat(this.getBuildCommonArgs(platformData.projectRoot, SimulatorPlatformSdkName)) + .concat(this.getBuildLoggingArgs()) + .concat(this.getXcodeProjectArgs(platformData.projectRoot, projectData)); + + return args; + } + + public async getBuildForDeviceArgs(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + const architectures = await this.getArchitecturesArgs(buildConfig); + const archivePath = path.join(platformData.getBuildOutputPath(buildConfig), projectData.projectName + ".xcarchive"); + const args = [ + "archive", + "-archivePath", archivePath, + "-configuration", buildConfig.release ? Configurations.Release : Configurations.Debug + ] + .concat(this.getXcodeProjectArgs(platformData.projectRoot, projectData, "scheme")) + .concat(architectures) + .concat(this.getBuildCommonArgs(platformData.projectRoot, DevicePlatformSdkName)) + .concat(this.getBuildLoggingArgs()); + + return args; + } + + private async getArchitecturesArgs(buildConfig: IBuildConfig): Promise { + const args = []; + + const devicesArchitectures = buildConfig.buildForDevice ? await this.getArchitecturesFromConnectedDevices(buildConfig) : []; + if (!buildConfig.buildForDevice || devicesArchitectures.length > 1) { + args.push("ONLY_ACTIVE_ARCH=NO"); + } + + return args; + } + + private getXcodeProjectArgs(projectRoot: string, projectData: IProjectData, product?: "scheme" | "target"): string[] { + const xcworkspacePath = path.join(projectRoot, `${projectData.projectName}.xcworkspace`); + if (this.$fs.exists(xcworkspacePath)) { + return [ "-workspace", xcworkspacePath, "-scheme", projectData.projectName ]; + } + + const xcodeprojPath = path.join(projectRoot, `${projectData.projectName}.xcodeproj`); + return [ "-project", xcodeprojPath, product ? "-" + product : "-target", projectData.projectName ]; + } + + private getBuildLoggingArgs(): string[] { + return this.$logger.getLevel() === "INFO" ? ["-quiet"] : []; + } + + private getBuildCommonArgs(projectRoot: string, platformSdkName: string): string[] { + const args = [ + "-sdk", platformSdkName, + "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR), + 'SHARED_PRECOMPS_DIR=' + path.join(projectRoot, 'build', 'sharedpch'), + '-allowProvisioningUpdates' + ]; + + return args; + } + + private async getArchitecturesFromConnectedDevices(buildConfig: IiOSBuildConfig): Promise { + const platform = this.$devicePlatformsConstants.iOS.toLowerCase(); + await this.$devicesService.initialize({ + platform, + deviceId: buildConfig.device, + skipEmulatorStart: true + }); + const instances = this.$devicesService.getDevicesForPlatform(platform); + const architectures = _(instances) + .map(d => d.deviceInfo.activeArchitecture) + .filter(d => !!d) + .uniq() + .value(); + + return architectures; + } +} +$injector.register("xcodebuildArgsService", XcodebuildArgsService); diff --git a/lib/services/ios/xcodebuild-command-service.ts b/lib/services/ios/xcodebuild-command-service.ts new file mode 100644 index 0000000000..eb81c3e2fa --- /dev/null +++ b/lib/services/ios/xcodebuild-command-service.ts @@ -0,0 +1,29 @@ +import * as constants from "../../constants"; + +export class XcodebuildCommandService implements IXcodebuildCommandService { + constructor( + private $childProcess: IChildProcess, + private $errors: IErrors, + private $logger: ILogger + ) { } + + public async executeCommand(args: string[], options: { cwd: string, stdio: string, message?: string, spawnOptions?: any }): Promise { + const { message, cwd, stdio = "inherit", spawnOptions } = options; + this.$logger.info(message || "Xcode build..."); + + const childProcessOptions = { cwd, stdio }; + + try { + const commandResult = await this.$childProcess.spawnFromEvent("xcodebuild", + args, + "exit", + childProcessOptions, + spawnOptions || { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }); + + return commandResult; + } catch (err) { + this.$errors.failWithoutHelp(err.message); + } + } +} +$injector.register("xcodebuildCommandService", XcodebuildCommandService); diff --git a/lib/services/ios/xcodebuild-service.ts b/lib/services/ios/xcodebuild-service.ts new file mode 100644 index 0000000000..d1a3affef9 --- /dev/null +++ b/lib/services/ios/xcodebuild-service.ts @@ -0,0 +1,58 @@ +import * as path from "path"; + +export class XcodebuildService implements IXcodebuildService { + constructor( + private $exportOptionsPlistService: IExportOptionsPlistService, + private $xcodebuildArgsService: IXcodebuildArgsService, + private $xcodebuildCommandService: IXcodebuildCommandService + ) { } + + public async buildForDevice(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + const args = await this.$xcodebuildArgsService.getBuildForDeviceArgs(platformData, projectData, buildConfig); + await this.$xcodebuildCommandService.executeCommand(args, { cwd: platformData.projectRoot, stdio: buildConfig && buildConfig.buildOutputStdio }); + const archivePath = await this.createDevelopmentArchive(platformData, projectData, buildConfig); + return archivePath; + } + + public async buildForSimulator(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + const args = await this.$xcodebuildArgsService.getBuildForSimulatorArgs(platformData, projectData, buildConfig); + await this.$xcodebuildCommandService.executeCommand(args, { cwd: platformData.projectRoot, stdio: buildConfig.buildOutputStdio }); + } + + public async buildForAppStore(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + const args = await this.$xcodebuildArgsService.getBuildForDeviceArgs(platformData, projectData, buildConfig); + await this.$xcodebuildCommandService.executeCommand(args, { cwd: platformData.projectRoot, stdio: buildConfig && buildConfig.buildOutputStdio }); + const archivePath = await this.createDistributionArchive(platformData, projectData, buildConfig); + return archivePath; + } + + private async createDevelopmentArchive(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + const archivePath = path.join(platformData.getBuildOutputPath(buildConfig), projectData.projectName + ".xcarchive"); + const output = this.$exportOptionsPlistService.createDevelopmentExportOptionsPlist(archivePath, projectData, buildConfig); + const args = [ + "-exportArchive", + "-archivePath", archivePath, + "-exportPath", output.exportFileDir, + "-exportOptionsPlist", output.exportOptionsPlistFilePath + ]; + await this.$xcodebuildCommandService.executeCommand(args, { cwd: platformData.projectRoot, stdio: buildConfig.buildOutputStdio }); + + return output.exportFilePath; + } + + private async createDistributionArchive(platformData: IPlatformData, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + const archivePath = path.join(platformData.getBuildOutputPath(buildConfig), projectData.projectName + ".xcarchive"); + const output = this.$exportOptionsPlistService.createDistributionExportOptionsPlist(archivePath, projectData, buildConfig); + const args = [ + "-exportArchive", + "-archivePath", archivePath, + "-exportPath", output.exportFileDir, + "-exportOptionsPlist", output.exportOptionsPlistFilePath + ]; + + await this.$xcodebuildCommandService.executeCommand(args, { cwd: platformData.projectRoot }); + + return output.exportFilePath; + } +} +$injector.register("xcodebuildService", XcodebuildService); diff --git a/lib/services/xcproj-service.ts b/lib/services/xcproj-service.ts index 2344a89065..966f20880c 100644 --- a/lib/services/xcproj-service.ts +++ b/lib/services/xcproj-service.ts @@ -14,8 +14,8 @@ class XcprojService implements IXcprojService { private $xcodeSelectService: IXcodeSelectService) { } - public getXcodeprojPath(projectData: IProjectData, platformData: IPlatformData): string { - return path.join(platformData.projectRoot, projectData.projectName + IosProjectConstants.XcodeProjExtName); + public getXcodeprojPath(projectData: IProjectData, projectRoot: string): string { + return path.join(projectRoot, projectData.projectName + IosProjectConstants.XcodeProjExtName); } public async verifyXcproj(opts: IVerifyXcprojOptions): Promise { diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index dae29f0f63..be0b2b1fb7 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -1,4 +1,4 @@ -import { join, resolve, dirname, basename, extname } from "path"; +import { join, dirname, basename, extname } from "path"; import { EOL } from "os"; import * as ChildProcessLib from "../lib/common/child-process"; import * as ConfigLib from "../lib/config"; @@ -35,6 +35,11 @@ import { ProjectDataStub } from "./stubs"; import { xcode } from "../lib/node/xcode"; import temp = require("temp"); import { CocoaPodsPlatformManager } from "../lib/services/cocoapods-platform-manager"; +import { XcodebuildService } from "../lib/services/ios/xcodebuild-service"; +import { XcodebuildCommandService } from "../lib/services/ios/xcodebuild-command-service"; +import { XcodebuildArgsService } from "../lib/services/ios/xcodebuild-args-service"; +import { ExportOptionsPlistService } from "../lib/services/ios/export-options-plist-service"; +import { IOSSigningService } from "../lib/services/ios/ios-signing-service"; temp.track(); class IOSSimulatorDiscoveryMock extends DeviceDiscovery { @@ -105,8 +110,8 @@ function createTestInjector(projectPath: string, projectName: string, xCode?: IX shouldUseXcproj: false }; }, - getXcodeprojPath: (projData: IProjectData, platformData: IPlatformData) => { - return join(platformData.projectRoot, projData.projectName + ".xcodeproj"); + getXcodeprojPath: (projData: IProjectData, projectRoot: string) => { + return join(projectRoot, projData.projectName + ".xcodeproj"); }, checkIfXcodeprojIsRequired: () => ({}) }); @@ -155,6 +160,13 @@ function createTestInjector(projectPath: string, projectName: string, xCode?: IX removeExtensions: () => { /* */ }, addExtensionsFromPath: () => Promise.resolve() }); + testInjector.register("timers", {}); + testInjector.register("iOSSigningService", IOSSigningService); + testInjector.register("xcodebuildService", XcodebuildService); + testInjector.register("xcodebuildCommandService", XcodebuildCommandService); + testInjector.register("xcodebuildArgsService", XcodebuildArgsService); + testInjector.register("exportOptionsPlistService", ExportOptionsPlistService); + return testInjector; } @@ -174,178 +186,6 @@ function createPackageJson(testInjector: IInjector, projectPath: string, project testInjector.resolve("fs").writeJson(join(projectPath, "package.json"), packageJsonData); } -function expectOption(args: string[], option: string, value: string, message?: string): void { - const index = args.indexOf(option); - assert.ok(index >= 0, "Expected " + option + " to be set."); - assert.ok(args.length > index + 1, "Expected " + option + " to have value"); - assert.equal(args[index + 1], value, message); -} - -function readOption(args: string[], option: string): string { - const index = args.indexOf(option); - assert.ok(index >= 0, "Expected " + option + " to be set."); - assert.ok(args.length > index + 1, "Expected " + option + " to have value"); - return args[index + 1]; -} - -describe("iOSProjectService", () => { - describe("archive", () => { - async function setupArchive(options?: { archivePath?: string }): Promise<{ run: () => Promise, assert: () => void }> { - const hasCustomArchivePath = options && options.archivePath; - - const projectName = "projectDirectory"; - const projectPath = temp.mkdirSync(projectName); - - const testInjector = createTestInjector(projectPath, projectName); - const iOSProjectService = testInjector.resolve("iOSProjectService"); - const projectData: IProjectData = testInjector.resolve("projectData"); - - const childProcess = testInjector.resolve("childProcess"); - let xcodebuildExeced = false; - - let archivePath: string; - - childProcess.spawnFromEvent = (cmd: string, args: string[]) => { - assert.equal(cmd, "xcodebuild", "Expected iOSProjectService.archive to call xcodebuild.archive"); - xcodebuildExeced = true; - - if (hasCustomArchivePath) { - archivePath = resolve(options.archivePath); - } else { - archivePath = join(projectPath, "platforms", "ios", "build", "Release-iphoneos", projectName + ".xcarchive"); - } - - assert.ok(args.indexOf("archive") >= 0, "Expected xcodebuild to be executed with archive param."); - - expectOption(args, "-archivePath", archivePath, hasCustomArchivePath ? "Wrong path passed to xcarchive" : "exports xcodearchive to platforms/ios/build/archive."); - expectOption(args, "-project", join(projectPath, "platforms", "ios", projectName + ".xcodeproj"), "Path to Xcode project is wrong."); - expectOption(args, "-scheme", projectName, "The provided scheme is wrong."); - - return Promise.resolve(); - }; - - let resultArchivePath: string; - - return { - run: async (): Promise => { - if (hasCustomArchivePath) { - resultArchivePath = await iOSProjectService.archive(projectData, null, { archivePath: options.archivePath }); - } else { - resultArchivePath = await iOSProjectService.archive(projectData, null); - } - }, - assert: () => { - assert.ok(xcodebuildExeced, "Expected xcodebuild archive to be executed"); - assert.equal(resultArchivePath, archivePath, "iOSProjectService.archive expected to return the path to the archive"); - } - }; - } - - if (require("os").platform() !== "darwin") { - console.log("Skipping iOS archive tests. They can work only on macOS"); - } else { - it("by default exports xcodearchive to platforms/ios/build/archive/.xcarchive", async () => { - const setup = await setupArchive(); - await setup.run(); - setup.assert(); - }); - it("can pass archivePath to xcodebuild -archivePath", async () => { - const setup = await setupArchive({ archivePath: "myarchive.xcarchive" }); - await setup.run(); - setup.assert(); - }); - } - }); - - describe("exportArchive", () => { - const noTeamPlist = ` - - - - method - app-store - uploadBitcode - - compileBitcode - - uploadSymbols - - -`; - - const myTeamPlist = ` - - - - teamID - MyTeam - method - app-store - uploadBitcode - - compileBitcode - - uploadSymbols - - -`; - - async function testExportArchive(options: { teamID?: string }, expectedPlistContent: string): Promise { - const projectName = "projectDirectory"; - const projectPath = temp.mkdirSync(projectName); - - const testInjector = createTestInjector(projectPath, projectName); - const iOSProjectService = testInjector.resolve("iOSProjectService"); - const projectData: IProjectData = testInjector.resolve("projectData"); - - const archivePath = join(projectPath, "platforms", "ios", "build", "archive", projectName + ".xcarchive"); - - const childProcess = testInjector.resolve("childProcess"); - const fs = testInjector.resolve("fs"); - - let xcodebuildExeced = false; - - childProcess.spawnFromEvent = (cmd: string, args: string[]) => { - assert.equal(cmd, "xcodebuild", "Expected xcodebuild to be called"); - xcodebuildExeced = true; - - assert.ok(args.indexOf("-exportArchive") >= 0, "Expected -exportArchive to be set on xcodebuild."); - - expectOption(args, "-archivePath", archivePath, "Expected the -archivePath to be passed to xcodebuild."); - expectOption(args, "-exportPath", join(projectPath, "platforms", "ios", "build", "archive"), "Expected the -archivePath to be passed to xcodebuild."); - const plist = readOption(args, "-exportOptionsPlist"); - - assert.ok(plist); - - const plistContent = fs.readText(plist); - // There may be better way to equal property lists - assert.equal(plistContent, expectedPlistContent, "Mismatch in exportOptionsPlist content"); - - return Promise.resolve(); - }; - - const resultIpa = await iOSProjectService.exportArchive(projectData, { archivePath, teamID: options.teamID }); - const expectedIpa = join(projectPath, "platforms", "ios", "build", "archive", projectName + ".ipa"); - - assert.equal(resultIpa, expectedIpa, "Expected IPA at the specified location"); - - assert.ok(xcodebuildExeced, "Expected xcodebuild to be executed"); - } - - if (require("os").platform() !== "darwin") { - console.log("Skipping iOS export archive tests. They can work only on macOS"); - } else { - it("calls xcodebuild -exportArchive to produce .IPA", async () => { - await testExportArchive({}, noTeamPlist); - }); - - it("passes the --team-id option down the xcodebuild -exportArchive throug the -exportOptionsPlist", async () => { - await testExportArchive({ teamID: "MyTeam" }, myTeamPlist); - }); - } - }); -}); - describe("Cocoapods support", () => { if (require("os").platform() !== "darwin") { console.log("Skipping Cocoapods tests. They cannot work on windows"); @@ -1025,91 +865,6 @@ describe("iOS Project Service Signing", () => { assert.isFalse(!!changes.signingChanged); }); }); - - describe("specifying provision", () => { - describe("from Automatic to provision name", () => { - beforeEach(() => { - files[pbxproj] = ""; - pbxprojDomXcode.Xcode.open = function (path: string) { - return { - getSigning(x: string) { - return { style: "Automatic", teamID: "AutoTeam" }; - } - }; - }; - }); - it("fails with proper error if the provision can not be found", async () => { - try { - await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDev2", teamId: undefined }); - } catch (e) { - assert.isTrue(e.toString().indexOf("Failed to find mobile provision with UUID or Name: NativeScriptDev2") >= 0); - } - }); - it("succeeds if the provision name is provided for development cert", async () => { - const stack: any = []; - pbxprojDomXcode.Xcode.open = function (path: string) { - assert.equal(path, pbxproj); - return { - getSigning() { - return { style: "Automatic", teamID: "AutoTeam" }; - }, - save() { - stack.push("save()"); - }, - setManualSigningStyle(targetName: string, manualSigning: any) { - stack.push({ targetName, manualSigning }); - }, - setManualSigningStyleByTargetProductType: () => ({}), - setManualSigningStyleByTargetKey: () => ({}) - }; - }; - await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDev", teamId: undefined }); - assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID101", uuid: "12345", name: "NativeScriptDev", identity: "iPhone Developer" } }, "save()"]); - }); - it("succeds if the provision name is provided for distribution cert", async () => { - const stack: any = []; - pbxprojDomXcode.Xcode.open = function (path: string) { - assert.equal(path, pbxproj); - return { - getSigning() { - return { style: "Automatic", teamID: "AutoTeam" }; - }, - save() { - stack.push("save()"); - }, - setManualSigningStyle(targetName: string, manualSigning: any) { - stack.push({ targetName, manualSigning }); - }, - setManualSigningStyleByTargetProductType: () => ({}), - setManualSigningStyleByTargetKey: () => ({}) - }; - }; - await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptDist", teamId: undefined }); - assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID202", uuid: "6789", name: "NativeScriptDist", identity: "iPhone Distribution" } }, "save()"]); - }); - it("succeds if the provision name is provided for adhoc cert", async () => { - const stack: any = []; - pbxprojDomXcode.Xcode.open = function (path: string) { - assert.equal(path, pbxproj); - return { - getSigning() { - return { style: "Automatic", teamID: "AutoTeam" }; - }, - save() { - stack.push("save()"); - }, - setManualSigningStyle(targetName: string, manualSigning: any) { - stack.push({ targetName, manualSigning }); - }, - setManualSigningStyleByTargetProductType: () => ({}), - setManualSigningStyleByTargetKey: () => ({}) - }; - }; - await iOSProjectService.prepareProject(projectData, { sdk: undefined, provision: "NativeScriptAdHoc", teamId: undefined }); - assert.deepEqual(stack, [{ targetName: projectDirName, manualSigning: { team: "TKID303", uuid: "1010", name: "NativeScriptAdHoc", identity: "iPhone Distribution" } }, "save()"]); - }); - }); - }); }); describe("Merge Project XCConfig files", () => { @@ -1247,163 +1002,6 @@ describe("Merge Project XCConfig files", () => { }); }); -describe("buildProject", () => { - let xcodeBuildCommandArgs: string[] = []; - - function setup(data: { frameworkVersion: string, deploymentTarget: string, devices?: Mobile.IDevice[] }): IInjector { - const projectPath = "myTestProjectPath"; - const projectName = "myTestProjectName"; - const testInjector = createTestInjector(projectPath, projectName); - - const childProcess = testInjector.resolve("childProcess"); - childProcess.spawnFromEvent = (command: string, args: string[]) => { - if (command === "xcodebuild" && args[0] !== "-exportArchive") { - xcodeBuildCommandArgs = args; - } - }; - - const projectDataService = testInjector.resolve("projectDataService"); - projectDataService.getNSValue = (projectDir: string, propertyName: string) => { - if (propertyName === "tns-ios") { - return { - name: "tns-ios", - version: data.frameworkVersion - }; - } - }; - - const projectData = testInjector.resolve("projectData"); - projectData.appResourcesDirectoryPath = join(projectPath, "app", "App_Resources"); - - const devicesService = testInjector.resolve("devicesService"); - devicesService.initialize = () => ({}); - devicesService.getDeviceInstances = () => data.devices || []; - - const xcconfigService = testInjector.resolve("xcconfigService"); - xcconfigService.readPropertyValue = (projectDir: string, propertyName: string) => { - if (propertyName === "IPHONEOS_DEPLOYMENT_TARGET") { - return data.deploymentTarget; - } - }; - - const pbxprojDomXcode = testInjector.resolve("pbxprojDomXcode"); - pbxprojDomXcode.Xcode = { - open: () => ({ - getSigning: () => ({}), - setAutomaticSigningStyle: () => ({}), - setAutomaticSigningStyleByTargetProductType: () => ({}), - setAutomaticSigningStyleByTargetKey: () => ({}), - save: () => ({}) - }) - }; - - const iOSProvisionService = testInjector.resolve("iOSProvisionService"); - iOSProvisionService.getDevelopmentTeams = () => ({}); - iOSProvisionService.getTeamIdsWithName = () => ({}); - - return testInjector; - } - - function executeTests(testCases: any[], data: { buildForDevice: boolean }) { - _.each(testCases, testCase => { - it(`${testCase.name}`, async () => { - const testInjector = setup({ frameworkVersion: testCase.frameworkVersion, deploymentTarget: testCase.deploymentTarget }); - const projectData: IProjectData = testInjector.resolve("projectData"); - - const iOSProjectService = testInjector.resolve("iOSProjectService"); - (iOSProjectService).getExportOptionsMethod = () => ({}); - await iOSProjectService.buildProject("myProjectRoot", projectData, { buildForDevice: data.buildForDevice }); - - const archsItem = xcodeBuildCommandArgs.find(item => item.startsWith("ARCHS=")); - if (testCase.expectedArchs) { - const archsValue = archsItem.split("=")[1]; - assert.deepEqual(archsValue, testCase.expectedArchs); - } else { - assert.deepEqual(undefined, archsItem); - } - }); - }); - } - - describe("for device", () => { - afterEach(() => { - xcodeBuildCommandArgs = []; - }); - - const testCases = [{ - name: "shouldn't exclude armv7 architecture when deployment target 10", - frameworkVersion: "5.0.0", - deploymentTarget: "10.0", - expectedArchs: "armv7 arm64" - }, { - name: "should exclude armv7 architecture when deployment target is 11", - frameworkVersion: "5.0.0", - deploymentTarget: "11.0", - expectedArchs: "arm64" - }, { - name: "shouldn't pass architecture to xcodebuild command when frameworkVersion is 5.1.0", - frameworkVersion: "5.1.0", - deploymentTarget: "11.0" - }, { - name: "should pass only 64bit architecture to xcodebuild command when frameworkVersion is 5.0.0 and deployment target is 11.0", - frameworkVersion: "5.0.0", - deploymentTarget: "11.0", - expectedArchs: "arm64" - }, { - name: "should pass both architectures to xcodebuild command when frameworkVersion is 5.0.0 and deployment target is 10.0", - frameworkVersion: "5.0.0", - deploymentTarget: "10.0", - expectedArchs: "armv7 arm64" - }, { - name: "should pass both architectures to xcodebuild command when frameworkVersion is 5.0.0 and no deployment target", - frameworkVersion: "5.0.0", - deploymentTarget: null, - expectedArchs: "armv7 arm64" - }]; - - executeTests(testCases, { buildForDevice: true }); - }); - - describe("for simulator", () => { - afterEach(() => { - xcodeBuildCommandArgs = []; - }); - - const testCases = [{ - name: "shouldn't exclude i386 architecture when deployment target is 10", - frameworkVersion: "5.0.0", - deploymentTarget: "10.0", - expectedArchs: "i386 x86_64" - }, { - name: "should exclude i386 architecture when deployment target is 11", - frameworkVersion: "5.0.0", - deploymentTarget: "11.0", - expectedArchs: "x86_64" - }, { - name: "shouldn't pass architecture to xcodebuild command when frameworkVersion is 5.1.0", - frameworkVersion: "5.1.0", - deploymentTarget: "11.0" - }, { - name: "should pass only 64bit architecture to xcodebuild command when frameworkVersion is 5.0.0 and deployment target is 11.0", - frameworkVersion: "5.0.0", - deploymentTarget: "11.0", - expectedArchs: "x86_64" - }, { - name: "should pass both architectures to xcodebuild command when frameworkVersion is 5.0.0 and deployment target is 10.0", - frameworkVersion: "5.0.0", - deploymentTarget: "10.0", - expectedArchs: "i386 x86_64" - }, { - name: "should pass both architectures to xcodebuild command when frameworkVersion is 5.0.0 and no deployment target", - frameworkVersion: "5.0.0", - deploymentTarget: null, - expectedArchs: "i386 x86_64" - }]; - - executeTests(testCases, { buildForDevice: false }); - }); -}); - describe("handleNativeDependenciesChange", () => { it("ensure the correct order of pod install and merging pod's xcconfig file", async () => { const executedCocoapodsMethods: string[] = []; From 1232e2bdc297f4ec8d2a2dff1938ae16a6e1b9a6 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 22 Apr 2019 01:06:28 +0300 Subject: [PATCH 013/102] test: add unit tests --- .../ios/export-options-plist-service.ts | 106 ++++++ test/services/ios/ios-signing-service.ts | 330 ++++++++++++++++++ test/services/ios/xcodebuild-args-service.ts | 132 +++++++ test/services/ios/xcodebuild-service.ts | 93 +++++ test/tns-appstore-upload.ts | 37 +- 5 files changed, 674 insertions(+), 24 deletions(-) create mode 100644 test/services/ios/export-options-plist-service.ts create mode 100644 test/services/ios/ios-signing-service.ts create mode 100644 test/services/ios/xcodebuild-args-service.ts create mode 100644 test/services/ios/xcodebuild-service.ts diff --git a/test/services/ios/export-options-plist-service.ts b/test/services/ios/export-options-plist-service.ts new file mode 100644 index 0000000000..e92c6e5a41 --- /dev/null +++ b/test/services/ios/export-options-plist-service.ts @@ -0,0 +1,106 @@ +import { Yok } from "../../../lib/common/yok"; +import { ExportOptionsPlistService } from "../../../lib/services/ios/export-options-plist-service"; +import { assert } from "chai"; +import { EOL } from "os"; + +let actualPlistTemplate: string = null; +const projectName = "myProjectName"; +const projectRoot = "/my/test/project/platforms/ios"; +const archivePath = "/my/test/archive/path"; + +function createTestInjector() { + const injector = new Yok(); + injector.register("fs", { + writeFile: (exportPath: string, plistTemplate: string) => { + actualPlistTemplate = plistTemplate; + } + }); + injector.register("exportOptionsPlistService", ExportOptionsPlistService); + + return injector; +} + +describe("ExportOptionsPlistService", () => { + describe("createDevelopmentExportOptionsPlist", () => { + const testCases = [ + { + name: "should create default export options plist", + buildConfig: {} + }, + { + name: "should create export options plist with provision", + buildConfig: { provision: "myTestProvision" }, + expectedPlist: "provisioningProfiles org.nativescript.myTestApp myTestProvision " + }, + { + name: "should create export options plist with mobileProvisionIdentifier", + buildConfig: { mobileProvisionIdentifier: "myTestProvision" }, + expectedPlist: "provisioningProfiles org.nativescript.myTestApp myTestProvision " + } + ]; + + _.each(testCases, testCase => { + _.each(["Development", "AdHoc", "Distribution", "Enterprise"], provisionType => { + it(testCase.name, () => { + const injector = createTestInjector(); + const exportOptionsPlistService = injector.resolve("exportOptionsPlistService"); + exportOptionsPlistService.getExportOptionsMethod = () => provisionType; + + const projectData = { projectName, projectIdentifiers: { ios: "org.nativescript.myTestApp" }}; + exportOptionsPlistService.createDevelopmentExportOptionsPlist(archivePath, projectData, testCase.buildConfig); + + const template = actualPlistTemplate.split(EOL).join(" "); + assert.isTrue(template.indexOf(`method ${provisionType}`) > 0); + assert.isTrue(template.indexOf("uploadBitcode ") > 0); + assert.isTrue(template.indexOf("compileBitcode ") > 0); + if (testCase.expectedPlist) { + assert.isTrue(template.indexOf(testCase.expectedPlist) > 0); + } + }); + }); + }); + }); + describe("createDistributionExportOptionsPlist", () => { + const testCases = [ + { + name: "should create default export options plist", + buildConfig: {} + }, + { + name: "should create export options plist with provision", + buildConfig: { provision: "myTestProvision" }, + expectedPlist: "provisioningProfiles org.nativescript.myTestApp myTestProvision " + }, + { + name: "should create export options plist with mobileProvisionIdentifier", + buildConfig: { mobileProvisionIdentifier: "myTestProvision" }, + expectedPlist: "provisioningProfiles org.nativescript.myTestApp myTestProvision " + }, + { + name: "should create export options plist with teamID", + buildConfig: { teamId: "myTeamId" }, + expectedPlist: "teamID myTeamId" + } + ]; + + _.each(testCases, testCase => { + it(testCase.name, () => { + const injector = createTestInjector(); + const exportOptionsPlistService = injector.resolve("exportOptionsPlistService"); + exportOptionsPlistService.getExportOptionsMethod = () => "app-store"; + + const projectData = { projectName, projectIdentifiers: { ios: "org.nativescript.myTestApp" }}; + exportOptionsPlistService.createDistributionExportOptionsPlist(projectRoot, projectData, testCase.buildConfig); + + const template = actualPlistTemplate.split(EOL).join(" "); + assert.isTrue(template.indexOf("method app-store") > 0); + assert.isTrue(template.indexOf("uploadBitcode ") > 0); + assert.isTrue(template.indexOf("compileBitcode ") > 0); + assert.isTrue(template.indexOf("uploadSymbols ") > 0); + if (testCase.expectedPlist) { + assert.isTrue(template.indexOf(testCase.expectedPlist) > 0); + } + }); + }); + }); +}); diff --git a/test/services/ios/ios-signing-service.ts b/test/services/ios/ios-signing-service.ts new file mode 100644 index 0000000000..c38f518094 --- /dev/null +++ b/test/services/ios/ios-signing-service.ts @@ -0,0 +1,330 @@ +import { Yok } from "../../../lib/common/yok"; +import { IOSSigningService } from "../../../lib/services/ios/ios-signing-service"; +import { assert } from "chai"; +import { ManualSigning } from "pbxproj-dom/xcode"; +import { Errors } from "../../../lib/common/errors"; + +interface IXcodeMock { + isSetManualSigningStyleCalled: boolean; + isSetManualSigningStyleByTargetProductTypeCalled: boolean; + isSetAutomaticSigningStyleCalled: boolean; + isSetAutomaticSigningStyleByTargetProductTypeCalled: boolean; + isSaveCalled: boolean; +} + +const projectRoot = "myProjectRoot"; +const teamId = "myTeamId"; +const projectData: any = { + projectName: "myProjectName", + appResourcesDirectoryPath: "app-resources/path", + projectIdentifiers: { + ios: "org.nativescript.testApp" + } +}; +const NativeScriptDev = { + Name: "NativeScriptDev", + TeamName: "Telerik AD", + TeamIdentifier: ["TKID101"], + Entitlements: { + "application-identifier": "*", + "com.apple.developer.team-identifier": "ABC" + }, + UUID: "12345", + ProvisionsAllDevices: false, + Type: "Development" +}; +const NativeScriptDist = { + Name: "NativeScriptDist", + TeamName: "Telerik AD", + TeamIdentifier: ["TKID202"], + Entitlements: { + "application-identifier": "*", + "com.apple.developer.team-identifier": "ABC" + }, + UUID: "6789", + ProvisionsAllDevices: true, + Type: "Distribution" +}; +const NativeScriptAdHoc = { + Name: "NativeScriptAdHoc", + TeamName: "Telerik AD", + TeamIdentifier: ["TKID303"], + Entitlements: { + "application-identifier": "*", + "com.apple.developer.team-identifier": "ABC" + }, + UUID: "1010", + ProvisionsAllDevices: true, + Type: "Distribution" +}; + +class XcodeMock implements IXcodeMock { + public isSetManualSigningStyleCalled = false; + public isSetManualSigningStyleByTargetProductTypeCalled = false; + public isSetAutomaticSigningStyleCalled = false; + public isSetAutomaticSigningStyleByTargetProductTypeCalled = false; + public isSaveCalled = false; + + constructor(private data: { signing: { style: string, team?: string } }) { } + + public getSigning() { + return this.data.signing; + } + + public setManualSigningStyle(projectName: string, configuration: ManualSigning) { + this.isSetManualSigningStyleCalled = true; + } + + public setManualSigningStyleByTargetProductType() { + this.isSetManualSigningStyleByTargetProductTypeCalled = true; + } + + public setAutomaticSigningStyle() { + this.isSetAutomaticSigningStyleCalled = true; + } + + public setAutomaticSigningStyleByTargetProductType() { + this.isSetAutomaticSigningStyleByTargetProductTypeCalled = true; + } + + public save() { + this.isSaveCalled = true; + } +} + +function setup(data: { + hasXCConfigrovisioning?: boolean, + hasXCConfigDevelopmentTeam?: boolean, + signing?: { style: string }, + teamIdsForName?: string[], + provision?: string +}): { injector: IInjector, xcodeMock: any } { + const { hasXCConfigrovisioning, hasXCConfigDevelopmentTeam, signing, teamIdsForName, provision = "myProvision" } = data; + const xcodeMock = new XcodeMock({ signing }); + + const injector = new Yok(); + injector.register("errors", Errors); + injector.register("fs", {}); + injector.register("iOSProvisionService", { + getTeamIdsWithName: () => teamIdsForName || [], + pick: async (uuidOrName: string, projId: string) => { + return ({ + NativeScriptDev, + NativeScriptDist, + NativeScriptAdHoc + })[uuidOrName]; + } + }); + injector.register("logger", { + trace: () => ({}) + }); + injector.register("pbxprojDomXcode", { + Xcode: { + open: () => xcodeMock + } + }); + injector.register("prompter", {}); + injector.register("xcconfigService", { + readPropertyValue: (xcconfigFilePath: string, propertyName: string) => { + if (propertyName.startsWith("PROVISIONING_PROFILE")) { + return hasXCConfigrovisioning ? provision : null; + } + if (propertyName.startsWith("DEVELOPMENT_TEAM")) { + return hasXCConfigDevelopmentTeam ? teamId : null; + } + } + }); + injector.register("xcprojService", { + getXcodeprojPath: () => "some/path" + }); + injector.register("iOSSigningService", IOSSigningService); + + return { injector, xcodeMock }; +} + +describe("IOSSigningService", () => { + describe("setupSigningForDevice", () => { + const testCases = [ + { + name: "should sign the project manually when PROVISIONING_PROFILE is provided from xcconfig and the project is still not signed", + arrangeData: { hasXCConfigrovisioning: true, signing: null }, + assert: (xcodeMock: IXcodeMock) => { + assert.isTrue(xcodeMock.isSetManualSigningStyleCalled); + assert.isTrue(xcodeMock.isSaveCalled); + } + }, + { + name: "should sign the project manually when PROVISIONING_PROFILE is provided from xcconfig and the project is automatically signed", + arrangeData: { hasXCConfigrovisioning: true, signing: { style: "Automatic" } }, + assert: (xcodeMock: IXcodeMock) => { + assert.isTrue(xcodeMock.isSetManualSigningStyleCalled); + assert.isTrue(xcodeMock.isSaveCalled); + } + }, + { + name: "shouldn't sign the project manually when PROVISIONING_PROFILE is provided from xcconfig and the project is already manually signed", + arrangeData: { hasXCConfigrovisioning: true, signing: { style: "Manual" } }, + assert: (xcodeMock: IXcodeMock) => { + assert.isFalse(xcodeMock.isSetManualSigningStyleCalled); + assert.isFalse(xcodeMock.isSaveCalled); + } + }, + { + name: "should sign the project automatically when PROVISIONING_PROFILE is not provided from xcconfig, DEVELOPMENT_TEAM is provided from xcconfig and the project is still not signed", + arrangeData: { hasXCConfigrovisioning: false, hasXCConfigDevelopmentTeam: true, signing: null }, + assert: (xcodeMock: IXcodeMock) => { + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleCalled); + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleByTargetProductTypeCalled); + assert.isTrue(xcodeMock.isSaveCalled); + } + }, + { + name: "should sign the project automatically when PROVISIONING_PROFILE is not provided from xcconfig, DEVELOPMENT_TEAM is provided from xcconfig and the project is automatically signed", + arrangeData: { hasXCConfigrovisioning: false, hasXCConfigDevelopmentTeam: true, signing: { style: "Automatic" } }, + assert: (xcodeMock: IXcodeMock) => { + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleCalled); + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleByTargetProductTypeCalled); + assert.isTrue(xcodeMock.isSaveCalled); + } + }, + { + name: "shouldn't sign the project when PROVISIONING_PROFILE is not provided from xcconfig, DEVELOPMENT_TEAM is provided from xcconfig and the project is already manually signed", + arrangeData: { hasXCConfigrovisioning: false, hasXCConfigDevelopmentTeam: true, signing: { style: "Manual" } }, + assert: (xcodeMock: IXcodeMock) => { + assert.isFalse(xcodeMock.isSetAutomaticSigningStyleCalled); + assert.isFalse(xcodeMock.isSetAutomaticSigningStyleByTargetProductTypeCalled); + assert.isFalse(xcodeMock.isSetManualSigningStyleCalled); + assert.isFalse(xcodeMock.isSaveCalled); + } + } + ]; + + _.each(testCases, testCase => { + it(testCase.name, async () => { + const { injector, xcodeMock } = setup(testCase.arrangeData); + + const iOSSigningService = injector.resolve("iOSSigningService"); + await iOSSigningService.setupSigningForDevice(projectRoot, projectData, (testCase).buildConfig || {}); + + testCase.assert(xcodeMock); + }); + }); + }); + describe("setupSigningFromTeam", () => { + const testCases = [ + { + name: "should sign the project for given teamId when the project is still not signed", + arrangeData: { signing: null }, + assert: (xcodeMock: IXcodeMock) => { + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleCalled); + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleByTargetProductTypeCalled); + assert.isTrue(xcodeMock.isSaveCalled); + assert.isFalse(xcodeMock.isSetManualSigningStyleCalled); + } + }, + { + name: "should sign the project for given teamId when the project is already automatically signed for another team", + arrangeData: { signing: { style: "Automatic" } }, + assert: (xcodeMock: IXcodeMock) => { + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleCalled); + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleByTargetProductTypeCalled); + assert.isTrue(xcodeMock.isSaveCalled); + assert.isFalse(xcodeMock.isSetManualSigningStyleCalled); + } + }, + { + name: "shouldn't sign the project for given teamId when the project is already automatically signed for this team", + arrangeData: { signing: { style: "Automatic", team: teamId }}, + assert: (xcodeMock: IXcodeMock) => { + assert.isFalse(xcodeMock.isSetAutomaticSigningStyleCalled); + assert.isFalse(xcodeMock.isSetAutomaticSigningStyleByTargetProductTypeCalled); + assert.isFalse(xcodeMock.isSaveCalled); + assert.isFalse(xcodeMock.isSetManualSigningStyleCalled); + } + }, + { + name: "shouldn't sign the project for given teamName when the project is already automatically signed for this team", + arrangeData: { signing: { style: "Automatic", team: "anotherTeamId" }, teamIdsForName: [ "anotherTeamId" ] }, + assert: (xcodeMock: IXcodeMock) => { + assert.isFalse(xcodeMock.isSetAutomaticSigningStyleCalled); + assert.isFalse(xcodeMock.isSetAutomaticSigningStyleByTargetProductTypeCalled); + assert.isFalse(xcodeMock.isSaveCalled); + assert.isFalse(xcodeMock.isSetManualSigningStyleCalled); + } + }, + { + name: "should set automatic signing style when the project is already manually signed", + arrangeData: { signing: { style: "Manual" }}, + assert: (xcodeMock: IXcodeMock) => { + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleCalled); + assert.isTrue(xcodeMock.isSetAutomaticSigningStyleByTargetProductTypeCalled); + assert.isTrue(xcodeMock.isSaveCalled); + assert.isFalse(xcodeMock.isSetManualSigningStyleCalled); + } + } + ]; + + _.each(testCases, testCase => { + it(testCase.name, async () => { + const { injector, xcodeMock } = setup(testCase.arrangeData); + + const iOSSigningService: IiOSSigningService = injector.resolve("iOSSigningService"); + await iOSSigningService.setupSigningFromTeam(projectRoot, projectData, teamId); + + testCase.assert(xcodeMock); + }); + }); + }); + describe("setupSigningFromProvision", () => { + const testCases = [ + { + name: "should sign the project manually when it is still not signed", + arrangeData: { signing: null }, + assert: (xcodeMock: IXcodeMock) => { + assert.isTrue(xcodeMock.isSetManualSigningStyleCalled); + assert.isTrue(xcodeMock.isSetManualSigningStyleByTargetProductTypeCalled); + assert.isTrue(xcodeMock.isSaveCalled); + } + }, + { + name: "should sign the project manually when it is automatically signed", + arrangeData: { signing: { style: "Automatic" } }, + assert: (xcodeMock: IXcodeMock) => { + assert.isTrue(xcodeMock.isSetManualSigningStyleCalled); + assert.isTrue(xcodeMock.isSetManualSigningStyleByTargetProductTypeCalled); + assert.isTrue(xcodeMock.isSaveCalled); + } + }, + { + name: "shouldn't sign the project when it is already manual signed", + arrangeData: { signing: { style: "Manual" } }, + assert: (xcodeMock: IXcodeMock) => { + assert.isFalse(xcodeMock.isSetManualSigningStyleCalled); + assert.isFalse(xcodeMock.isSetManualSigningStyleByTargetProductTypeCalled); + assert.isFalse(xcodeMock.isSaveCalled); + } + } + ]; + + _.each(testCases, testCase => { + _.each(["NativeScriptDev", "NativeScriptDist", "NativeScriptAdHoc"], provision => { + it(`${testCase.name} for ${provision} provision`, async () => { + const { injector, xcodeMock } = setup(testCase.arrangeData); + + const iOSSigningService: IiOSSigningService = injector.resolve("iOSSigningService"); + await iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision); + + testCase.assert(xcodeMock); + }); + }); + }); + + it("should throw an error when no mobileProvisionData", async () => { + const provision = "myTestProvision"; + const { injector } = setup({ signing: null }); + + const iOSSigningService: IiOSSigningService = injector.resolve("iOSSigningService"); + assert.isRejected(iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision), `Failed to find mobile provision with UUID or Name: ${provision}`); + }); + }); +}); diff --git a/test/services/ios/xcodebuild-args-service.ts b/test/services/ios/xcodebuild-args-service.ts new file mode 100644 index 0000000000..84f477c027 --- /dev/null +++ b/test/services/ios/xcodebuild-args-service.ts @@ -0,0 +1,132 @@ +import { Yok } from "../../../lib/common/yok"; +import { DevicePlatformsConstants } from "../../../lib/common/mobile/device-platforms-constants"; +import { XcodebuildArgsService } from "../../../lib/services/ios/xcodebuild-args-service"; +import * as path from "path"; +import { assert } from "chai"; + +function createTestInjector(data: { logLevel: string, hasProjectWorkspace: boolean, connectedDevices?: any[] }): IInjector { + const injector = new Yok(); + injector.register("devicePlatformsConstants", DevicePlatformsConstants); + injector.register("devicesService", { + initialize: async () => ({}), + getDevicesForPlatform: () => data.connectedDevices || [] + }); + injector.register("fs", { + exists: () => data.hasProjectWorkspace + }); + injector.register("logger", { + getLevel: () => data.logLevel + }); + injector.register("xcodebuildArgsService", XcodebuildArgsService); + + return injector; +} + +const projectRoot = "path/to/my/app/folder/platforms/ios"; +const projectName = "myApp"; +const buildOutputPath = path.join(projectRoot, projectName, "archive"); + +function getCommonArgs() { + return [ + "BUILD_DIR=" + path.join(projectRoot, "build"), + "SHARED_PRECOMPS_DIR=" + path.join(projectRoot, 'build', 'sharedpch'), + "-allowProvisioningUpdates" + ]; +} + +function getXcodeProjectArgs(data?: { hasProjectWorkspace: boolean, hasTarget?: boolean }) { + return data && data.hasProjectWorkspace ? [ + "-workspace", path.join(projectRoot, `${projectName}.xcworkspace`), + "-scheme", projectName + ] : [ + "-project", path.join(projectRoot, `${projectName}.xcodeproj`), + data && data.hasTarget ? "-target" : "-scheme", projectName + ]; +} + +function getBuildLoggingArgs(logLevel: string): string[] { + if (logLevel === "INFO") { + return ["-quiet"]; + } + + return []; +} + +describe("xcodebuildArgsService", () => { + describe("getBuildForSimulatorArgs", () => { + _.each([true, false], hasProjectWorkspace => { + _.each(["INFO", "TRACE"], logLevel => { + _.each(["Debug", "Release"], configuration => { + it(`should return correct args when workspace is ${hasProjectWorkspace} with ${logLevel} log level and ${configuration} configuration`, async () => { + const injector = createTestInjector({ logLevel, hasProjectWorkspace }); + + const buildConfig = { buildForDevice: false, release: configuration === "Release" }; + const xcodebuildArgsService = injector.resolve("xcodebuildArgsService"); + const actualArgs = await xcodebuildArgsService.getBuildForSimulatorArgs({ projectRoot }, { projectName }, buildConfig); + + const expectedArgs = [ + "ONLY_ACTIVE_ARCH=NO", + "build", + "-configuration", configuration, + "CODE_SIGN_IDENTITY=", + "-sdk", "iphonesimulator" + ] + .concat(getCommonArgs()) + .concat(getBuildLoggingArgs(logLevel)) + .concat(getXcodeProjectArgs({ hasProjectWorkspace, hasTarget: true })); + + assert.deepEqual(actualArgs, expectedArgs); + }); + }); + }); + }); + }); + describe("getBuildForDeviceArgs", () => { + const testCases = [ + { + name: "should return correct args when there are more than one connected device", + connectedDevices: [{deviceInfo: {activeArchitecture: "arm64"}}, {deviceInfo: {activeArchitecture: "armv7"}}], + expectedArgs: ["ONLY_ACTIVE_ARCH=NO", "-sdk", "iphoneos"].concat(getCommonArgs()) + }, + { + name: "should return correct args when there is only one connected device", + connectedDevices: [{deviceInfo: {activeArchitecture: "arm64"}}], + expectedArgs: ["-sdk", "iphoneos"].concat(getCommonArgs()) + }, + { + name: "should return correct args when no connected devices", + connectedDevices: [], + expectedArgs: ["-sdk", "iphoneos"].concat(getCommonArgs()) + } + ]; + + _.each(testCases, testCase => { + _.each([true, false], hasProjectWorkspace => { + _.each(["INFO", "TRACE"], logLevel => { + _.each(["Debug", "Release"], configuration => { + it(`${testCase.name} when hasProjectWorkspace is ${hasProjectWorkspace} with ${logLevel} log level and ${configuration} configuration`, async () => { + const injector = createTestInjector({ logLevel, hasProjectWorkspace, connectedDevices: testCase.connectedDevices }); + + const platformData = { projectRoot, getBuildOutputPath: () => buildOutputPath }; + const projectData = { projectName }; + const buildConfig = { buildForDevice: true, release: configuration === "Release" }; + const xcodebuildArgsService: IXcodebuildArgsService = injector.resolve("xcodebuildArgsService"); + const actualArgs = await xcodebuildArgsService.getBuildForDeviceArgs(platformData, projectData, buildConfig); + + const expectedArgs = [ + "archive", + "-archivePath", path.join(buildOutputPath, `${projectName}.xcarchive`), + "-configuration", configuration + ] + .concat(getXcodeProjectArgs({ hasProjectWorkspace })) + .concat(testCase.expectedArgs) + .concat(getBuildLoggingArgs(logLevel)); + + assert.deepEqual(actualArgs, expectedArgs); + }); + }); + }); + }); + }); + }); +}); diff --git a/test/services/ios/xcodebuild-service.ts b/test/services/ios/xcodebuild-service.ts new file mode 100644 index 0000000000..17f1dfebca --- /dev/null +++ b/test/services/ios/xcodebuild-service.ts @@ -0,0 +1,93 @@ +import { Yok } from "../../../lib/common/yok"; +import { XcodebuildService } from "../../../lib/services/ios/xcodebuild-service"; +import * as path from "path"; +import { assert } from "chai"; + +const projectRoot = "path/to/my/app/folder/platforms/ios"; +const projectName = "myApp"; +const buildOutputPath = path.join(projectRoot, projectName, "archive"); +const exportOptionsPlistOutput = { + exportFileDir: buildOutputPath, + exportFilePath: path.join(buildOutputPath, `${projectName}.ipa`), + exportOptionsPlistFilePath: "/my/temp/options/plist/file/path" +}; +let actualBuildArgs: string[] = []; +let actualBuildOptions:IXcodebuildCommandOptions = null; + +function createTestInjector(): IInjector { + const injector = new Yok(); + injector.register("exportOptionsPlistService", { + createDevelopmentExportOptionsPlist: () => exportOptionsPlistOutput, + createDistributionExportOptionsPlist: () => exportOptionsPlistOutput + }); + injector.register("xcodebuildArgsService", { + getBuildForDeviceArgs: async () => [], + getBuildForSimulatorArgs: async () => [] + }); + injector.register("xcodebuildCommandService", { + executeCommand: async (args: string[], options: IXcodebuildCommandOptions) => { + actualBuildArgs = args; + actualBuildOptions = options; + } + }); + + injector.register("xcodebuildService", XcodebuildService); + + return injector; +} + +describe("xcodebuildService", () => { + describe("buildForDevice", () => { + it("should build correctly for device", async () => { + const injector = createTestInjector(); + const xcodebuildService = injector.resolve("xcodebuildService"); + const platformData = { getBuildOutputPath: () => buildOutputPath, projectRoot }; + const projectData = { projectName }; + + const buildResult = await xcodebuildService.buildForDevice(platformData, projectData, { }); + + const expectedBuildArgs = [ + '-exportArchive', + '-archivePath', path.join(platformData.getBuildOutputPath(), `${projectName}.xcarchive`), + '-exportPath', exportOptionsPlistOutput.exportFileDir, + '-exportOptionsPlist', exportOptionsPlistOutput.exportOptionsPlistFilePath + ]; + assert.deepEqual(actualBuildArgs, expectedBuildArgs); + assert.deepEqual(actualBuildOptions, { cwd: projectRoot, stdio: undefined }); + assert.deepEqual(buildResult, exportOptionsPlistOutput.exportFilePath); + }); + }); + describe("buildForSimulator", () => { + it("should build correctly for simulator", async () => { + const injector = createTestInjector(); + const xcodebuildService = injector.resolve("xcodebuildService"); + const platformData = { getBuildOutputPath: () => buildOutputPath, projectRoot }; + const projectData = { projectName }; + + await xcodebuildService.buildForSimulator(platformData, projectData, {}); + + assert.deepEqual(actualBuildArgs, []); + assert.deepEqual(actualBuildOptions, { cwd: projectRoot, stdio: undefined }); + }); + }); + describe("buildForAppStore", () => { + it("should build correctly for Appstore", async () => { + const injector = createTestInjector(); + const xcodebuildService = injector.resolve("xcodebuildService"); + const platformData = { getBuildOutputPath: () => buildOutputPath, projectRoot }; + const projectData = { projectName }; + + const buildResult = await xcodebuildService.buildForAppStore(platformData, projectData, {}); + + const expectedBuildArgs = [ + '-exportArchive', + '-archivePath', path.join(platformData.getBuildOutputPath(), `${projectName}.xcarchive`), + '-exportPath', exportOptionsPlistOutput.exportFileDir, + '-exportOptionsPlist', exportOptionsPlistOutput.exportOptionsPlistFilePath + ]; + assert.deepEqual(actualBuildArgs, expectedBuildArgs); + assert.deepEqual(actualBuildOptions, { cwd: projectRoot }); + assert.deepEqual(buildResult, exportOptionsPlistOutput.exportFilePath); + }); + }); +}); diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index 970a4d7771..47499f0766 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -18,6 +18,7 @@ class AppStore { platformService: any; iOSPlatformData: any; iOSProjectService: any; + xcodebuildService: IXcodebuildService; loggerService: LoggerStub; itmsTransporterService: any; @@ -27,16 +28,11 @@ class AppStore { archiveCalls: number = 0; expectedArchiveCalls: number = 0; exportArchiveCalls: number = 0; - expectedExportArchiveCalls: number = 0; itmsTransporterServiceUploadCalls: number = 0; expectedItmsTransporterServiceUploadCalls: number = 0; before() { this.iOSPlatformData = { - "platformProjectService": this.iOSProjectService = { - archive() { console.log("Archive!"); }, - exportArchive() { console.log("Export Archive!"); } - }, "projectRoot": "/Users/person/git/MyProject" }; this.initInjector({ @@ -62,6 +58,17 @@ class AppStore { 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: async () => ({}), + buildForAppStore: async () => { + this.archiveCalls++; + return "/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"; + } } } }); @@ -85,7 +92,6 @@ class AppStore { 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.exportArchiveCalls, this.expectedExportArchiveCalls, "Mismatched number of iOSProjectService.exportArchive calls."); chai.assert.equal(this.itmsTransporterServiceUploadCalls, this.expectedItmsTransporterServiceUploadCalls, "Mismatched number of itmsTransporterService.upload calls."); } @@ -107,27 +113,13 @@ class AppStore { expectArchive() { this.expectedArchiveCalls = 1; - this.iOSProjectService.archive = (projectData: IProjectData) => { + this.xcodebuildService.buildForDevice = (platformData: any, projectData: IProjectData) => { this.archiveCalls++; chai.assert.equal(projectData.projectDir, "/Users/person/git/MyProject"); return Promise.resolve("/Users/person/git/MyProject/platforms/ios/archive/MyProject.xcarchive"); }; } - expectExportArchive(expectedOptions?: { teamID?: string }) { - this.expectedExportArchiveCalls = 1; - this.iOSProjectService.exportArchive = (projectData: IProjectData, options?: { teamID?: string, archivePath?: string }) => { - this.exportArchiveCalls++; - chai.assert.equal(options.archivePath, "/Users/person/git/MyProject/platforms/ios/archive/MyProject.xcarchive", "Expected xcarchive path to be the one that we just archived."); - if (expectedOptions && expectedOptions.teamID) { - chai.assert.equal(options.teamID, expectedOptions.teamID, "Expected --team-id to be passed as teamID to the exportArchive"); - } else { - chai.assert.isUndefined(options.teamID, "Expected teamID in exportArchive to be undefined"); - } - return Promise.resolve("/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"); - }; - } - expectITMSTransporterUpload() { this.expectedItmsTransporterServiceUploadCalls = 1; this.itmsTransporterService.upload = (options: IITMSData) => { @@ -144,7 +136,6 @@ class AppStore { this.expectItunesPrompt(); this.expectPreparePlatform(); this.expectArchive(); - this.expectExportArchive(); this.expectITMSTransporterUpload(); await this.command.execute([]); @@ -155,7 +146,6 @@ class AppStore { async itunesconnectArgs() { this.expectPreparePlatform(); this.expectArchive(); - this.expectExportArchive(); this.expectITMSTransporterUpload(); await this.command.execute([AppStore.itunesconnect.user, AppStore.itunesconnect.pass]); @@ -167,7 +157,6 @@ class AppStore { this.expectItunesPrompt(); this.expectPreparePlatform(); this.expectArchive(); - this.expectExportArchive({ teamID: "MyTeamID" }); this.expectITMSTransporterUpload(); this.options.teamId = "MyTeamID"; From d5c9e096f056e219cd44d5c837e7a65f4cc8bf10 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 22 Apr 2019 16:27:13 +0300 Subject: [PATCH 014/102] chore: fix PR comments --- lib/services/android-project-service.ts | 4 ++-- lib/services/ios-project-service.ts | 1 - lib/services/ios/export-options-plist-service.ts | 4 ++-- lib/services/ios/xcodebuild-command-service.ts | 4 ++-- lib/services/platform-service.ts | 2 +- test/tns-appstore-upload.ts | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 97b3b9e58f..a73447b5a1 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -348,9 +348,9 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return nativescript && (nativescript.android || (nativescript.platforms && nativescript.platforms.android)); } - public async stopServices(platformData: IPlatformData): Promise { + public async stopServices(projectRoot: string): Promise { const result = await this.$gradleCommandService.executeCommand(["--stop", "--quiet"], { - cwd: platformData.projectRoot, + cwd: projectRoot, message: "Gradle stop services...", stdio: "pipe" }); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 6ddf99e502..33fd672742 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -210,7 +210,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.$xcodebuildService.buildForSimulator(platformData, projectData, buildConfig)); } - // TODO: Check if we need to validate the identifier here this.validateApplicationIdentifier(projectData); } diff --git a/lib/services/ios/export-options-plist-service.ts b/lib/services/ios/export-options-plist-service.ts index b25507b011..d64a3dfe44 100644 --- a/lib/services/ios/export-options-plist-service.ts +++ b/lib/services/ios/export-options-plist-service.ts @@ -41,7 +41,7 @@ export class ExportOptionsPlistService implements IExportOptionsPlistService { return { exportFileDir, exportFilePath, exportOptionsPlistFilePath }; } - public createDistributionExportOptionsPlist(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): IExportOptionsPlistOutput { + public createDistributionExportOptionsPlist(archivePath: string, projectData: IProjectData, buildConfig: IBuildConfig): IExportOptionsPlistOutput { const provision = buildConfig.provision || buildConfig.mobileProvisionIdentifier; const teamId = buildConfig.teamId; let plistTemplate = ` @@ -77,7 +77,7 @@ export class ExportOptionsPlistService implements IExportOptionsPlistService { const exportOptionsPlistFilePath = temp.path({ prefix: "export-", suffix: ".plist" }); this.$fs.writeFile(exportOptionsPlistFilePath, plistTemplate); - const exportFileDir = path.resolve(path.join(projectRoot, "/build/archive")); + const exportFileDir = path.resolve(path.dirname(archivePath)); const exportFilePath = path.join(exportFileDir, projectData.projectName + ".ipa"); return { exportFileDir, exportFilePath, exportOptionsPlistFilePath }; diff --git a/lib/services/ios/xcodebuild-command-service.ts b/lib/services/ios/xcodebuild-command-service.ts index eb81c3e2fa..172a19b873 100644 --- a/lib/services/ios/xcodebuild-command-service.ts +++ b/lib/services/ios/xcodebuild-command-service.ts @@ -8,10 +8,10 @@ export class XcodebuildCommandService implements IXcodebuildCommandService { ) { } public async executeCommand(args: string[], options: { cwd: string, stdio: string, message?: string, spawnOptions?: any }): Promise { - const { message, cwd, stdio = "inherit", spawnOptions } = options; + const { message, cwd, stdio, spawnOptions } = options; this.$logger.info(message || "Xcode build..."); - const childProcessOptions = { cwd, stdio }; + const childProcessOptions = { cwd, stdio: stdio || "inherit" }; try { const commandResult = await this.$childProcess.spawnFromEvent("xcodebuild", diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 615b5a7193..d6b935eca6 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -694,7 +694,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { let errorMessage; try { - await platformData.platformProjectService.stopServices(platformData); + await platformData.platformProjectService.stopServices(platformData.projectRoot); } catch (err) { errorMessage = err.message; } diff --git a/test/tns-appstore-upload.ts b/test/tns-appstore-upload.ts index 47499f0766..f6225139f9 100644 --- a/test/tns-appstore-upload.ts +++ b/test/tns-appstore-upload.ts @@ -64,7 +64,7 @@ class AppStore { this.archiveCalls++; return "/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"; }, - buildForSimulator: async () => ({}), + buildForSimulator: () => Promise.resolve(), buildForAppStore: async () => { this.archiveCalls++; return "/Users/person/git/MyProject/platforms/ios/archive/MyProject.ipa"; From 89c5f6cc832fd61deb9175696f351516a30913c9 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 25 Apr 2019 10:32:03 +0300 Subject: [PATCH 015/102] 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 016/102] 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 017/102] 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 018/102] 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 019/102] 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 020/102] 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 021/102] 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 022/102] 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 023/102] 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 024/102] 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 025/102] 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 026/102] 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 027/102] 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 028/102] 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 029/102] 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 030/102] 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 031/102] 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 032/102] 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 033/102] 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 034/102] 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 035/102] 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 036/102] 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 037/102] 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 038/102] 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 039/102] 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 040/102] 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 041/102] 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 042/102] 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 043/102] 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 044/102] 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 () => { From 0341a157be3f923c2566a1e52eb383b181ab6e02 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 17 May 2019 16:32:46 +0300 Subject: [PATCH 045/102] feat: don't leak android debugging session and remove deprecated methods from logger https://github.com/NativeScript/nativescript-cli/issues/4219 --- lib/common/definitions/logger.d.ts | 30 -------------- lib/common/logger/logger.ts | 31 -------------- .../mobile-core/android-process-service.ts | 7 ++-- lib/common/services/commands-service.ts | 13 +----- lib/declarations.d.ts | 3 +- lib/definitions/project.d.ts | 8 ---- lib/options.ts | 34 +++------------ lib/services/android-device-debug-service.ts | 5 ++- lib/services/project-data-service.ts | 35 ---------------- npm-shrinkwrap.json | 41 ++++++++++++++----- test/options.ts | 35 +++++----------- test/services/android-device-debug-service.ts | 4 +- test/stubs.ts | 3 -- 13 files changed, 58 insertions(+), 191 deletions(-) diff --git a/lib/common/definitions/logger.d.ts b/lib/common/definitions/logger.d.ts index 6e098bc4c1..6077c31d7d 100644 --- a/lib/common/definitions/logger.d.ts +++ b/lib/common/definitions/logger.d.ts @@ -25,36 +25,6 @@ declare global { trace(formatStr?: any, ...args: any[]): void; printMarkdown(...args: any[]): void; prepare(item: any): string; - - /** - * DEPRECATED - * Do not use it. - */ - out(formatStr?: any, ...args: any[]): void; - - /** - * DEPRECATED - * Do not use it. - */ - write(...args: any[]): void; - - /** - * DEPRECATED - * Do not use it. - */ - printInfoMessageOnSameLine(message: string): void; - - /** - * DEPRECATED - * Do not use it. - */ - printMsgWithTimeout(message: string, timeout: number): Promise; - - /** - * DEPRECATED - * Do not use it. - */ - printOnStderr(formatStr?: any, ...args: any[]): void; } interface Log4JSAppenderConfiguration extends Configuration { diff --git a/lib/common/logger/logger.ts b/lib/common/logger/logger.ts index ca80f80747..cd105e4969 100644 --- a/lib/common/logger/logger.ts +++ b/lib/common/logger/logger.ts @@ -191,37 +191,6 @@ export class Logger implements ILogger { return argument; }); } - - /******************************************************************************************* - * Metods below are deprecated. Delete them in 6.0.0 release: * - * Present only for backwards compatibility as some plugins (nativescript-plugin-firebase) * - * use these methods in their hooks * - *******************************************************************************************/ - - out(...args: any[]): void { - this.info(args); - } - - write(...args: any[]): void { - this.info(args, { [LoggerConfigData.skipNewLine]: true }); - } - - printOnStderr(...args: string[]): void { - this.error(args); - } - - printInfoMessageOnSameLine(message: string): void { - this.info(message, { [LoggerConfigData.skipNewLine]: true }); - } - - printMsgWithTimeout(message: string, timeout: number): Promise { - return new Promise((resolve, reject) => { - setTimeout(() => { - this.printInfoMessageOnSameLine(message); - resolve(); - }, timeout); - }); - } } $injector.register("logger", Logger); diff --git a/lib/common/mobile/mobile-core/android-process-service.ts b/lib/common/mobile/mobile-core/android-process-service.ts index ee6b4e5c88..f8a8f3397e 100644 --- a/lib/common/mobile/mobile-core/android-process-service.ts +++ b/lib/common/mobile/mobile-core/android-process-service.ts @@ -8,8 +8,10 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService { private _forwardedLocalPorts: IDictionary; constructor(private $errors: IErrors, + private $cleanupService: ICleanupService, private $injector: IInjector, - private $net: INet) { + private $net: INet, + private $staticConfig: IStaticConfig) { this._devicesAdbs = {}; this._forwardedLocalPorts = {}; } @@ -120,8 +122,7 @@ export class AndroidProcessService implements Mobile.IAndroidProcessService { } this._forwardedLocalPorts[portForwardInputData.deviceIdentifier] = localPort; - // TODO: Uncomment for 6.0.0 release - // await this.$cleanupService.addCleanupCommand({ command: await this.$staticConfig.getAdbFilePath(), args: ["-s", portForwardInputData.deviceIdentifier, "forward", "--remove", `tcp:${localPort}`] }); + await this.$cleanupService.addCleanupCommand({ command: await this.$staticConfig.getAdbFilePath(), args: ["-s", portForwardInputData.deviceIdentifier, "forward", "--remove", `tcp:${localPort}`] }); return localPort && +localPort; } diff --git a/lib/common/services/commands-service.ts b/lib/common/services/commands-service.ts index 315bbfa6e1..8b3283e773 100644 --- a/lib/common/services/commands-service.ts +++ b/lib/common/services/commands-service.ts @@ -27,17 +27,8 @@ export class CommandsService implements ICommandsService { private $staticConfig: Config.IStaticConfig, private $helpService: IHelpService, private $extensibilityService: IExtensibilityService, - private $optionsTracker: IOptionsTracker, - private $projectDataService: IProjectDataService) { - let projectData = null; - try { - projectData = this.$projectDataService.getProjectData(); - } catch (err) { - this.$logger.trace(`Error while trying to get project data. More info: ${err}`); - } - - this.$options.setupOptions(projectData); - this.$options.printMessagesForDeprecatedOptions(this.$logger); + private $optionsTracker: IOptionsTracker) { + this.$options.setupOptions(); } public allCommands(opts: { includeDevCommands: boolean }): string[] { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index c1144597dd..1372cda3ff 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -557,8 +557,7 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai analyticsLogFile: string; performance: Object; cleanupLogFile: string; - setupOptions(projectData: IProjectData): void; - printMessagesForDeprecatedOptions(logger: ILogger): void; + setupOptions(): void; } interface IEnvOptions { diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 83140bb0ba..4e6008991a 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -142,14 +142,6 @@ interface IProjectDataService { */ setNSValue(projectDir: string, key: string, value: any): void; - /** - * Sets a value in the `useLegacyWorkflow` key in a project's nsconfig.json. - * @param {string} projectDir The project directory - the place where the root package.json is located. - * @param {any} value Value of the key to be set to `useLegacyWorkflow` key in project's nsconfig.json. - * @returns {void} - */ - setUseLegacyWorkflow(projectDir: string, value: any): void; - /** * Removes a property from `nativescript` key in project's package.json. * @param {string} projectDir The project directory - the place where the root package.json is located. diff --git a/lib/options.ts b/lib/options.ts index dc312d1590..9dd4d1fc9c 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -21,32 +21,18 @@ export class Options { public options: IDictionary; - public setupOptions(projectData: IProjectData): void { + public setupOptions(): void { if (this.argv.release && this.argv.hmr) { this.$errors.failWithoutHelp("The options --release and --hmr cannot be used simultaneously."); } - // HACK: temporary solution for 5.3.0 release (until the webpack only feature) - const parsed = require("yargs-parser")(process.argv.slice(2), { 'boolean-negation': false }); - const noBundle = parsed && (parsed.bundle === false || parsed.bundle === 'false'); - if (noBundle && this.argv.hmr) { - this.$errors.failWithoutHelp("The options --no-bundle and --hmr cannot be used simultaneously."); - } - - if (projectData && projectData.useLegacyWorkflow === false) { - this.argv.bundle = this.argv.bundle !== undefined ? this.argv.bundle : "webpack"; - this.argv.hmr = !this.argv.release; - } + this.argv.bundle = "webpack"; + const parsed = require("yargs-parser")(process.argv.slice(2), { 'boolean-negation': false }); // --no-hmr -> hmr: false or --hmr false -> hmr: 'false' const noHmr = parsed && (parsed.hmr === false || parsed.hmr === 'false'); - if (noHmr) { - this.argv.hmr = false; - } - - if (noBundle) { - this.argv.bundle = undefined; - this.argv.hmr = false; + if (!noHmr) { + this.argv.hmr = !this.argv.release; } if (this.argv.debugBrk) { @@ -207,16 +193,6 @@ export class Options { }); } - public printMessagesForDeprecatedOptions($logger: ILogger) { - if (this.argv.platformTemplate) { - $logger.warn(`"--platformTemplate" option has been deprecated and will be removed in the upcoming NativeScript CLI v6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); - } - - if (this.argv.syncAllFiles) { - $logger.warn(`"--syncAllFiles" option has been deprecated and will be removed in the upcoming NativeScript CLI v6.0.0. More info can be found in this issue https://github.com/NativeScript/nativescript-cli/issues/4518.`); - } - } - private getCorrectOptionName(optionName: string): string { const secondaryOptionName = this.getNonDashedOptionName(optionName); return _.includes(this.optionNames, secondaryOptionName) ? secondaryOptionName : optionName; diff --git a/lib/services/android-device-debug-service.ts b/lib/services/android-device-debug-service.ts index b079862778..bb1bf5c5e1 100644 --- a/lib/services/android-device-debug-service.ts +++ b/lib/services/android-device-debug-service.ts @@ -13,9 +13,11 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi constructor(protected device: Mobile.IAndroidDevice, protected $devicesService: Mobile.IDevicesService, + protected $cleanupService: ICleanupService, private $errors: IErrors, private $logger: ILogger, private $androidProcessService: Mobile.IAndroidProcessService, + private $staticConfig: IStaticConfig, private $net: INet, private $deviceLogProvider: Mobile.IDeviceLogProvider) { @@ -69,8 +71,7 @@ export class AndroidDeviceDebugService extends DebugServiceBase implements IDevi await this.unixSocketForward(port, `${unixSocketName}`); } - // TODO: Uncomment for 6.0.0 release - // await this.$cleanupService.addCleanupCommand({ command: await this.$staticConfig.getAdbFilePath(), args: ["-s", deviceId, "forward", "--remove", `tcp:${port}`] }); + await this.$cleanupService.addCleanupCommand({ command: await this.$staticConfig.getAdbFilePath(), args: ["-s", deviceId, "forward", "--remove", `tcp:${port}`] }); return port; } diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index 9a443bbc12..8815582329 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -1,5 +1,4 @@ import * as path from "path"; -import * as constants from "../constants"; import { ProjectData } from "../project-data"; import { exported } from "../common/decorators"; import { @@ -139,13 +138,6 @@ export class ProjectDataService implements IProjectDataService { }; } - public setUseLegacyWorkflow(projectDir: string, value: any): void { - this.$logger.trace(`useLegacyWorkflow will be set to ${value}`); - this.updateNsConfigValue(projectDir, { useLegacyWorkflow: value }); - this.refreshProjectData(projectDir); - this.$logger.trace(`useLegacyWorkflow was set to ${value}`); - } - public getAppExecutableFiles(projectDir: string): string[] { const projectData = this.getProjectData(projectDir); @@ -179,33 +171,6 @@ export class ProjectDataService implements IProjectDataService { return files; } - private refreshProjectData(projectDir: string) { - if (this.projectDataCache[projectDir]) { - this.projectDataCache[projectDir].initializeProjectData(projectDir); - } - } - - private updateNsConfigValue(projectDir: string, updateObject: INsConfig): void { - const nsConfigPath = path.join(projectDir, constants.CONFIG_NS_FILE_NAME); - const currentNsConfig = this.getNsConfig(nsConfigPath); - const newNsConfig = Object.assign(currentNsConfig, updateObject); - - this.$fs.writeJson(nsConfigPath, newNsConfig); - } - - private getNsConfig(nsConfigPath: string): INsConfig { - let result = this.getNsConfigDefaultObject(); - if (this.$fs.exists(nsConfigPath)) { - try { - result = this.$fs.readJson(nsConfigPath); - } catch (e) { - // default - } - } - - return result; - } - private getImageDefinitions(): IImageDefinitionsStructure { const pathToImageDefinitions = path.join(__dirname, "..", "..", CLI_RESOURCES_DIR_NAME, AssetConstants.assets, AssetConstants.imageDefinitionsFileName); const imageDefinitions = this.$fs.readJson(pathToImageDefinitions); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 067e08dfb0..d69765b45d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2547,7 +2547,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2565,11 +2566,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2582,15 +2585,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2693,7 +2699,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2703,6 +2710,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2715,17 +2723,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2742,6 +2753,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2814,7 +2826,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2824,6 +2837,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2899,7 +2913,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2929,6 +2944,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2946,6 +2962,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2984,11 +3001,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, diff --git a/test/options.ts b/test/options.ts index 1727991746..4b88dcd5eb 100644 --- a/test/options.ts +++ b/test/options.ts @@ -268,9 +268,9 @@ describe("options", () => { name: "no options are provided", args: [], data: [ - { useLegacyWorkflow: undefined, expectedHmr: false, expectedBundle: false }, + { useLegacyWorkflow: undefined, expectedHmr: true, expectedBundle: true }, { useLegacyWorkflow: false, expectedHmr: true, expectedBundle: true }, - { useLegacyWorkflow: true, expectedHmr: false, expectedBundle: false } + { useLegacyWorkflow: true, expectedHmr: true, expectedBundle: true } ] }, { @@ -286,36 +286,27 @@ describe("options", () => { name: " --no-hmr is provided", args: ["--no-hmr"], data: [ - { useLegacyWorkflow: undefined, expectedHmr: false, expectedBundle: false }, + { useLegacyWorkflow: undefined, expectedHmr: false, expectedBundle: true }, { useLegacyWorkflow: false, expectedHmr: false, expectedBundle: true }, - { useLegacyWorkflow: true, expectedHmr: false, expectedBundle: false } + { useLegacyWorkflow: true, expectedHmr: false, expectedBundle: true } ] }, { name: " --bundle is provided", args: ["--bundle"], data: [ - { useLegacyWorkflow: undefined, expectedHmr: false, expectedBundle: true }, + { useLegacyWorkflow: undefined, expectedHmr: true, expectedBundle: true }, { useLegacyWorkflow: false, expectedHmr: true, expectedBundle: true }, - { useLegacyWorkflow: true, expectedHmr: false, expectedBundle: true } - ] - }, - { - name: " --no-bundle is provided", - args: ["--no-bundle"], - data: [ - { useLegacyWorkflow: undefined, expectedHmr: false, expectedBundle: false }, - { useLegacyWorkflow: false, expectedHmr: false, expectedBundle: false }, - { useLegacyWorkflow: true, expectedHmr: false, expectedBundle: false } + { useLegacyWorkflow: true, expectedHmr: true, expectedBundle: true } ] }, { name: " --release is provided", args: ["--release"], data: [ - { useLegacyWorkflow: undefined, expectedHmr: false, expectedBundle: false }, + { useLegacyWorkflow: undefined, expectedHmr: false, expectedBundle: true }, { useLegacyWorkflow: false, expectedHmr: false, expectedBundle: true }, - { useLegacyWorkflow: true, expectedHmr: false, expectedBundle: false } + { useLegacyWorkflow: true, expectedHmr: false, expectedBundle: true } ] } ]; @@ -326,8 +317,7 @@ describe("options", () => { (testCase.args || []).forEach(arg => process.argv.push(arg)); const options = createOptions(testInjector); - const projectData = { useLegacyWorkflow }; - options.setupOptions(projectData); + options.setupOptions(); (testCase.args || []).forEach(arg => process.argv.pop()); @@ -344,11 +334,6 @@ describe("options", () => { name: "--release --hmr", args: ["--release", "--hmr"], expectedError: "The options --release and --hmr cannot be used simultaneously." - }, - { - name: "--no-bundle --hmr", - args: ["--no-bundle", "--hmr"], - expectedError: "The options --no-bundle and --hmr cannot be used simultaneously." } ]; @@ -360,7 +345,7 @@ describe("options", () => { (testCase.args || []).forEach(arg => process.argv.push(arg)); const options = createOptions(testInjector); - options.setupOptions(null); + options.setupOptions(); (testCase.args || []).forEach(arg => process.argv.pop()); diff --git a/test/services/android-device-debug-service.ts b/test/services/android-device-debug-service.ts index e3b208008c..438fa77c5e 100644 --- a/test/services/android-device-debug-service.ts +++ b/test/services/android-device-debug-service.ts @@ -7,12 +7,14 @@ const expectedDevToolsCommitSha = "02e6bde1bbe34e43b309d4ef774b1168d25fd024"; class AndroidDeviceDebugServiceInheritor extends AndroidDeviceDebugService { constructor(protected $devicesService: Mobile.IDevicesService, + $cleanupService: ICleanupService, $errors: IErrors, $logger: ILogger, $androidProcessService: Mobile.IAndroidProcessService, + $staticConfig: IStaticConfig, $net: INet, $deviceLogProvider: Mobile.IDeviceLogProvider) { - super({ deviceInfo: { identifier: "123" } }, $devicesService, $errors, $logger, $androidProcessService, $net, $deviceLogProvider); + super({ deviceInfo: { identifier: "123" } }, $devicesService, $cleanupService, $errors, $logger, $androidProcessService, $staticConfig, $net, $deviceLogProvider); } public getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { diff --git a/test/stubs.ts b/test/stubs.ts index 0a1e725ec6..a1ba362197 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -493,9 +493,6 @@ export class NativeProjectDataStub extends EventEmitter implements IPlatformsDat } export class ProjectDataService implements IProjectDataService { - setUseLegacyWorkflow(projectDir: string, value: any): Promise { - return; - } getNSValue(propertyName: string): any { return {}; } From da752d07eeaeb8eb0e490db076a638d131c350a6 Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 18 May 2019 11:49:04 +0300 Subject: [PATCH 046/102] fix: move buildinfo related logic to buildInfoFileService --- lib/controllers/build-controller.ts | 10 ++-- lib/data/build-data.ts | 2 +- lib/definitions/build.d.ts | 12 ++-- lib/definitions/run.d.ts | 1 - lib/services/build-artefacts-service.ts | 8 +-- lib/services/build-info-file-service.ts | 57 ++++++++++++++----- .../device/device-install-app-service.ts | 52 ++++------------- 7 files changed, 73 insertions(+), 69 deletions(-) diff --git a/lib/controllers/build-controller.ts b/lib/controllers/build-controller.ts index 66d29fa6a7..1818f6e564 100644 --- a/lib/controllers/build-controller.ts +++ b/lib/controllers/build-controller.ts @@ -58,14 +58,14 @@ export class BuildController extends EventEmitter implements IBuildController { 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.$buildInfoFileService.saveLocalBuildInfo(platformData, buildInfoFileDir); this.$logger.info("Project successfully built."); - const result = await this.$buildArtefactsService.getLatestApplicationPackagePath(platformData, buildData); + const result = await this.$buildArtefactsService.getLatestAppPackagePath(platformData, buildData); if (buildData.copyTo) { - this.$buildArtefactsService.copyLastOutput(buildData.copyTo, platformData, buildData); + this.$buildArtefactsService.copyLatestAppPackage(buildData.copyTo, platformData, buildData); this.$logger.info(`The build result is located at: ${buildInfoFileDir}`); } @@ -102,13 +102,13 @@ export class BuildController extends EventEmitter implements IBuildController { } const validBuildOutputData = platformData.getValidBuildOutputData(buildData); - const packages = this.$buildArtefactsService.getAllApplicationPackages(outputPath, validBuildOutputData); + const packages = this.$buildArtefactsService.getAllAppPackages(outputPath, validBuildOutputData); if (packages.length === 0) { return true; } const prepareInfo = this.$projectChangesService.getPrepareInfo(platformData); - const buildInfo = this.$buildInfoFileService.getBuildInfoFromFile(platformData, buildData); + const buildInfo = this.$buildInfoFileService.getLocalBuildInfo(platformData, buildData); if (!prepareInfo || !buildInfo) { return true; } diff --git a/lib/data/build-data.ts b/lib/data/build-data.ts index 9ab809739b..dfc736afab 100644 --- a/lib/data/build-data.ts +++ b/lib/data/build-data.ts @@ -54,6 +54,6 @@ export class AndroidBuildData extends BuildData { this.keyStorePath = data.keyStorePath; this.keyStoreAliasPassword = data.keyStoreAliasPassword; this.keyStorePassword = data.keyStorePassword; - this.androidBundle = data.androidBundle; + this.androidBundle = data.androidBundle || data.aab; } } diff --git a/lib/definitions/build.d.ts b/lib/definitions/build.d.ts index fffc6fa53e..0733fbed91 100644 --- a/lib/definitions/build.d.ts +++ b/lib/definitions/build.d.ts @@ -36,12 +36,14 @@ interface IBuildDataService { } interface IBuildArtefactsService { - getLatestApplicationPackagePath(platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): Promise; - getAllApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[]; - copyLastOutput(targetPath: string, platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): void; + getAllAppPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[]; + getLatestAppPackagePath(platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): Promise; + copyLatestAppPackage(targetPath: string, platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): void; } interface IBuildInfoFileService { - saveBuildInfoFile(platformData: IPlatformData, buildInfoFileDirname: string): void; - getBuildInfoFromFile(platformData: IPlatformData, buildData: IBuildData): IBuildInfo; + getLocalBuildInfo(platformData: IPlatformData, buildData: IBuildData): IBuildInfo; + getDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData): Promise; + saveLocalBuildInfo(platformData: IPlatformData, buildInfoFileDirname: string): void; + saveDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData, outputFilePath: string): Promise; } \ No newline at end of file diff --git a/lib/definitions/run.d.ts b/lib/definitions/run.d.ts index 5ef09b0ab1..b75ee68e13 100644 --- a/lib/definitions/run.d.ts +++ b/lib/definitions/run.d.ts @@ -22,7 +22,6 @@ interface IRunEmitter { 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; } diff --git a/lib/services/build-artefacts-service.ts b/lib/services/build-artefacts-service.ts index 5a62f83c76..a05f083d82 100644 --- a/lib/services/build-artefacts-service.ts +++ b/lib/services/build-artefacts-service.ts @@ -7,7 +7,7 @@ export class BuildArtefactsService implements IBuildArtefactsService { private $logger: ILogger ) { } - public async getLatestApplicationPackagePath(platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): Promise { + public async getLatestAppPackagePath(platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): Promise { const outputPath = buildOutputOptions.outputPath || platformData.getBuildOutputPath(buildOutputOptions); const applicationPackage = this.getLatestApplicationPackage(outputPath, platformData.getValidBuildOutputData(buildOutputOptions)); const packageFile = applicationPackage.packageName; @@ -19,7 +19,7 @@ export class BuildArtefactsService implements IBuildArtefactsService { return packageFile; } - public getAllApplicationPackages(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage[] { + public getAllAppPackages(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,7 +40,7 @@ export class BuildArtefactsService implements IBuildArtefactsService { return []; } - public copyLastOutput(targetPath: string, platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): void { + public copyLatestAppPackage(targetPath: string, platformData: IPlatformData, buildOutputOptions: IBuildOutputOptions): void { targetPath = path.resolve(targetPath); const outputPath = buildOutputOptions.outputPath || platformData.getBuildOutputPath(buildOutputOptions); @@ -59,7 +59,7 @@ export class BuildArtefactsService implements IBuildArtefactsService { } private getLatestApplicationPackage(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage { - let packages = this.getAllApplicationPackages(buildOutputPath, validBuildOutputData); + let packages = this.getAllAppPackages(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/build-info-file-service.ts b/lib/services/build-info-file-service.ts index f23679b195..707caaaffe 100644 --- a/lib/services/build-info-file-service.ts +++ b/lib/services/build-info-file-service.ts @@ -1,26 +1,17 @@ import * as path from "path"; +import * as helpers from "../common/helpers"; const buildInfoFileName = ".nsbuildinfo"; export class BuildInfoFileService implements IBuildInfoFileService { constructor( + private $devicePathProvider: IDevicePathProvider, private $fs: IFileSystem, + private $mobileHelper: Mobile.IMobileHelper, 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, buildData: IBuildData): IBuildInfo { + public getLocalBuildInfo(platformData: IPlatformData, buildData: IBuildData): IBuildInfo { const outputPath = buildData.outputPath || platformData.getBuildOutputPath(buildData); const buildInfoFile = path.join(outputPath, buildInfoFileName); if (this.$fs.exists(buildInfoFile)) { @@ -34,5 +25,45 @@ export class BuildInfoFileService implements IBuildInfoFileService { return null; } + + public 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; + } + } + + public saveLocalBuildInfo(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 async saveDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData, outputFilePath: string): Promise { + const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); + const appIdentifier = projectData.projectIdentifiers[device.deviceInfo.platform]; + + await device.fileSystem.putFile(path.join(outputFilePath, buildInfoFileName), deviceFilePath, 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 + }); + const result = helpers.fromWindowsRelativePathToUnix(path.join(deviceRootPath, buildInfoFileName)); + + return result; + } } $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 0a31726f54..02fe9ecd71 100644 --- a/lib/services/device/device-install-app-service.ts +++ b/lib/services/device/device-install-app-service.ts @@ -1,18 +1,14 @@ import { TrackActionNames, HASHES_FILE_NAME } from "../../constants"; -import * as helpers from "../../common/helpers"; import * as path from "path"; -const buildInfoFileName = ".nsbuildinfo"; - export class DeviceInstallAppService { constructor( private $analyticsService: IAnalyticsService, private $buildArtefactsService: IBuildArtefactsService, - private $devicePathProvider: IDevicePathProvider, + private $buildInfoFileService: IBuildInfoFileService, private $fs: IFileSystem, private $logger: ILogger, private $mobileHelper: Mobile.IMobileHelper, - private $buildInfoFileService: IBuildInfoFileService, private $projectDataService: IProjectDataService, private $platformsDataService: IPlatformsDataService ) { } @@ -20,8 +16,9 @@ export class DeviceInstallAppService { public async installOnDevice(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise { this.$logger.info(`Installing on device ${device.deviceInfo.identifier}...`); + const platform = device.deviceInfo.platform.toLowerCase(); const projectData = this.$projectDataService.getProjectData(buildData.projectDir); - const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); await this.$analyticsService.trackEventActionInGoogleAnalytics({ action: TrackActionNames.Deploy, @@ -30,31 +27,25 @@ export class DeviceInstallAppService { }); if (!packageFile) { - packageFile = await this.$buildArtefactsService.getLatestApplicationPackagePath(platformData, buildData); + packageFile = await this.$buildArtefactsService.getLatestAppPackagePath(platformData, buildData); } await platformData.platformProjectService.cleanDeviceTempFolder(device.deviceInfo.identifier, projectData); - const platform = device.deviceInfo.platform.toLowerCase(); - await device.applicationManager.reinstallApplication(projectData.projectIdentifiers[platform], packageFile); + const appIdentifier = projectData.projectIdentifiers[platform]; + const outputFilePath = buildData.outputPath || platformData.getBuildOutputPath(buildData); - const outputFilePath = buildData.outputPath; + await device.applicationManager.reinstallApplication(appIdentifier, packageFile); await this.updateHashesOnDevice({ device, - appIdentifier: projectData.projectIdentifiers[platform], + appIdentifier, outputFilePath, platformData }); if (!buildData.release) { - const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - const options = buildData; - options.buildForDevice = !device.isEmulator; - const buildInfoFilePath = outputFilePath || platformData.getBuildOutputPath(buildData); - const appIdentifier = projectData.projectIdentifiers[platform]; - - await device.fileSystem.putFile(path.join(buildInfoFilePath, buildInfoFileName), deviceFilePath, appIdentifier); + await this.$buildInfoFileService.saveDeviceBuildInfo(device, projectData, outputFilePath); } this.$logger.info(`Successfully installed on device with identifier '${device.deviceInfo.identifier}'.`); @@ -67,15 +58,6 @@ export class DeviceInstallAppService { } } - 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)); - } - 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); @@ -84,8 +66,8 @@ export class DeviceInstallAppService { return true; } - const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); - const localBuildInfo = this.$buildInfoFileService.getBuildInfoFromFile(platformData, { ...buildData, buildForDevice: !device.isEmulator }); + const deviceBuildInfo: IBuildInfo = await this.$buildInfoFileService.getDeviceBuildInfo(device, projectData); + const localBuildInfo = this.$buildInfoFileService.getLocalBuildInfo(platformData, { ...buildData, buildForDevice: !device.isEmulator }); return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; } @@ -98,22 +80,12 @@ export class DeviceInstallAppService { } let hashes = {}; - const hashesFilePath = path.join(outputFilePath || platformData.getBuildOutputPath(null), HASHES_FILE_NAME); + const hashesFilePath = path.join(outputFilePath, HASHES_FILE_NAME); if (this.$fs.exists(hashesFilePath)) { hashes = this.$fs.readJson(hashesFilePath); } await device.fileSystem.updateHashesOnDevice(hashes, appIdentifier); } - - 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("deviceInstallAppService", DeviceInstallAppService); From 44eb3c55fdc44d0aa1ddcadbeb165e8fbc090140 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 20 May 2019 11:54:47 +0300 Subject: [PATCH 047/102] feat: introduce debugController --- lib/bootstrap.ts | 3 +- lib/commands/debug.ts | 10 +- lib/commands/run.ts | 2 +- .../debug-controller.ts} | 64 ++++++-- lib/controllers/run-controller.ts | 85 +++++++---- lib/definitions/debug.d.ts | 9 +- lib/definitions/deploy.d.ts | 3 + lib/definitions/livesync.d.ts | 4 + lib/definitions/run.d.ts | 14 +- lib/helpers/livesync-command-helper.ts | 144 ++++++++++-------- lib/resolvers/livesync-service-resolver.ts | 2 +- lib/services/debug-service.ts | 22 +-- .../device/device-refresh-app-service.ts | 51 ------- lib/services/ios-device-debug-service.ts | 5 + test/controllers/run-controller.ts | 6 - test/services/debug-service.ts | 16 -- test/services/ios-device-debug-service.ts | 10 ++ 17 files changed, 231 insertions(+), 219 deletions(-) rename lib/{services/device/device-debug-app-service.ts => controllers/debug-controller.ts} (67%) delete mode 100644 lib/services/device/device-refresh-app-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 125beede5b..ddd9ce8278 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -41,9 +41,7 @@ $injector.require("platformValidationService", "./services/platform/platform-val $injector.require("buildArtefactsService", "./services/build-artefacts-service"); -$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("runEmitter", "./emitters/run-emitter"); $injector.require("previewAppEmitter", "./emitters/preview-app-emitter"); @@ -53,6 +51,7 @@ $injector.require("prepareController", "./controllers/prepare-controller"); $injector.require("buildController", "./controllers/build-controller"); $injector.require("deployController", "./controllers/deploy-controller"); $injector.require("runController", "./controllers/run-controller"); +$injector.require("debugController", "./controllers/debug-controller"); $injector.require("previewAppController", "./controllers/preview-app-controller"); $injector.require("prepareDataService", "./services/prepare-data-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index a44833fb44..bb80ed752a 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,7 +1,6 @@ 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[] = []; @@ -17,7 +16,7 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements protected $logger: ILogger, protected $errors: IErrors, private $debugDataService: IDebugDataService, - private $deviceDebugAppService: DeviceDebugAppService, + private $debugController: IDebugController, private $liveSyncCommandHelper: ILiveSyncCommandHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { super($options, $platformsDataService, $platformValidationService, $projectData); @@ -31,8 +30,6 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements skipDeviceDetectionInterval: true }); - const debugOptions = _.cloneDeep(this.$options.argv); - const selectedDeviceForDebug = await this.$devicesService.pickSingleDevice({ onlyEmulators: this.$options.emulator, onlyDevices: this.$options.forDevice, @@ -42,11 +39,12 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements const debugData = this.$debugDataService.createDebugData(this.$projectData, { device: selectedDeviceForDebug.deviceInfo.identifier }); if (this.$options.start) { - await this.$deviceDebugAppService.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); + const debugOptions = _.cloneDeep(this.$options.argv); + await this.$debugController.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); return; } - await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.platform, { + await this.$liveSyncCommandHelper.executeLiveSyncOperationWithDebug([selectedDeviceForDebug], this.platform, { deviceDebugMap: { [selectedDeviceForDebug.deviceInfo.identifier]: true }, diff --git a/lib/commands/run.ts b/lib/commands/run.ts index b5465736b7..32985e65d5 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -13,7 +13,7 @@ export class RunCommandBase implements ICommand { private $errors: IErrors, private $hostInfo: IHostInfo, private $liveSyncCommandHelper: ILiveSyncCommandHelper, - private $projectData: IProjectData, + private $projectData: IProjectData ) { } public allowedParameters: ICommandParameter[] = []; diff --git a/lib/services/device/device-debug-app-service.ts b/lib/controllers/debug-controller.ts similarity index 67% rename from lib/services/device/device-debug-app-service.ts rename to lib/controllers/debug-controller.ts index 2b1f767f14..3e7c23c91a 100644 --- a/lib/services/device/device-debug-app-service.ts +++ b/lib/controllers/debug-controller.ts @@ -1,17 +1,50 @@ -import { performanceLog } from "../../common/decorators"; -import { RunEmitter } from "../../emitters/run-emitter"; +import { performanceLog } from "../common/decorators"; import { EOL } from "os"; +import { RunController } from "./run-controller"; + +export class DebugController extends RunController implements IDebugController { -export class DeviceDebugAppService { constructor( + $analyticsService: IAnalyticsService, + $buildDataService: IBuildDataService, + $buildController: IBuildController, private $debugDataService: IDebugDataService, private $debugService: IDebugService, - private $devicesService: Mobile.IDevicesService, - private $errors: IErrors, - private $logger: ILogger, - private $projectDataService: IProjectDataService, - private $runEmitter: RunEmitter - ) { } + $deviceInstallAppService: IDeviceInstallAppService, + $devicesService: Mobile.IDevicesService, + $errors: IErrors, + $hmrStatusService: IHmrStatusService, + $hooksService: IHooksService, + $liveSyncServiceResolver: ILiveSyncServiceResolver, + $logger: ILogger, + $platformsDataService: IPlatformsDataService, + $pluginsService: IPluginsService, + $prepareController: IPrepareController, + $prepareDataService: IPrepareDataService, + $prepareNativePlatformService: IPrepareNativePlatformService, + $projectDataService: IProjectDataService, + $runEmitter: IRunEmitter + ) { + super( + $analyticsService, + $buildDataService, + $buildController, + $deviceInstallAppService, + $devicesService, + $errors, + $hmrStatusService, + $hooksService, + $liveSyncServiceResolver, + $logger, + $platformsDataService, + $pluginsService, + $prepareController, + $prepareDataService, + $prepareNativePlatformService, + $projectDataService, + $runEmitter + ); + } @performanceLog() public async enableDebugging(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise { @@ -64,6 +97,17 @@ export class DeviceDebugAppService { return debugInformation; } + // IMPORTANT: This method overrides the refresh logic of runController as enables debugging for provided deviceDescriptor + public async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo): Promise { + liveSyncResultInfo.waitForDebugger = deviceDescriptor.debugOptions && deviceDescriptor.debugOptions.debugBrk; + + const refreshInfo = await super.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + + await this.enableDebugging(projectData, deviceDescriptor, refreshInfo); + + return refreshInfo; + } + @performanceLog() private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, deviceDescriptor: ILiveSyncDeviceInfo, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { if (!deviceDescriptor) { @@ -99,4 +143,4 @@ export class DeviceDebugAppService { return debugInformation; } } -$injector.register("deviceDebugAppService", DeviceDebugAppService); +$injector.register("debugController", DebugController); diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index 686183f846..ae88eea96d 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -1,33 +1,29 @@ -import { EventEmitter } from "events"; -import { LiveSyncServiceResolver } from "../resolvers/livesync-service-resolver"; import { HmrConstants, DeviceDiscoveryEventNames } from "../common/constants"; import { PREPARE_READY_EVENT_NAME, TrackActionNames } from "../constants"; -import { cache } from "../common/decorators"; +import { cache, performanceLog } from "../common/decorators"; -export class RunController extends EventEmitter { +export class RunController implements IRunController { private processesInfo: IDictionary = {}; constructor( 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, + protected $devicesService: Mobile.IDevicesService, + protected $errors: IErrors, private $hmrStatusService: IHmrStatusService, public $hooksService: IHooksService, - private $liveSyncServiceResolver: LiveSyncServiceResolver, - private $logger: ILogger, + private $liveSyncServiceResolver: ILiveSyncServiceResolver, + protected $logger: ILogger, private $platformsDataService: IPlatformsDataService, private $pluginsService: IPluginsService, private $prepareController: IPrepareController, private $prepareDataService: IPrepareDataService, private $prepareNativePlatformService: IPrepareNativePlatformService, - private $projectDataService: IProjectDataService, - private $runEmitter: IRunEmitter - ) { super(); } + protected $projectDataService: IProjectDataService, + protected $runEmitter: IRunEmitter + ) { } public async run(runData: IRunData): Promise { const { projectDir, liveSyncInfo, deviceDescriptors } = runData; @@ -115,6 +111,40 @@ export class RunController extends EventEmitter { return currentDescriptors || []; } + @performanceLog() + protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, 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) { + shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); + } + + if (shouldRestart) { + this.$runEmitter.emitDebuggerDetachedEvent(liveSyncResultInfo.deviceAppData.device); + 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.$runEmitter.emitRunNotificationEvent(projectData, liveSyncResultInfo.deviceAppData.device, msg); + } + + if (settings && settings.shouldCheckDeveloperDiscImage && (err.message || err) === "Could not find developer disk image") { + this.$runEmitter.emitUserInteractionNeededEvent(projectData, liveSyncResultInfo.deviceAppData.device, deviceDescriptor); + } + } + + return result; + } + private getDeviceDescriptorsForInitialSync(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]) { const currentRunData = this.processesInfo[projectDir]; const isAlreadyLiveSyncing = currentRunData && !currentRunData.isStopped; @@ -178,17 +208,13 @@ export class RunController 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.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); this.$runEmitter.emitRunExecutedEvent(projectData, device, { syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); - if (liveSyncResultInfo && deviceDescriptor.debuggingEnabled) { - await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo); - } - this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); this.$runEmitter.emitRunStartedEvent(projectData, device); @@ -236,6 +262,11 @@ export class RunController extends EventEmitter { await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + this.$runEmitter.emitRunExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + isFullSync: liveSyncResultInfo.isFullSync + }); + if (!liveSyncResultInfo.didRecover && isInHMRMode) { const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash); if (status === HmrConstants.HMR_ERROR_STATUS) { @@ -244,6 +275,11 @@ export class RunController extends EventEmitter { // We want to force a restart of the application. liveSyncResultInfo.isFullSync = true; await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + + this.$runEmitter.emitRunExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), + isFullSync: liveSyncResultInfo.isFullSync + }); } } @@ -266,19 +302,6 @@ export class RunController extends EventEmitter { })); } - private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo) { - const refreshInfo = await this.$deviceRefreshAppService.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); - - this.$runEmitter.emitRunExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { - syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), - isFullSync: liveSyncResultInfo.isFullSync - }); - - if (liveSyncResultInfo && deviceDescriptor.debuggingEnabled) { - await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo); - } - } - private async addActionToChain(projectDir: string, action: () => Promise): Promise { const liveSyncInfo = this.processesInfo[projectDir]; if (liveSyncInfo) { diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 469ddacd9e..2cf4700bc8 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -107,7 +107,7 @@ interface IDebugDataService { * @param {IOptions} options The options based on which debugData will be created * @returns {IDebugData} Data describing the required information for starting debug process. */ - createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData; + createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData; } /** @@ -154,3 +154,10 @@ interface IDeviceDebugService extends IPlatform, NodeJS.EventEmitter { interface IDebugResultInfo { debugUrl: string; } + +interface IDebugController extends IRunController { + // TODO: add disableDebugging method + enableDebugging(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise; + attachDebugger(settings: IAttachDebuggerOptions): Promise; + printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent?: boolean): IDebugInformation; +} diff --git a/lib/definitions/deploy.d.ts b/lib/definitions/deploy.d.ts index e69de29bb2..9dc839421a 100644 --- a/lib/definitions/deploy.d.ts +++ b/lib/definitions/deploy.d.ts @@ -0,0 +1,3 @@ +interface IDeployController { + deploy(data: IRunData): Promise; +} \ No newline at end of file diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 38ca7a93ba..d55b1a88c7 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -468,6 +468,7 @@ declare global { * @returns {Promise} */ executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; + executeLiveSyncOperationWithDebug(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; getPlatformsForOperation(platform: string): string[]; /** @@ -486,4 +487,7 @@ declare global { executeCommandLiveSync(platform?: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; } + interface ILiveSyncServiceResolver { + resolveLiveSyncService(platform: string): IPlatformLiveSyncService; + } } \ No newline at end of file diff --git a/lib/definitions/run.d.ts b/lib/definitions/run.d.ts index b75ee68e13..b2014cf661 100644 --- a/lib/definitions/run.d.ts +++ b/lib/definitions/run.d.ts @@ -5,7 +5,9 @@ interface IRunData { } interface IRunController { - + run(runData: IRunData): Promise; + stop(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise; + getDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; } interface IRunEmitter { @@ -24,13 +26,3 @@ interface IDeviceInstallAppService { installOnDeviceIfNeeded(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): 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/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index d965913393..2413dd749c 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,30 +1,28 @@ -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"; +import { DeployController } from "../controllers/deploy-controller"; export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; constructor( - private $buildDataService: BuildDataService, + private $buildDataService: IBuildDataService, private $projectData: IProjectData, private $options: IOptions, - private $runController: RunController, private $runEmitter: RunEmitter, private $deployController: DeployController, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $devicesService: Mobile.IDevicesService, private $injector: IInjector, - private $buildController: BuildController, + private $buildController: IBuildController, private $analyticsService: IAnalyticsService, private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, - private $cleanupService: ICleanupService + private $cleanupService: ICleanupService, + private $debugController: IDebugController, + private $runController: IRunController ) { } private get $platformsDataService(): IPlatformsDataService { @@ -63,26 +61,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } 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."); - } else { - this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation and unable to start emulator when platform is not specified."); - } - } - - const workingWithiOSDevices = !platform || this.$mobileHelper.isiOSPlatform(platform); - const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; - if (shouldKeepProcessAlive) { - this.$analyticsService.setShouldDispose(false); - this.$cleanupService.setShouldDispose(false); - - if (workingWithiOSDevices) { - this.$iosDeviceOperations.setShouldDispose(false); - this.$iOSSimulatorLogProvider.setShouldDispose(false); - } - } - // Now let's take data for each device: const deviceDescriptors: ILiveSyncDeviceInfo[] = devices .map(d => { @@ -139,42 +117,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } 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, - 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 - }; - - 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 }); - } - - return; - } + const { liveSyncInfo, deviceDescriptors } = await this.executeLiveSyncOperationCore(devices, platform, additionalOptions); await this.$runController.run({ projectDir: this.$projectData.projectDir, @@ -208,6 +151,79 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return result; } + + public async executeLiveSyncOperationWithDebug(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { + const { liveSyncInfo, deviceDescriptors } = await this.executeLiveSyncOperationCore(devices, platform, additionalOptions); + + await this.$debugController.run({ + projectDir: this.$projectData.projectDir, + liveSyncInfo, + deviceDescriptors + }); + + const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); + this.$runEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: { projectDir: string, deviceIdentifier: string }) => { + _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); + + if (remainingDevicesToSync.length === 0) { + process.exit(ErrorCodes.ALL_DEVICES_DISCONNECTED); + } + }); + } + + private async executeLiveSyncOperationCore(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise<{liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]}> { + 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."); + } else { + this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation and unable to start emulator when platform is not specified."); + } + } + + const workingWithiOSDevices = !platform || this.$mobileHelper.isiOSPlatform(platform); + const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; + if (shouldKeepProcessAlive) { + this.$analyticsService.setShouldDispose(false); + this.$cleanupService.setShouldDispose(false); + + if (workingWithiOSDevices) { + this.$iosDeviceOperations.setShouldDispose(false); + this.$iOSSimulatorLogProvider.setShouldDispose(false); + } + } + + // Extract this to 2 separate services -> deviceDescriptorsService, liveSyncDataService -> getLiveSyncData() + const deviceDescriptors = await this.createDeviceDescriptors(devices, platform, additionalOptions); + const liveSyncInfo = this.createLiveSyncInfo(); + + if (this.$options.release) { + await this.runInRelease(platform, deviceDescriptors, liveSyncInfo); + return; + } + + return { liveSyncInfo, deviceDescriptors }; + } + + private async runInRelease(platform: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + await this.$devicesService.initialize({ + platform, + deviceId: this.$options.device, + emulator: this.$options.emulator, + skipInferPlatform: !platform, + sdk: this.$options.sdk + }); + + await this.$deployController.deploy({ + projectDir: this.$projectData.projectDir, + liveSyncInfo: { ...liveSyncInfo, clean: true, skipWatcher: true }, + deviceDescriptors + }); + + 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 }); + } + } } $injector.register("liveSyncCommandHelper", LiveSyncCommandHelper); diff --git a/lib/resolvers/livesync-service-resolver.ts b/lib/resolvers/livesync-service-resolver.ts index 5d97b1d2c9..d6eebdef7d 100644 --- a/lib/resolvers/livesync-service-resolver.ts +++ b/lib/resolvers/livesync-service-resolver.ts @@ -1,4 +1,4 @@ -export class LiveSyncServiceResolver { +export class LiveSyncServiceResolver implements ILiveSyncServiceResolver { constructor( private $errors: IErrors, private $injector: IInjector, diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index a52cecce2a..eca19315b1 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -1,4 +1,3 @@ -import { platform } from "os"; import { parse } from "url"; import { EventEmitter } from "events"; import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../constants"; @@ -11,13 +10,13 @@ export class DebugService extends EventEmitter implements IDebugService { constructor(private $devicesService: Mobile.IDevicesService, private $errors: IErrors, private $injector: IInjector, - private $hostInfo: IHostInfo, private $mobileHelper: Mobile.IMobileHelper, private $analyticsService: IAnalyticsService) { super(); this._platformDebugServices = {}; } + // TODO: Move this logic to debugController @performanceLog() public async debug(debugData: IDebugData, options: IDebugOptions): Promise { const device = this.$devicesService.getDeviceByIdentifier(debugData.deviceIdentifier); @@ -47,24 +46,14 @@ export class DebugService extends EventEmitter implements IDebugService { this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`); } - // TODO: Consider to move this code to ios-device-debug-service - if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { - if (device.isEmulator && debugOptions.debugBrk) { - this.$errors.failWithoutHelp("To debug on iOS simulator you need to provide path to the app package."); - } - - if (!this.$hostInfo.isWindows && !this.$hostInfo.isDarwin) { - this.$errors.failWithoutHelp(`Debugging on iOS devices is not supported for ${platform()} yet.`); - } - } - const debugResultInfo = await debugService.debug(debugData, debugOptions); return this.getDebugInformation(debugResultInfo, device.deviceInfo.identifier); } public debugStop(deviceIdentifier: string): Promise { - const debugService = this.getDeviceDebugServiceByIdentifier(deviceIdentifier); + const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); + const debugService = this.getDeviceDebugService(device); return debugService.debugStop(); } @@ -85,11 +74,6 @@ export class DebugService extends EventEmitter implements IDebugService { return this._platformDebugServices[device.deviceInfo.identifier]; } - private getDeviceDebugServiceByIdentifier(deviceIdentifier: string): IDeviceDebugService { - const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); - return this.getDeviceDebugService(device); - } - private attachConnectionErrorHandlers(platformDebugService: IDeviceDebugService) { let connectionErrorHandler = (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e); connectionErrorHandler = connectionErrorHandler.bind(this); diff --git a/lib/services/device/device-refresh-app-service.ts b/lib/services/device/device-refresh-app-service.ts deleted file mode 100644 index db2656f3a9..0000000000 --- a/lib/services/device/device-refresh-app-service.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { performanceLog } from "../../common/decorators"; -import { RunEmitter } from "../../emitters/run-emitter"; -import { LiveSyncServiceResolver } from "../../resolvers/livesync-service-resolver"; - -export class DeviceRefreshAppService implements IDeviceRefreshAppService { - - constructor( - private $liveSyncServiceResolver: LiveSyncServiceResolver, - private $logger: ILogger, - private $runEmitter: RunEmitter - ) { } - - @performanceLog() - public async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, settings?: IRefreshApplicationSettings): Promise { - if (deviceDescriptor && deviceDescriptor.debuggingEnabled) { - liveSyncResultInfo.waitForDebugger = deviceDescriptor.debugOptions && deviceDescriptor.debugOptions.debugBrk; - } - - 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) { - shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); - } - - if (shouldRestart) { - this.$runEmitter.emitDebuggerDetachedEvent(liveSyncResultInfo.deviceAppData.device); - 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.$runEmitter.emitRunNotificationEvent(projectData, liveSyncResultInfo.deviceAppData.device, msg); - } - - if (settings && settings.shouldCheckDeveloperDiscImage && (err.message || err) === "Could not find developer disk image") { - this.$runEmitter.emitUserInteractionNeededEvent(projectData, liveSyncResultInfo.deviceAppData.device, deviceDescriptor); - } - } - - return result; - } -} -$injector.register("deviceRefreshAppService", DeviceRefreshAppService); diff --git a/lib/services/ios-device-debug-service.ts b/lib/services/ios-device-debug-service.ts index 16b34c3207..1205d7a50d 100644 --- a/lib/services/ios-device-debug-service.ts +++ b/lib/services/ios-device-debug-service.ts @@ -6,6 +6,7 @@ const inspectorAppName = "NativeScript Inspector.app"; const inspectorNpmPackageName = "tns-ios-inspector"; const inspectorUiDir = "WebInspectorUI/"; import { performanceLog } from "../common/decorators"; +import { platform } from "os"; export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDebugService { private deviceIdentifier: string; @@ -44,6 +45,10 @@ export class IOSDeviceDebugService extends DebugServiceBase implements IDeviceDe } private validateOptions(debugOptions: IDebugOptions) { + if (!this.$hostInfo.isWindows && !this.$hostInfo.isDarwin) { + this.$errors.failWithoutHelp(`Debugging on iOS devices is not supported for ${platform()} yet.`); + } + if (debugOptions.debugBrk && debugOptions.start) { this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options."); } diff --git a/test/controllers/run-controller.ts b/test/controllers/run-controller.ts index 6c4c4eeda0..bfdc76caff 100644 --- a/test/controllers/run-controller.ts +++ b/test/controllers/run-controller.ts @@ -77,12 +77,6 @@ function createTestInjector() { injector.register("deviceInstallAppService", { installOnDeviceIfNeeded: () => ({}) }); - injector.register("deviceRefreshAppService", { - refreshApplication: () => ({}) - }); - injector.register("deviceDebugAppService", { - enableDebugging: () => ({}) - }); injector.register("iOSLiveSyncService", { fullSync: async () => getFullSyncResult(), liveSyncWatchAction: () => ({}) diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index f839043e28..0fe938a557 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -138,14 +138,6 @@ describe("debugService", () => { await assertIsRejected(testData, "is not installed on device with identifier"); }); - it("the OS is neither Windows or macOS and device is iOS", async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = "iOS"; - testData.hostInfo.isDarwin = testData.hostInfo.isWindows = false; - - await assertIsRejected(testData, "Debugging on iOS devices is not supported for"); - }); - it("device is neither iOS or Android", async () => { const testData = getDefaultTestData(); testData.deviceInformation.deviceInfo.platform = "WP8"; @@ -153,14 +145,6 @@ describe("debugService", () => { await assertIsRejected(testData, DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); }); - it("when trying to debug on iOS Simulator on macOS, debug-brk is passed, but pathToAppPackage is not", async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = "iOS"; - testData.deviceInformation.isEmulator = true; - - await assertIsRejected(testData, "To debug on iOS simulator you need to provide path to the app package.", { debugBrk: true }); - }); - const assertIsRejectedWhenPlatformDebugServiceFails = async (platform: string): Promise => { const testData = getDefaultTestData(); testData.deviceInformation.deviceInfo.platform = platform; diff --git a/test/services/ios-device-debug-service.ts b/test/services/ios-device-debug-service.ts index df52aaf7cd..cff0409d54 100644 --- a/test/services/ios-device-debug-service.ts +++ b/test/services/ios-device-debug-service.ts @@ -188,4 +188,14 @@ describe("iOSDeviceDebugService", () => { } }); + describe("validate", () => { + it("the OS is neither Windows or macOS and device is iOS", async () => { + const testInjector = createTestInjector(); + const hostInfo = testInjector.resolve("hostInfo"); + hostInfo.isDarwin = hostInfo.isWindows = false; + + const iOSDeviceDebugService = testInjector.resolve(IOSDeviceDebugServiceInheritor); + assert.isRejected(iOSDeviceDebugService.debug(null, null), "Debugging on iOS devices is not supported for"); + }); + }); }); From 081fe18cc7a350748feb464e3087591ed60ab87c Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 21 May 2019 12:02:53 +0300 Subject: [PATCH 048/102] fix: set bundle option to webpack --- lib/options.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/options.ts b/lib/options.ts index 9dd4d1fc9c..2f2e4dcfe1 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -279,10 +279,7 @@ export class Options { this.argv.js = true; } - // Default to "nativescript-dev-webpack" if only `--bundle` is passed - if (this.argv.bundle !== undefined || this.argv.hmr) { - this.argv.bundle = this.argv.bundle || "webpack"; - } + this.argv.bundle = "webpack"; this.adjustDashedOptions(); } From f2d1655b9d5f76a6573cf707ac45ff8136b01488 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 30 May 2019 16:52:23 +0300 Subject: [PATCH 049/102] feat: expose public API https://github.com/NativeScript/nativescript-cli/issues/4644 --- lib/bootstrap.ts | 14 +- lib/commands/debug.ts | 9 +- lib/commands/preview.ts | 5 +- lib/commands/run.ts | 6 +- lib/commands/test.ts | 10 +- lib/common/definitions/mobile.d.ts | 2 +- .../mobile/mobile-core/devices-service.ts | 2 +- lib/controllers/debug-controller.ts | 270 ++++++++++++------ lib/controllers/deploy-controller.ts | 4 +- lib/controllers/preview-app-controller.ts | 57 +++- lib/controllers/run-controller.ts | 176 ++++++++---- lib/data/run-data.ts | 2 +- lib/definitions/debug.d.ts | 68 ++--- lib/definitions/livesync.d.ts | 69 +++-- lib/definitions/preview-app-livesync.d.ts | 10 +- lib/definitions/project.d.ts | 2 +- lib/definitions/run.d.ts | 47 ++- lib/emitters/preview-app-emitter.ts | 14 - lib/emitters/run-emitter.ts | 79 ----- lib/helpers/deploy-command-helper.ts | 5 +- lib/helpers/livesync-command-helper.ts | 72 ++--- lib/services/debug-data-service.ts | 10 +- lib/services/debug-service.ts | 101 ------- lib/services/livesync-process-data-service.ts | 34 +++ .../android-device-livesync-service-base.ts | 2 +- .../livesync/device-livesync-service-base.ts | 2 +- lib/services/livesync/ios-livesync-service.ts | 2 +- .../platform-livesync-service-base.ts | 8 +- .../preview-app-livesync-service.ts | 55 ---- .../platform-environment-requirements.ts | 17 +- lib/services/test-execution-service.ts | 3 +- .../webpack/webpack-compiler-service.ts | 12 +- lib/services/webpack/webpack.d.ts | 4 - test/services/debug-service.ts | 267 ----------------- 34 files changed, 539 insertions(+), 901 deletions(-) delete mode 100644 lib/emitters/preview-app-emitter.ts delete mode 100644 lib/emitters/run-emitter.ts delete mode 100644 lib/services/debug-service.ts create mode 100644 lib/services/livesync-process-data-service.ts delete mode 100644 lib/services/livesync/playground/preview-app-livesync-service.ts delete mode 100644 test/services/debug-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index ddd9ce8278..167bb6f21d 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -43,24 +43,21 @@ $injector.require("buildArtefactsService", "./services/build-artefacts-service") $injector.require("deviceInstallAppService", "./services/device/device-install-app-service"); -$injector.require("runEmitter", "./emitters/run-emitter"); -$injector.require("previewAppEmitter", "./emitters/preview-app-emitter"); - $injector.require("platformController", "./controllers/platform-controller"); $injector.require("prepareController", "./controllers/prepare-controller"); -$injector.require("buildController", "./controllers/build-controller"); $injector.require("deployController", "./controllers/deploy-controller"); -$injector.require("runController", "./controllers/run-controller"); -$injector.require("debugController", "./controllers/debug-controller"); -$injector.require("previewAppController", "./controllers/preview-app-controller"); +$injector.requirePublicClass("buildController", "./controllers/build-controller"); +$injector.requirePublicClass("runController", "./controllers/run-controller"); +$injector.requirePublicClass("debugController", "./controllers/debug-controller"); +$injector.requirePublicClass("previewAppController", "./controllers/preview-app-controller"); $injector.require("prepareDataService", "./services/prepare-data-service"); $injector.require("buildDataService", "./services/build-data-service"); $injector.require("liveSyncServiceResolver", "./resolvers/livesync-service-resolver"); +$injector.require("liveSyncProcessDataService", "./services/livesync-process-data-service"); $injector.require("debugDataService", "./services/debug-data-service"); -$injector.requirePublicClass("debugService", "./services/debug-service"); $injector.require("iOSDeviceDebugService", "./services/ios-device-debug-service"); $injector.require("androidDeviceDebugService", "./services/android-device-debug-service"); @@ -162,7 +159,6 @@ $injector.require("androidLiveSyncService", "./services/livesync/android-livesyn $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript $injector.require("previewAppFilesService", "./services/livesync/playground/preview-app-files-service"); -$injector.require("previewAppLiveSyncService", "./services/livesync/playground/preview-app-livesync-service"); $injector.require("previewAppLogProvider", "./services/livesync/playground/preview-app-log-provider"); $injector.require("previewAppPluginsService", "./services/livesync/playground/preview-app-plugins-service"); $injector.require("previewSdkService", "./services/livesync/playground/preview-sdk-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index bb80ed752a..30a2cfe5ab 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -7,7 +7,6 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements constructor(private platform: string, private $bundleValidatorHelper: IBundleValidatorHelper, - private $debugService: IDebugService, protected $devicesService: Mobile.IDevicesService, $platformValidationService: IPlatformValidationService, $projectData: IProjectData, @@ -36,19 +35,17 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements deviceId: this.$options.device }); - const debugData = this.$debugDataService.createDebugData(this.$projectData, { device: selectedDeviceForDebug.deviceInfo.identifier }); - if (this.$options.start) { const debugOptions = _.cloneDeep(this.$options.argv); - await this.$debugController.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); + const debugData = this.$debugDataService.getDebugData(selectedDeviceForDebug.deviceInfo.identifier, this.$projectData, debugOptions); + await this.$debugController.printDebugInformation(await this.$debugController.startDebug(debugData)); return; } - await this.$liveSyncCommandHelper.executeLiveSyncOperationWithDebug([selectedDeviceForDebug], this.platform, { + await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.platform, { deviceDebugMap: { [selectedDeviceForDebug.deviceInfo.identifier]: true }, - // This will default in the liveSyncCommandHelper buildPlatform: undefined, skipNativePrepare: false }); diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index 08dccb9994..a8bffb0bc4 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -1,5 +1,4 @@ import { DEVICE_LOG_EVENT_NAME } from "../common/constants"; -import { PreviewAppController } from "../controllers/preview-app-controller"; export class PreviewCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; @@ -9,7 +8,7 @@ export class PreviewCommand implements ICommand { private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, private $logger: ILogger, - private $previewAppController: PreviewAppController, + private $previewAppController: IPreviewAppController, private $networkConnectivityValidator: INetworkConnectivityValidator, private $projectData: IProjectData, private $options: IOptions, @@ -25,7 +24,7 @@ export class PreviewCommand implements ICommand { this.$logger.info(message); }); - await this.$previewAppController.preview({ + await this.$previewAppController.startPreview({ projectDir: this.$projectData.projectDir, useHotModuleReload: this.$options.hmr, env: this.$options.env diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 32985e65d5..e8fcda7cdc 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -36,12 +36,8 @@ export class RunCommandBase implements ICommand { this.platform = this.$devicePlatformsConstants.Android; } - const validatePlatformOutput = await this.$liveSyncCommandHelper.validatePlatform(this.platform); + await this.$liveSyncCommandHelper.validatePlatform(this.platform); - if (this.platform && validatePlatformOutput && validatePlatformOutput[this.platform.toLowerCase()]) { - const checkEnvironmentRequirementsOutput = validatePlatformOutput[this.platform.toLowerCase()].checkEnvironmentRequirementsOutput; - this.liveSyncCommandHelperAdditionalOptions.syncToPreviewApp = checkEnvironmentRequirementsOutput && checkEnvironmentRequirementsOutput.selectedOption === "Sync to Playground"; - } return true; } } diff --git a/lib/commands/test.ts b/lib/commands/test.ts index 7a47e2904b..abe1502d5f 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -1,5 +1,3 @@ -import { LiveSyncCommandHelper } from "../helpers/livesync-command-helper"; - abstract class TestCommandBase { public allowedParameters: ICommandParameter[] = []; protected abstract platform: string; @@ -10,7 +8,7 @@ abstract class TestCommandBase { protected abstract $platformEnvironmentRequirements: IPlatformEnvironmentRequirements; protected abstract $errors: IErrors; protected abstract $cleanupService: ICleanupService; - protected abstract $liveSyncCommandHelper: LiveSyncCommandHelper; + protected abstract $liveSyncCommandHelper: ILiveSyncCommandHelper; protected abstract $devicesService: Mobile.IDevicesService; async execute(args: string[]): Promise { @@ -31,7 +29,7 @@ abstract class TestCommandBase { if (!this.$options.env) { this.$options.env = { }; } this.$options.env.unitTesting = true; - const liveSyncInfo = this.$liveSyncCommandHelper.createLiveSyncInfo(); + const liveSyncInfo = this.$liveSyncCommandHelper.getLiveSyncData(this.$projectData.projectDir); const deviceDebugMap: IDictionary = {}; devices.forEach(device => deviceDebugMap[device.deviceInfo.identifier] = this.$options.debugBrk); @@ -79,7 +77,7 @@ class TestAndroidCommand extends TestCommandBase implements ICommand { protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, protected $errors: IErrors, protected $cleanupService: ICleanupService, - protected $liveSyncCommandHelper: LiveSyncCommandHelper, + protected $liveSyncCommandHelper: ILiveSyncCommandHelper, protected $devicesService: Mobile.IDevicesService) { super(); } @@ -95,7 +93,7 @@ class TestIosCommand extends TestCommandBase implements ICommand { protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, protected $errors: IErrors, protected $cleanupService: ICleanupService, - protected $liveSyncCommandHelper: LiveSyncCommandHelper, + protected $liveSyncCommandHelper: ILiveSyncCommandHelper, protected $devicesService: Mobile.IDevicesService) { super(); } diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 6997689d80..328f5467ab 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -497,7 +497,7 @@ declare module Mobile { */ pickSingleDevice(options: IPickSingleDeviceOptions): Promise; - getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceInfo[]): string[]; + getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceDescriptor[]): string[]; } interface IPickSingleDeviceOptions { diff --git a/lib/common/mobile/mobile-core/devices-service.ts b/lib/common/mobile/mobile-core/devices-service.ts index 41190f2fae..0670b21d40 100644 --- a/lib/common/mobile/mobile-core/devices-service.ts +++ b/lib/common/mobile/mobile-core/devices-service.ts @@ -601,7 +601,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi } } - public getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceInfo[]): string[] { + public getPlatformsFromDeviceDescriptors(deviceDescriptors: ILiveSyncDeviceDescriptor[]): string[] { const platforms = _(deviceDescriptors) .map(device => this.getDeviceByIdentifier(device.identifier)) .map(device => device.deviceInfo.platform.toLowerCase()) diff --git a/lib/controllers/debug-controller.ts b/lib/controllers/debug-controller.ts index 3e7c23c91a..b4ac68f1c3 100644 --- a/lib/controllers/debug-controller.ts +++ b/lib/controllers/debug-controller.ts @@ -1,139 +1,147 @@ import { performanceLog } from "../common/decorators"; import { EOL } from "os"; -import { RunController } from "./run-controller"; +import { parse } from "url"; +import { CONNECTED_STATUS } from "../common/constants"; +import { TrackActionNames, DebugCommandErrors, CONNECTION_ERROR_EVENT_NAME, DebugTools, DEBUGGER_DETACHED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "../constants"; +import { EventEmitter } from "events"; -export class DebugController extends RunController implements IDebugController { +export class DebugController extends EventEmitter implements IDebugController { + private _platformDebugServices: IDictionary = {}; constructor( - $analyticsService: IAnalyticsService, - $buildDataService: IBuildDataService, - $buildController: IBuildController, + private $analyticsService: IAnalyticsService, private $debugDataService: IDebugDataService, - private $debugService: IDebugService, - $deviceInstallAppService: IDeviceInstallAppService, - $devicesService: Mobile.IDevicesService, - $errors: IErrors, - $hmrStatusService: IHmrStatusService, - $hooksService: IHooksService, - $liveSyncServiceResolver: ILiveSyncServiceResolver, - $logger: ILogger, - $platformsDataService: IPlatformsDataService, - $pluginsService: IPluginsService, - $prepareController: IPrepareController, - $prepareDataService: IPrepareDataService, - $prepareNativePlatformService: IPrepareNativePlatformService, - $projectDataService: IProjectDataService, - $runEmitter: IRunEmitter + private $devicesService: Mobile.IDevicesService, + private $errors: IErrors, + private $injector: IInjector, + private $liveSyncProcessDataService: ILiveSyncProcessDataService, + private $logger: ILogger, + private $mobileHelper: Mobile.IMobileHelper, + private $projectDataService: IProjectDataService ) { - super( - $analyticsService, - $buildDataService, - $buildController, - $deviceInstallAppService, - $devicesService, - $errors, - $hmrStatusService, - $hooksService, - $liveSyncServiceResolver, - $logger, - $platformsDataService, - $pluginsService, - $prepareController, - $prepareDataService, - $prepareNativePlatformService, - $projectDataService, - $runEmitter - ); + super(); } @performanceLog() - public async enableDebugging(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise { - const { debugOptions } = deviceDescriptor; - // we do not stop the application when debugBrk is false, so we need to attach, instead of launch - // if we try to send the launch request, the debugger port will not be printed and the command will timeout - debugOptions.start = !debugOptions.debugBrk; - - debugOptions.forceDebuggerAttachedEvent = refreshInfo.didRestart; - const deviceOption = { - deviceIdentifier: deviceDescriptor.identifier, - debugOptions: debugOptions, - }; + public async startDebug(debugData: IDebugData): Promise { + const { debugOptions: options } = debugData; + const device = this.$devicesService.getDeviceByIdentifier(debugData.deviceIdentifier); + + if (!device) { + this.$errors.failWithoutHelp(`Cannot find device with identifier ${debugData.deviceIdentifier}.`); + } + + if (device.deviceInfo.status !== CONNECTED_STATUS) { + this.$errors.failWithoutHelp(`The device with identifier ${debugData.deviceIdentifier} is unreachable. Make sure it is Trusted and try again.`); + } + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.Debug, + device, + additionalData: this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && options && options.inspector ? DebugTools.Inspector : DebugTools.Chrome, + projectDir: debugData.projectDir + }); + + if (!(await device.applicationManager.isApplicationInstalled(debugData.applicationIdentifier))) { + this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); + } + + const debugService = this.getDeviceDebugService(device); + if (!debugService) { + this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`); + } - return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, deviceDescriptor, { projectDir: projectData.projectDir }); + const debugOptions: IDebugOptions = _.cloneDeep(options); + const debugResultInfo = await debugService.debug(debugData, debugOptions); + + return this.getDebugInformation(debugResultInfo, device.deviceInfo.identifier); } - public async attachDebugger(settings: IAttachDebuggerOptions): Promise { + public enableDebugging(enableDebuggingData: IEnableDebuggingData): Promise[] { + const { deviceIdentifiers } = enableDebuggingData; + + return _.map(deviceIdentifiers, deviceIdentifier => this.enableDebuggingCore(enableDebuggingData.projectDir, deviceIdentifier, enableDebuggingData.debugOptions)); + } + + public async disableDebugging(disableDebuggingData: IDisableDebuggingData): Promise { + const { deviceIdentifiers, projectDir } = disableDebuggingData; + + for (const deviceIdentifier of deviceIdentifiers) { + const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir); + if (liveSyncProcessInfo.currentSyncAction) { + await liveSyncProcessInfo.currentSyncAction; + } + + const currentDeviceDescriptor = this.getDeviceDescriptor(projectDir, deviceIdentifier); + + if (currentDeviceDescriptor) { + currentDeviceDescriptor.debuggingEnabled = false; + } else { + this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceIdentifier}`); + } + + const currentDevice = this.$devicesService.getDeviceByIdentifier(currentDeviceDescriptor.identifier); + if (!currentDevice) { + this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceIdentifier}. Could not find device.`); + } + + await this.stopDebug(currentDevice.deviceInfo.identifier); + + this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); + } + } + + public async attachDebugger(attachDebuggerData: IAttachDebuggerData): Promise { // Default values - if (settings.debugOptions) { - settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; - settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; + if (attachDebuggerData.debugOptions) { + attachDebuggerData.debugOptions.chrome = attachDebuggerData.debugOptions.chrome === undefined ? true : attachDebuggerData.debugOptions.chrome; + attachDebuggerData.debugOptions.start = attachDebuggerData.debugOptions.start === undefined ? true : attachDebuggerData.debugOptions.start; } else { - settings.debugOptions = { + attachDebuggerData.debugOptions = { chrome: true, start: true }; } - const projectData = this.$projectDataService.getProjectData(settings.projectDir); - const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); + const projectData = this.$projectDataService.getProjectData(attachDebuggerData.projectDir); + const debugData = this.$debugDataService.getDebugData(attachDebuggerData.deviceIdentifier, projectData, attachDebuggerData.debugOptions); // const platformData = this.$platformsDataService.getPlatformData(settings.platform, projectData); // Of the properties below only `buildForDevice` and `release` are currently used. // Leaving the others with placeholder values so that they may not be forgotten in future implementations. - const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions); - const result = this.printDebugInformation(debugInfo, settings.debugOptions.forceDebuggerAttachedEvent); + const debugInfo = await this.startDebug(debugData); + const result = this.printDebugInformation(debugInfo, attachDebuggerData.debugOptions.forceDebuggerAttachedEvent); return result; } - public printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean = true): IDebugInformation { - if (!!debugInformation.url) { - if (fireDebuggerAttachedEvent) { - this.$runEmitter.emitDebuggerAttachedEvent(debugInformation); - } - - this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan); - } - - return debugInformation; - } - - // IMPORTANT: This method overrides the refresh logic of runController as enables debugging for provided deviceDescriptor - public async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo): Promise { - liveSyncResultInfo.waitForDebugger = deviceDescriptor.debugOptions && deviceDescriptor.debugOptions.debugBrk; - - const refreshInfo = await super.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); - - await this.enableDebugging(projectData, deviceDescriptor, refreshInfo); - - return refreshInfo; - } - @performanceLog() - private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, deviceDescriptor: ILiveSyncDeviceInfo, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + public async enableDebuggingCoreWithoutWaitingCurrentAction(projectDir: string, deviceIdentifier: string, debugOptions: IDebugOptions): Promise { + const deviceDescriptor = this.getDeviceDescriptor(projectDir, deviceIdentifier); if (!deviceDescriptor) { - this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); + this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceIdentifier}`); } deviceDescriptor.debuggingEnabled = true; - deviceDescriptor.debugOptions = deviceOption.debugOptions; - const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); - const attachDebuggerOptions: IAttachDebuggerOptions = { - deviceIdentifier: deviceOption.deviceIdentifier, + deviceDescriptor.debugOptions = debugOptions; + + const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); + const attachDebuggerData: IAttachDebuggerData = { + deviceIdentifier, isEmulator: currentDeviceInstance.isEmulator, outputPath: deviceDescriptor.outputPath, platform: currentDeviceInstance.deviceInfo.platform, - projectDir: debuggingAdditionalOptions.projectDir, - debugOptions: deviceOption.debugOptions + projectDir, + debugOptions }; let debugInformation: IDebugInformation; try { - debugInformation = await this.attachDebugger(attachDebuggerOptions); + debugInformation = await this.attachDebugger(attachDebuggerData); } catch (err) { this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); - attachDebuggerOptions.debugOptions.start = false; + attachDebuggerData.debugOptions.start = false; try { - debugInformation = await this.attachDebugger(attachDebuggerOptions); + debugInformation = await this.attachDebugger(attachDebuggerData); } catch (innerErr) { this.$logger.trace("Couldn't attach debugger with modified options.", innerErr); throw err; @@ -142,5 +150,79 @@ export class DebugController extends RunController implements IDebugController { return debugInformation; } + + public printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent: boolean = true): IDebugInformation { + if (!!debugInformation.url) { + if (fireDebuggerAttachedEvent) { + this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); + } + + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan); + } + + return debugInformation; + } + + public async stopDebug(deviceIdentifier: string): Promise { + const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); + const debugService = this.getDeviceDebugService(device); + await debugService.debugStop(); + } + + private getDeviceDescriptor(projectDir: string, deviceIdentifier: string): ILiveSyncDeviceDescriptor { + const deviceDescriptors = this.$liveSyncProcessDataService.getDeviceDescriptors(projectDir); + const currentDeviceDescriptor = _.find(deviceDescriptors, d => d.identifier === deviceIdentifier); + + return currentDeviceDescriptor; + } + + private getDeviceDebugService(device: Mobile.IDevice): IDeviceDebugService { + if (!this._platformDebugServices[device.deviceInfo.identifier]) { + const devicePlatform = device.deviceInfo.platform; + if (this.$mobileHelper.isiOSPlatform(devicePlatform)) { + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("iOSDeviceDebugService", { device }); + } else if (this.$mobileHelper.isAndroidPlatform(devicePlatform)) { + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("androidDeviceDebugService", { device }); + } else { + this.$errors.failWithoutHelp(DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); + } + + this.attachConnectionErrorHandlers(this._platformDebugServices[device.deviceInfo.identifier]); + } + + return this._platformDebugServices[device.deviceInfo.identifier]; + } + + private attachConnectionErrorHandlers(platformDebugService: IDeviceDebugService) { + let connectionErrorHandler = (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e); + connectionErrorHandler = connectionErrorHandler.bind(this); + platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); + } + + private getDebugInformation(debugResultInfo: IDebugResultInfo, deviceIdentifier: string): IDebugInformation { + const debugInfo: IDebugInformation = { + url: debugResultInfo.debugUrl, + port: 0, + deviceIdentifier + }; + + if (debugResultInfo.debugUrl) { + const parseQueryString = true; + const wsQueryParam = parse(debugResultInfo.debugUrl, parseQueryString).query.ws; + const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); + debugInfo.port = hostPortSplit && +hostPortSplit[1]; + } + + return debugInfo; + } + + private async enableDebuggingCore(projectDir: string, deviceIdentifier: string, debugOptions: IDebugOptions): Promise { + const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir); + if (liveSyncProcessInfo && liveSyncProcessInfo.currentSyncAction) { + await liveSyncProcessInfo.currentSyncAction; + } + + return this.enableDebuggingCoreWithoutWaitingCurrentAction(projectDir, deviceIdentifier, debugOptions); + } } $injector.register("debugController", DebugController); diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index 5f43a8d08f..080d0ad142 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -8,10 +8,10 @@ export class DeployController { ) { } public async deploy(data: IRunData): Promise { - const { projectDir, liveSyncInfo, deviceDescriptors } = data; + const { liveSyncInfo, deviceDescriptors } = data; const executeAction = async (device: Mobile.IDevice) => { - const buildData = this.$buildDataService.getBuildData(projectDir, device.deviceInfo.platform, liveSyncInfo); + const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, device.deviceInfo.platform, liveSyncInfo); await this.$buildController.prepareAndBuild(buildData); await this.$deviceInstallAppService.installOnDevice(device, buildData); }; diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts index 9dd961d5e1..1531958def 100644 --- a/lib/controllers/preview-app-controller.ts +++ b/lib/controllers/preview-app-controller.ts @@ -5,10 +5,10 @@ import { performanceLog } from "../common/decorators"; import { stringify } from "../common/helpers"; import { HmrConstants } from "../common/constants"; import { EventEmitter } from "events"; -import { PreviewAppEmitter } from "../emitters/preview-app-emitter"; import { PrepareDataService } from "../services/prepare-data-service"; +import { PreviewAppLiveSyncEvents } from "../services/livesync/playground/preview-app-constants"; -export class PreviewAppController extends EventEmitter { +export class PreviewAppController extends EventEmitter implements IPreviewAppController { private deviceInitializationPromise: IDictionary> = {}; private promise = Promise.resolve(); @@ -18,16 +18,29 @@ export class PreviewAppController extends EventEmitter { private $hmrStatusService: IHmrStatusService, private $logger: ILogger, private $prepareController: PrepareController, - private $previewAppEmitter: PreviewAppEmitter, private $previewAppFilesService: IPreviewAppFilesService, - private $previewAppLiveSyncService: IPreviewAppLiveSyncService, private $previewAppPluginsService: IPreviewAppPluginsService, private $previewDevicesService: IPreviewDevicesService, + private $previewQrCodeService: IPreviewQrCodeService, private $previewSdkService: IPreviewSdkService, private $prepareDataService: PrepareDataService ) { super(); } - public async preview(data: IPreviewAppLiveSyncData): Promise { + public async startPreview(data: IPreviewAppLiveSyncData): Promise { + await this.previewCore(data); + + const url = this.$previewSdkService.getQrCodeUrl({ projectDir: data.projectDir, useHotModuleReload: data.useHotModuleReload }); + const result = await this.$previewQrCodeService.getLiveSyncQrCode(url); + + return result; + } + + public async stopPreview(): Promise { + this.$previewSdkService.stop(); + this.$previewDevicesService.updateConnectedDevices([]); + } + + private async previewCore(data: IPreviewAppLiveSyncData): Promise { await this.$previewSdkService.initialize(data.projectDir, async (device: Device) => { try { if (!device) { @@ -72,24 +85,24 @@ export class PreviewAppController extends EventEmitter { } } catch (error) { this.$logger.trace(`Error while sending files on device ${device && device.id}. Error is`, error); - this.$previewAppEmitter.emitPreviewAppLiveSyncError(data, device.id, error, device.platform); + this.emit(PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, { + error, + data, + platform: device.platform, + deviceId: device.id + }); } }); return null; } - public async stopPreview(): Promise { - this.$previewSdkService.stop(); - this.$previewDevicesService.updateConnectedDevices([]); - } - @performanceLog() private async handlePrepareReadyEvent(data: IPreviewAppLiveSyncData, hmrData: IPlatformHmrData, files: string[], platform: string) { await this.promise .then(async () => { const platformHmrData = _.cloneDeep(hmrData); - this.promise = this.$previewAppLiveSyncService.syncFilesForPlatformSafe(data, { filesToSync: files }, platform); + this.promise = this.syncFilesForPlatformSafe(data, { filesToSync: files }, platform); await this.promise; if (data.useHotModuleReload && platformHmrData.hash) { @@ -100,7 +113,7 @@ export class PreviewAppController extends EventEmitter { if (status === HmrConstants.HMR_ERROR_STATUS) { const originalUseHotModuleReload = data.useHotModuleReload; data.useHotModuleReload = false; - await this.$previewAppLiveSyncService.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id ); + await this.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id ); data.useHotModuleReload = originalUseHotModuleReload; } })); @@ -119,5 +132,23 @@ export class PreviewAppController extends EventEmitter { this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${stringify(err)}`); } } + + private async syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): Promise { + try { + const payloads = this.$previewAppFilesService.getFilesPayload(data, filesData, platform); + if (payloads && payloads.files && payloads.files.length) { + this.$logger.info(`Start syncing changes for platform ${platform}.`); + await this.$previewSdkService.applyChanges(payloads); + this.$logger.info(`Successfully synced ${payloads.files.map(filePayload => filePayload.file.yellow)} for platform ${platform}.`); + } + } catch (error) { + this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${error}, ${JSON.stringify(error, null, 2)}.`); + this.emit(PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, { + error, + data, + deviceId + }); + } + } } $injector.register("previewAppController", PreviewAppController); diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index ae88eea96d..055484a6e9 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -1,32 +1,38 @@ import { HmrConstants, DeviceDiscoveryEventNames } from "../common/constants"; -import { PREPARE_READY_EVENT_NAME, TrackActionNames } from "../constants"; +import { PREPARE_READY_EVENT_NAME, TrackActionNames, DEBUGGER_DETACHED_EVENT_NAME, RunOnDeviceEvents, USER_INTERACTION_NEEDED_EVENT_NAME } from "../constants"; import { cache, performanceLog } from "../common/decorators"; +import { EventEmitter } from "events"; -export class RunController implements IRunController { - private processesInfo: IDictionary = {}; +export class RunController extends EventEmitter implements IRunController { constructor( - private $analyticsService: IAnalyticsService, + protected $analyticsService: IAnalyticsService, private $buildDataService: IBuildDataService, private $buildController: IBuildController, + private $debugController: IDebugController, private $deviceInstallAppService: IDeviceInstallAppService, protected $devicesService: Mobile.IDevicesService, protected $errors: IErrors, + protected $injector: IInjector, private $hmrStatusService: IHmrStatusService, public $hooksService: IHooksService, private $liveSyncServiceResolver: ILiveSyncServiceResolver, + private $liveSyncProcessDataService: ILiveSyncProcessDataService, protected $logger: ILogger, + protected $mobileHelper: Mobile.IMobileHelper, private $platformsDataService: IPlatformsDataService, private $pluginsService: IPluginsService, private $prepareController: IPrepareController, private $prepareDataService: IPrepareDataService, private $prepareNativePlatformService: IPrepareNativePlatformService, - protected $projectDataService: IProjectDataService, - protected $runEmitter: IRunEmitter - ) { } + protected $projectDataService: IProjectDataService + ) { + super(); + } public async run(runData: IRunData): Promise { - const { projectDir, liveSyncInfo, deviceDescriptors } = runData; + const { liveSyncInfo, deviceDescriptors } = runData; + const { projectDir } = liveSyncInfo; const projectData = this.$projectDataService.getProjectData(projectDir); await this.initializeSetup(projectData); @@ -34,9 +40,9 @@ export class RunController implements IRunController { const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); const deviceDescriptorsForInitialSync = this.getDeviceDescriptorsForInitialSync(projectDir, deviceDescriptors); - this.persistData(projectDir, deviceDescriptors, platforms); + this.$liveSyncProcessDataService.persistData(projectDir, deviceDescriptors, platforms); - const shouldStartWatcher = !liveSyncInfo.skipWatcher && !!this.processesInfo[projectDir].deviceDescriptors.length; + const shouldStartWatcher = !liveSyncInfo.skipWatcher && this.$liveSyncProcessDataService.hasDeviceDescriptors(projectDir); if (shouldStartWatcher && liveSyncInfo.useHotModuleReload) { this.$hmrStatusService.attachToHmrStatusEvent(); } @@ -50,8 +56,9 @@ export class RunController implements IRunController { this.attachDeviceLostHandler(); } - public async stop(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise { - const liveSyncProcessInfo = this.processesInfo[projectDir]; + public async stop(data: IStopRunData): Promise { + const { projectDir, deviceIdentifiers, stopOptions } = data; + const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir); if (liveSyncProcessInfo && !liveSyncProcessInfo.isStopped) { // In case we are coming from error during livesync, the current action is the one that erred (but we are still executing it), // so we cannot await it as this will cause infinite loop. @@ -100,32 +107,62 @@ export class RunController implements IRunController { // Emit RunOnDevice stopped when we've really stopped. _.each(removedDeviceIdentifiers, deviceIdentifier => { - this.$runEmitter.emitRunStoppedEvent(projectDir, deviceIdentifier); + this.emitCore(RunOnDeviceEvents.runOnDeviceStopped, { + projectDir, + deviceIdentifier + }); }); } } - public getDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { - const liveSyncProcessesInfo = this.processesInfo[projectDir] || {}; - const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; - return currentDescriptors || []; + public getDeviceDescriptors(data: { projectDir: string }): ILiveSyncDeviceDescriptor[] { + return this.$liveSyncProcessDataService.getDeviceDescriptors(data.projectDir); + } + + protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, filesChangeEventData: IFilesChangeEventData, deviceDescriptor: ILiveSyncDeviceDescriptor, settings?: IRefreshApplicationSettings): Promise { + const result = deviceDescriptor.debuggingEnabled ? + await this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, settings) : + await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, settings); + + return result; + } + + protected async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, filesChangeEventData: IFilesChangeEventData, deviceDescriptor: ILiveSyncDeviceDescriptor, settings?: IRefreshApplicationSettings): Promise { + const debugOptions = deviceDescriptor.debugOptions || {}; + + liveSyncResultInfo.waitForDebugger = !!debugOptions.debugBrk; + + const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, filesChangeEventData, deviceDescriptor, settings); + + // we do not stop the application when debugBrk is false, so we need to attach, instead of launch + // if we try to send the launch request, the debugger port will not be printed and the command will timeout + debugOptions.start = !debugOptions.debugBrk; + debugOptions.forceDebuggerAttachedEvent = refreshInfo.didRestart; + + await this.$debugController.enableDebuggingCoreWithoutWaitingCurrentAction(projectData.projectDir, deviceDescriptor.identifier, debugOptions); + + return refreshInfo; } @performanceLog() - protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, settings?: IRefreshApplicationSettings): Promise { + protected async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, filesChangeEventData: IFilesChangeEventData, deviceDescriptor: ILiveSyncDeviceDescriptor, settings?: IRefreshApplicationSettings): Promise { const result = { didRestart: false }; const platform = liveSyncResultInfo.deviceAppData.platform; const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()]; const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(platform); try { - let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); + let shouldRestart = filesChangeEventData && filesChangeEventData.hasNativeChanges; + if (!shouldRestart) { + shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo); + } + if (!shouldRestart) { shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo); } if (shouldRestart) { - this.$runEmitter.emitDebuggerDetachedEvent(liveSyncResultInfo.deviceAppData.device); + this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier }); await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo); result.didRestart = true; } @@ -133,20 +170,37 @@ export class RunController implements IRunController { this.$logger.info(`Error while trying to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`); const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; this.$logger.warn(msg); + + const device = liveSyncResultInfo.deviceAppData.device; + const deviceIdentifier = device.deviceInfo.identifier; + if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { - this.$runEmitter.emitRunNotificationEvent(projectData, liveSyncResultInfo.deviceAppData.device, msg); + this.emitCore(RunOnDeviceEvents.runOnDeviceNotification, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], + notification: msg + }); } if (settings && settings.shouldCheckDeveloperDiscImage && (err.message || err) === "Could not find developer disk image") { - this.$runEmitter.emitUserInteractionNeededEvent(projectData, liveSyncResultInfo.deviceAppData.device, deviceDescriptor); + const attachDebuggerOptions: IAttachDebuggerData = { + platform: device.deviceInfo.platform, + isEmulator: device.isEmulator, + projectDir: projectData.projectDir, + deviceIdentifier, + debugOptions: deviceDescriptor.debugOptions, + outputPath: deviceDescriptor.outputPath + }; + this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); } } return result; } - private getDeviceDescriptorsForInitialSync(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]) { - const currentRunData = this.processesInfo[projectDir]; + private getDeviceDescriptorsForInitialSync(projectDir: string, deviceDescriptors: ILiveSyncDeviceDescriptor[]) { + const currentRunData = this.$liveSyncProcessDataService.getPersistedData(projectDir); const isAlreadyLiveSyncing = currentRunData && !currentRunData.isStopped; // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentRunData.deviceDescriptors, "identifier") : deviceDescriptors; @@ -168,11 +222,11 @@ export class RunController implements IRunController { this.$devicesService.on(DeviceDiscoveryEventNames.DEVICE_LOST, async (device: Mobile.IDevice) => { this.$logger.trace(`Received ${DeviceDiscoveryEventNames.DEVICE_LOST} event in LiveSync service for ${device.deviceInfo.identifier}. Will stop LiveSync operation for this device.`); - for (const projectDir in this.processesInfo) { + for (const projectDir in this.$liveSyncProcessDataService.getAllPersistedData()) { try { - const deviceDescriptors = this.getDeviceDescriptors(projectDir); + const deviceDescriptors = this.getDeviceDescriptors({ projectDir }); if (_.find(deviceDescriptors, d => d.identifier === device.deviceInfo.identifier)) { - await this.stop(projectDir, [device.deviceInfo.identifier]); + await this.stop({ projectDir, deviceIdentifiers: [device.deviceInfo.identifier] }); } } catch (err) { this.$logger.warn(`Unable to stop LiveSync operation for ${device.deviceInfo.identifier}.`, err); @@ -181,7 +235,7 @@ export class RunController implements IRunController { }); } - private async syncInitialDataOnDevices(projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + private async syncInitialDataOnDevices(projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); @@ -206,29 +260,41 @@ export class RunController implements IRunController { const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(platformData.platformNameLowerCase); const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; - const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor }); + const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceData: deviceDescriptor }); - await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + await this.refreshApplication(projectData, liveSyncResultInfo, null, deviceDescriptor); - this.$runEmitter.emitRunExecutedEvent(projectData, device, { + this.emitCore(RunOnDeviceEvents.runOnDeviceExecuted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); - this.$runEmitter.emitRunStartedEvent(projectData, device); + this.emitCore(RunOnDeviceEvents.runOnDeviceStarted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] + }); } catch (err) { this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`); - this.$runEmitter.emitRunErrorEvent(projectData, device, err); + this.emitCore(RunOnDeviceEvents.runOnDeviceError, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], + error: err, + }); } }; await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } - private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); const platformData = this.$platformsDataService.getPlatformData(data.platform, projectData); @@ -238,7 +304,7 @@ export class RunController implements IRunController { try { if (data.hasNativeChanges) { await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); - await this.$buildController.prepareAndBuild(buildData); + await this.$buildController.build(buildData); } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; @@ -248,7 +314,7 @@ export class RunController implements IRunController { const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(device.deviceInfo.platform); const watchInfo = { - liveSyncDeviceInfo: deviceDescriptor, + liveSyncDeviceData: deviceDescriptor, projectData, filesToRemove: [], filesToSync: data.files, @@ -260,9 +326,12 @@ export class RunController implements IRunController { }; let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); - await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor); - this.$runEmitter.emitRunExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + this.emitCore(RunOnDeviceEvents.runOnDeviceExecuted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); @@ -274,9 +343,12 @@ export class RunController implements IRunController { liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo); // We want to force a restart of the application. liveSyncResultInfo.isFullSync = true; - await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor); + await this.refreshApplication(projectData, liveSyncResultInfo, data, deviceDescriptor); - this.$runEmitter.emitRunExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, { + this.emitCore(RunOnDeviceEvents.runOnDeviceExecuted, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()), isFullSync: liveSyncResultInfo.isFullSync }); @@ -290,20 +362,26 @@ export class RunController implements IRunController { if (allErrors && _.isArray(allErrors)) { for (const deviceError of allErrors) { this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); - this.$runEmitter.emitRunErrorEvent(projectData, device, deviceError); + + this.emitCore(RunOnDeviceEvents.runOnDeviceError, { + projectDir: projectData.projectDir, + deviceIdentifier: device.deviceInfo.identifier, + applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], + error: err, + }); } } } }; await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => { - const liveSyncProcessInfo = this.processesInfo[projectData.projectDir]; + const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectData.projectDir); return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); })); } private async addActionToChain(projectDir: string, action: () => Promise): Promise { - const liveSyncInfo = this.processesInfo[projectDir]; + const liveSyncInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir); if (liveSyncInfo) { liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => { if (!liveSyncInfo.isStopped) { @@ -318,15 +396,9 @@ export class RunController implements IRunController { } } - private persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[], platforms: string[]): void { - this.processesInfo[projectDir] = this.processesInfo[projectDir] || Object.create(null); - this.processesInfo[projectDir].actionsChain = this.processesInfo[projectDir].actionsChain || Promise.resolve(); - this.processesInfo[projectDir].currentSyncAction = this.processesInfo[projectDir].actionsChain; - this.processesInfo[projectDir].isStopped = false; - this.processesInfo[projectDir].platforms = platforms; - - const currentDeviceDescriptors = this.getDeviceDescriptors(projectDir); - this.processesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); + private emitCore(event: string, data: ILiveSyncEventData): void { + this.$logger.trace(`Will emit event ${event} with data`, data); + this.emit(event, data); } } $injector.register("runController", RunController); diff --git a/lib/data/run-data.ts b/lib/data/run-data.ts index 4d67d8043d..5018a1b9ae 100644 --- a/lib/data/run-data.ts +++ b/lib/data/run-data.ts @@ -1,5 +1,5 @@ export class RunData { constructor(public projectDir: string, public liveSyncInfo: ILiveSyncInfo, - public deviceDescriptors: ILiveSyncDeviceInfo[]) { } + public deviceDescriptors: ILiveSyncDeviceDescriptor[]) { } } diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 2cf4700bc8..b835ab261b 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -1,21 +1,5 @@ -/** - * Describes information for starting debug process. - */ -interface IDebugData extends IAppDebugData, Mobile.IDeviceIdentifier { -} - -/** - * Describes information for application that will be debugged. - */ -interface IAppDebugData extends IProjectDir { - /** - * Application identifier of the app that it will be debugged. - */ +interface IDebugData extends IProjectDir, Mobile.IDeviceIdentifier, IOptionalDebuggingOptions { applicationIdentifier: string; - - /** - * The name of the application, for example `MyProject`. - */ projectName?: string; } @@ -103,33 +87,12 @@ interface IDebugOptions { interface IDebugDataService { /** * Creates the debug data based on specified options. + * @param {string} deviceIdentifier The identifier of the device * @param {IProjectData} projectData The data describing project that will be debugged. - * @param {IOptions} options The options based on which debugData will be created + * @param {IDebugOptions} debugOptions The debug options * @returns {IDebugData} Data describing the required information for starting debug process. */ - createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData; -} - -/** - * Describes methods for debug operation. - */ -interface IDebugServiceBase extends NodeJS.EventEmitter { - /** - * Starts debug operation based on the specified debug data. - * @param {IDebugData} debugData Describes information for device and application that will be debugged. - * @param {IDebugOptions} debugOptions Describe possible options to modify the behaivor of the debug operation, for example stop on the first line. - * @returns {Promise} Device Identifier, full url and port where the frontend client can be connected. - */ - debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; -} - -interface IDebugService extends IDebugServiceBase { - /** - * Stops debug operation for a specific device. - * @param {string} deviceIdentifier Identifier of the device fo which debugging will be stopped. - * @returns {Promise} - */ - debugStop(deviceIdentifier: string): Promise; + getDebugData(deviceIdentifier: string, projectData: IProjectData, debugOptions: IDebugOptions): IDebugData; } /** @@ -155,9 +118,24 @@ interface IDebugResultInfo { debugUrl: string; } -interface IDebugController extends IRunController { - // TODO: add disableDebugging method - enableDebugging(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise; - attachDebugger(settings: IAttachDebuggerOptions): Promise; +interface IAppDebugData extends IProjectDir { + /** + * Application identifier of the app that it will be debugged. + */ + applicationIdentifier: string; + + /** + * The name of the application, for example `MyProject`. + */ + projectName?: string; +} + +interface IDebugController { + startDebug(debugData: IDebugData): Promise; + stopDebug(deviceIdentifier: string): Promise; printDebugInformation(debugInformation: IDebugInformation, fireDebuggerAttachedEvent?: boolean): IDebugInformation; + enableDebuggingCoreWithoutWaitingCurrentAction(projectDir: string, deviceIdentifier: string, debugOptions: IDebugOptions): Promise; + enableDebugging(enableDebuggingData: IEnableDebuggingData): Promise[]; + disableDebugging(disableDebuggingData: IDisableDebuggingData): Promise; + attachDebugger(attachDebuggerData: IAttachDebuggerData): Promise; } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index d55b1a88c7..dd72481d34 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -1,11 +1,11 @@ import { EventEmitter } from "events"; declare global { - interface IRunOnDeviceProcessInfo { + interface ILiveSyncProcessData { timer: NodeJS.Timer; actionsChain: Promise; isStopped: boolean; - deviceDescriptors: ILiveSyncDeviceInfo[]; + deviceDescriptors: ILiveSyncDeviceDescriptor[]; currentSyncAction: Promise; syncToPreviewApp: boolean; platforms: string[]; @@ -48,7 +48,7 @@ declare global { /** * Describes information for LiveSync on a device. */ - interface ILiveSyncDeviceInfo extends IOptionalOutputPath, IOptionalDebuggingOptions { + interface ILiveSyncDeviceDescriptor extends IOptionalOutputPath, IOptionalDebuggingOptions { /** * Device identifier. */ @@ -73,7 +73,7 @@ declare global { /** * Describes a LiveSync operation. */ - interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IRelease, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { + interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IRelease, IHasUseHotModuleReloadOption { emulator?: boolean; /** @@ -102,13 +102,6 @@ declare global { nativePrepare?: INativePrepare; } - interface IHasSyncToPreviewAppOption { - /** - * Defines if the livesync should be executed in preview app on device. - */ - syncToPreviewApp?: boolean; - } - interface IHasUseHotModuleReloadOption { /** * Defines if the hot module reload should be used. @@ -140,11 +133,11 @@ declare global { interface ILiveSyncService extends EventEmitter { /** * Starts LiveSync operation by rebuilding the application if necessary and starting watcher. - * @param {ILiveSyncDeviceInfo[]} deviceDescriptors Describes each device for which we would like to sync the application - identifier, outputPath and action to rebuild the app. + * @param {ILiveSyncDeviceDescriptor[]} deviceDescriptors Describes each device for which we would like to sync the application - identifier, outputPath and action to rebuild the app. * @param {ILiveSyncInfo} liveSyncData Describes the LiveSync operation - for which project directory is the operation and other settings. * @returns {Promise} */ - liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise; + liveSync(deviceDescriptors: ILiveSyncDeviceDescriptor[], liveSyncData: ILiveSyncInfo): Promise; /** * Starts LiveSync operation to Preview app. @@ -167,16 +160,11 @@ declare global { * In case LiveSync has been started on many devices, but stopped for some of them at a later point, * calling the method after that will return information only for devices for which LiveSync operation is in progress. * @param {string} projectDir The path to project for which the LiveSync operation is executed - * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. + * @returns {ILiveSyncDeviceDescriptor[]} Array of elements describing parameters used to start LiveSync on each device. */ - getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; + getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceDescriptor[]; } - /** - * Describes additional debugging settings. - */ - interface IDebuggingAdditionalOptions extends IProjectDir { } - /** * Describes settings used when disabling debugging. */ @@ -189,10 +177,15 @@ declare global { debugOptions?: IDebugOptions; } - /** - * Describes settings used when enabling debugging. - */ - interface IEnableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier, IOptionalDebuggingOptions { } + interface IEnableDebuggingData extends IProjectDir, IOptionalDebuggingOptions { + deviceIdentifiers: string[]; + } + + interface IDisableDebuggingData extends IProjectDir { + deviceIdentifiers: string[]; + } + + interface IAttachDebuggerData extends IProjectDir, Mobile.IDeviceIdentifier, IOptionalDebuggingOptions, IIsEmulator, IPlatform, IOptionalOutputPath { } /** * Describes settings passed to livesync service in order to control event emitting during refresh application. @@ -202,12 +195,6 @@ declare global { shouldCheckDeveloperDiscImage: boolean; } - /** - * Describes settings used for attaching a debugger. - */ - interface IAttachDebuggerOptions extends IDebuggingAdditionalOptions, IEnableDebuggingDeviceOptions, IIsEmulator, IPlatform, IOptionalOutputPath { - } - interface IConnectTimeoutOption { /** * Time to wait for successful connection. Defaults to 30000 miliseconds. @@ -219,7 +206,7 @@ declare global { filesToRemove: string[]; filesToSync: string[]; isReinstalled: boolean; - liveSyncDeviceInfo: ILiveSyncDeviceInfo; + liveSyncDeviceData: ILiveSyncDeviceDescriptor; hmrData: IPlatformHmrData; force?: boolean; } @@ -237,7 +224,7 @@ declare global { interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { device: Mobile.IDevice; watch: boolean; - liveSyncDeviceInfo: ILiveSyncDeviceInfo; + liveSyncDeviceData: ILiveSyncDeviceDescriptor; force?: boolean; } @@ -305,7 +292,7 @@ declare global { * @param {boolean} isFullSync Indicates if the operation is part of a fullSync * @return {Promise} Returns the ILocalToDevicePathData of all transfered files */ - transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise; + transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceData: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise; } interface IAndroidNativeScriptDeviceLiveSyncService extends INativeScriptDeviceLiveSyncService { @@ -445,7 +432,7 @@ declare global { /** * Describes additional options, that can be passed to LiveSyncCommandHelper. */ - interface ILiveSyncCommandHelperAdditionalOptions extends IBuildPlatformAction, INativePrepare, IHasSyncToPreviewAppOption { + interface ILiveSyncCommandHelperAdditionalOptions extends IBuildPlatformAction, INativePrepare { /** * A map representing devices which have debugging enabled initially. */ @@ -468,7 +455,6 @@ declare global { * @returns {Promise} */ executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; - executeLiveSyncOperationWithDebug(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; getPlatformsForOperation(platform: string): string[]; /** @@ -485,9 +471,20 @@ declare global { * @returns {Promise} */ executeCommandLiveSync(platform?: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; + createDeviceDescriptors(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise; + getDeviceInstances(platform?: string): Promise; + getLiveSyncData(projectDir: string): ILiveSyncInfo; } interface ILiveSyncServiceResolver { resolveLiveSyncService(platform: string): IPlatformLiveSyncService; } -} \ No newline at end of file + + interface ILiveSyncProcessDataService { + getPersistedData(projectDir: string): ILiveSyncProcessData; + getDeviceDescriptors(projectDir: string): ILiveSyncDeviceDescriptor[]; + getAllPersistedData(): IDictionary; + persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceDescriptor[], platforms: string[]): void; + hasDeviceDescriptors(projectDir: string): boolean; + } +} diff --git a/lib/definitions/preview-app-livesync.d.ts b/lib/definitions/preview-app-livesync.d.ts index 1fb10a0972..414b4cffe7 100644 --- a/lib/definitions/preview-app-livesync.d.ts +++ b/lib/definitions/preview-app-livesync.d.ts @@ -2,11 +2,6 @@ import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk"; import { EventEmitter } from "events"; declare global { - interface IPreviewAppLiveSyncService extends EventEmitter { - syncFiles(data: IPreviewAppLiveSyncData, filesToSync: string[], filesToRemove: string[]): Promise; - syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): Promise; - } - interface IPreviewAppFilesService { getInitialFilesPayload(liveSyncData: IPreviewAppLiveSyncData, platform: string, deviceId?: string): FilesPayload; getFilesPayload(liveSyncData: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): FilesPayload; @@ -79,4 +74,9 @@ declare global { subscribeKey: string; default?: boolean; } + + interface IPreviewAppController { + startPreview(data: IPreviewAppLiveSyncData): Promise; + stopPreview(): Promise; + } } \ No newline at end of file diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 4e6008991a..4cca0dee0f 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -358,7 +358,7 @@ interface IValidatePlatformOutput { } interface ITestExecutionService { - startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise; + startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise; canStartKarmaServer(projectData: IProjectData): Promise; } diff --git a/lib/definitions/run.d.ts b/lib/definitions/run.d.ts index b2014cf661..2fb421c716 100644 --- a/lib/definitions/run.d.ts +++ b/lib/definitions/run.d.ts @@ -1,28 +1,27 @@ -interface IRunData { - projectDir: string; - liveSyncInfo: ILiveSyncInfo; - deviceDescriptors: ILiveSyncDeviceInfo[]; -} +import { EventEmitter } from "events"; -interface IRunController { - run(runData: IRunData): Promise; - stop(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise; - getDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; -} +declare global { -interface IRunEmitter { - emitRunStartedEvent(projectData: IProjectData, device: Mobile.IDevice): void; - emitRunNotificationEvent(projectData: IProjectData, device: Mobile.IDevice, notification: string): void; - emitRunErrorEvent(projectData: IProjectData, device: Mobile.IDevice, error: Error): void; - emitRunExecutedEvent(projectData: IProjectData, device: Mobile.IDevice, options: { syncedFiles: string[], isFullSync: boolean }): void; - emitRunStoppedEvent(projectDir: string, deviceIdentifier: string): void; - emitDebuggerAttachedEvent(debugInformation: IDebugInformation): void; - emitDebuggerDetachedEvent(device: Mobile.IDevice): void; - emitUserInteractionNeededEvent(projectData: IProjectData, device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo): void; -} + interface IRunData { + liveSyncInfo: ILiveSyncInfo; + deviceDescriptors: ILiveSyncDeviceDescriptor[]; + } + + interface IStopRunData { + projectDir: string; + deviceIdentifiers?: string[]; + stopOptions?: { shouldAwaitAllActions: boolean }; + } + + interface IRunController extends EventEmitter { + run(runData: IRunData): Promise; + stop(data: IStopRunData): Promise; + getDeviceDescriptors(data: { projectDir: string }): ILiveSyncDeviceDescriptor[]; + } -interface IDeviceInstallAppService { - installOnDevice(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; - installOnDeviceIfNeeded(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; - shouldInstall(device: Mobile.IDevice, buildData: IBuildData): Promise; + interface IDeviceInstallAppService { + installOnDevice(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; + installOnDeviceIfNeeded(device: Mobile.IDevice, buildData: IBuildData, packageFile?: string): Promise; + shouldInstall(device: Mobile.IDevice, buildData: IBuildData): Promise; + } } diff --git a/lib/emitters/preview-app-emitter.ts b/lib/emitters/preview-app-emitter.ts deleted file mode 100644 index 99923fbef2..0000000000 --- a/lib/emitters/preview-app-emitter.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { EventEmitter } from "events"; -import { PreviewAppLiveSyncEvents } from "../services/livesync/playground/preview-app-constants"; - -export class PreviewAppEmitter extends EventEmitter { - public emitPreviewAppLiveSyncError(data: IPreviewAppLiveSyncData, deviceId: string, error: Error, platform?: string) { - this.emit(PreviewAppLiveSyncEvents.PREVIEW_APP_LIVE_SYNC_ERROR, { - error, - data, - platform, - deviceId - }); - } -} -$injector.register("previewAppEmitter", PreviewAppEmitter); diff --git a/lib/emitters/run-emitter.ts b/lib/emitters/run-emitter.ts deleted file mode 100644 index 947222292a..0000000000 --- a/lib/emitters/run-emitter.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { EventEmitter } from "events"; -import { RunOnDeviceEvents, DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "../constants"; - -export class RunEmitter extends EventEmitter implements IRunEmitter { - constructor( - private $logger: ILogger - ) { super(); } - - public emitRunStartedEvent(projectData: IProjectData, device: Mobile.IDevice): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceStarted, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()] - }); - } - - public emitRunNotificationEvent(projectData: IProjectData, device: Mobile.IDevice, notification: string): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceNotification, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], - notification - }); - } - - public emitRunErrorEvent(projectData: IProjectData, device: Mobile.IDevice, error: Error): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceError, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], - error, - }); - } - - public emitRunExecutedEvent(projectData: IProjectData, device: Mobile.IDevice, options: { syncedFiles: string[], isFullSync: boolean }): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceExecuted, { - projectDir: projectData.projectDir, - deviceIdentifier: device.deviceInfo.identifier, - applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], - syncedFiles: options.syncedFiles, - isFullSync: options.isFullSync - }); - } - - public emitRunStoppedEvent(projectDir: string, deviceIdentifier: string): void { - this.emitCore(RunOnDeviceEvents.runOnDeviceStopped, { - projectDir, - deviceIdentifier - }); - } - - public emitDebuggerAttachedEvent(debugInformation: IDebugInformation): void { - this.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation); - } - - public emitDebuggerDetachedEvent(device: Mobile.IDevice): void { - const deviceIdentifier = device.deviceInfo.identifier; - this.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier }); - } - - public emitUserInteractionNeededEvent(projectData: IProjectData, device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo): void { - const deviceIdentifier = device.deviceInfo.identifier; - const attachDebuggerOptions: IAttachDebuggerOptions = { - platform: device.deviceInfo.platform, - isEmulator: device.isEmulator, - projectDir: projectData.projectDir, - deviceIdentifier, - debugOptions: deviceDescriptor.debugOptions, - outputPath: deviceDescriptor.outputPath - }; - this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); - } - - private emitCore(event: string, data: ILiveSyncEventData): void { - this.$logger.trace(`Will emit event ${event} with data`, data); - this.emit(event, data); - } -} -$injector.register("runEmitter", RunEmitter); diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index d037ad435d..40a78e01cd 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -23,7 +23,7 @@ export class DeployCommandHelper { const devices = this.$devicesService.getDeviceInstances() .filter(d => !platform || d.deviceInfo.platform.toLowerCase() === platform.toLowerCase()); - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices + const deviceDescriptors: ILiveSyncDeviceDescriptor[] = devices .map(d => { const buildConfig: IBuildConfig = { buildForDevice: !d.isEmulator, @@ -50,7 +50,7 @@ export class DeployCommandHelper { projectDir: this.$projectData.projectDir }); - const info: ILiveSyncDeviceInfo = { + const info: ILiveSyncDeviceDescriptor = { identifier: d.deviceInfo.identifier, buildAction, debuggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], @@ -75,7 +75,6 @@ export class DeployCommandHelper { }; await this.$deployController.deploy({ - projectDir: this.$projectData.projectDir, liveSyncInfo, deviceDescriptors }); diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 2413dd749c..419694233e 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -1,5 +1,4 @@ import { RunOnDeviceEvents } from "../constants"; -import { RunEmitter } from "../emitters/run-emitter"; import { DeployController } from "../controllers/deploy-controller"; export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { @@ -9,7 +8,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $buildDataService: IBuildDataService, private $projectData: IProjectData, private $options: IOptions, - private $runEmitter: RunEmitter, private $deployController: DeployController, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, @@ -21,7 +19,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $errors: IErrors, private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, private $cleanupService: ICleanupService, - private $debugController: IDebugController, private $runController: IRunController ) { } @@ -29,6 +26,23 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return this.$injector.resolve("platformsDataService"); } + // TODO: Remove this and replace it with buildData + public getLiveSyncData(projectDir: string): ILiveSyncInfo { + const liveSyncInfo: ILiveSyncInfo = { + projectDir, + skipWatcher: !this.$options.watch || this.$options.justlaunch, + clean: this.$options.clean, + release: this.$options.release, + env: this.$options.env, + timeout: this.$options.timeout, + useHotModuleReload: this.$options.hmr, + force: this.$options.force, + emulator: this.$options.emulator + }; + + return liveSyncInfo; + } + public async getDeviceInstances(platform?: string): Promise { await this.$devicesService.initialize({ platform, @@ -44,25 +58,9 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return devices; } - public createLiveSyncInfo(): ILiveSyncInfo { - const liveSyncInfo: ILiveSyncInfo = { - projectDir: this.$projectData.projectDir, - skipWatcher: !this.$options.watch || this.$options.justlaunch, - clean: this.$options.clean, - release: this.$options.release, - env: this.$options.env, - timeout: this.$options.timeout, - useHotModuleReload: this.$options.hmr, - force: this.$options.force, - emulator: this.$options.emulator - }; - - return liveSyncInfo; - } - - public async createDeviceDescriptors(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { + public async createDeviceDescriptors(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { // Now let's take data for each device: - const deviceDescriptors: ILiveSyncDeviceInfo[] = devices + const deviceDescriptors: ILiveSyncDeviceDescriptor[] = devices .map(d => { const buildConfig: IBuildConfig = { buildForDevice: !d.isEmulator, @@ -91,7 +89,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { projectDir: this.$projectData.projectDir }); - const info: ILiveSyncDeviceInfo = { + const info: ILiveSyncDeviceDescriptor = { identifier: d.deviceInfo.identifier, buildAction, debuggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], @@ -120,13 +118,12 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const { liveSyncInfo, deviceDescriptors } = await this.executeLiveSyncOperationCore(devices, platform, additionalOptions); await this.$runController.run({ - projectDir: this.$projectData.projectDir, liveSyncInfo, deviceDescriptors }); const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); - this.$runEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: { projectDir: string, deviceIdentifier: string }) => { + this.$runController.on(RunOnDeviceEvents.runOnDeviceStopped, (data: { projectDir: string, deviceIdentifier: string }) => { _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); if (remainingDevicesToSync.length === 0) { @@ -152,26 +149,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return result; } - public async executeLiveSyncOperationWithDebug(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { - const { liveSyncInfo, deviceDescriptors } = await this.executeLiveSyncOperationCore(devices, platform, additionalOptions); - - await this.$debugController.run({ - projectDir: this.$projectData.projectDir, - liveSyncInfo, - deviceDescriptors - }); - - const remainingDevicesToSync = devices.map(d => d.deviceInfo.identifier); - this.$runEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: { projectDir: string, deviceIdentifier: string }) => { - _.remove(remainingDevicesToSync, d => d === data.deviceIdentifier); - - if (remainingDevicesToSync.length === 0) { - process.exit(ErrorCodes.ALL_DEVICES_DISCONNECTED); - } - }); - } - - private async executeLiveSyncOperationCore(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise<{liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]}> { + private async executeLiveSyncOperationCore(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise<{liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]}> { if (!devices || !devices.length) { if (platform) { this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation. Ensure connected devices are trusted and try again."); @@ -192,9 +170,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } } - // Extract this to 2 separate services -> deviceDescriptorsService, liveSyncDataService -> getLiveSyncData() const deviceDescriptors = await this.createDeviceDescriptors(devices, platform, additionalOptions); - const liveSyncInfo = this.createLiveSyncInfo(); + const liveSyncInfo = this.getLiveSyncData(this.$projectData.projectDir); if (this.$options.release) { await this.runInRelease(platform, deviceDescriptors, liveSyncInfo); @@ -204,7 +181,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return { liveSyncInfo, deviceDescriptors }; } - private async runInRelease(platform: string, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncInfo: ILiveSyncInfo): Promise { + private async runInRelease(platform: string, deviceDescriptors: ILiveSyncDeviceDescriptor[], liveSyncInfo: ILiveSyncInfo): Promise { await this.$devicesService.initialize({ platform, deviceId: this.$options.device, @@ -214,7 +191,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { }); await this.$deployController.deploy({ - projectDir: this.$projectData.projectDir, liveSyncInfo: { ...liveSyncInfo, clean: true, skipWatcher: true }, deviceDescriptors }); diff --git a/lib/services/debug-data-service.ts b/lib/services/debug-data-service.ts index 77ae6705f8..910799d986 100644 --- a/lib/services/debug-data-service.ts +++ b/lib/services/debug-data-service.ts @@ -2,14 +2,16 @@ export class DebugDataService implements IDebugDataService { constructor( private $devicesService: Mobile.IDevicesService ) { } - public createDebugData(projectData: IProjectData, options: IDeviceIdentifier): IDebugData { - const device = this.$devicesService.getDeviceByIdentifier(options.device); + + public getDebugData(deviceIdentifier: string, projectData: IProjectData, debugOptions: IDebugOptions): IDebugData { + const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); return { applicationIdentifier: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()], projectDir: projectData.projectDir, - deviceIdentifier: options.device, - projectName: projectData.projectName + deviceIdentifier, + projectName: projectData.projectName, + debugOptions }; } } diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts deleted file mode 100644 index eca19315b1..0000000000 --- a/lib/services/debug-service.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { parse } from "url"; -import { EventEmitter } from "events"; -import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../constants"; -import { CONNECTED_STATUS } from "../common/constants"; -import { DebugTools, TrackActionNames } from "../constants"; -import { performanceLog } from "../common/decorators"; - -export class DebugService extends EventEmitter implements IDebugService { - private _platformDebugServices: IDictionary; - constructor(private $devicesService: Mobile.IDevicesService, - private $errors: IErrors, - private $injector: IInjector, - private $mobileHelper: Mobile.IMobileHelper, - private $analyticsService: IAnalyticsService) { - super(); - this._platformDebugServices = {}; - } - - // TODO: Move this logic to debugController - @performanceLog() - public async debug(debugData: IDebugData, options: IDebugOptions): Promise { - const device = this.$devicesService.getDeviceByIdentifier(debugData.deviceIdentifier); - - if (!device) { - this.$errors.failWithoutHelp(`Cannot find device with identifier ${debugData.deviceIdentifier}.`); - } - - if (device.deviceInfo.status !== CONNECTED_STATUS) { - this.$errors.failWithoutHelp(`The device with identifier ${debugData.deviceIdentifier} is unreachable. Make sure it is Trusted and try again.`); - } - - await this.$analyticsService.trackEventActionInGoogleAnalytics({ - action: TrackActionNames.Debug, - device, - additionalData: this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform) && options && options.inspector ? DebugTools.Inspector : DebugTools.Chrome, - projectDir: debugData.projectDir - }); - - if (!(await device.applicationManager.isApplicationInstalled(debugData.applicationIdentifier))) { - this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); - } - - const debugOptions: IDebugOptions = _.cloneDeep(options); - const debugService = this.getDeviceDebugService(device); - if (!debugService) { - this.$errors.failWithoutHelp(`Unsupported device OS: ${device.deviceInfo.platform}. You can debug your applications only on iOS or Android.`); - } - - const debugResultInfo = await debugService.debug(debugData, debugOptions); - - return this.getDebugInformation(debugResultInfo, device.deviceInfo.identifier); - } - - public debugStop(deviceIdentifier: string): Promise { - const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); - const debugService = this.getDeviceDebugService(device); - return debugService.debugStop(); - } - - protected getDeviceDebugService(device: Mobile.IDevice): IDeviceDebugService { - if (!this._platformDebugServices[device.deviceInfo.identifier]) { - const devicePlatform = device.deviceInfo.platform; - if (this.$mobileHelper.isiOSPlatform(devicePlatform)) { - this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("iOSDeviceDebugService", { device }); - } else if (this.$mobileHelper.isAndroidPlatform(devicePlatform)) { - this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("androidDeviceDebugService", { device }); - } else { - this.$errors.failWithoutHelp(DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); - } - - this.attachConnectionErrorHandlers(this._platformDebugServices[device.deviceInfo.identifier]); - } - - return this._platformDebugServices[device.deviceInfo.identifier]; - } - - private attachConnectionErrorHandlers(platformDebugService: IDeviceDebugService) { - let connectionErrorHandler = (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e); - connectionErrorHandler = connectionErrorHandler.bind(this); - platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); - } - - private getDebugInformation(debugResultInfo: IDebugResultInfo, deviceIdentifier: string): IDebugInformation { - const debugInfo: IDebugInformation = { - url: debugResultInfo.debugUrl, - port: 0, - deviceIdentifier - }; - - if (debugResultInfo.debugUrl) { - const parseQueryString = true; - const wsQueryParam = parse(debugResultInfo.debugUrl, parseQueryString).query.ws; - const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); - debugInfo.port = hostPortSplit && +hostPortSplit[1]; - } - - return debugInfo; - } -} - -$injector.register("debugService", DebugService); diff --git a/lib/services/livesync-process-data-service.ts b/lib/services/livesync-process-data-service.ts new file mode 100644 index 0000000000..6edd76ef88 --- /dev/null +++ b/lib/services/livesync-process-data-service.ts @@ -0,0 +1,34 @@ +export class LiveSyncProcessDataService implements ILiveSyncProcessDataService { + protected processes: IDictionary = {}; + + public persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceDescriptor[], platforms: string[]): void { + this.processes[projectDir] = this.processes[projectDir] || Object.create(null); + this.processes[projectDir].actionsChain = this.processes[projectDir].actionsChain || Promise.resolve(); + this.processes[projectDir].currentSyncAction = this.processes[projectDir].actionsChain; + this.processes[projectDir].isStopped = false; + this.processes[projectDir].platforms = platforms; + + const currentDeviceDescriptors = this.getDeviceDescriptors(projectDir); + this.processes[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), "identifier"); + } + + public getPersistedData(projectDir: string): ILiveSyncProcessData { + return this.processes[projectDir]; + } + + public getDeviceDescriptors(projectDir: string): ILiveSyncDeviceDescriptor[] { + const liveSyncProcessesInfo = this.processes[projectDir] || {}; + const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; + return currentDescriptors || []; + } + + public hasDeviceDescriptors(projectDir: string): boolean { + const deviceDescriptors = this.getDeviceDescriptors(projectDir); + return !!deviceDescriptors.length; + } + + public getAllPersistedData() { + return this.processes; + } +} +$injector.register("liveSyncProcessDataService", LiveSyncProcessDataService); diff --git a/lib/services/livesync/android-device-livesync-service-base.ts b/lib/services/livesync/android-device-livesync-service-base.ts index 2459d625dd..1f3049232d 100644 --- a/lib/services/livesync/android-device-livesync-service-base.ts +++ b/lib/services/livesync/android-device-livesync-service-base.ts @@ -12,7 +12,7 @@ export abstract class AndroidDeviceLiveSyncServiceBase extends DeviceLiveSyncSer public abstract async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise; public abstract async transferDirectoryOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise; - public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise { + public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceDescriptor: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise { const deviceHashService = this.device.fileSystem.getDeviceHashService(deviceAppData.appIdentifier); const currentHashes = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths); const transferredFiles = await this.transferFilesCore(deviceAppData, localToDevicePaths, projectFilesPath, currentHashes, options); diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts index c10c23fd7d..bd250bbbe4 100644 --- a/lib/services/livesync/device-livesync-service-base.ts +++ b/lib/services/livesync/device-livesync-service-base.ts @@ -29,7 +29,7 @@ export abstract class DeviceLiveSyncServiceBase { } @performanceLog() - public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise { + public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceDescriptor: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise { let transferredFiles: Mobile.ILocalToDevicePathData[] = []; if (options.isFullSync) { diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index 89b367616e..70928ea873 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -62,7 +62,7 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I return this.fullSync({ projectData: liveSyncInfo.projectData, device, - liveSyncDeviceInfo: liveSyncInfo.liveSyncDeviceInfo, + liveSyncDeviceData: liveSyncInfo.liveSyncDeviceData, watch: true, useHotModuleReload: liveSyncInfo.useHotModuleReload }); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index e3e3b6e615..577a2f9048 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -62,7 +62,7 @@ export abstract class PlatformLiveSyncServiceBase { const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); - const modifiedFilesData = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, syncInfo.liveSyncDeviceInfo, { isFullSync: true, force: syncInfo.force }); + const modifiedFilesData = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, syncInfo.liveSyncDeviceData, { isFullSync: true, force: syncInfo.force }); return { modifiedFilesData, @@ -101,7 +101,7 @@ export abstract class PlatformLiveSyncServiceBase { const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, existingFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - modifiedLocalToDevicePaths = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncInfo.liveSyncDeviceInfo, { isFullSync: false, force: liveSyncInfo.force }); + modifiedLocalToDevicePaths = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncInfo.liveSyncDeviceData, { isFullSync: false, force: liveSyncInfo.force }); } } @@ -128,11 +128,11 @@ export abstract class PlatformLiveSyncServiceBase { }; } - protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise { + protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceData: ILiveSyncDeviceDescriptor, options: ITransferFilesOptions): Promise { let transferredFiles: Mobile.ILocalToDevicePathData[] = []; const deviceLiveSyncService = this.getDeviceLiveSyncService(deviceAppData.device, projectData); - transferredFiles = await deviceLiveSyncService.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncDeviceInfo, options); + transferredFiles = await deviceLiveSyncService.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, projectData, liveSyncDeviceData, options); this.logFilesSyncInformation(transferredFiles, "Successfully transferred %s on device %s.", this.$logger.info, deviceAppData.device.deviceInfo.identifier); diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts deleted file mode 100644 index 66e0e1ac84..0000000000 --- a/lib/services/livesync/playground/preview-app-livesync-service.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { APP_RESOURCES_FOLDER_NAME } from "../../../constants"; -import { EventEmitter } from "events"; -import { performanceLog } from "../../../common/decorators"; -import { PreviewAppEmitter } from "../../../emitters/preview-app-emitter"; - -export class PreviewAppLiveSyncService extends EventEmitter implements IPreviewAppLiveSyncService { - - constructor( - private $logger: ILogger, - private $previewAppEmitter: PreviewAppEmitter, - private $previewAppFilesService: IPreviewAppFilesService, - private $previewAppPluginsService: IPreviewAppPluginsService, - private $previewDevicesService: IPreviewDevicesService, - private $previewSdkService: IPreviewSdkService, - ) { super(); } - - @performanceLog() - public async syncFiles(data: IPreviewAppLiveSyncData, filesToSync: string[], filesToRemove: string[]): Promise { - this.showWarningsForNativeFiles(filesToSync); - - const connectedDevices = this.$previewDevicesService.getConnectedDevices(); - for (const device of connectedDevices) { - await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); - } - - const platforms = _(connectedDevices) - .map(device => device.platform) - .uniq() - .value(); - - for (const platform of platforms) { - await this.syncFilesForPlatformSafe(data, { filesToSync, filesToRemove }, platform); - } - } - - public async syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): Promise { - try { - const payloads = this.$previewAppFilesService.getFilesPayload(data, filesData, platform); - if (payloads && payloads.files && payloads.files.length) { - this.$logger.info(`Start syncing changes for platform ${platform}.`); - await this.$previewSdkService.applyChanges(payloads); - this.$logger.info(`Successfully synced ${payloads.files.map(filePayload => filePayload.file.yellow)} for platform ${platform}.`); - } - } catch (error) { - this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${error}, ${JSON.stringify(error, null, 2)}.`); - this.$previewAppEmitter.emitPreviewAppLiveSyncError(data, deviceId, error); - } - } - - private showWarningsForNativeFiles(files: string[]): void { - _.filter(files, file => file.indexOf(APP_RESOURCES_FOLDER_NAME) > -1) - .forEach(file => this.$logger.warn(`Unable to apply changes from ${APP_RESOURCES_FOLDER_NAME} folder. You need to build your application in order to make changes in ${APP_RESOURCES_FOLDER_NAME} folder.`)); - } -} -$injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService); diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index c7cf8f52c6..d7bdadd84f 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -11,9 +11,13 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private $prompter: IPrompter, private $staticConfig: IStaticConfig, private $analyticsService: IAnalyticsService, - // private $previewAppLiveSyncService: IPreviewAppLiveSyncService, + private $injector: IInjector, private $previewQrCodeService: IPreviewQrCodeService) { } + public get $previewAppController(): IPreviewAppController { + return this.$injector.resolve("previewAppController"); + } + public static CLOUD_SETUP_OPTION_NAME = "Configure for Cloud Builds"; public static LOCAL_SETUP_OPTION_NAME = "Configure for Local Builds"; public static TRY_CLOUD_OPERATION_OPTION_NAME = "Try Cloud Operation"; @@ -175,12 +179,11 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ this.$errors.failWithoutHelp(`No project found. In order to sync to playground you need to go to project directory or specify --path option.`); } - // await this.$previewAppLiveSyncService.initialize({ - // projectDir, - // env: options.env, - // useHotModuleReload: options.hmr, - // bundle: true - // }); + await this.$previewAppController.startPreview({ + projectDir, + env: options.env, + useHotModuleReload: options.hmr, + }); await this.$previewQrCodeService.printLiveSyncQrCode({ projectDir, useHotModuleReload: options.hmr, link: options.link }); } diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 1a050404a1..9f9ebb4728 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -25,7 +25,7 @@ export class TestExecutionService implements ITestExecutionService { public platform: string; - public async startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + public async startKarmaServer(platform: string, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { platform = platform.toLowerCase(); this.platform = platform; @@ -57,7 +57,6 @@ export class TestExecutionService implements ITestExecutionService { // so it will be sent to device. await this.$runController.run({ - projectDir: liveSyncInfo.projectDir, liveSyncInfo, deviceDescriptors }); diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index c657583381..6a3ad3c342 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -11,8 +11,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp constructor( private $childProcess: IChildProcess, public $hooksService: IHooksService, - private $logger: ILogger, - private $projectData: IProjectData, + private $logger: ILogger ) { super(); } public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { @@ -101,7 +100,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp @performanceLog() @hook('prepareJSApp') private async startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { - const envData = this.buildEnvData(platformData.platformNameLowerCase, config.env); + const envData = this.buildEnvData(platformData.platformNameLowerCase, config.env, projectData); const envParams = this.buildEnvCommandLineParams(envData, platformData); const args = [ @@ -123,14 +122,15 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return childProcess; } - private buildEnvData(platform: string, env: any) { + private buildEnvData(platform: string, env: any, projectData: IProjectData) { const envData = Object.assign({}, env, { [platform.toLowerCase()]: true } ); - const appPath = this.$projectData.getAppDirectoryRelativePath(); - const appResourcesPath = this.$projectData.getAppResourcesRelativeDirectoryPath(); + const appPath = projectData.getAppDirectoryRelativePath(); + const appResourcesPath = projectData.getAppResourcesRelativeDirectoryPath(); + Object.assign(envData, appPath && { appPath }, appResourcesPath && { appResourcesPath } diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index d58c7978ac..34b34c5399 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -35,10 +35,6 @@ declare global { hasNativeChanges: boolean; } - interface IDeviceRestartApplicationService { - restartOnDevice(deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, platformLiveSyncService: IPlatformLiveSyncService): Promise; - } - interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectServiceBase { getPlatformData(projectData: IProjectData): IPlatformData; validate(projectData: IProjectData, options: IOptions, notConfiguredEnvOptions?: INotConfiguredEnvOptions): Promise; diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts deleted file mode 100644 index 0fe938a557..0000000000 --- a/test/services/debug-service.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { DebugService } from "../../lib/services/debug-service"; -import { Yok } from "../../lib/common/yok"; -import * as stubs from "../stubs"; -import { assert } from "chai"; -import { EventEmitter } from "events"; -import * as constants from "../../lib/common/constants"; -import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors, TrackActionNames, DebugTools } from "../../lib/constants"; - -const fakeChromeDebugPort = 123; -const fakeChromeDebugUrl = `fakeChromeDebugUrl?experiments=true&ws=localhost:${fakeChromeDebugPort}`; -const defaultDeviceIdentifier = "Nexus5"; - -class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { - return { debugUrl: fakeChromeDebugUrl }; - } -} - -interface IDebugTestDeviceInfo { - deviceInfo: { - status: string; - platform: string; - identifier: string; - }; - - isEmulator: boolean; -} - -interface IDebugTestData { - isDeviceFound: boolean; - deviceInformation: IDebugTestDeviceInfo; - isApplicationInstalledOnDevice: boolean; - hostInfo: { - isWindows: boolean; - isDarwin: boolean; - }; -} - -const getDefaultDeviceInformation = (platform?: string): IDebugTestDeviceInfo => ({ - deviceInfo: { - status: constants.CONNECTED_STATUS, - platform: platform || "Android", - identifier: defaultDeviceIdentifier - }, - - isEmulator: false -}); - -const getDefaultTestData = (platform?: string): IDebugTestData => ({ - isDeviceFound: true, - deviceInformation: getDefaultDeviceInformation(platform), - isApplicationInstalledOnDevice: true, - hostInfo: { - isWindows: false, - isDarwin: true - } -}); - -describe("debugService", () => { - const getTestInjectorForTestConfiguration = (testData: IDebugTestData): IInjector => { - const testInjector = new Yok(); - testInjector.register("devicesService", { - getDeviceByIdentifier: (identifier: string): Mobile.IDevice => { - return testData.isDeviceFound ? - { - deviceInfo: testData.deviceInformation.deviceInfo, - - applicationManager: { - isApplicationInstalled: async (appIdentifier: string): Promise => testData.isApplicationInstalledOnDevice - }, - - isEmulator: testData.deviceInformation.isEmulator - } : null; - } - }); - - testInjector.register("androidDeviceDebugService", PlatformDebugService); - - testInjector.register("iOSDeviceDebugService", PlatformDebugService); - - testInjector.register("mobileHelper", { - isAndroidPlatform: (platform: string) => { - return platform.toLowerCase() === "android"; - }, - isiOSPlatform: (platform: string) => { - return platform.toLowerCase() === "ios"; - } - }); - - testInjector.register("errors", stubs.ErrorsStub); - - testInjector.register("hostInfo", testData.hostInfo); - - testInjector.register("logger", stubs.LoggerStub); - - testInjector.register("analyticsService", { - trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve() - }); - - return testInjector; - }; - - describe("debug", () => { - const getDebugData = (deviceIdentifier?: string): IDebugData => ({ - deviceIdentifier: deviceIdentifier || defaultDeviceIdentifier, - applicationIdentifier: "org.nativescript.app1", - projectDir: "/Users/user/app1", - projectName: "app1" - }); - - describe("rejects the result promise when", () => { - const assertIsRejected = async (testData: IDebugTestData, expectedError: string, userSpecifiedOptions?: IDebugOptions): Promise => { - const testInjector = getTestInjectorForTestConfiguration(testData); - const debugService = testInjector.resolve(DebugService); - - const debugData = getDebugData(); - await assert.isRejected(debugService.debug(debugData, userSpecifiedOptions), expectedError); - }; - - it("there's no attached device as the specified identifier", async () => { - const testData = getDefaultTestData(); - testData.isDeviceFound = false; - - await assertIsRejected(testData, "Cannot find device with identifier"); - }); - - it("the device is not trusted", async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.status = constants.UNREACHABLE_STATUS; - - await assertIsRejected(testData, "is unreachable. Make sure it is Trusted "); - }); - - it("the application is not installed on device", async () => { - const testData = getDefaultTestData(); - testData.isApplicationInstalledOnDevice = false; - - await assertIsRejected(testData, "is not installed on device with identifier"); - }); - - it("device is neither iOS or Android", async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = "WP8"; - - await assertIsRejected(testData, DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); - }); - - const assertIsRejectedWhenPlatformDebugServiceFails = async (platform: string): Promise => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = platform; - - const testInjector = getTestInjectorForTestConfiguration(testData); - const expectedErrorMessage = "Platform specific error"; - const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); - platformDebugService.debug = async (data: IDebugData, debugOptions: IDebugOptions): Promise => { - throw new Error(expectedErrorMessage); - }; - - const debugService = testInjector.resolve(DebugService); - - const debugData = getDebugData(); - await assert.isRejected(debugService.debug(debugData, null), expectedErrorMessage); - }; - - it("androidDeviceDebugService's debug method fails", async () => { - await assertIsRejectedWhenPlatformDebugServiceFails("android"); - }); - - it("iOSDeviceDebugService's debug method fails", async () => { - await assertIsRejectedWhenPlatformDebugServiceFails("iOS"); - }); - }); - - describe(`raises ${CONNECTION_ERROR_EVENT_NAME} event`, () => { - _.each(["android", "iOS"], platform => { - it(`when ${platform}DebugService raises ${CONNECTION_ERROR_EVENT_NAME} event`, async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = platform; - - const testInjector = getTestInjectorForTestConfiguration(testData); - const debugService = testInjector.resolve(DebugService); - let dataRaisedForConnectionError: any = null; - debugService.on(CONNECTION_ERROR_EVENT_NAME, (data: any) => { - dataRaisedForConnectionError = data; - }); - - const debugData = getDebugData(); - await assert.isFulfilled(debugService.debug(debugData, null)); - - const expectedErrorData = { deviceIdentifier: "deviceId", message: "my message", code: 2048 }; - const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); - platformDebugService.emit(CONNECTION_ERROR_EVENT_NAME, expectedErrorData); - assert.deepEqual(dataRaisedForConnectionError, expectedErrorData); - }); - }); - }); - - describe("returns chrome url along with port returned by platform specific debug service", () => { - _.each(["android", "iOS"], platform => { - it(`for ${platform} device`, async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = platform; - - const testInjector = getTestInjectorForTestConfiguration(testData); - const debugService = testInjector.resolve(DebugService); - - const debugData = getDebugData(); - const debugInfo = await debugService.debug(debugData, null); - - assert.deepEqual(debugInfo, { - url: fakeChromeDebugUrl, - port: fakeChromeDebugPort, - deviceIdentifier: debugData.deviceIdentifier - }); - }); - }); - }); - - describe("tracks to google analytics", () => { - _.each([ - { - testName: "Inspector when --inspector is passed", - debugOptions: { inspector: true }, - additionalData: DebugTools.Inspector - }, - { - testName: "Chrome when no options are passed", - debugOptions: null, - additionalData: DebugTools.Chrome - }, - { - testName: "Chrome when --chrome is passed", - debugOptions: { chrome: true }, - additionalData: DebugTools.Chrome - }], testCase => { - - it(testCase.testName, async () => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = "iOS"; - - const testInjector = getTestInjectorForTestConfiguration(testData); - const analyticsService = testInjector.resolve("analyticsService"); - let dataTrackedToGA: IEventActionData = null; - analyticsService.trackEventActionInGoogleAnalytics = async (data: IEventActionData): Promise => { - dataTrackedToGA = data; - }; - - const debugService = testInjector.resolve(DebugService); - const debugData = getDebugData(); - await debugService.debug(debugData, testCase.debugOptions); - const devicesService = testInjector.resolve("devicesService"); - const device = devicesService.getDeviceByIdentifier(testData.deviceInformation.deviceInfo.identifier); - - const expectedData = JSON.stringify({ - action: TrackActionNames.Debug, - device, - additionalData: testCase.additionalData, - projectDir: debugData.projectDir - }, null, 2); - - // Use JSON.stringify as the compared objects link to new instances of different classes. - assert.deepEqual(JSON.stringify(dataTrackedToGA, null, 2), expectedData); - }); - }); - }); - }); -}); From 5cec974ca79089c4ea3ce649a6b0ee433338d112 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 30 May 2019 16:52:41 +0300 Subject: [PATCH 050/102] test: fix unit tests --- test/controllers/debug-controller.ts | 296 ++++++++++++++++++ test/controllers/run-controller.ts | 23 +- test/nativescript-cli-lib.ts | 7 +- .../preview-app-livesync-service.ts | 13 +- test/stubs.ts | 4 +- 5 files changed, 317 insertions(+), 26 deletions(-) create mode 100644 test/controllers/debug-controller.ts diff --git a/test/controllers/debug-controller.ts b/test/controllers/debug-controller.ts new file mode 100644 index 0000000000..a0b6a64b3f --- /dev/null +++ b/test/controllers/debug-controller.ts @@ -0,0 +1,296 @@ +import { Yok } from "../../lib/common/yok"; +import * as stubs from "../stubs"; +import { assert } from "chai"; +import { EventEmitter } from "events"; +import * as constants from "../../lib/common/constants"; +import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors, TrackActionNames, DebugTools } from "../../lib/constants"; +import { DebugController } from "../../lib/controllers/debug-controller"; +import { BuildDataService } from "../../lib/services/build-data-service"; +import { DebugDataService } from "../../lib/services/debug-data-service"; +import { LiveSyncServiceResolver } from "../../lib/resolvers/livesync-service-resolver"; +import { PrepareDataService } from "../../lib/services/prepare-data-service"; +import { ProjectDataService } from "../../lib/services/project-data-service"; +import { StaticConfig } from "../../lib/config"; +import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants"; +import { LiveSyncProcessDataService } from "../../lib/services/livesync-process-data-service"; + +const fakeChromeDebugPort = 123; +const fakeChromeDebugUrl = `fakeChromeDebugUrl?experiments=true&ws=localhost:${fakeChromeDebugPort}`; +const defaultDeviceIdentifier = "Nexus5"; + +class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { + public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + return { debugUrl: fakeChromeDebugUrl }; + } +} + +interface IDebugTestDeviceInfo { + deviceInfo: { + status: string; + platform: string; + identifier: string; + }; + + isEmulator: boolean; +} + +interface IDebugTestData { + isDeviceFound: boolean; + deviceInformation: IDebugTestDeviceInfo; + isApplicationInstalledOnDevice: boolean; + hostInfo: { + isWindows: boolean; + isDarwin: boolean; + }; +} + +const getDefaultDeviceInformation = (platform?: string): IDebugTestDeviceInfo => ({ + deviceInfo: { + status: constants.CONNECTED_STATUS, + platform: platform || "Android", + identifier: defaultDeviceIdentifier + }, + + isEmulator: false +}); + +const getDefaultTestData = (platform?: string): IDebugTestData => ({ + isDeviceFound: true, + deviceInformation: getDefaultDeviceInformation(platform), + isApplicationInstalledOnDevice: true, + hostInfo: { + isWindows: false, + isDarwin: true + } +}); + +describe("debugController", () => { + const getTestInjectorForTestConfiguration = (testData: IDebugTestData): IInjector => { + const testInjector = new Yok(); + testInjector.register("devicesService", { + getDeviceByIdentifier: (identifier: string): Mobile.IDevice => { + return testData.isDeviceFound ? + { + deviceInfo: testData.deviceInformation.deviceInfo, + + applicationManager: { + isApplicationInstalled: async (appIdentifier: string): Promise => testData.isApplicationInstalledOnDevice + }, + + isEmulator: testData.deviceInformation.isEmulator + } : null; + } + }); + + testInjector.register("androidDeviceDebugService", PlatformDebugService); + + testInjector.register("iOSDeviceDebugService", PlatformDebugService); + + testInjector.register("mobileHelper", { + isAndroidPlatform: (platform: string) => { + return platform.toLowerCase() === "android"; + }, + isiOSPlatform: (platform: string) => { + return platform.toLowerCase() === "ios"; + } + }); + + testInjector.register("errors", stubs.ErrorsStub); + + testInjector.register("hostInfo", testData.hostInfo); + + testInjector.register("logger", stubs.LoggerStub); + + testInjector.register("analyticsService", { + trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve() + }); + + testInjector.register("buildDataService", BuildDataService); + testInjector.register("buildController", {}); + + testInjector.register("debugDataService", DebugDataService); + testInjector.register("deviceInstallAppService", {}); + testInjector.register("hmrStatusService", {}); + testInjector.register("hooksService", {}); + testInjector.register("liveSyncServiceResolver", LiveSyncServiceResolver); + testInjector.register("platformsDataService", {}); + testInjector.register("pluginsService", {}); + testInjector.register("prepareController", {}); + testInjector.register("prepareDataService", PrepareDataService); + testInjector.register("prepareNativePlatformService", {}); + testInjector.register("projectDataService", ProjectDataService); + testInjector.register("fs", {}); + testInjector.register("staticConfig", StaticConfig); + testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); + testInjector.register("androidResourcesMigrationService", {}); + testInjector.register("liveSyncProcessDataService", LiveSyncProcessDataService); + + return testInjector; + }; + + describe("debug", () => { + const getDebugData = (debugOptions?: IDebugOptions): IDebugData => ({ + deviceIdentifier: defaultDeviceIdentifier, + applicationIdentifier: "org.nativescript.app1", + projectDir: "/Users/user/app1", + projectName: "app1", + debugOptions: debugOptions || {} + }); + + describe("rejects the result promise when", () => { + const assertIsRejected = async (testData: IDebugTestData, expectedError: string, userSpecifiedOptions?: IDebugOptions): Promise => { + const testInjector = getTestInjectorForTestConfiguration(testData); + const debugController = testInjector.resolve(DebugController); + + const debugData = getDebugData(); + await assert.isRejected(debugController.startDebug(debugData, userSpecifiedOptions), expectedError); + }; + + it("there's no attached device as the specified identifier", async () => { + const testData = getDefaultTestData(); + testData.isDeviceFound = false; + + await assertIsRejected(testData, "Cannot find device with identifier"); + }); + + it("the device is not trusted", async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.status = constants.UNREACHABLE_STATUS; + + await assertIsRejected(testData, "is unreachable. Make sure it is Trusted "); + }); + + it("the application is not installed on device", async () => { + const testData = getDefaultTestData(); + testData.isApplicationInstalledOnDevice = false; + + await assertIsRejected(testData, "is not installed on device with identifier"); + }); + + it("device is neither iOS or Android", async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = "WP8"; + + await assertIsRejected(testData, DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); + }); + + const assertIsRejectedWhenPlatformDebugServiceFails = async (platform: string): Promise => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const expectedErrorMessage = "Platform specific error"; + const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); + platformDebugService.debug = async (data: IDebugData, debugOptions: IDebugOptions): Promise => { + throw new Error(expectedErrorMessage); + }; + + const debugController = testInjector.resolve(DebugController); + + const debugData = getDebugData(); + await assert.isRejected(debugController.startDebug(debugData, null), expectedErrorMessage); + }; + + it("androidDeviceDebugService's debug method fails", async () => { + await assertIsRejectedWhenPlatformDebugServiceFails("android"); + }); + + it("iOSDeviceDebugService's debug method fails", async () => { + await assertIsRejectedWhenPlatformDebugServiceFails("iOS"); + }); + }); + + describe(`raises ${CONNECTION_ERROR_EVENT_NAME} event`, () => { + _.each(["android", "iOS"], platform => { + it(`when ${platform}DebugService raises ${CONNECTION_ERROR_EVENT_NAME} event`, async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const debugController = testInjector.resolve(DebugController); + let dataRaisedForConnectionError: any = null; + debugController.on(CONNECTION_ERROR_EVENT_NAME, (data: any) => { + dataRaisedForConnectionError = data; + }); + + const debugData = getDebugData(); + await assert.isFulfilled(debugController.startDebug(debugData, null)); + + const expectedErrorData = { deviceIdentifier: "deviceId", message: "my message", code: 2048 }; + const platformDebugService = testInjector.resolve(`${platform}DeviceDebugService`); + platformDebugService.emit(CONNECTION_ERROR_EVENT_NAME, expectedErrorData); + assert.deepEqual(dataRaisedForConnectionError, expectedErrorData); + }); + }); + }); + + describe("returns chrome url along with port returned by platform specific debug service", () => { + _.each(["android", "iOS"], platform => { + it(`for ${platform} device`, async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = platform; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const debugController = testInjector.resolve(DebugController); + + const debugData = getDebugData(); + const debugInfo = await debugController.startDebug(debugData, null); + + assert.deepEqual(debugInfo, { + url: fakeChromeDebugUrl, + port: fakeChromeDebugPort, + deviceIdentifier: debugData.deviceIdentifier + }); + }); + }); + }); + + describe("tracks to google analytics", () => { + _.each([ + { + testName: "Inspector when --inspector is passed", + debugOptions: { inspector: true }, + additionalData: DebugTools.Inspector + }, + { + testName: "Chrome when no options are passed", + debugOptions: null, + additionalData: DebugTools.Chrome + }, + { + testName: "Chrome when --chrome is passed", + debugOptions: { chrome: true }, + additionalData: DebugTools.Chrome + }], testCase => { + + it(testCase.testName, async () => { + const testData = getDefaultTestData(); + testData.deviceInformation.deviceInfo.platform = "iOS"; + + const testInjector = getTestInjectorForTestConfiguration(testData); + const analyticsService = testInjector.resolve("analyticsService"); + let dataTrackedToGA: IEventActionData = null; + analyticsService.trackEventActionInGoogleAnalytics = async (data: IEventActionData): Promise => { + dataTrackedToGA = data; + }; + + const debugController = testInjector.resolve(DebugController); + const debugData = getDebugData(testCase.debugOptions); + await debugController.startDebug(debugData); + const devicesService = testInjector.resolve("devicesService"); + const device = devicesService.getDeviceByIdentifier(testData.deviceInformation.deviceInfo.identifier); + + const expectedData = JSON.stringify({ + action: TrackActionNames.Debug, + device, + additionalData: testCase.additionalData, + projectDir: debugData.projectDir + }, null, 2); + + // Use JSON.stringify as the compared objects link to new instances of different classes. + assert.deepEqual(JSON.stringify(dataTrackedToGA, null, 2), expectedData); + }); + }); + }); + }); +}); diff --git a/test/controllers/run-controller.ts b/test/controllers/run-controller.ts index bfdc76caff..84ff0a9774 100644 --- a/test/controllers/run-controller.ts +++ b/test/controllers/run-controller.ts @@ -3,12 +3,12 @@ import { InjectorStub } from "../stubs"; import { LiveSyncServiceResolver } from "../../lib/resolvers/livesync-service-resolver"; import { MobileHelper } from "../../lib/common/mobile/mobile-helper"; import { assert } from "chai"; -import { RunEmitter } from "../../lib/emitters/run-emitter"; import { RunOnDeviceEvents } from "../../lib/constants"; import { PrepareData } from "../../lib/data/prepare-data"; import { PrepareDataService } from "../../lib/services/prepare-data-service"; import { BuildDataService } from "../../lib/services/build-data-service"; import { PrepareController } from "../../lib/controllers/prepare-controller"; +import { LiveSyncProcessDataService } from "../../lib/services/livesync-process-data-service"; let isAttachToHmrStatusCalled = false; let prepareData: IPrepareData = null; @@ -22,7 +22,7 @@ const iOSDeviceDescriptor = { identifier: "myiOSDevice", buildAction: async () = const androidDevice = { deviceInfo: { identifier: "myAndroidDevice", platform: "android" } }; const androidDeviceDescriptor = { identifier: "myAndroidDevice", buildAction: async () => buildOutputPath }; -const map: IDictionary<{ device: Mobile.IDevice, descriptor: ILiveSyncDeviceInfo }> = { +const map: IDictionary<{ device: Mobile.IDevice, descriptor: ILiveSyncDeviceDescriptor }> = { myiOSDevice: { device: iOSDevice, descriptor: iOSDeviceDescriptor @@ -101,14 +101,15 @@ function createTestInjector() { injector.register("prepareNativePlatformService", {}); injector.register("projectChangesService", {}); injector.register("runController", RunController); - injector.register("runEmitter", RunEmitter); injector.register("prepareDataService", PrepareDataService); injector.register("buildDataService", BuildDataService); injector.register("analyticsService", ({})); + injector.register("debugController", {}); + injector.register("liveSyncProcessDataService", LiveSyncProcessDataService); const devicesService = injector.resolve("devicesService"); devicesService.getDevicesForPlatform = () => [{ identifier: "myTestDeviceId1" }]; - devicesService.getPlatformsFromDeviceDescriptors = (devices: ILiveSyncDeviceInfo[]) => devices.map(d => map[d.identifier].device.deviceInfo.platform); + devicesService.getPlatformsFromDeviceDescriptors = (devices: ILiveSyncDeviceDescriptor[]) => devices.map(d => map[d.identifier].device.deviceInfo.platform); devicesService.on = () => ({}); return injector; @@ -117,7 +118,6 @@ function createTestInjector() { describe("RunController", () => { let injector: IInjector = null; let runController: RunController = null; - let runEmitter: RunEmitter = null; beforeEach(() => { isAttachToHmrStatusCalled = false; @@ -125,7 +125,6 @@ describe("RunController", () => { injector = createTestInjector(); runController = injector.resolve("runController"); - runEmitter = injector.resolve("runEmitter"); }); describe("runOnDevices", () => { @@ -134,7 +133,6 @@ describe("RunController", () => { mockDevicesService(injector, [iOSDevice]); await runController.run({ - projectDir, liveSyncInfo: { ...liveSyncInfo, skipWatcher: true }, deviceDescriptors: [iOSDeviceDescriptor] }); @@ -145,7 +143,6 @@ describe("RunController", () => { mockDevicesService(injector, [iOSDevice]); await runController.run({ - projectDir, liveSyncInfo: { ...liveSyncInfo, skipWatcher: true, useHotModuleReload: true }, deviceDescriptors: [iOSDeviceDescriptor] }); @@ -156,7 +153,6 @@ describe("RunController", () => { mockDevicesService(injector, [iOSDevice]); await runController.run({ - projectDir, liveSyncInfo, deviceDescriptors: [iOSDeviceDescriptor] }); @@ -167,7 +163,6 @@ describe("RunController", () => { mockDevicesService(injector, [iOSDevice]); await runController.run({ - projectDir, liveSyncInfo, deviceDescriptors: [] }); @@ -206,7 +201,6 @@ describe("RunController", () => { }; await runController.run({ - projectDir, liveSyncInfo, deviceDescriptors: testCase.connectedDevices }); @@ -251,16 +245,17 @@ describe("RunController", () => { for (const testCase of testCases) { it(testCase.name, async () => { - (runController).persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier })), ["ios"]); + const liveSyncProcessDataService = injector.resolve("liveSyncProcessDataService"); + (liveSyncProcessDataService).persistData(projectDir, testCase.currentDeviceIdentifiers.map(identifier => ({ identifier })), ["ios"]); const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; - runEmitter.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { + runController.on(RunOnDeviceEvents.runOnDeviceStopped, (data: any) => { assert.equal(data.projectDir, projectDir); emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); }); - await runController.stop(projectDir, testCase.deviceIdentifiersToBeStopped); + await runController.stop({ projectDir, deviceIdentifiers: testCase.deviceIdentifiersToBeStopped }); assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); }); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 1b6cb50e0e..d51dc9cfa7 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -23,13 +23,14 @@ describe("nativescript-cli-lib", () => { "getIOSAssetsStructure", "getAndroidAssetsStructure" ], - // localBuildService: ["build"], + buildController: ["build"], constants: ["CONFIG_NS_APP_RESOURCES_ENTRY", "CONFIG_NS_APP_ENTRY", "CONFIG_NS_FILE_NAME", "LoggerLevel", "LoggerAppenders"], deviceLogProvider: null, packageManager: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], - // liveSyncService: ["liveSync", "stopLiveSync", "enableDebugging", "disableDebugging", "attachDebugger"], - debugService: ["debug"], + runController: ["run", "stop"], + debugController: ["enableDebugging", "disableDebugging", "attachDebugger"], + previewAppController: ["startPreview", "stopPreview"], analyticsSettingsService: ["getClientId"], devicesService: [ "addDeviceDiscovery", diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index fea024d29d..eb63848ea5 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -10,7 +10,6 @@ import { PreviewAppFilesService } from "../../../lib/services/livesync/playgroun import { PREPARE_READY_EVENT_NAME } from "../../../lib/constants"; import { PrepareData } from "../../../lib/data/prepare-data"; import { PreviewAppController } from "../../../lib/controllers/preview-app-controller"; -import { PreviewAppEmitter } from "../../../lib/emitters/preview-app-emitter"; import { PrepareDataService } from "../../../lib/services/prepare-data-service"; import { MobileHelper } from "../../../lib/common/mobile/mobile-helper"; import { DevicePlatformsConstants } from "../../../lib/common/mobile/device-platforms-constants"; @@ -142,10 +141,6 @@ function createTestInjector(options?: { getExternalPlugins: () => [] }); injector.register("projectFilesManager", ProjectFilesManager); - injector.register("previewAppLiveSyncService", { - syncFilesForPlatformSafe: () => ({}) - }); - injector.register("previewAppEmitter", PreviewAppEmitter); injector.register("previewAppController", PreviewAppController); injector.register("prepareController", PrepareControllerMock); injector.register("prepareDataService", PrepareDataService); @@ -178,6 +173,10 @@ function createTestInjector(options?: { getConnectedDevices: () => [deviceMockData] }); injector.register("previewAppFilesService", PreviewAppFilesService); + injector.register("previewQrCodeService", { + getQrCodeUrl: () => ({}), + getLiveSyncQrCode: () => ({}) + }); injector.register("analyticsService", { trackEventActionInGoogleAnalytics: () => ({}) }); @@ -206,7 +205,7 @@ async function initialSync(input?: IActInput) { const { previewAppController, previewSdkService, actOptions } = input; const syncFilesData = _.cloneDeep(syncFilesMockData); syncFilesData.useHotModuleReload = actOptions.hmr; - await previewAppController.preview(syncFilesData); + await previewAppController.startPreview(syncFilesData); if (actOptions.callGetInitialFiles) { await previewSdkService.getInitialFiles(deviceMockData); } @@ -219,7 +218,7 @@ async function syncFiles(input?: IActInput) { const syncFilesData = _.cloneDeep(syncFilesMockData); syncFilesData.useHotModuleReload = actOptions.hmr; - await previewAppController.preview(syncFilesData); + await previewAppController.startPreview(syncFilesData); if (actOptions.callGetInitialFiles) { await previewSdkService.getInitialFiles(deviceMockData); } diff --git a/test/stubs.ts b/test/stubs.ts index a1ba362197..29bdf55dc5 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -667,7 +667,7 @@ export class LiveSyncServiceStub extends EventEmitter implements ILiveSyncServic return; } - public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { + public async liveSync(deviceDescriptors: ILiveSyncDeviceDescriptor[], liveSyncData: ILiveSyncInfo): Promise { return; } @@ -675,7 +675,7 @@ export class LiveSyncServiceStub extends EventEmitter implements ILiveSyncServic return; } - public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceDescriptor[] { return []; } } From 3692d832fbeec4a20c655d6583dde3760795b1a7 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 31 May 2019 08:59:30 +0300 Subject: [PATCH 051/102] fix: respect App_Resources directly from project Currently App_Resources are copied to platforms folder and after that NativeScript CLI moves them to the correct places in native project and deletes the App_Resources folder in platforms. On the other side, nativescript-dev-webpack plugin copies the App_Resources to platforms folder only on initial start (not when some file is changed). This led to the problem that when native file is changed, NativeScript CLI moves App_Resources to the correct places in native project and deletes App_Resources folder inside platforms (the one that nativescript-dev-webpack plugin has previously copied on initial webpack's compilation). On the next native file change, NativeScript CLI tries to move the platforms/App_Resources to the correct places inside native project, but this directory doesn't exist. So `cp unable to find source directory` error is thrown. Fixes: https://github.com/NativeScript/nativescript-cli/issues/4377 --- lib/services/android-project-service.ts | 20 +++++++- lib/services/ios-project-service.ts | 25 +++++----- .../prepare-native-platform-service.ts | 47 +------------------ lib/services/webpack/webpack.d.ts | 2 +- test/stubs.ts | 2 +- 5 files changed, 36 insertions(+), 60 deletions(-) diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 56604202c9..bfecc6f763 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -280,8 +280,24 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void { - this.cleanUpPreparedResources(appResourcesDirectoryPath, projectData); + public prepareAppResources(projectData: IProjectData): void { + const platformData = this.getPlatformData(projectData); + const projectAppResourcesPath = projectData.getAppResourcesDirectoryPath(projectData.projectDir); + const platformsAppResourcesPath = this.getAppResourcesDestinationDirectoryPath(projectData); + + this.cleanUpPreparedResources(projectAppResourcesPath, projectData); + + this.$fs.ensureDirectoryExists(platformsAppResourcesPath); + + this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "*"), platformsAppResourcesPath); + const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(projectAppResourcesPath); + if (appResourcesDirStructureHasMigrated) { + this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "src", "*"), platformsAppResourcesPath); + } else { + // https://github.com/NativeScript/android-runtime/issues/899 + // App_Resources/Android/libs is reserved to user's aars and jars, but they should not be copied as resources + this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, "libs")); + } } public async preparePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index dd4d1ff697..149fec0188 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -335,20 +335,23 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } } - public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void { - const platformFolder = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName); - const filterFile = (filename: string) => this.$fs.deleteFile(path.join(platformFolder, filename)); + public prepareAppResources(projectData: IProjectData): void { + const platformData = this.getPlatformData(projectData); + const projectAppResourcesPath = projectData.getAppResourcesDirectoryPath(projectData.projectDir); + const platformsAppResourcesPath = this.getAppResourcesDestinationDirectoryPath(projectData); + + this.$fs.deleteDirectory(platformsAppResourcesPath); + this.$fs.ensureDirectoryExists(platformsAppResourcesPath); - filterFile(this.getPlatformData(projectData).configurationFileName); - filterFile(constants.PODFILE_NAME); + this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "*"), platformsAppResourcesPath); - // src folder should not be copied as the pbxproject will have references to its files - this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_SOURCE_FOLDER)); - this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_EXTENSION_FOLDER)); - this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "watchapp")); - this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "watchextension")); + this.$fs.deleteFile(path.join(platformsAppResourcesPath, platformData.configurationFileName)); + this.$fs.deleteFile(path.join(platformsAppResourcesPath, constants.PODFILE_NAME)); - this.$fs.deleteDirectory(this.getAppResourcesDestinationDirectoryPath(projectData)); + this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, constants.NATIVE_SOURCE_FOLDER)); + this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, constants.NATIVE_EXTENSION_FOLDER)); + this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, "watchapp")); + this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, "watchextension")); } public async processConfigurationFilesFromAppResources(projectData: IProjectData, opts: IRelease): Promise { diff --git a/lib/services/platform/prepare-native-platform-service.ts b/lib/services/platform/prepare-native-platform-service.ts index b017932d70..2e5ed21b88 100644 --- a/lib/services/platform/prepare-native-platform-service.ts +++ b/lib/services/platform/prepare-native-platform-service.ts @@ -1,14 +1,11 @@ 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 { NativePlatformStatus } from "../../constants"; export class PrepareNativePlatformService implements IPrepareNativePlatformService { constructor( - private $androidResourcesMigrationService: IAndroidResourcesMigrationService, - private $fs: IFileSystem, public $hooksService: IHooksService, private $nodeModulesBuilder: INodeModulesBuilder, private $projectChangesService: IProjectChangesService, @@ -34,10 +31,7 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi 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(platformData, projectData); + platformData.platformProjectService.prepareAppResources(projectData); if (hasChangesRequirePrepare) { await platformData.platformProjectService.prepareProject(projectData, prepareData); @@ -58,43 +52,6 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi return hasChanges; } - private prepareAppResources(platformData: IPlatformData, projectData: IProjectData): void { - 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); - const appResourcesDestination = platformData.platformProjectService.getAppResourcesDestinationDirectoryPath(projectData); - this.$fs.ensureDirectoryExists(appResourcesDestination); - - if (platformData.normalizedPlatformName.toLowerCase() === "android") { - const appResourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); - const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(appResourcesDirectoryPath); - const appResourcesAndroid = path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName); - - if (appResourcesDirStructureHasMigrated) { - this.$fs.copyFile(path.join(appResourcesAndroid, "src", "*"), appResourcesDestination); - - this.$fs.deleteDirectory(appResourcesDestinationDirectoryPath); - return; - } - - // https://github.com/NativeScript/android-runtime/issues/899 - // App_Resources/Android/libs is reserved to user's aars and jars, but they should not be copied as resources - this.$fs.copyFile(path.join(appResourcesDestinationDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination); - this.$fs.deleteDirectory(path.join(appResourcesDestination, "libs")); - - this.$fs.deleteDirectory(appResourcesDestinationDirectoryPath); - - return; - } - - this.$fs.copyFile(path.join(appResourcesDestinationDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination); - - this.$fs.deleteDirectory(appResourcesDestinationDirectoryPath); - } - } - 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 diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index d58c7978ac..86617fee0f 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -78,7 +78,7 @@ declare global { * @param {IProjectData} projectData DTO with information about the project. * @returns {void} */ - prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void; + prepareAppResources(projectData: IProjectData): void; /** * Defines if current platform is prepared (i.e. if /platforms/ dir exists). diff --git a/test/stubs.ts b/test/stubs.ts index a1ba362197..74fe81c052 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -425,7 +425,7 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor async updatePlatform(currentVersion: string, newVersion: string, canUpdate: boolean): Promise { return Promise.resolve(true); } - prepareAppResources(appResourcesDirectoryPath: string): void { } + prepareAppResources(projectData: IProjectData): void { } async preparePluginNativeCode(pluginData: IPluginData): Promise { return Promise.resolve(); From cee74870e613a6e9624f4e487282737547db5b7c Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 31 May 2019 10:13:32 +0300 Subject: [PATCH 052/102] fix: pass --env.verbose to webpack compiler --- lib/common/definitions/logger.d.ts | 1 + lib/common/logger/logger.ts | 4 ++++ lib/common/test/unit-tests/stubs.ts | 1 + lib/services/webpack/webpack-compiler-service.ts | 4 +++- test/stubs.ts | 2 ++ 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/common/definitions/logger.d.ts b/lib/common/definitions/logger.d.ts index 6077c31d7d..5d51828965 100644 --- a/lib/common/definitions/logger.d.ts +++ b/lib/common/definitions/logger.d.ts @@ -25,6 +25,7 @@ declare global { trace(formatStr?: any, ...args: any[]): void; printMarkdown(...args: any[]): void; prepare(item: any): string; + isVerbose(): boolean; } interface Log4JSAppenderConfiguration extends Configuration { diff --git a/lib/common/logger/logger.ts b/lib/common/logger/logger.ts index cd105e4969..1dc980725e 100644 --- a/lib/common/logger/logger.ts +++ b/lib/common/logger/logger.ts @@ -135,6 +135,10 @@ export class Logger implements ILogger { this.info(formattedMessage, { [LoggerConfigData.skipNewLine]: true }); } + public isVerbose(): boolean { + return log4js.levels.DEBUG.isGreaterThanOrEqualTo(this.getLevel()); + } + private logMessage(inputData: any[], logMethod: string): void { this.initialize(); diff --git a/lib/common/test/unit-tests/stubs.ts b/lib/common/test/unit-tests/stubs.ts index 075dcb4806..199980b088 100644 --- a/lib/common/test/unit-tests/stubs.ts +++ b/lib/common/test/unit-tests/stubs.ts @@ -51,6 +51,7 @@ export class CommonLoggerStub implements ILogger { printInfoMessageOnSameLine(message: string): void { } async printMsgWithTimeout(message: string, timeout: number): Promise { } printOnStderr(formatStr?: any, ...args: any[]): void { } + isVerbose(): boolean { return false; } } export class ErrorsStub implements IErrors { diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index c657583381..3bd67c10e5 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -133,9 +133,11 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const appResourcesPath = this.$projectData.getAppResourcesRelativeDirectoryPath(); Object.assign(envData, appPath && { appPath }, - appResourcesPath && { appResourcesPath } + appResourcesPath && { appResourcesPath }, ); + envData.verbose = this.$logger.isVerbose(); + return envData; } diff --git a/test/stubs.ts b/test/stubs.ts index a1ba362197..47c9013371 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -41,6 +41,8 @@ export class LoggerStub implements ILogger { printInfoMessageOnSameLine(message: string): void { } async printMsgWithTimeout(message: string, timeout: number): Promise { } printOnStderr(formatStr?: any, ...args: any[]): void { } + + isVerbose(): boolean { return false; } } export class FileSystemStub implements IFileSystem { From 897b2a43f04de625bce1f1ae7886658963b1e851 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 31 May 2019 13:11:59 +0300 Subject: [PATCH 053/102] fix: prepare the project only once per run The project is prepared every time on initial sync, but buildAction prepares again and builds the project (buildAction is executed only the project should be built). This led to the problem that project is prepares twice when buildAction is executed. --- lib/helpers/livesync-command-helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 2413dd749c..99ba9eacc4 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -83,7 +83,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : - this.$buildController.prepareAndBuild.bind(this.$buildController, buildData); + this.$buildController.build.bind(this.$buildController, buildData); const outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ platform: d.deviceInfo.platform, From 707992a55f2e762d2a23d0b71968da904723f539 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 31 May 2019 14:35:41 +0300 Subject: [PATCH 054/102] fix: fix "InstallationLookupFailed" error when deploying on iOS device "InstallationLookupFailed" error is thrown when trying to deploy on iOS device. The reason is that NativeScript CLI is not able to find applicationIdentifier from projectData.projectIdentifiers as the platform is not lower case. For iOS simulator it is fine as the applicationIdentifier is not used when uploading files to the simulator. --- lib/controllers/run-controller.ts | 2 +- lib/services/build-info-file-service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index ae88eea96d..064cb2f592 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -186,7 +186,7 @@ export class RunController implements IRunController { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); 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 buildData = this.$buildDataService.getBuildData(projectData.projectDir, device.deviceInfo.platform, { ...liveSyncInfo, outputPath: deviceDescriptor.outputPath, buildForDevice: !device.isEmulator }); const prepareResultData = await this.$prepareController.prepare(prepareData); try { diff --git a/lib/services/build-info-file-service.ts b/lib/services/build-info-file-service.ts index 707caaaffe..abb8e5fd34 100644 --- a/lib/services/build-info-file-service.ts +++ b/lib/services/build-info-file-service.ts @@ -50,7 +50,7 @@ export class BuildInfoFileService implements IBuildInfoFileService { public async saveDeviceBuildInfo(device: Mobile.IDevice, projectData: IProjectData, outputFilePath: string): Promise { const deviceFilePath = await this.getDeviceBuildInfoFilePath(device, projectData); - const appIdentifier = projectData.projectIdentifiers[device.deviceInfo.platform]; + const appIdentifier = projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()]; await device.fileSystem.putFile(path.join(outputFilePath, buildInfoFileName), deviceFilePath, appIdentifier); } From 045df5103d576b6b18885188e4c546e964f285d0 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 31 May 2019 11:17:09 +0300 Subject: [PATCH 055/102] feat: set webpack in production mode based on `--release` option Implements: https://github.com/NativeScript/nativescript-dev-webpack/issues/911 --- lib/controllers/prepare-controller.ts | 8 +++---- .../webpack/webpack-compiler-service.ts | 22 ++++++++++--------- lib/services/webpack/webpack.d.ts | 10 +++------ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 095ac6c622..79b533bf81 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -41,7 +41,7 @@ export class PrepareController extends EventEmitter { if (prepareData.watch) { result = await this.startWatchersWithPrepare(platformData, projectData, prepareData); } else { - await this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, { watch: false, env: prepareData.env }); + await this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, prepareData); await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); } @@ -79,7 +79,7 @@ export class PrepareController extends EventEmitter { }; } - await this.startJSWatcherWithPrepare(platformData, projectData, { env: prepareData.env }); // -> start watcher + initial compilation + await this.startJSWatcherWithPrepare(platformData, projectData, prepareData); // -> start watcher + initial compilation const hasNativeChanges = await this.startNativeWatcherWithPrepare(platformData, projectData, prepareData); // -> start watcher + initial prepare const result = { platform: platformData.platformNameLowerCase, hasNativeChanges }; @@ -97,13 +97,13 @@ export class PrepareController extends EventEmitter { return result; } - private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): 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); + const childProcess = await this.$webpackCompilerService.compileWithWatch(platformData, projectData, prepareData); this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess = childProcess; } } diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index c549ebc0bc..6f42ba8f7d 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -14,7 +14,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp private $logger: ILogger ) { super(); } - public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { return new Promise(async (resolve, reject) => { if (this.webpackProcesses[platformData.platformNameLowerCase]) { resolve(); @@ -22,8 +22,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp } let isFirstWebpackWatchCompilation = true; - config.watch = true; - const childProcess = await this.startWebpackProcess(platformData, projectData, config); + prepareData.watch = true; + const childProcess = await this.startWebpackProcess(platformData, projectData, prepareData); childProcess.on("message", (message: any) => { if (message === "Webpack compilation complete.") { @@ -68,14 +68,14 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp }); } - public async compileWithoutWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { + public async compileWithoutWatch(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { return new Promise(async (resolve, reject) => { if (this.webpackProcesses[platformData.platformNameLowerCase]) { resolve(); return; } - const childProcess = await this.startWebpackProcess(platformData, projectData, config); + const childProcess = await this.startWebpackProcess(platformData, projectData, prepareData); childProcess.on("close", (arg: any) => { const exitCode = typeof arg === "number" ? arg : arg && arg.code; if (exitCode === 0) { @@ -99,8 +99,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp @performanceLog() @hook('prepareJSApp') - private async startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise { - const envData = this.buildEnvData(platformData.platformNameLowerCase, config.env, projectData); + private async startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { + const envData = this.buildEnvData(platformData.platformNameLowerCase, projectData, prepareData); const envParams = this.buildEnvCommandLineParams(envData, platformData); const args = [ @@ -110,11 +110,11 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp ...envParams ]; - if (config.watch) { + if (prepareData.watch) { args.push("--watch"); } - const stdio = config.watch ? ["inherit", "inherit", "inherit", "ipc"] : "inherit"; + const stdio = prepareData.watch ? ["inherit", "inherit", "inherit", "ipc"] : "inherit"; const childProcess = this.$childProcess.spawn("node", args, { cwd: projectData.projectDir, stdio }); this.webpackProcesses[platformData.platformNameLowerCase] = childProcess; @@ -122,7 +122,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return childProcess; } - private buildEnvData(platform: string, env: any, projectData: IProjectData) { + private buildEnvData(platform: string, projectData: IProjectData, prepareData: IPrepareData) { + const { env } = prepareData; const envData = Object.assign({}, env, { [platform.toLowerCase()]: true } @@ -137,6 +138,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp ); envData.verbose = this.$logger.isVerbose(); + envData.production = prepareData.release; return envData; } diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index 0777d63f74..b0e5e56388 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -4,19 +4,15 @@ import { PrepareData } from "../../data/prepare-data"; declare global { interface IWebpackCompilerService extends EventEmitter { - compileWithWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; - compileWithoutWatch(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise; + compileWithWatch(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise; + compileWithoutWatch(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise; stopWebpackCompiler(platform: string): void; } - interface IWebpackCompilerConfig { - env: IWebpackEnvOptions; - watch?: boolean; - } - interface IWebpackEnvOptions { sourceMap?: boolean; uglify?: boolean; + production?: boolean; } interface IProjectChangesService { From 1937b7acfd7795ded06fbe82523532d4d1f26db6 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 31 May 2019 15:27:43 +0300 Subject: [PATCH 056/102] fix: fix the application crash on device on deploy command Rel to: https://github.com/NativeScript/nativescript-cli/issues/4637 --- lib/commands/build.ts | 1 + lib/commands/deploy.ts | 4 ++++ lib/commands/prepare.ts | 1 + lib/controllers/deploy-controller.ts | 3 ++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 8ab88e7672..6acdb2c8f0 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -18,6 +18,7 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { public dashedOptions = { watch: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, + hmr: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, }; public async executeCore(args: string[]): Promise { diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index 739ba5384d..62a35ee7b4 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -5,6 +5,10 @@ import { DeployCommandHelper } from "../helpers/deploy-command-helper"; export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; + public dashedOptions = { + hmr: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, + }; + constructor($platformValidationService: IPlatformValidationService, private $platformCommandParameter: ICommandParameter, $options: IOptions, diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index d047cc1d3a..71f6e6fdd3 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -7,6 +7,7 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm public dashedOptions = { watch: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, + hmr: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, }; constructor($options: IOptions, diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index 080d0ad142..78d3aaeff8 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -11,7 +11,8 @@ export class DeployController { const { liveSyncInfo, deviceDescriptors } = data; const executeAction = async (device: Mobile.IDevice) => { - const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, device.deviceInfo.platform, liveSyncInfo); + const options = { ...liveSyncInfo, buildForDevice: !device.isEmulator }; + const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, device.deviceInfo.platform, options); await this.$buildController.prepareAndBuild(buildData); await this.$deviceInstallAppService.installOnDevice(device, buildData); }; From 72d5f6d25c3817e649cf39f60ac5e3f67e5f65bc Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 3 Jun 2019 17:49:25 +0300 Subject: [PATCH 057/102] fix: fix cloud run command --- lib/controllers/run-controller.ts | 3 +-- lib/helpers/livesync-command-helper.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index b010543d1c..ddcba150c1 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -299,12 +299,11 @@ export class RunController extends EventEmitter implements IRunController { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); 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 }); try { if (data.hasNativeChanges) { await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); - await this.$buildController.build(buildData); + await deviceDescriptor.buildAction(); } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index b9a69ffe5b..38500958d9 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -77,18 +77,18 @@ 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.build.bind(this.$buildController, buildData); - const outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ platform: d.deviceInfo.platform, emulator: d.isEmulator, projectDir: this.$projectData.projectDir }); + const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, { ...buildConfig, outputPath }); + + const buildAction = additionalOptions && additionalOptions.buildPlatform ? + additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : + this.$buildController.build.bind(this.$buildController, buildData); + const info: ILiveSyncDeviceDescriptor = { identifier: d.deviceInfo.identifier, buildAction, From 8ebfe6ca44d9903a9fe1ee3af988c176b80d81cc Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 5 Jun 2019 08:28:56 +0300 Subject: [PATCH 058/102] refactor: remove IAndroidBuildConfig and use IAndroidBuildData instead --- lib/controllers/build-controller.ts | 2 +- lib/definitions/gradle.d.ts | 12 +++----- lib/services/android-project-service.ts | 14 +++++----- .../android/gradle-build-args-service.ts | 28 +++++++++---------- lib/services/android/gradle-build-service.ts | 10 +++---- .../prepare-native-platform-service.ts | 6 ++-- lib/services/webpack/webpack.d.ts | 3 +- .../android/gradle-build-args-service.ts | 6 ++-- test/stubs.ts | 2 +- 9 files changed, 39 insertions(+), 44 deletions(-) diff --git a/lib/controllers/build-controller.ts b/lib/controllers/build-controller.ts index 1818f6e564..a5e0bbf2f2 100644 --- a/lib/controllers/build-controller.ts +++ b/lib/controllers/build-controller.ts @@ -47,7 +47,7 @@ export class BuildController extends EventEmitter implements IBuildController { }); if (buildData.clean) { - await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); + await platformData.platformProjectService.cleanProject(platformData.projectRoot); } const handler = (data: any) => { diff --git a/lib/definitions/gradle.d.ts b/lib/definitions/gradle.d.ts index aa6bdf6ac8..5e40bb1ed6 100644 --- a/lib/definitions/gradle.d.ts +++ b/lib/definitions/gradle.d.ts @@ -9,16 +9,12 @@ interface IGradleCommandOptions { spawnOptions?: ISpawnFromEventOptions; } -interface IAndroidBuildConfig extends IRelease, IAndroidReleaseOptions, IHasAndroidBundle { - buildOutputStdio?: string; -} - interface IGradleBuildService { - buildProject(projectRoot: string, buildConfig: IAndroidBuildConfig): Promise; - cleanProject(projectRoot: string, buildConfig: IAndroidBuildConfig): Promise; + buildProject(projectRoot: string, buildData: IAndroidBuildData): Promise; + cleanProject(projectRoot: string, buildData: IAndroidBuildData): Promise; } interface IGradleBuildArgsService { - getBuildTaskArgs(buildConfig: IAndroidBuildConfig): string[]; - getCleanTaskArgs(buildConfig: IAndroidBuildConfig): string[]; + getBuildTaskArgs(buildData: IAndroidBuildData): string[]; + getCleanTaskArgs(buildData: IAndroidBuildData): string[]; } diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index bfecc6f763..cea0b9ec23 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -233,16 +233,16 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } @performanceLog() - public async buildProject(projectRoot: string, projectData: IProjectData, buildConfig: IBuildConfig): Promise { + public async buildProject(projectRoot: string, projectData: IProjectData, buildData: IAndroidBuildData): Promise { const platformData = this.getPlatformData(projectData); - await this.$gradleBuildService.buildProject(platformData.projectRoot, buildConfig); + await this.$gradleBuildService.buildProject(platformData.projectRoot, buildData); - const outputPath = platformData.getBuildOutputPath(buildConfig); + const outputPath = platformData.getBuildOutputPath(buildData); await this.$filesHashService.saveHashesForProject(this._platformData, outputPath); } - public async buildForDeploy(projectRoot: string, projectData: IProjectData, buildConfig?: IBuildConfig): Promise { - return this.buildProject(projectRoot, projectData, buildConfig); + public async buildForDeploy(projectRoot: string, projectData: IProjectData, buildData?: IAndroidBuildData): Promise { + return this.buildProject(projectRoot, projectData, buildData); } public isPlatformPrepared(projectRoot: string, projectData: IProjectData): boolean { @@ -376,8 +376,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return result; } - public async cleanProject(projectRoot: string, projectData: IProjectData): Promise { - await this.$gradleBuildService.cleanProject(projectRoot, { release: false }); + public async cleanProject(projectRoot: string): Promise { + await this.$gradleBuildService.cleanProject(projectRoot, { release: false }); } public async cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise { diff --git a/lib/services/android/gradle-build-args-service.ts b/lib/services/android/gradle-build-args-service.ts index e00f71d784..d2d50a8468 100644 --- a/lib/services/android/gradle-build-args-service.ts +++ b/lib/services/android/gradle-build-args-service.ts @@ -5,21 +5,21 @@ export class GradleBuildArgsService implements IGradleBuildArgsService { constructor(private $androidToolsInfo: IAndroidToolsInfo, private $logger: ILogger) { } - public getBuildTaskArgs(buildConfig: IAndroidBuildConfig): string[] { - const args = this.getBaseTaskArgs(buildConfig); - args.unshift(this.getBuildTaskName(buildConfig)); + public getBuildTaskArgs(buildData: IAndroidBuildData): string[] { + const args = this.getBaseTaskArgs(buildData); + args.unshift(this.getBuildTaskName(buildData)); return args; } - public getCleanTaskArgs(buildConfig: IAndroidBuildConfig): string[] { - const args = this.getBaseTaskArgs(buildConfig); + public getCleanTaskArgs(buildData: IAndroidBuildData): string[] { + const args = this.getBaseTaskArgs(buildData); args.unshift("clean"); return args; } - private getBaseTaskArgs(buildConfig: IAndroidBuildConfig): string[] { + private getBaseTaskArgs(buildData: IAndroidBuildData): string[] { const args = this.getBuildLoggingArgs(); const toolsInfo = this.$androidToolsInfo.getToolsInfo(); @@ -30,13 +30,13 @@ export class GradleBuildArgsService implements IGradleBuildArgsService { `-PgenerateTypings=${toolsInfo.generateTypings}` ); - if (buildConfig.release) { + if (buildData.release) { args.push( "-Prelease", - `-PksPath=${path.resolve(buildConfig.keyStorePath)}`, - `-Palias=${buildConfig.keyStoreAlias}`, - `-Ppassword=${buildConfig.keyStoreAliasPassword}`, - `-PksPassword=${buildConfig.keyStorePassword}` + `-PksPath=${path.resolve(buildData.keyStorePath)}`, + `-Palias=${buildData.keyStoreAlias}`, + `-Ppassword=${buildData.keyStoreAliasPassword}`, + `-PksPassword=${buildData.keyStorePassword}` ); } @@ -56,9 +56,9 @@ export class GradleBuildArgsService implements IGradleBuildArgsService { return args; } - private getBuildTaskName(buildConfig: IAndroidBuildConfig): string { - const baseTaskName = buildConfig.androidBundle ? "bundle" : "assemble"; - const buildTaskName = buildConfig.release ? `${baseTaskName}${Configurations.Release}` : `${baseTaskName}${Configurations.Debug}`; + private getBuildTaskName(buildData: IAndroidBuildData): string { + const baseTaskName = buildData.androidBundle ? "bundle" : "assemble"; + const buildTaskName = buildData.release ? `${baseTaskName}${Configurations.Release}` : `${baseTaskName}${Configurations.Debug}`; return buildTaskName; } diff --git a/lib/services/android/gradle-build-service.ts b/lib/services/android/gradle-build-service.ts index 499d5bd9ab..1f34367b5c 100644 --- a/lib/services/android/gradle-build-service.ts +++ b/lib/services/android/gradle-build-service.ts @@ -9,10 +9,10 @@ export class GradleBuildService extends EventEmitter implements IGradleBuildServ private $gradleCommandService: IGradleCommandService, ) { super(); } - public async buildProject(projectRoot: string, buildConfig: IAndroidBuildConfig): Promise { - const buildTaskArgs = this.$gradleBuildArgsService.getBuildTaskArgs(buildConfig); + public async buildProject(projectRoot: string, buildData: IAndroidBuildData): Promise { + const buildTaskArgs = this.$gradleBuildArgsService.getBuildTaskArgs(buildData); const spawnOptions = { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }; - const gradleCommandOptions = { cwd: projectRoot, message: "Gradle build...", stdio: buildConfig.buildOutputStdio, spawnOptions }; + const gradleCommandOptions = { cwd: projectRoot, message: "Gradle build...", stdio: buildData.buildOutputStdio, spawnOptions }; await attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, @@ -21,8 +21,8 @@ export class GradleBuildService extends EventEmitter implements IGradleBuildServ ); } - public async cleanProject(projectRoot: string, buildConfig: IAndroidBuildConfig): Promise { - const cleanTaskArgs = this.$gradleBuildArgsService.getCleanTaskArgs(buildConfig); + public async cleanProject(projectRoot: string, buildData: IAndroidBuildData): Promise { + const cleanTaskArgs = this.$gradleBuildArgsService.getCleanTaskArgs(buildData); const gradleCommandOptions = { cwd: projectRoot, message: "Gradle clean..." }; await this.$gradleCommandService.executeCommand(cleanTaskArgs, gradleCommandOptions); } diff --git a/lib/services/platform/prepare-native-platform-service.ts b/lib/services/platform/prepare-native-platform-service.ts index 2e5ed21b88..253f3d189b 100644 --- a/lib/services/platform/prepare-native-platform-service.ts +++ b/lib/services/platform/prepare-native-platform-service.ts @@ -28,7 +28,7 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi const hasChanges = hasModulesChange || hasConfigChange || hasChangesRequirePrepare; if (changesInfo.hasChanges) { - await this.cleanProject(platformData, projectData, { release }); + await this.cleanProject(platformData, { release }); } platformData.platformProjectService.prepareAppResources(projectData); @@ -52,7 +52,7 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi return hasChanges; } - private async cleanProject(platformData: IPlatformData, projectData: IProjectData, options: { release: boolean }): Promise { + private async cleanProject(platformData: IPlatformData, options: { release: boolean }): Promise { // android build artifacts need to be cleaned up // when switching between debug, release and webpack builds if (platformData.platformNameLowerCase !== "android") { @@ -67,7 +67,7 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi const { release: previousWasRelease } = previousPrepareInfo; const { release: currentIsRelease } = options; if (previousWasRelease !== currentIsRelease) { - await platformData.platformProjectService.cleanProject(platformData.projectRoot, projectData); + await platformData.platformProjectService.cleanProject(platformData.projectRoot); } } } diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index b0e5e56388..e6732c79b3 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -129,10 +129,9 @@ declare global { /** * 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 + cleanProject?(projectRoot: string): Promise /** * Check the current state of the project, and validate against the options. diff --git a/test/services/android/gradle-build-args-service.ts b/test/services/android/gradle-build-args-service.ts index 0a93c8ebdf..7c23bdb2cd 100644 --- a/test/services/android/gradle-build-args-service.ts +++ b/test/services/android/gradle-build-args-service.ts @@ -18,7 +18,7 @@ function createTestInjector(): IInjector { return injector; } -function executeTests(testCases: any[], testFunction: (gradleBuildArgsService: IGradleBuildArgsService, buildConfig: IAndroidBuildConfig) => string[]) { +function executeTests(testCases: any[], testFunction: (gradleBuildArgsService: IGradleBuildArgsService, buildData: IAndroidBuildData) => string[]) { _.each(testCases, testCase => { it(testCase.name, () => { const injector = createTestInjector(); @@ -102,7 +102,7 @@ describe("GradleBuildArgsService", () => { } ]; - executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildConfig: IAndroidBuildConfig) => gradleBuildArgsService.getBuildTaskArgs(buildConfig)); + executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildData: IAndroidBuildData) => gradleBuildArgsService.getBuildTaskArgs(buildData)); }); describe("getCleanTaskArgs", () => { @@ -157,6 +157,6 @@ describe("GradleBuildArgsService", () => { } ]; - executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildConfig: IAndroidBuildConfig) => gradleBuildArgsService.getCleanTaskArgs(buildConfig)); + executeTests(testCases, (gradleBuildArgsService: IGradleBuildArgsService, buildData: IAndroidBuildData) => gradleBuildArgsService.getCleanTaskArgs(buildData)); }); }); diff --git a/test/stubs.ts b/test/stubs.ts index 1d24713d9a..05964e8250 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -453,7 +453,7 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor async stopServices(): Promise { return Promise.resolve({ stderr: "", stdout: "", exitCode: 0 }); } - async cleanProject(projectRoot: string, projectData: IProjectData): Promise { + async cleanProject(projectRoot: string): Promise { return Promise.resolve(); } async checkForChanges(changesInfo: IProjectChangesInfo, options: any, projectData: IProjectData): Promise { From dcd50ae842310e7a81fd5397207ebaa46ced0891 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 5 Jun 2019 08:30:51 +0300 Subject: [PATCH 059/102] fix: pass correct keyStore* args when building in release mode --- lib/controllers/deploy-controller.ts | 7 ++--- lib/definitions/run.d.ts | 5 ++++ lib/helpers/deploy-command-helper.ts | 40 ++++++-------------------- lib/helpers/livesync-command-helper.ts | 33 +++++++-------------- 4 files changed, 25 insertions(+), 60 deletions(-) diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index 78d3aaeff8..7f572f3fd5 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -1,18 +1,15 @@ export class DeployController { constructor( - private $buildDataService: IBuildDataService, private $buildController: IBuildController, private $deviceInstallAppService: IDeviceInstallAppService, private $devicesService: Mobile.IDevicesService ) { } - public async deploy(data: IRunData): Promise { - const { liveSyncInfo, deviceDescriptors } = data; + public async deploy(data: IDeployData): Promise { + const { buildData, deviceDescriptors } = data; const executeAction = async (device: Mobile.IDevice) => { - const options = { ...liveSyncInfo, buildForDevice: !device.isEmulator }; - const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, device.deviceInfo.platform, options); await this.$buildController.prepareAndBuild(buildData); await this.$deviceInstallAppService.installOnDevice(device, buildData); }; diff --git a/lib/definitions/run.d.ts b/lib/definitions/run.d.ts index 2fb421c716..3dfe93169a 100644 --- a/lib/definitions/run.d.ts +++ b/lib/definitions/run.d.ts @@ -7,6 +7,11 @@ declare global { deviceDescriptors: ILiveSyncDeviceDescriptor[]; } + interface IDeployData { + buildData: IBuildData; + deviceDescriptors: ILiveSyncDeviceDescriptor[]; + } + interface IStopRunData { projectDir: string; deviceIdentifiers?: string[]; diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index 40a78e01cd..2fa86c18c6 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -3,6 +3,7 @@ import { BuildController } from "../controllers/build-controller"; export class DeployCommandHelper { constructor( + private $buildDataService: IBuildDataService, private $buildController: BuildController, private $devicesService: Mobile.IDevicesService, private $deployController: DeployController, @@ -25,31 +26,18 @@ export class DeployCommandHelper { const deviceDescriptors: ILiveSyncDeviceDescriptor[] = 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.$buildController.prepareAndBuild.bind(this.$buildController, 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 buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, { ...this.$options, outputPath, buildForDevice: !d.isEmulator }); + + const buildAction = additionalOptions && additionalOptions.buildPlatform ? + additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildData, this.$projectData) : + this.$buildController.prepareAndBuild.bind(this.$buildController, d.deviceInfo.platform, buildData, this.$projectData); + const info: ILiveSyncDeviceDescriptor = { identifier: d.deviceInfo.identifier, buildAction, @@ -62,20 +50,8 @@ export class DeployCommandHelper { 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.$deployController.deploy({ - liveSyncInfo, + buildData: this.$buildDataService.getBuildData(this.$projectData.projectDir, platform, { ...this.$options, skipWatcher: !this.$options.watch }), deviceDescriptors }); } diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 38500958d9..343e970e2e 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -62,31 +62,16 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { // Now let's take data for each device: const deviceDescriptors: ILiveSyncDeviceDescriptor[] = 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 outputPath = additionalOptions && additionalOptions.getOutputDirectory && additionalOptions.getOutputDirectory({ platform: d.deviceInfo.platform, emulator: d.isEmulator, projectDir: this.$projectData.projectDir }); - const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, { ...buildConfig, outputPath }); + const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, { ...this.$options, outputPath, buildForDevice: !d.isEmulator }); const buildAction = additionalOptions && additionalOptions.buildPlatform ? - additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildConfig, this.$projectData) : + additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildData, this.$projectData) : this.$buildController.build.bind(this.$buildController, buildData); const info: ILiveSyncDeviceDescriptor = { @@ -117,6 +102,11 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { public async executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { const { liveSyncInfo, deviceDescriptors } = await this.executeLiveSyncOperationCore(devices, platform, additionalOptions); + if (this.$options.release) { + await this.runInRelease(platform, deviceDescriptors, liveSyncInfo); + return; + } + await this.$runController.run({ liveSyncInfo, deviceDescriptors @@ -173,11 +163,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const deviceDescriptors = await this.createDeviceDescriptors(devices, platform, additionalOptions); const liveSyncInfo = this.getLiveSyncData(this.$projectData.projectDir); - if (this.$options.release) { - await this.runInRelease(platform, deviceDescriptors, liveSyncInfo); - return; - } - return { liveSyncInfo, deviceDescriptors }; } @@ -190,8 +175,10 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { sdk: this.$options.sdk }); + const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, platform, { ...this.$options, clean: true, skipWatcher: true }); + await this.$deployController.deploy({ - liveSyncInfo: { ...liveSyncInfo, clean: true, skipWatcher: true }, + buildData, deviceDescriptors }); From 4b653c60f9cf973d37c74ed419517c593a80bede Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 5 Jun 2019 08:34:59 +0300 Subject: [PATCH 060/102] fix: fix run with --justlaunch --- lib/controllers/prepare-controller.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 79b533bf81..e7d16fc9e1 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -42,7 +42,8 @@ export class PrepareController extends EventEmitter { result = await this.startWatchersWithPrepare(platformData, projectData, prepareData); } else { await this.$webpackCompilerService.compileWithoutWatch(platformData, projectData, prepareData); - await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); + const hasNativeChanges = await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); + result = { hasNativeChanges, platform: prepareData.platform.toLowerCase() }; } this.$projectChangesService.savePrepareInfo(platformData); From 6090362966c4a303107e18c9bef197d4c26d2563 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 5 Jun 2019 11:55:10 +0300 Subject: [PATCH 061/102] feat: remove init command --- docs/man_pages/project/creation/init.md | 25 ----- lib/bootstrap.ts | 3 - lib/commands/init.ts | 12 --- lib/declarations.d.ts | 4 - lib/services/project-init-service.ts | 134 ------------------------ 5 files changed, 178 deletions(-) delete mode 100644 docs/man_pages/project/creation/init.md delete mode 100644 lib/commands/init.ts delete mode 100644 lib/services/project-init-service.ts diff --git a/docs/man_pages/project/creation/init.md b/docs/man_pages/project/creation/init.md deleted file mode 100644 index bb5a0ebc1d..0000000000 --- a/docs/man_pages/project/creation/init.md +++ /dev/null @@ -1,25 +0,0 @@ -<% if (isJekyll) { %>--- -title: tns init -position: 2 ----<% } %> -# tns init - - -Usage | Synopsis ----|--- -General | `$ tns init [--path ] [--force]` - -Initializes a project for development. The command prompts you to provide your project configuration interactively and uses the information to create a new `package.json` file or update the existing one. - -### Options -* `--path` - Specifies the directory where you want to initialize the project, if different from the current directory. The directory must be empty. -* `--force` - If set, applies the default project configuration and does not show the interactive prompt. The default project configuration targets the latest official runtimes and sets `org.nativescript.` for application identifier. - -<% if(isHtml) { %> -### Related Commands - -Command | Description -----------|---------- -[create](create.html) | Creates a new project for native development with NativeScript from the default template or from an existing NativeScript project. -[install](/lib-management/install.html) | Installs all platforms and dependencies described in the `package.json` file in the current directory. -<% } %> \ No newline at end of file diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 167bb6f21d..ccc3349544 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -131,9 +131,6 @@ $injector.require("xcprojService", "./services/xcproj-service"); $injector.require("versionsService", "./services/versions-service"); $injector.requireCommand("install", "./commands/install"); -$injector.require("projectInitService", "./services/project-init-service"); -$injector.requireCommand("init", "./commands/init"); - $injector.require("infoService", "./services/info-service"); $injector.requireCommand("info", "./commands/info"); diff --git a/lib/commands/init.ts b/lib/commands/init.ts deleted file mode 100644 index 36984c0dd6..0000000000 --- a/lib/commands/init.ts +++ /dev/null @@ -1,12 +0,0 @@ -export class ProjectInitCommand implements ICommand { - public allowedParameters: ICommandParameter[] = []; - public enableHooks = false; - - constructor(private $projectInitService: IProjectInitService) { } - - public async execute(args: string[]): Promise { - return this.$projectInitService.initialize(); - } -} - -$injector.registerCommand("init", ProjectInitCommand); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 1372cda3ff..688c1aca76 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -586,10 +586,6 @@ interface IUpdatePlatformOptions { canUpdate: boolean; } -interface IProjectInitService { - initialize(): Promise; -} - interface IInfoService { printComponentsInfo(): Promise; } diff --git a/lib/services/project-init-service.ts b/lib/services/project-init-service.ts deleted file mode 100644 index 81ee369093..0000000000 --- a/lib/services/project-init-service.ts +++ /dev/null @@ -1,134 +0,0 @@ -import * as constants from "../constants"; -import * as helpers from "../common/helpers"; -import * as path from "path"; -import * as semver from "semver"; - -export class ProjectInitService implements IProjectInitService { - private static MIN_SUPPORTED_FRAMEWORK_VERSIONS: IStringDictionary = { - "tns-ios": "1.1.0", - "tns-android": "1.1.0", - "tns-core-modules": "1.2.0" - }; - - private static VERSION_KEY_NAME = "version"; - - private _projectFilePath: string; - - constructor(private $fs: IFileSystem, - private $logger: ILogger, - private $options: IOptions, - private $injector: IInjector, - private $staticConfig: IStaticConfig, - private $projectHelper: IProjectHelper, - private $prompter: IPrompter, - private $packageManager: INodePackageManager, - private $packageInstallationManager: IPackageInstallationManager) { } - - public async initialize(): Promise { - let projectData: any = {}; - - if (this.$fs.exists(this.projectFilePath)) { - projectData = this.$fs.readJson(this.projectFilePath); - } - - const projectDataBackup = _.extend({}, projectData); - - 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 platformsDataService - } - - try { - projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE]["id"] = await this.getProjectId(); - - if (this.$options.frameworkName && this.$options.frameworkVersion) { - const currentPlatformData = projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][this.$options.frameworkName] || {}; - - projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][this.$options.frameworkName] = _.extend(currentPlatformData, this.buildVersionData(this.$options.frameworkVersion)); - } else { - 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 $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] || {}; - - projectData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE][platformData.frameworkPackageName] = _.extend(currentPlatformData, await this.getVersionData(platformData.frameworkPackageName)); - } - } - } - - const dependencies = projectData.dependencies; - if (!dependencies) { - projectData.dependencies = Object.create(null); - } - - // In case console is interactive and --force is not specified, do not read the version from package.json, show all available versions to the user. - const tnsCoreModulesVersionInPackageJson = this.useDefaultValue ? projectData.dependencies[constants.TNS_CORE_MODULES_NAME] : null; - projectData.dependencies[constants.TNS_CORE_MODULES_NAME] = tnsCoreModulesVersionInPackageJson || (await this.getVersionData(constants.TNS_CORE_MODULES_NAME))["version"]; - - this.$fs.writeJson(this.projectFilePath, projectData); - } catch (err) { - this.$fs.writeJson(this.projectFilePath, projectDataBackup); - throw err; - } - - this.$logger.info("Project successfully initialized."); - } - - private get projectFilePath(): string { - if (!this._projectFilePath) { - const projectDir = path.resolve(this.$options.path || "."); - this._projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME); - } - - return this._projectFilePath; - } - - private async getProjectId(): Promise { - if (this.$options.appid) { - return this.$options.appid; - } - - const defaultAppId = this.$projectHelper.generateDefaultAppId(path.basename(path.dirname(this.projectFilePath)), constants.DEFAULT_APP_IDENTIFIER_PREFIX); - if (this.useDefaultValue) { - return defaultAppId; - } - - return await this.$prompter.getString("Id:", { defaultAction: () => defaultAppId }); - } - - private async getVersionData(packageName: string): Promise { - const latestVersion = await this.$packageInstallationManager.getLatestCompatibleVersion(packageName); - - if (this.useDefaultValue) { - return this.buildVersionData(latestVersion); - } - - const allVersions: any = await this.$packageManager.view(packageName, { "versions": true }); - const versions = _.filter(allVersions, (v: string) => semver.gte(v, ProjectInitService.MIN_SUPPORTED_FRAMEWORK_VERSIONS[packageName])); - if (versions.length === 1) { - this.$logger.info(`Only ${versions[0]} version is available for ${packageName}.`); - return this.buildVersionData(versions[0]); - } - const sortedVersions = versions.sort(helpers.versionCompare).reverse(); - //TODO: plamen5kov: don't offer versions from next (they are not available) - const version = await this.$prompter.promptForChoice(`${packageName} version:`, sortedVersions); - return this.buildVersionData(version); - } - - private buildVersionData(version: string): IStringDictionary { - const result: IStringDictionary = {}; - - result[ProjectInitService.VERSION_KEY_NAME] = version; - - return result; - } - - private get useDefaultValue(): boolean { - return !helpers.isInteractive() || this.$options.force; - } -} -$injector.register("projectInitService", ProjectInitService); From 86d185819240ba1e5ac1a290ad3bf6f66dedf231 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 3 Jun 2019 15:53:57 +0300 Subject: [PATCH 062/102] fix: check only platforms folders of nativescript's plugins on checkForChanges method Rel to: https://github.com/NativeScript/nativescript-cli/issues/4647 --- lib/services/project-changes-service.ts | 27 +++++++++++++------------ test/project-changes-service.ts | 1 + 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 70605294ab..87751f0d15 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -47,7 +47,8 @@ export class ProjectChangesService implements IProjectChangesService { private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $fs: IFileSystem, private $logger: ILogger, - public $hooksService: IHooksService) { + public $hooksService: IHooksService, + private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder) { } public get currentChanges(): IProjectChangesInfo { @@ -64,13 +65,16 @@ export class ProjectChangesService implements IProjectChangesService { this._changesInfo.packageChanged = this.isProjectFileChanged(projectData.projectDir, platformData); 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 = 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._changesInfo.appResourcesChanged = this.containsNewerFiles(platformResourcesDir, projectData); + + this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir) + .filter(dep => dep.nativescript && this.$fs.exists(path.join(dep.directory, "platforms", platformData.platformNameLowerCase))) + .map(dep => { + this._changesInfo.nativeChanged = this.containsNewerFiles( + path.join(dep.directory, "platforms", platformData.platformNameLowerCase), + projectData, + this.fileChangeRequiresBuild); + }); this.$logger.trace(`Set nativeChanged to ${this._changesInfo.nativeChanged}.`); @@ -229,7 +233,7 @@ export class ProjectChangesService implements IProjectChangesService { return false; } - private containsNewerFiles(dir: string, skipDir: string, projectData: IProjectData, processFunc?: (filePath: string, projectData: IProjectData) => boolean): boolean { + private containsNewerFiles(dir: string, projectData: IProjectData, processFunc?: (filePath: string, projectData: IProjectData) => boolean): boolean { const dirName = path.basename(dir); this.$logger.trace(`containsNewerFiles will check ${dir}`); if (_.startsWith(dirName, '.')) { @@ -246,9 +250,6 @@ export class ProjectChangesService implements IProjectChangesService { const files = this.$fs.readDirectory(dir); for (const file of files) { const filePath = path.join(dir, file); - if (filePath === skipDir) { - continue; - } const fileStats = this.$fs.getFsStats(filePath); const changed = this.isFileModified(fileStats, filePath); @@ -270,7 +271,7 @@ export class ProjectChangesService implements IProjectChangesService { } if (fileStats.isDirectory()) { - if (this.containsNewerFiles(filePath, skipDir, projectData, processFunc)) { + if (this.containsNewerFiles(filePath, projectData, processFunc)) { this.$logger.trace(`containsNewerFiles returns true for ${dir}.`); return true; } diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index a5383ae4ee..b7bd3ffdfd 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -36,6 +36,7 @@ class ProjectChangesServiceTest extends BaseServiceTest { }); this.injector.register("logger", LoggerStub); this.injector.register("hooksService", HooksServiceStub); + this.injector.register("nodeModulesDependenciesBuilder", {}); const fs = this.injector.resolve("fs"); fs.writeJson(path.join(this.projectDir, Constants.PACKAGE_JSON_FILE_NAME), { From a698405e320524bbfaae833b50cbfd12d1db637b Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 3 Jun 2019 16:28:15 +0300 Subject: [PATCH 063/102] refactor: remove modulesChange property modulesChange property was used to indicate .js change in node_modules but webpack watches for changes in `.js` files from node_modules if they are required in application, so no more need from this property. --- lib/definitions/project-changes.d.ts | 1 - .../platform/prepare-native-platform-service.ts | 9 +++++---- lib/services/project-changes-service.ts | 17 ++--------------- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index 8841e98b41..51cbd52a5f 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -14,7 +14,6 @@ interface IPrepareInfo extends IAddedNativePlatform, IAppFilesHashes { interface IProjectChangesInfo extends IAddedNativePlatform { appResourcesChanged: boolean; - modulesChanged: boolean; configChanged: boolean; packageChanged: boolean; nativeChanged: boolean; diff --git a/lib/services/platform/prepare-native-platform-service.ts b/lib/services/platform/prepare-native-platform-service.ts index 253f3d189b..689efd2c22 100644 --- a/lib/services/platform/prepare-native-platform-service.ts +++ b/lib/services/platform/prepare-native-platform-service.ts @@ -21,11 +21,12 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi const changesInfo = await this.$projectChangesService.checkForChanges(platformData, projectData, prepareData); - const hasModulesChange = !changesInfo || changesInfo.modulesChanged; + const hasNativeModulesChange = !changesInfo || changesInfo.nativeChanged; + const hasPackageChange = !changesInfo || changesInfo.packageChanged; const hasConfigChange = !changesInfo || changesInfo.configChanged; const hasChangesRequirePrepare = !changesInfo || changesInfo.changesRequirePrepare; - const hasChanges = hasModulesChange || hasConfigChange || hasChangesRequirePrepare; + const hasChanges = hasNativeModulesChange || hasPackageChange || hasConfigChange || hasChangesRequirePrepare; if (changesInfo.hasChanges) { await this.cleanProject(platformData, { release }); @@ -37,11 +38,11 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi await platformData.platformProjectService.prepareProject(projectData, prepareData); } - if (hasModulesChange) { + if (hasNativeModulesChange || hasPackageChange) { await this.$nodeModulesBuilder.prepareNodeModules(platformData, projectData); } - if (hasModulesChange || hasConfigChange) { + if (hasNativeModulesChange || hasPackageChange || hasConfigChange) { await platformData.platformProjectService.processConfigurationFilesFromAppResources(projectData, { release }); await platformData.platformProjectService.handleNativeDependenciesChange(projectData, { release }); } diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 87751f0d15..6ac626bf4d 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -8,7 +8,6 @@ const prepareInfoFileName = ".nsprepareinfo"; class ProjectChangesInfo implements IProjectChangesInfo { public appResourcesChanged: boolean; - public modulesChanged: boolean; public configChanged: boolean; public packageChanged: boolean; public nativeChanged: boolean; @@ -18,7 +17,6 @@ class ProjectChangesInfo implements IProjectChangesInfo { public get hasChanges(): boolean { return this.packageChanged || this.appResourcesChanged || - this.modulesChanged || this.configChanged || this.signingChanged; } @@ -78,11 +76,6 @@ export class ProjectChangesService implements IProjectChangesService { 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}`); - this._changesInfo.modulesChanged = true; - } - if (platformData.platformNameLowerCase === this.$devicePlatformsConstants.iOS.toLowerCase()) { this._changesInfo.configChanged = this.filesChanged([path.join(platformResourcesDir, platformData.configurationFileName), path.join(platformResourcesDir, "LaunchScreen.storyboard"), @@ -105,16 +98,11 @@ export class ProjectChangesService implements IProjectChangesService { 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 = prepareData.release; } - if (this._changesInfo.packageChanged) { - this.$logger.trace("Set modulesChanged to true as packageChanged is true"); - this._changesInfo.modulesChanged = true; - } - if (this._changesInfo.modulesChanged || this._changesInfo.appResourcesChanged) { - this.$logger.trace(`Set configChanged to true, current value of moduleChanged is: ${this._changesInfo.modulesChanged}, appResourcesChanged is: ${this._changesInfo.appResourcesChanged}`); + if (this._changesInfo.appResourcesChanged) { + this.$logger.trace(`Set configChanged to true, appResourcesChanged is: ${this._changesInfo.appResourcesChanged}`); this._changesInfo.configChanged = true; } if (this._changesInfo.hasChanges) { @@ -195,7 +183,6 @@ export class ProjectChangesService implements IProjectChangesService { this._outputProjectCTime = 0; this._changesInfo = this._changesInfo || new ProjectChangesInfo(); this._changesInfo.appResourcesChanged = true; - this._changesInfo.modulesChanged = true; this._changesInfo.configChanged = true; return true; } From ed7b3d5d7a5f17d8fcd15547c74a6f8526da9fe6 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 3 Jun 2019 16:56:12 +0300 Subject: [PATCH 064/102] fix: stop checking for newer files if a plugin with native change is already found --- lib/services/project-changes-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 6ac626bf4d..87e65922f7 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -68,7 +68,7 @@ export class ProjectChangesService implements IProjectChangesService { this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir) .filter(dep => dep.nativescript && this.$fs.exists(path.join(dep.directory, "platforms", platformData.platformNameLowerCase))) .map(dep => { - this._changesInfo.nativeChanged = this.containsNewerFiles( + this._changesInfo.nativeChanged = this._changesInfo.nativeChanged || this.containsNewerFiles( path.join(dep.directory, "platforms", platformData.platformNameLowerCase), projectData, this.fileChangeRequiresBuild); From dbd37f327d8bedaf097e6092fe2bbbca94128bd1 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 3 Jun 2019 17:36:47 +0300 Subject: [PATCH 065/102] refactor: remove fileChangeRequiresBuild function --- lib/services/project-changes-service.ts | 54 ++++--------------------- 1 file changed, 7 insertions(+), 47 deletions(-) diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 87e65922f7..066b1b2ea7 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -1,5 +1,5 @@ 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 { NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME } from "../constants"; import { getHash, hook } from "../common/helpers"; import { PrepareData } from "../data/prepare-data"; @@ -37,7 +37,6 @@ export class ProjectChangesService implements IProjectChangesService { private _changesInfo: IProjectChangesInfo; private _prepareInfo: IPrepareInfo; - private _newFiles: number = 0; private _outputProjectMtime: number; private _outputProjectCTime: number; @@ -58,8 +57,6 @@ export class ProjectChangesService implements IProjectChangesService { this._changesInfo = new ProjectChangesInfo(); const isNewPrepareInfo = await this.ensurePrepareInfo(platformData, projectData, prepareData); if (!isNewPrepareInfo) { - this._newFiles = 0; - this._changesInfo.packageChanged = this.isProjectFileChanged(projectData.projectDir, platformData); const platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName); @@ -68,10 +65,8 @@ export class ProjectChangesService implements IProjectChangesService { this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir) .filter(dep => dep.nativescript && this.$fs.exists(path.join(dep.directory, "platforms", platformData.platformNameLowerCase))) .map(dep => { - this._changesInfo.nativeChanged = this._changesInfo.nativeChanged || this.containsNewerFiles( - path.join(dep.directory, "platforms", platformData.platformNameLowerCase), - projectData, - this.fileChangeRequiresBuild); + this._changesInfo.nativeChanged = this._changesInfo.nativeChanged || + this.containsNewerFiles(path.join(dep.directory, "platforms", platformData.platformNameLowerCase), projectData); }); this.$logger.trace(`Set nativeChanged to ${this._changesInfo.nativeChanged}.`); @@ -220,7 +215,7 @@ export class ProjectChangesService implements IProjectChangesService { return false; } - private containsNewerFiles(dir: string, projectData: IProjectData, processFunc?: (filePath: string, projectData: IProjectData) => boolean): boolean { + private containsNewerFiles(dir: string, projectData: IProjectData): boolean { const dirName = path.basename(dir); this.$logger.trace(`containsNewerFiles will check ${dir}`); if (_.startsWith(dirName, '.')) { @@ -242,23 +237,12 @@ export class ProjectChangesService implements IProjectChangesService { const changed = this.isFileModified(fileStats, filePath); if (changed) { - this.$logger.trace(`File ${filePath} has been changed.`); - if (processFunc) { - this._newFiles++; - this.$logger.trace(`Incremented the newFiles counter. Current value is ${this._newFiles}`); - const filePathRelative = path.relative(projectData.projectDir, filePath); - if (processFunc.call(this, filePathRelative, projectData)) { - this.$logger.trace(`containsNewerFiles returns true for ${dir}. The modified file is ${filePath}`); - return true; - } - } else { - this.$logger.trace(`containsNewerFiles returns true for ${dir}. The modified file is ${filePath}`); - return true; - } + this.$logger.trace(`containsNewerFiles returns true for ${dir}. The modified file is ${filePath}`); + return true; } if (fileStats.isDirectory()) { - if (this.containsNewerFiles(filePath, projectData, processFunc)) { + if (this.containsNewerFiles(filePath, projectData)) { this.$logger.trace(`containsNewerFiles returns true for ${dir}.`); return true; } @@ -281,29 +265,5 @@ export class ProjectChangesService implements IProjectChangesService { return changed; } - - private fileChangeRequiresBuild(file: string, projectData: IProjectData) { - if (path.basename(file) === PACKAGE_JSON_FILE_NAME) { - return true; - } - const projectDir = projectData.projectDir; - if (_.startsWith(path.join(projectDir, file), projectData.appResourcesDirectoryPath)) { - return true; - } - if (_.startsWith(file, NODE_MODULES_FOLDER_NAME)) { - let filePath = file; - while (filePath !== NODE_MODULES_FOLDER_NAME) { - filePath = path.dirname(filePath); - const fullFilePath = path.join(projectDir, path.join(filePath, PACKAGE_JSON_FILE_NAME)); - if (this.$fs.exists(fullFilePath)) { - const json = this.$fs.readJson(fullFilePath); - if (json["nativescript"] && _.startsWith(file, path.join(filePath, "platforms"))) { - return true; - } - } - } - } - return false; - } } $injector.register("projectChangesService", ProjectChangesService); From 7e79e0b2da70e6c724c09ec9c446cf22bd59af11 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 5 Jun 2019 15:27:35 +0300 Subject: [PATCH 066/102] fix: fix plugin development workflow Chokidar raises changed event when `.js` file is changed from within symlinked plugin and this led to a native build on every .js change for plugin's developer. As a workaround, we checked if there are native changes and rebuild the project only in this case. --- lib/controllers/prepare-controller.ts | 4 ++- lib/controllers/run-controller.ts | 5 +-- lib/definitions/project-changes.d.ts | 1 - .../prepare-native-platform-service.ts | 7 ++-- lib/services/project-changes-service.ts | 35 ++++++++++--------- test/stubs.ts | 14 +++----- 6 files changed, 31 insertions(+), 35 deletions(-) diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index e7d16fc9e1..d829313e38 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -4,7 +4,7 @@ import { hook } from "../common/helpers"; import { performanceLog } from "../common/decorators"; import { EventEmitter } from "events"; import * as path from "path"; -import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE } from "../constants"; +import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE, PACKAGE_JSON_FILE_NAME } from "../constants"; interface IPlatformWatcherData { webpackCompilerProcess: child_process.ChildProcess; @@ -115,6 +115,8 @@ export class PrepareController extends EventEmitter { } const patterns = [ + path.join(projectData.projectDir, PACKAGE_JSON_FILE_NAME), + path.join(projectData.getAppDirectoryPath(), PACKAGE_JSON_FILE_NAME), path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), `node_modules/**/platforms/${platformData.platformNameLowerCase}/` ]; diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index ddcba150c1..485f4291db 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -302,8 +302,9 @@ export class RunController extends EventEmitter implements IRunController { try { if (data.hasNativeChanges) { - await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); - await deviceDescriptor.buildAction(); + if (await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData)) { + await deviceDescriptor.buildAction(); + } } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; diff --git a/lib/definitions/project-changes.d.ts b/lib/definitions/project-changes.d.ts index 51cbd52a5f..e799efbb5d 100644 --- a/lib/definitions/project-changes.d.ts +++ b/lib/definitions/project-changes.d.ts @@ -15,7 +15,6 @@ interface IPrepareInfo extends IAddedNativePlatform, IAppFilesHashes { interface IProjectChangesInfo extends IAddedNativePlatform { appResourcesChanged: boolean; configChanged: boolean; - packageChanged: boolean; nativeChanged: boolean; signingChanged: boolean; diff --git a/lib/services/platform/prepare-native-platform-service.ts b/lib/services/platform/prepare-native-platform-service.ts index 689efd2c22..6328c50955 100644 --- a/lib/services/platform/prepare-native-platform-service.ts +++ b/lib/services/platform/prepare-native-platform-service.ts @@ -22,11 +22,10 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi const changesInfo = await this.$projectChangesService.checkForChanges(platformData, projectData, prepareData); const hasNativeModulesChange = !changesInfo || changesInfo.nativeChanged; - const hasPackageChange = !changesInfo || changesInfo.packageChanged; const hasConfigChange = !changesInfo || changesInfo.configChanged; const hasChangesRequirePrepare = !changesInfo || changesInfo.changesRequirePrepare; - const hasChanges = hasNativeModulesChange || hasPackageChange || hasConfigChange || hasChangesRequirePrepare; + const hasChanges = hasNativeModulesChange || hasConfigChange || hasChangesRequirePrepare; if (changesInfo.hasChanges) { await this.cleanProject(platformData, { release }); @@ -38,11 +37,11 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi await platformData.platformProjectService.prepareProject(projectData, prepareData); } - if (hasNativeModulesChange || hasPackageChange) { + if (hasNativeModulesChange) { await this.$nodeModulesBuilder.prepareNodeModules(platformData, projectData); } - if (hasNativeModulesChange || hasPackageChange || hasConfigChange) { + if (hasNativeModulesChange || hasConfigChange) { await platformData.platformProjectService.processConfigurationFilesFromAppResources(projectData, { release }); await platformData.platformProjectService.handleNativeDependenciesChange(projectData, { release }); } diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 066b1b2ea7..6ac2717e17 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -1,5 +1,5 @@ import * as path from "path"; -import { NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME } from "../constants"; +import { NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME, PLATFORMS_DIR_NAME } from "../constants"; import { getHash, hook } from "../common/helpers"; import { PrepareData } from "../data/prepare-data"; @@ -9,21 +9,19 @@ class ProjectChangesInfo implements IProjectChangesInfo { public appResourcesChanged: boolean; public configChanged: boolean; - public packageChanged: boolean; public nativeChanged: boolean; public signingChanged: boolean; public nativePlatformStatus: NativePlatformStatus; public get hasChanges(): boolean { - return this.packageChanged || + return this.nativeChanged || this.appResourcesChanged || this.configChanged || this.signingChanged; } public get changesRequireBuild(): boolean { - return this.packageChanged || - this.appResourcesChanged || + return this.appResourcesChanged || this.nativeChanged; } @@ -57,18 +55,23 @@ export class ProjectChangesService implements IProjectChangesService { this._changesInfo = new ProjectChangesInfo(); const isNewPrepareInfo = await this.ensurePrepareInfo(platformData, projectData, prepareData); if (!isNewPrepareInfo) { - this._changesInfo.packageChanged = this.isProjectFileChanged(projectData.projectDir, platformData); - const platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName); this._changesInfo.appResourcesChanged = this.containsNewerFiles(platformResourcesDir, projectData); this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir) - .filter(dep => dep.nativescript && this.$fs.exists(path.join(dep.directory, "platforms", platformData.platformNameLowerCase))) + .filter(dep => dep.nativescript && this.$fs.exists(path.join(dep.directory, PLATFORMS_DIR_NAME, platformData.platformNameLowerCase))) .map(dep => { this._changesInfo.nativeChanged = this._changesInfo.nativeChanged || - this.containsNewerFiles(path.join(dep.directory, "platforms", platformData.platformNameLowerCase), projectData); + this.containsNewerFiles(path.join(dep.directory, PLATFORMS_DIR_NAME, platformData.platformNameLowerCase), projectData) || + this.isFileModified(path.join(dep.directory, PACKAGE_JSON_FILE_NAME)); }); + if (!this._changesInfo.nativeChanged) { + const packageJsonChanged = this.isProjectFileChanged(projectData.projectDir, platformData); + this._prepareInfo.projectFileHash = this.getProjectFileStrippedHash(projectData.projectDir, platformData); + this._changesInfo.nativeChanged = packageJsonChanged; + } + this.$logger.trace(`Set nativeChanged to ${this._changesInfo.nativeChanged}.`); if (platformData.platformNameLowerCase === this.$devicePlatformsConstants.iOS.toLowerCase()) { @@ -106,8 +109,6 @@ export class ProjectChangesService implements IProjectChangesService { if (this._prepareInfo.changesRequireBuild) { this._prepareInfo.changesRequireBuildTime = this._prepareInfo.time; } - - this._prepareInfo.projectFileHash = this.getProjectFileStrippedHash(projectData.projectDir, platformData); } this._changesInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus; @@ -223,8 +224,7 @@ export class ProjectChangesService implements IProjectChangesService { return false; } - const dirFileStat = this.$fs.getFsStats(dir); - if (this.isFileModified(dirFileStat, dir)) { + if (this.isFileModified(dir)) { this.$logger.trace(`containsNewerFiles returns true for ${dir} as the dir itself has been modified.`); return true; } @@ -234,7 +234,7 @@ export class ProjectChangesService implements IProjectChangesService { const filePath = path.join(dir, file); const fileStats = this.$fs.getFsStats(filePath); - const changed = this.isFileModified(fileStats, filePath); + const changed = this.isFileModified(filePath, fileStats); if (changed) { this.$logger.trace(`containsNewerFiles returns true for ${dir}. The modified file is ${filePath}`); @@ -253,9 +253,10 @@ export class ProjectChangesService implements IProjectChangesService { return false; } - private isFileModified(filePathStat: IFsStats, filePath: string): boolean { - let changed = filePathStat.mtime.getTime() >= this._outputProjectMtime || - filePathStat.ctime.getTime() >= this._outputProjectCTime; + private isFileModified(filePath: string, filePathStats?: IFsStats): boolean { + filePathStats = filePathStats || this.$fs.getFsStats(filePath); + let changed = filePathStats.mtime.getTime() >= this._outputProjectMtime || + filePathStats.ctime.getTime() >= this._outputProjectCTime; if (!changed) { const lFileStats = this.$fs.getLsStats(filePath); diff --git a/test/stubs.ts b/test/stubs.ts index 05964e8250..e61e357fae 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -506,16 +506,10 @@ export class ProjectDataService implements IProjectDataService { removeDependency(dependencyName: string): void { } 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" - }, - getAppResourcesRelativeDirectoryPath: () => "/path/to/my/projecDir/App_Resources" - }; + const projectData = new ProjectDataStub(); + projectData.initializeProjectData(projectDir); + + return projectData; } async getAssetsStructure(opts: IProjectDir): Promise { From 74a0edb6f3f6e53ecf9eadbd5954c6226cca9eb9 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 5 Jun 2019 16:38:13 +0300 Subject: [PATCH 067/102] fix: fix tns preview --- lib/controllers/preview-app-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts index 1531958def..cb8914fcde 100644 --- a/lib/controllers/preview-app-controller.ts +++ b/lib/controllers/preview-app-controller.ts @@ -72,7 +72,7 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon 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 } ); + const prepareData = this.$prepareDataService.getPrepareData(data.projectDir, device.platform.toLowerCase(), { ...data, nativePrepare: { skipNativePrepare: true }, watch: true }); await this.$prepareController.prepare(prepareData); this.deviceInitializationPromise[device.id] = this.getInitialFilesForPlatformSafe(data, device.platform); From 96c7cd73d74a8df173fa002450e28ed30637a5d6 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 5 Jun 2019 17:41:13 +0300 Subject: [PATCH 068/102] fix: handle correctly runtime files --- lib/services/webpack/webpack-compiler-service.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 6f42ba8f7d..7870e2a36b 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -37,7 +37,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return; } - const result = this.getUpdatedEmittedFiles(message.emittedFiles); + const result = this.getUpdatedEmittedFiles(message.emittedFiles, message.webpackRuntimeFiles); const files = result.emittedFiles .filter((file: string) => file.indexOf("App_Resources") === -1) @@ -173,7 +173,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return args; } - private getUpdatedEmittedFiles(emittedFiles: string[]) { + private getUpdatedEmittedFiles(emittedFiles: string[], webpackRuntimeFiles: string[]) { let fallbackFiles: string[] = []; let hotHash; if (emittedFiles.some(x => x.endsWith('.hot-update.json'))) { @@ -184,6 +184,10 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp 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 (webpackRuntimeFiles && webpackRuntimeFiles.length) { + // remove files containing only the Webpack runtime (e.g. runtime.js) + result = result.filter(file => webpackRuntimeFiles.indexOf(file) === -1); + } }); //if applying of hot update fails, we must fallback to the full files fallbackFiles = emittedFiles.filter(file => result.indexOf(file) === -1); From ddf89d8f739ee307260def3caf948dce06b5e1d6 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 6 Jun 2019 09:30:32 +0300 Subject: [PATCH 069/102] chore: fix PR comments --- lib/controllers/prepare-controller.ts | 2 +- lib/services/project-changes-service.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index d829313e38..88fde3fc04 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -132,7 +132,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.info(`Chokidar raised event ${event} for ${filePath}.`); + this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`); this.emitPrepareEvent({ files: [], hmrData: null, hasNativeChanges: true, platform: platformData.platformNameLowerCase }); }); diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 6ac2717e17..8148b806aa 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -60,16 +60,15 @@ export class ProjectChangesService implements IProjectChangesService { this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir) .filter(dep => dep.nativescript && this.$fs.exists(path.join(dep.directory, PLATFORMS_DIR_NAME, platformData.platformNameLowerCase))) - .map(dep => { + .forEach(dep => { this._changesInfo.nativeChanged = this._changesInfo.nativeChanged || this.containsNewerFiles(path.join(dep.directory, PLATFORMS_DIR_NAME, platformData.platformNameLowerCase), projectData) || this.isFileModified(path.join(dep.directory, PACKAGE_JSON_FILE_NAME)); }); if (!this._changesInfo.nativeChanged) { - const packageJsonChanged = this.isProjectFileChanged(projectData.projectDir, platformData); this._prepareInfo.projectFileHash = this.getProjectFileStrippedHash(projectData.projectDir, platformData); - this._changesInfo.nativeChanged = packageJsonChanged; + this._changesInfo.nativeChanged = this.isProjectFileChanged(projectData.projectDir, platformData); } this.$logger.trace(`Set nativeChanged to ${this._changesInfo.nativeChanged}.`); From bad1fcb5fdb3fff29a9df708476c3f40034253f2 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 7 Jun 2019 08:13:31 +0300 Subject: [PATCH 070/102] fix: respect `--for-device` option when building for iOS device `tns build ios --for-device` command doesn't produce .ipa as `--for-device` option is not respected and the application is built for simulator. --- lib/data/build-data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/build-data.ts b/lib/data/build-data.ts index dfc736afab..08d15e99ff 100644 --- a/lib/data/build-data.ts +++ b/lib/data/build-data.ts @@ -15,7 +15,7 @@ export class BuildData extends PrepareData implements IBuildData { this.device = data.device; this.emulator = data.emulator; this.clean = data.clean; - this.buildForDevice = data.buildForDevice; + this.buildForDevice = data.buildForDevice || data.forDevice; this.buildOutputStdio = data.buildOutputStdio; this.outputPath = data.outputPath; this.copyTo = data.copyTo; From 013817b88093b32bbc4eabcfc3827382f25e2abf Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 7 Jun 2019 08:00:52 +0300 Subject: [PATCH 071/102] fix: emit only `bundle..hot-update.js` and `.hot-update.json` files Currently `inspector.modules.js` file is reported on change and this led to the problem that `inspcector-modules.js` file is transferred on every sync. As we need to transfer only `.hot-update` files when `--hmr` is provided, we should exclude all other files. --- .../webpack/webpack-compiler-service.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 7870e2a36b..54e09929ad 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -37,10 +37,9 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return; } - const result = this.getUpdatedEmittedFiles(message.emittedFiles, message.webpackRuntimeFiles); + const result = this.getUpdatedEmittedFiles(message.emittedFiles, message.webpackRuntimeFiles, message.entryPointFiles); const files = result.emittedFiles - .filter((file: string) => file.indexOf("App_Resources") === -1) .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)); const data = { @@ -173,23 +172,25 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return args; } - private getUpdatedEmittedFiles(emittedFiles: string[], webpackRuntimeFiles: string[]) { + private getUpdatedEmittedFiles(emittedFiles: string[], webpackRuntimeFiles: string[], entryPointFiles: 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')); + if (webpackRuntimeFiles && webpackRuntimeFiles.length) { + result = result.filter(file => webpackRuntimeFiles.indexOf(file) === -1); + } + if (entryPointFiles && entryPointFiles.length) { + result = result.filter(file => entryPointFiles.indexOf(file) === -1); + } 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 (webpackRuntimeFiles && webpackRuntimeFiles.length) { - // remove files containing only the Webpack runtime (e.g. runtime.js) - result = result.filter(file => webpackRuntimeFiles.indexOf(file) === -1); - } }); - //if applying of hot update fails, we must fallback to the full files + // 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 }; } From 6ef6998c792a04e61bcb88a80b662e1ccf78d6ab Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 11 Jun 2019 07:58:12 +0300 Subject: [PATCH 072/102] fix: enable source maps by default --- lib/services/webpack/webpack-compiler-service.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 7870e2a36b..d6a73640a4 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -139,6 +139,11 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp envData.verbose = this.$logger.isVerbose(); envData.production = prepareData.release; + if (prepareData.env.sourceMap === false || prepareData.env.sourceMap === 'false') { + delete envData.sourceMap; + } else if (!prepareData.release) { + envData.sourceMap = true; + } return envData; } From 91415e9b6a96ca8f7bc125af5ab3a113f9197ca3 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 11 Jun 2019 16:10:34 +0300 Subject: [PATCH 073/102] fix: fix cannot read property sourceMap of undefined --- lib/services/webpack/webpack-compiler-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 651b774753..c1504a5efe 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -138,7 +138,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp envData.verbose = this.$logger.isVerbose(); envData.production = prepareData.release; - if (prepareData.env.sourceMap === false || prepareData.env.sourceMap === 'false') { + if (prepareData.env && (prepareData.env.sourceMap === false || prepareData.env.sourceMap === 'false')) { delete envData.sourceMap; } else if (!prepareData.release) { envData.sourceMap = true; From 4cbd3497c1b9196d15818846685ecd6b0a7d4f2c Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 11 Jun 2019 19:49:50 +0300 Subject: [PATCH 074/102] fix: ensure dependencies are installed before executing webpack --- lib/services/webpack/webpack-compiler-service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 651b774753..a3c0ef9b71 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -11,7 +11,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp constructor( private $childProcess: IChildProcess, public $hooksService: IHooksService, - private $logger: ILogger + private $logger: ILogger, + private $pluginsService: IPluginsService ) { super(); } public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { @@ -102,6 +103,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const envData = this.buildEnvData(platformData.platformNameLowerCase, projectData, prepareData); const envParams = this.buildEnvCommandLineParams(envData, platformData); + await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); + const args = [ path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), "--preserve-symlinks", From e6e0aadfba2fa62882a05f9a4ad875b0b6c521ec Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 11 Jun 2019 19:59:55 +0300 Subject: [PATCH 075/102] fix: add preview-sync hook as nativescript-kinvey-sdk plugin uses it --- lib/controllers/preview-app-controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts index cb8914fcde..f9af93ffa0 100644 --- a/lib/controllers/preview-app-controller.ts +++ b/lib/controllers/preview-app-controller.ts @@ -2,7 +2,7 @@ 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 { stringify, hook } from "../common/helpers"; import { HmrConstants } from "../common/constants"; import { EventEmitter } from "events"; import { PrepareDataService } from "../services/prepare-data-service"; @@ -17,6 +17,7 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon private $errors: IErrors, private $hmrStatusService: IHmrStatusService, private $logger: ILogger, + public $hooksService: IHooksService, private $prepareController: PrepareController, private $previewAppFilesService: IPreviewAppFilesService, private $previewAppPluginsService: IPreviewAppPluginsService, @@ -26,6 +27,7 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon private $prepareDataService: PrepareDataService ) { super(); } + @hook("preview-sync") public async startPreview(data: IPreviewAppLiveSyncData): Promise { await this.previewCore(data); From 68246f8c7c5a34e2e44404f92262c477f9d25ed0 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 11 Jun 2019 20:10:22 +0300 Subject: [PATCH 076/102] fix: add watchPatterns hook as some plugins relies on it --- lib/controllers/prepare-controller.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 88fde3fc04..062163953e 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -114,12 +114,8 @@ export class PrepareController extends EventEmitter { return false; } - const patterns = [ - path.join(projectData.projectDir, PACKAGE_JSON_FILE_NAME), - path.join(projectData.getAppDirectoryPath(), PACKAGE_JSON_FILE_NAME), - path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), - `node_modules/**/platforms/${platformData.platformNameLowerCase}/` - ]; + const patterns = await this.getWatcherPatterns(platformData, projectData); + const watcherOptions: choki.WatchOptions = { ignoreInitial: true, cwd: projectData.projectDir, @@ -143,6 +139,18 @@ export class PrepareController extends EventEmitter { return hasNativeChanges; } + @hook('watchPatterns') + public async getWatcherPatterns(platformData: IPlatformData, projectData: IProjectData): Promise { + const patterns = [ + path.join(projectData.projectDir, PACKAGE_JSON_FILE_NAME), + path.join(projectData.getAppDirectoryPath(), PACKAGE_JSON_FILE_NAME), + path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), + `node_modules/**/platforms/${platformData.platformNameLowerCase}/` + ]; + + return patterns; + } + private emitPrepareEvent(filesChangeEventData: IFilesChangeEventData) { if (this.isInitialPrepareReady) { this.emit(PREPARE_READY_EVENT_NAME, filesChangeEventData); From 0b1d6cdcda54783983033c0ae3737297722d1b2e Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 11 Jun 2019 20:51:41 +0300 Subject: [PATCH 077/102] chore: fix tests --- test/services/playground/preview-app-livesync-service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index eb63848ea5..a93d4a7562 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -180,6 +180,10 @@ function createTestInjector(options?: { injector.register("analyticsService", { trackEventActionInGoogleAnalytics: () => ({}) }); + injector.register("hooksService", { + executeBeforeHooks: () => ({}), + executeAfterHooks: () => ({}) + }); return injector; } From b8e4eae689d05cf41e2c7575de84468e88f09280 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 12 Jun 2019 01:43:44 +0300 Subject: [PATCH 078/102] fix: populate correctly prepareData and buildData when they are based on $options --- lib/helpers/livesync-command-helper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 343e970e2e..6e4811c591 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -68,7 +68,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { projectDir: this.$projectData.projectDir }); - const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, { ...this.$options, outputPath, buildForDevice: !d.isEmulator }); + const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, { ...this.$options.argv, outputPath, buildForDevice: !d.isEmulator }); const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildData, this.$projectData) : @@ -175,7 +175,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { sdk: this.$options.sdk }); - const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, platform, { ...this.$options, clean: true, skipWatcher: true }); + const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, platform, { ...this.$options.argv, clean: true, skipWatcher: true }); await this.$deployController.deploy({ buildData, From b95b7ca53ce7893ce6de83161debd980ae9f0908 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 11 Jun 2019 15:30:22 +0300 Subject: [PATCH 079/102] fix: fix plugin's development workflow --- lib/controllers/prepare-controller.ts | 8 ++++++-- lib/controllers/run-controller.ts | 5 ++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 062163953e..fa5c12d4d9 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -20,6 +20,7 @@ export class PrepareController extends EventEmitter { private $platformController: IPlatformController, public $hooksService: IHooksService, private $logger: ILogger, + private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, private $platformsDataService: IPlatformsDataService, private $prepareNativePlatformService: IPrepareNativePlatformService, private $projectChangesService: IProjectChangesService, @@ -141,12 +142,15 @@ export class PrepareController extends EventEmitter { @hook('watchPatterns') public async getWatcherPatterns(platformData: IPlatformData, projectData: IProjectData): Promise { + const pluginsNativeDirectories = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir) + .filter(dep => dep.nativescript) + .map(dep => path.join(dep.directory, "platforms", "ios")); + const patterns = [ path.join(projectData.projectDir, PACKAGE_JSON_FILE_NAME), path.join(projectData.getAppDirectoryPath(), PACKAGE_JSON_FILE_NAME), path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), - `node_modules/**/platforms/${platformData.platformNameLowerCase}/` - ]; + ].concat(pluginsNativeDirectories); return patterns; } diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index 485f4291db..ddcba150c1 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -302,9 +302,8 @@ export class RunController extends EventEmitter implements IRunController { try { if (data.hasNativeChanges) { - if (await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData)) { - await deviceDescriptor.buildAction(); - } + await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); + await deviceDescriptor.buildAction(); } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; From 4066ecccb89b62a3ceeca856908e6e7b13225f22 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 11 Jun 2019 20:58:12 +0300 Subject: [PATCH 080/102] chore: fix tests --- test/controllers/prepare-controller.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/controllers/prepare-controller.ts b/test/controllers/prepare-controller.ts index 604a07c3cc..c730a3f6d5 100644 --- a/test/controllers/prepare-controller.ts +++ b/test/controllers/prepare-controller.ts @@ -45,6 +45,10 @@ function createTestInjector(data: { hasNativeChanges: boolean }): IInjector { injector.register("prepareController", PrepareController); + injector.register("nodeModulesDependenciesBuilder", { + getProductionDependencies: () => ([]) + }); + const prepareController: PrepareController = injector.resolve("prepareController"); prepareController.emit = (eventName: string, eventData: any) => { emittedEventNames.push(eventName); From 2dc7a81b542b40020ffc838d07d137c9fd97822a Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 12 Jun 2019 08:42:02 +0300 Subject: [PATCH 081/102] chore: fix PR comments --- lib/controllers/prepare-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index fa5c12d4d9..6680861f12 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -4,7 +4,7 @@ import { hook } from "../common/helpers"; import { performanceLog } from "../common/decorators"; import { EventEmitter } from "events"; import * as path from "path"; -import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE, PACKAGE_JSON_FILE_NAME } from "../constants"; +import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE, PACKAGE_JSON_FILE_NAME, PLATFORMS_DIR_NAME } from "../constants"; interface IPlatformWatcherData { webpackCompilerProcess: child_process.ChildProcess; @@ -144,7 +144,7 @@ export class PrepareController extends EventEmitter { public async getWatcherPatterns(platformData: IPlatformData, projectData: IProjectData): Promise { const pluginsNativeDirectories = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir) .filter(dep => dep.nativescript) - .map(dep => path.join(dep.directory, "platforms", "ios")); + .map(dep => path.join(dep.directory, PLATFORMS_DIR_NAME, platformData.platformNameLowerCase)); const patterns = [ path.join(projectData.projectDir, PACKAGE_JSON_FILE_NAME), From 1e4415bf95a1038c8294d3bfc093e0088c502e2b Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 13 Jun 2019 08:56:43 +0300 Subject: [PATCH 082/102] fix: increase the max process's size in order to prevent "JavaScript heap out of memory error" --- 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 2d40f2ae5d..db776e0fe5 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -107,6 +107,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const args = [ path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), + "--max_old_space_size=4096", "--preserve-symlinks", `--config=${path.join(projectData.projectDir, "webpack.config.js")}`, ...envParams From 5f3dd1b0bf675ac38d494d753a39fb5ce0e77dc1 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 13 Jun 2019 11:00:33 +0300 Subject: [PATCH 083/102] fix: extend process's max size only for `x64` processes --- lib/services/webpack/webpack-compiler-service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index db776e0fe5..af2c0de597 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -107,12 +107,15 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const args = [ path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), - "--max_old_space_size=4096", "--preserve-symlinks", `--config=${path.join(projectData.projectDir, "webpack.config.js")}`, ...envParams ]; + if (process.arch === "x64") { + args.push("--max_old_space_size=4096"); + } + if (prepareData.watch) { args.push("--watch"); } From f925e24923563130f3b8d8262204cec2af00d502 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 13 Jun 2019 16:55:44 +0300 Subject: [PATCH 084/102] fix: save correct .nsprepareinfo file from cloud commands --- lib/controllers/prepare-controller.ts | 2 +- lib/services/platform/add-platform-service.ts | 2 +- .../platform/prepare-native-platform-service.ts | 2 +- lib/services/project-changes-service.ts | 10 +++++++--- lib/services/webpack/webpack.d.ts | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 6680861f12..4fe1c87582 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -47,7 +47,7 @@ export class PrepareController extends EventEmitter { result = { hasNativeChanges, platform: prepareData.platform.toLowerCase() }; } - this.$projectChangesService.savePrepareInfo(platformData); + await this.$projectChangesService.savePrepareInfo(platformData, projectData, prepareData); this.$logger.info(`Project successfully prepared (${prepareData.platform.toLowerCase()})`); diff --git a/lib/services/platform/add-platform-service.ts b/lib/services/platform/add-platform-service.ts index 115fe3d1b7..e97e3b801b 100644 --- a/lib/services/platform/add-platform-service.ts +++ b/lib/services/platform/add-platform-service.ts @@ -61,7 +61,7 @@ export class AddPlatformService implements IAddPlatformService { platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); await platformData.platformProjectService.interpolateData(projectData); platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); - this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: NativePlatformStatus.requiresPrepare }); + await this.$projectChangesService.setNativePlatformStatus(platformData, projectData, { nativePlatformStatus: NativePlatformStatus.requiresPrepare }); } } $injector.register("addPlatformService", AddPlatformService); diff --git a/lib/services/platform/prepare-native-platform-service.ts b/lib/services/platform/prepare-native-platform-service.ts index 6328c50955..6b3549cb3f 100644 --- a/lib/services/platform/prepare-native-platform-service.ts +++ b/lib/services/platform/prepare-native-platform-service.ts @@ -47,7 +47,7 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi } platformData.platformProjectService.interpolateConfigurationFile(projectData); - this.$projectChangesService.setNativePlatformStatus(platformData, { nativePlatformStatus: NativePlatformStatus.alreadyPrepared }); + await this.$projectChangesService.setNativePlatformStatus(platformData, projectData, { nativePlatformStatus: NativePlatformStatus.alreadyPrepared }); return hasChanges; } diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 8148b806aa..6df11e4140 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -136,12 +136,16 @@ export class ProjectChangesService implements IProjectChangesService { return prepareInfo; } - public savePrepareInfo(platformData: IPlatformData): void { + public async savePrepareInfo(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { + if (!this._prepareInfo) { + await this.ensurePrepareInfo(platformData, projectData, prepareData); + } + const prepareInfoFilePath = this.getPrepareInfoFilePath(platformData); this.$fs.writeJson(prepareInfoFilePath, this._prepareInfo); } - public setNativePlatformStatus(platformData: IPlatformData, addedPlatform: IAddedNativePlatform): void { + public async setNativePlatformStatus(platformData: IPlatformData, projectData: IProjectData, addedPlatform: IAddedNativePlatform): Promise { this._prepareInfo = this._prepareInfo || this.getPrepareInfo(platformData); if (this._prepareInfo && addedPlatform.nativePlatformStatus === NativePlatformStatus.alreadyPrepared) { this._prepareInfo.nativePlatformStatus = addedPlatform.nativePlatformStatus; @@ -151,7 +155,7 @@ export class ProjectChangesService implements IProjectChangesService { }; } - this.savePrepareInfo(platformData); + await this.savePrepareInfo(platformData, projectData, null); } private async ensurePrepareInfo(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { diff --git a/lib/services/webpack/webpack.d.ts b/lib/services/webpack/webpack.d.ts index e6732c79b3..3a597b4ae2 100644 --- a/lib/services/webpack/webpack.d.ts +++ b/lib/services/webpack/webpack.d.ts @@ -19,8 +19,8 @@ declare global { checkForChanges(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise; getPrepareInfoFilePath(platformData: IPlatformData): string; getPrepareInfo(platformData: IPlatformData): IPrepareInfo; - savePrepareInfo(platformData: IPlatformData): void; - setNativePlatformStatus(platformData: IPlatformData, addedPlatform: IAddedNativePlatform): void; + savePrepareInfo(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise; + setNativePlatformStatus(platformData: IPlatformData, projectData: IProjectData, addedPlatform: IAddedNativePlatform): void; currentChanges: IProjectChangesInfo; } From 9de2c48a7bc3f42c4aaf95e0eb222313bfcb7c72 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 13 Jun 2019 17:01:02 +0300 Subject: [PATCH 085/102] chore: fix tests --- test/project-changes-service.ts | 10 +++++----- test/stubs.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/project-changes-service.ts b/test/project-changes-service.ts index b7bd3ffdfd..737e3e66c5 100644 --- a/test/project-changes-service.ts +++ b/test/project-changes-service.ts @@ -170,9 +170,9 @@ describe("Project Changes Service Tests", () => { }); describe("setNativePlatformStatus", () => { - it("creates prepare info and sets only the native platform status when there isn't an existing prepare info", () => { + it("creates prepare info and sets only the native platform status when there isn't an existing prepare info", async () => { for (const platform of ["ios", "android"]) { - serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), { nativePlatformStatus: Constants.NativePlatformStatus.requiresPrepare }); + await serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), serviceTest.projectData, { nativePlatformStatus: Constants.NativePlatformStatus.requiresPrepare }); const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); @@ -183,10 +183,10 @@ 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(serviceTest.getPlatformData(platform), serviceTest.projectData, {}); - serviceTest.projectChangesService.savePrepareInfo(serviceTest.getPlatformData(platform)); + await serviceTest.projectChangesService.savePrepareInfo(serviceTest.getPlatformData(platform), serviceTest.projectData, null); const prepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); - serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), { nativePlatformStatus: Constants.NativePlatformStatus.alreadyPrepared }); + await serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), serviceTest.projectData, { nativePlatformStatus: Constants.NativePlatformStatus.alreadyPrepared }); const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); prepareInfo.nativePlatformStatus = Constants.NativePlatformStatus.alreadyPrepared; @@ -198,7 +198,7 @@ describe("Project Changes Service Tests", () => { 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(serviceTest.getPlatformData(platform), serviceTest.projectData, {}); - serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), { nativePlatformStatus: nativePlatformStatus }); + await serviceTest.projectChangesService.setNativePlatformStatus(serviceTest.getPlatformData(platform), serviceTest.projectData, { nativePlatformStatus: nativePlatformStatus }); const actualPrepareInfo = serviceTest.projectChangesService.getPrepareInfo(serviceTest.getPlatformData(platform)); assert.deepEqual(actualPrepareInfo, { nativePlatformStatus: nativePlatformStatus }); diff --git a/test/stubs.ts b/test/stubs.ts index e61e357fae..353a0162d4 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -749,7 +749,7 @@ export class ProjectChangesService implements IProjectChangesService { return null; } - public savePrepareInfo(platformData: IPlatformData): void { + public async savePrepareInfo(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { } public getPrepareInfoFilePath(platformData: IPlatformData): string { @@ -760,7 +760,7 @@ export class ProjectChangesService implements IProjectChangesService { return {}; } - public setNativePlatformStatus(platformData: IPlatformData, addedPlatform: IAddedNativePlatform): void { + public async setNativePlatformStatus(platformData: IPlatformData, projectData: IProjectData, addedPlatform: IAddedNativePlatform): Promise { return; } } From 9075b883316bd7807b9b26991aaf9b22fd83ca1c Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 15 Jun 2019 12:03:34 +0300 Subject: [PATCH 086/102] fix: fix the way android appResources are processed Currently we copy all App_Resources from ./app/App_Resources/Android/* to ./platforms/../app/src. As there is src folder in ./app/App_Resources/Android/*, we receive as a result inside platforms ./platforms/app/../src/src. --- lib/services/android-project-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index cea0b9ec23..e96984a368 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -289,8 +289,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.$fs.ensureDirectoryExists(platformsAppResourcesPath); - this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "*"), platformsAppResourcesPath); - const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(projectAppResourcesPath); + this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "*"), path.dirname(platformsAppResourcesPath)); + const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(projectAppResourcesPath); if (appResourcesDirStructureHasMigrated) { this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "src", "*"), platformsAppResourcesPath); } else { From 4b971debe33c6907861b4d150da72c23af898b2e Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 15 Jun 2019 22:32:51 +0300 Subject: [PATCH 087/102] fix: restore application when the broken xml file is fixed The files on android device are transferred through socket. When the application is in errorActivity, the runtime reports to the CLI that the application is in broken state (e.g. didRefresh property). In this case NativeScript CLI transfers all fallback files (bundle.js and runtime.js). CLI has a logic to ignore all non-existing files when transferring them on the device. As the fallback files are reported with relative paths, CLI consider them as non-existing and doesn't transfer them on device. So in order to fix this issue, we need to make fallbackFiles with absolute paths. To reproduce: 1. tns run android 2. Broke some xml file from the application 3. Fix the broken xml file --- lib/services/webpack/webpack-compiler-service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index db776e0fe5..0116ca1fe1 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -42,12 +42,14 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp const files = result.emittedFiles .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)); + const fallbackFiles = result.fallbackFiles + .map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file)); const data = { files, hmrData: { hash: result.hash, - fallbackFiles: result.fallbackFiles + fallbackFiles } }; From dfaf494209a3f2d6af5c66d93694ccbf886ee6a2 Mon Sep 17 00:00:00 2001 From: fatme Date: Sun, 16 Jun 2019 00:46:05 +0300 Subject: [PATCH 088/102] fix: build only once when there are more than one android devices --- lib/controllers/run-controller.ts | 38 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index ddcba150c1..251bfd5016 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -4,6 +4,7 @@ import { cache, performanceLog } from "../common/decorators"; import { EventEmitter } from "events"; export class RunController extends EventEmitter implements IRunController { + private rebuiltInformation: IDictionary = {}; constructor( protected $analyticsService: IAnalyticsService, @@ -236,6 +237,8 @@ export class RunController extends EventEmitter implements IRunController { } private async syncInitialDataOnDevices(projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { + this.rebuiltInformation = {}; + const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); @@ -245,18 +248,27 @@ export class RunController extends EventEmitter implements IRunController { try { let packageFilePath: string = null; - const shouldBuild = prepareResultData.hasNativeChanges || await this.$buildController.shouldBuild(buildData); - if (shouldBuild) { - packageFilePath = await deviceDescriptor.buildAction(); + + // 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. + if (this.rebuiltInformation[platformData.platformNameLowerCase] && (this.$mobileHelper.isAndroidPlatform(platformData.platformNameLowerCase) || this.rebuiltInformation[platformData.platformNameLowerCase].isEmulator === device.isEmulator)) { + packageFilePath = this.rebuiltInformation[platformData.platformNameLowerCase].packageFilePath; + await this.$deviceInstallAppService.installOnDevice(device, buildData, packageFilePath); } else { - await this.$analyticsService.trackEventActionInGoogleAnalytics({ - action: TrackActionNames.LiveSync, - device, - projectDir: projectData.projectDir - }); - } + const shouldBuild = prepareResultData.hasNativeChanges || await this.$buildController.shouldBuild(buildData); + if (shouldBuild) { + packageFilePath = await deviceDescriptor.buildAction(); + this.rebuiltInformation[platformData.platformNameLowerCase] = { isEmulator: device.isEmulator, platform: platformData.platformNameLowerCase, packageFilePath }; + } else { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.LiveSync, + device, + projectDir: projectData.projectDir + }); + } - await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, buildData, packageFilePath); + await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, buildData, packageFilePath); + } const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(platformData.platformNameLowerCase); const { force, useHotModuleReload, skipWatcher } = liveSyncInfo; @@ -295,15 +307,19 @@ export class RunController extends EventEmitter implements IRunController { } private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { + this.rebuiltInformation = {}; + 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 prepareData = this.$prepareDataService.getPrepareData(projectData.projectDir, data.platform, { ...liveSyncInfo, watch: !liveSyncInfo.skipWatcher }); try { - if (data.hasNativeChanges) { + const rebuiltInfo = this.rebuiltInformation[platformData.platformNameLowerCase] && (this.$mobileHelper.isAndroidPlatform(platformData.platformNameLowerCase) || this.rebuiltInformation[platformData.platformNameLowerCase].isEmulator === device.isEmulator); + if (data.hasNativeChanges && !rebuiltInfo) { await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); await deviceDescriptor.buildAction(); + this.rebuiltInformation[platformData.platformNameLowerCase] = { isEmulator: device.isEmulator, platform: platformData.platformNameLowerCase, packageFilePath: null }; } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; From bb3486f67b82a7fadd632ed4786ecc3184f738a5 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 17 Jun 2019 08:22:42 +0300 Subject: [PATCH 089/102] fix: fix deploy on iOS devices --- lib/controllers/deploy-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index 7f572f3fd5..f988ee9da5 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -10,7 +10,7 @@ export class DeployController { const { buildData, deviceDescriptors } = data; const executeAction = async (device: Mobile.IDevice) => { - await this.$buildController.prepareAndBuild(buildData); + await this.$buildController.prepareAndBuild({ ...buildData, buildForDevice: !device.isEmulator }); await this.$deviceInstallAppService.installOnDevice(device, buildData); }; From 896f959eb209a9ebd8e5dd92f4773152e176b75a Mon Sep 17 00:00:00 2001 From: fatme Date: Sat, 15 Jun 2019 18:33:57 +0300 Subject: [PATCH 090/102] fix: respect skipWatcher option when setting a value to watch option When `tns run android --release --env.snapshot` command is executed, `{ skipWatcher: true }` option is provided. On the other side watch option has default true value so as a result we have { skipWatcher: true and watch: true }. This led to the problem that release run is started in watch mode. As the watch option is true, NativeScript CLI starts webpack in watch mode. This led to the problem that webpack reports emitted files before the snapshot generation is completed. (note: there is an another issue inside nativescript-dev-webpack plugin itself about the way how emitted files are reported - it should be fixed there.) As a result, an async issue arise between snapshot generation and gradle build and the following error is thrown: ``` Warning: The static binding generator will generate extend from:vendor.js implementation Exception in thread "main" java.io.IOException: File already exists. This may lead to undesired behavior. Please change the name of one of the extended classes. File: ./platforms/android/app/src/main/java/com/tns/FragmentClass.java Class: com.tns.FragmentClass at org.nativescript.staticbindinggenerator.Generator.writeBindings(Generator.java:112) at org.nativescript.staticbindinggenerator.Main.main(Main.java:48) ``` --- lib/helpers/livesync-command-helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 6e4811c591..e2535f295e 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -175,7 +175,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { sdk: this.$options.sdk }); - const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, platform, { ...this.$options.argv, clean: true, skipWatcher: true }); + const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, platform, { ...this.$options.argv, clean: true, watch: false }); await this.$deployController.deploy({ buildData, From 23a9fa2d4d95c76e9c66d8d8654d906426336c07 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 17 Jun 2019 23:08:29 +0300 Subject: [PATCH 091/102] fix: fix platform clean when there is a platform with specific version already added To reproduce: 1. `tns platform add ios@5.1.0` 2. `tns platform clean ios` Expected output: Platform ios successfully removed. Copying template files... Platform ios successfully added. v5.1.0 Actual output: Platform ios successfully removed. Copying template files... Platform ios successfully added. v5.4.2 --- lib/helpers/platform-command-helper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/helpers/platform-command-helper.ts b/lib/helpers/platform-command-helper.ts index 106d6034c4..c532c83f9f 100644 --- a/lib/helpers/platform-command-helper.ts +++ b/lib/helpers/platform-command-helper.ts @@ -43,9 +43,10 @@ export class PlatformCommandHelper implements IPlatformCommandHelper { public async cleanPlatforms(platforms: string[], projectData: IProjectData, framworkPath: string): Promise { for (const platform of platforms) { + const version: string = this.getCurrentPlatformVersion(platform, projectData); + await this.removePlatforms([platform], projectData); - const version: string = this.getCurrentPlatformVersion(platform, projectData); const platformParam = version ? `${platform}@${version}` : platform; await this.addPlatforms([platformParam], projectData, framworkPath); } From 1b21b95f55e8fef0d39566c4d76f5ea968568df9 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 17 Jun 2019 23:24:15 +0300 Subject: [PATCH 092/102] fix: fix platform list command NativeScript CLI returns that there are no added platforms when iOS platform is already added as the comparison is case sensitive. --- lib/helpers/platform-command-helper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/helpers/platform-command-helper.ts b/lib/helpers/platform-command-helper.ts index c532c83f9f..18e48a280b 100644 --- a/lib/helpers/platform-command-helper.ts +++ b/lib/helpers/platform-command-helper.ts @@ -104,7 +104,8 @@ export class PlatformCommandHelper implements IPlatformCommandHelper { } const subDirs = this.$fs.readDirectory(projectData.platformsDir); - return _.filter(subDirs, p => this.$mobileHelper.platformNames.indexOf(p) > -1); + const platforms = this.$mobileHelper.platformNames.map(p => p.toLowerCase()); + return _.filter(subDirs, p => platforms.indexOf(p) > -1); } public getAvailablePlatforms(projectData: IProjectData): string[] { From 6a565cf1b4065fda88bee194b760fd0e7944e412 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 18 Jun 2019 08:12:28 +0300 Subject: [PATCH 093/102] fix: fix the workflow of preview-sync hook The `kinvey-sdk` plugin relies on `preview-sync` hook and mainly on platform property from `hookArgs`. This PR changes the workflow of `preview-sync` hook and makes it the same as in NativeScript v5.4.0. In NativeScript v5.4.0 the hook is executed when pubnub reports the device that the QR code is scanned with. This way we're able to get the platform from device and adds it to the `hookArgs` passed to the hook. --- lib/controllers/preview-app-controller.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts index f9af93ffa0..9cf26564d8 100644 --- a/lib/controllers/preview-app-controller.ts +++ b/lib/controllers/preview-app-controller.ts @@ -2,7 +2,7 @@ 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, hook } from "../common/helpers"; +import { stringify } from "../common/helpers"; import { HmrConstants } from "../common/constants"; import { EventEmitter } from "events"; import { PrepareDataService } from "../services/prepare-data-service"; @@ -27,7 +27,6 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon private $prepareDataService: PrepareDataService ) { super(); } - @hook("preview-sync") public async startPreview(data: IPreviewAppLiveSyncData): Promise { await this.previewCore(data); @@ -61,6 +60,8 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon }); } + await this.$hooksService.executeBeforeHooks("preview-sync", { ...data, platform: device.platform }); + if (data.useHotModuleReload) { this.$hmrStatusService.attachToHmrStatusEvent(); } From 2f214b6dd04d9f9503359f62f730cdda11a6e04c Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 18 Jun 2019 10:20:45 +0300 Subject: [PATCH 094/102] fix: copy Android App_Resources only once Currently, in case the Android's App_Resources structure is migrated, we copy the files twice. Fix this by setting correct folders to copy the files. --- lib/services/android-project-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index e96984a368..e6e8c362e6 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -289,11 +289,11 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.$fs.ensureDirectoryExists(platformsAppResourcesPath); - this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "*"), path.dirname(platformsAppResourcesPath)); const appResourcesDirStructureHasMigrated = this.$androidResourcesMigrationService.hasMigrated(projectAppResourcesPath); if (appResourcesDirStructureHasMigrated) { - this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "src", "*"), platformsAppResourcesPath); + this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, constants.SRC_DIR, "*"), platformsAppResourcesPath); } else { + this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "*"), platformsAppResourcesPath); // https://github.com/NativeScript/android-runtime/issues/899 // App_Resources/Android/libs is reserved to user's aars and jars, but they should not be copied as resources this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, "libs")); From b010e000cd0aa00b11cfff4f0656bc107b4fbc9b Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 18 Jun 2019 12:15:47 +0300 Subject: [PATCH 095/102] fix: show warning when --env.snapshot is provided for debug builds --- .../webpack/webpack-compiler-service.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index a91a5481e6..0ed6917848 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -1,5 +1,6 @@ import * as path from "path"; import * as child_process from "child_process"; +import * as os from "os"; import { EventEmitter } from "events"; import { performanceLog } from "../../common/decorators"; import { hook } from "../../common/helpers"; @@ -12,7 +13,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp private $childProcess: IChildProcess, public $hooksService: IHooksService, private $logger: ILogger, - private $pluginsService: IPluginsService + private $pluginsService: IPluginsService, + private $mobileHelper: Mobile.IMobileHelper ) { super(); } public async compileWithWatch(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { @@ -103,7 +105,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp @hook('prepareJSApp') private async startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { const envData = this.buildEnvData(platformData.platformNameLowerCase, projectData, prepareData); - const envParams = this.buildEnvCommandLineParams(envData, platformData); + const envParams = this.buildEnvCommandLineParams(envData, platformData, prepareData); await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); @@ -156,13 +158,16 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return envData; } - private buildEnvCommandLineParams(envData: any, platformData: IPlatformData) { + private buildEnvCommandLineParams(envData: any, platformData: IPlatformData, prepareData: IPrepareData) { const envFlagNames = Object.keys(envData); - // const snapshotEnvIndex = envFlagNames.indexOf("snapshot"); - // if (snapshotEnvIndex > -1 && !utils.shouldSnapshot(config)) { - // logSnapshotWarningMessage($logger); - // envFlagNames.splice(snapshotEnvIndex, 1); - // } + const snapshotEnvIndex = envFlagNames.indexOf("snapshot"); + const shouldSnapshot = prepareData.release && os.type() !== "Windows_NT" && this.$mobileHelper.isAndroidPlatform(platformData.normalizedPlatformName); + if (snapshotEnvIndex > -1 && !shouldSnapshot) { + this.$logger.warn("Stripping the snapshot flag. " + + "Bear in mind that snapshot is only available in release builds and " + + "is NOT available on Windows systems."); + envFlagNames.splice(snapshotEnvIndex, 1); + } const args: any[] = []; envFlagNames.map(item => { From 1eb3496b6a2e84a3c62e49b2fc65261411472049 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 18 Jun 2019 12:36:41 +0300 Subject: [PATCH 096/102] chore: fix PR comments --- lib/services/webpack/webpack-compiler-service.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 0ed6917848..693345ae84 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -1,6 +1,5 @@ import * as path from "path"; import * as child_process from "child_process"; -import * as os from "os"; import { EventEmitter } from "events"; import { performanceLog } from "../../common/decorators"; import { hook } from "../../common/helpers"; @@ -12,6 +11,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp constructor( private $childProcess: IChildProcess, public $hooksService: IHooksService, + public $hostInfo: IHostInfo, private $logger: ILogger, private $pluginsService: IPluginsService, private $mobileHelper: Mobile.IMobileHelper @@ -160,13 +160,12 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp private buildEnvCommandLineParams(envData: any, platformData: IPlatformData, prepareData: IPrepareData) { const envFlagNames = Object.keys(envData); - const snapshotEnvIndex = envFlagNames.indexOf("snapshot"); - const shouldSnapshot = prepareData.release && os.type() !== "Windows_NT" && this.$mobileHelper.isAndroidPlatform(platformData.normalizedPlatformName); - if (snapshotEnvIndex > -1 && !shouldSnapshot) { + const shouldSnapshot = prepareData.release && !this.$hostInfo.isWindows && this.$mobileHelper.isAndroidPlatform(platformData.normalizedPlatformName); + if (envData && envData.snapshot && !shouldSnapshot) { this.$logger.warn("Stripping the snapshot flag. " + "Bear in mind that snapshot is only available in release builds and " + "is NOT available on Windows systems."); - envFlagNames.splice(snapshotEnvIndex, 1); + envFlagNames.splice(envFlagNames.indexOf("snapshot"), 1); } const args: any[] = []; From 22eecad37f7257e484cf802f47fdd121eb0be825 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 18 Jun 2019 13:54:52 +0300 Subject: [PATCH 097/102] fix: enable passing iOS codesigning options to LiveSync operations Enable passing iOS Code signing options to operations like run/debug, etc. Currently the provision and teamId options are not passed to the controllers, so we do not use them during project preparation. To resolve this, include the buildData in the deviceDescriptors - this way we'll have all the codesigning data required for the specific device and we'll also allow having different codesign options for different devices. Also remove outputPath from deviceDescriptors as we can use it directly from the buildData. --- lib/controllers/debug-controller.ts | 2 +- lib/controllers/run-controller.ts | 22 +++++++++++++++++----- lib/definitions/livesync.d.ts | 9 +++++++-- lib/definitions/prepare.d.ts | 6 +++--- lib/helpers/deploy-command-helper.ts | 2 +- lib/helpers/livesync-command-helper.ts | 2 +- test/controllers/run-controller.ts | 4 ++-- 7 files changed, 32 insertions(+), 15 deletions(-) diff --git a/lib/controllers/debug-controller.ts b/lib/controllers/debug-controller.ts index b4ac68f1c3..6b079294d8 100644 --- a/lib/controllers/debug-controller.ts +++ b/lib/controllers/debug-controller.ts @@ -128,7 +128,7 @@ export class DebugController extends EventEmitter implements IDebugController { const attachDebuggerData: IAttachDebuggerData = { deviceIdentifier, isEmulator: currentDeviceInstance.isEmulator, - outputPath: deviceDescriptor.outputPath, + outputPath: deviceDescriptor.buildData.outputPath, platform: currentDeviceInstance.deviceInfo.platform, projectDir, debugOptions diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index 251bfd5016..29606a480e 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -8,7 +8,6 @@ export class RunController extends EventEmitter implements IRunController { constructor( protected $analyticsService: IAnalyticsService, - private $buildDataService: IBuildDataService, private $buildController: IBuildController, private $debugController: IDebugController, private $deviceInstallAppService: IDeviceInstallAppService, @@ -191,7 +190,7 @@ export class RunController extends EventEmitter implements IRunController { projectDir: projectData.projectDir, deviceIdentifier, debugOptions: deviceDescriptor.debugOptions, - outputPath: deviceDescriptor.outputPath + outputPath: deviceDescriptor.buildData.outputPath }; this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); } @@ -242,9 +241,16 @@ export class RunController extends EventEmitter implements IRunController { const deviceAction = async (device: Mobile.IDevice) => { const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); const platformData = this.$platformsDataService.getPlatformData(device.deviceInfo.platform, projectData); - 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, buildForDevice: !device.isEmulator }); + const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, device.deviceInfo.platform, + { + ...liveSyncInfo, + watch: !liveSyncInfo.skipWatcher, + nativePrepare: { skipNativePrepare: !!deviceDescriptor.skipNativePrepare }, + ...deviceDescriptor.buildData, + }); + const prepareResultData = await this.$prepareController.prepare(prepareData); + const buildData = { ...deviceDescriptor.buildData, buildForDevice: !device.isEmulator }; try { let packageFilePath: string = null; @@ -312,7 +318,13 @@ export class RunController extends EventEmitter implements IRunController { 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 prepareData = this.$prepareDataService.getPrepareData(projectData.projectDir, data.platform, { ...liveSyncInfo, watch: !liveSyncInfo.skipWatcher }); + const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, device.deviceInfo.platform, + { + ...liveSyncInfo, + watch: !liveSyncInfo.skipWatcher, + nativePrepare: { skipNativePrepare: !!deviceDescriptor.skipNativePrepare }, + ...deviceDescriptor.buildData, + }); try { const rebuiltInfo = this.rebuiltInformation[platformData.platformNameLowerCase] && (this.$mobileHelper.isAndroidPlatform(platformData.platformNameLowerCase) || this.rebuiltInformation[platformData.platformNameLowerCase].isEmulator === device.isEmulator); diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index dd72481d34..b224149091 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -48,7 +48,7 @@ declare global { /** * Describes information for LiveSync on a device. */ - interface ILiveSyncDeviceDescriptor extends IOptionalOutputPath, IOptionalDebuggingOptions { + interface ILiveSyncDeviceDescriptor extends IOptionalDebuggingOptions { /** * Device identifier. */ @@ -68,6 +68,11 @@ declare global { * Whether debugging has been enabled for this device or not */ debuggingEnabled?: boolean; + + /** + * Describes the data used for building the application + */ + buildData: IBuildData; } /** @@ -80,7 +85,7 @@ declare global { * Defines if the watcher should be skipped. If not passed, fs.Watcher will be started. */ skipWatcher?: boolean; - + /** * Forces a build before the initial livesync. */ diff --git a/lib/definitions/prepare.d.ts b/lib/definitions/prepare.d.ts index 15c6c981b9..c8ca2e0012 100644 --- a/lib/definitions/prepare.d.ts +++ b/lib/definitions/prepare.d.ts @@ -9,13 +9,13 @@ declare global { watch?: boolean; } - interface IiOSPrepareData extends IPrepareData { + interface IiOSCodeSigningData { teamId: string; provision: string; mobileProvisionData: any; } - interface IAndroidPrepareData extends IPrepareData { } + interface IiOSPrepareData extends IPrepareData, IiOSCodeSigningData { } interface IPrepareDataService { getPrepareData(projectDir: string, platform: string, data: any): IPrepareData; @@ -34,4 +34,4 @@ declare global { interface IPrepareNativePlatformService { prepareNativePlatform(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise; } -} \ No newline at end of file +} diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index 2fa86c18c6..c010c96522 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -43,8 +43,8 @@ export class DeployCommandHelper { buildAction, debuggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], debugOptions: this.$options, - outputPath, skipNativePrepare: additionalOptions && additionalOptions.skipNativePrepare, + buildData }; return info; diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index e2535f295e..8c4157a67f 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -79,8 +79,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { buildAction, debuggingEnabled: additionalOptions && additionalOptions.deviceDebugMap && additionalOptions.deviceDebugMap[d.deviceInfo.identifier], debugOptions: this.$options, - outputPath, skipNativePrepare: additionalOptions && additionalOptions.skipNativePrepare, + buildData }; return info; diff --git a/test/controllers/run-controller.ts b/test/controllers/run-controller.ts index 84ff0a9774..9dc2e674da 100644 --- a/test/controllers/run-controller.ts +++ b/test/controllers/run-controller.ts @@ -18,9 +18,9 @@ const projectDir = "/path/to/my/projecDir"; const buildOutputPath = `${projectDir}/platform/ios/build/myproject.app`; const iOSDevice = { deviceInfo: { identifier: "myiOSDevice", platform: "ios" } }; -const iOSDeviceDescriptor = { identifier: "myiOSDevice", buildAction: async () => buildOutputPath }; +const iOSDeviceDescriptor = { identifier: "myiOSDevice", buildAction: async () => buildOutputPath, buildData: {} }; const androidDevice = { deviceInfo: { identifier: "myAndroidDevice", platform: "android" } }; -const androidDeviceDescriptor = { identifier: "myAndroidDevice", buildAction: async () => buildOutputPath }; +const androidDeviceDescriptor = { identifier: "myAndroidDevice", buildAction: async () => buildOutputPath, buildData: {} }; const map: IDictionary<{ device: Mobile.IDevice, descriptor: ILiveSyncDeviceDescriptor }> = { myiOSDevice: { From 065bd141b02d9b9851a388d3750c082edee43816 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 18 Jun 2019 17:53:06 +0300 Subject: [PATCH 098/102] fix: initialize nativeChanged with true when checking for changes --- lib/services/project-changes-service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 6df11e4140..c539525cc7 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -183,6 +183,7 @@ export class ProjectChangesService implements IProjectChangesService { this._changesInfo = this._changesInfo || new ProjectChangesInfo(); this._changesInfo.appResourcesChanged = true; this._changesInfo.configChanged = true; + this._changesInfo.nativeChanged = true; return true; } From 4970d218714f5b1086ce18f221e05ee363718170 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 19 Jun 2019 09:05:53 +0300 Subject: [PATCH 099/102] fix: install app on device when there is native change during livesync --- lib/controllers/run-controller.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index 29606a480e..6e94280e2e 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -4,7 +4,7 @@ import { cache, performanceLog } from "../common/decorators"; import { EventEmitter } from "events"; export class RunController extends EventEmitter implements IRunController { - private rebuiltInformation: IDictionary = {}; + private rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = { }; constructor( protected $analyticsService: IAnalyticsService, @@ -327,11 +327,15 @@ export class RunController extends EventEmitter implements IRunController { }); try { - const rebuiltInfo = this.rebuiltInformation[platformData.platformNameLowerCase] && (this.$mobileHelper.isAndroidPlatform(platformData.platformNameLowerCase) || this.rebuiltInformation[platformData.platformNameLowerCase].isEmulator === device.isEmulator); - if (data.hasNativeChanges && !rebuiltInfo) { - await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); - await deviceDescriptor.buildAction(); - this.rebuiltInformation[platformData.platformNameLowerCase] = { isEmulator: device.isEmulator, platform: platformData.platformNameLowerCase, packageFilePath: null }; + if (data.hasNativeChanges) { + const rebuiltInfo = this.rebuiltInformation[platformData.platformNameLowerCase] && (this.$mobileHelper.isAndroidPlatform(platformData.platformNameLowerCase) || this.rebuiltInformation[platformData.platformNameLowerCase].isEmulator === device.isEmulator); + if (!rebuiltInfo) { + await this.$prepareNativePlatformService.prepareNativePlatform(platformData, projectData, prepareData); + await deviceDescriptor.buildAction(); + this.rebuiltInformation[platformData.platformNameLowerCase] = { isEmulator: device.isEmulator, platform: platformData.platformNameLowerCase, packageFilePath: null }; + } + + this.$deviceInstallAppService.installOnDevice(device, deviceDescriptor.buildData, this.rebuiltInformation[platformData.platformNameLowerCase].packageFilePath); } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash; From 745c8cba5d5b6c1a893528c92a9c2701fb6ef3bf Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 19 Jun 2019 09:21:32 +0300 Subject: [PATCH 100/102] fix: fix `tns deploy ios --justlaunch` when there are connected device and simulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NativeScript CLI doesn't respect correctly the built application package when there are connected device and simulator. That is the reason why `tns deploy ios --path TestApp --provision NativeScriptDevProfile –justlaunch` throws the following error: ENOENT: no such file or directory, scandir '/Users/mivanova/Projects/TestApp/platforms/ios/build/Debug-iphonesimulator' --- lib/controllers/deploy-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index f988ee9da5..1574926ea4 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -10,8 +10,8 @@ export class DeployController { const { buildData, deviceDescriptors } = data; const executeAction = async (device: Mobile.IDevice) => { - await this.$buildController.prepareAndBuild({ ...buildData, buildForDevice: !device.isEmulator }); - await this.$deviceInstallAppService.installOnDevice(device, buildData); + const packageFilePath = await this.$buildController.prepareAndBuild({ ...buildData, buildForDevice: !device.isEmulator }); + await this.$deviceInstallAppService.installOnDevice(device, { ...buildData, buildForDevice: !device.isEmulator }, packageFilePath); }; await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); From 39c54c33a2df56d215756c0c84541e4196a5c895 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 19 Jun 2019 09:59:20 +0300 Subject: [PATCH 101/102] fix: set correct nativePrepare value As buildData has nativePrepare property, when using the runController. Set the nativePrepare last in the merged object. --- lib/controllers/run-controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index 6e94280e2e..283b49bed2 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -244,9 +244,9 @@ export class RunController extends EventEmitter implements IRunController { const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, device.deviceInfo.platform, { ...liveSyncInfo, - watch: !liveSyncInfo.skipWatcher, - nativePrepare: { skipNativePrepare: !!deviceDescriptor.skipNativePrepare }, ...deviceDescriptor.buildData, + nativePrepare: { skipNativePrepare: !!deviceDescriptor.skipNativePrepare }, + watch: !liveSyncInfo.skipWatcher, }); const prepareResultData = await this.$prepareController.prepare(prepareData); @@ -321,9 +321,9 @@ export class RunController extends EventEmitter implements IRunController { const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, device.deviceInfo.platform, { ...liveSyncInfo, - watch: !liveSyncInfo.skipWatcher, - nativePrepare: { skipNativePrepare: !!deviceDescriptor.skipNativePrepare }, ...deviceDescriptor.buildData, + nativePrepare: { skipNativePrepare: !!deviceDescriptor.skipNativePrepare }, + watch: !liveSyncInfo.skipWatcher, }); try { From 53641b8af82ec6b94ebae776cd25950d9b652b26 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 19 Jun 2019 10:17:35 +0300 Subject: [PATCH 102/102] fix: add missing await --- lib/controllers/run-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index 283b49bed2..df5ec94d47 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -335,7 +335,7 @@ export class RunController extends EventEmitter implements IRunController { this.rebuiltInformation[platformData.platformNameLowerCase] = { isEmulator: device.isEmulator, platform: platformData.platformNameLowerCase, packageFilePath: null }; } - this.$deviceInstallAppService.installOnDevice(device, deviceDescriptor.buildData, this.rebuiltInformation[platformData.platformNameLowerCase].packageFilePath); + await this.$deviceInstallAppService.installOnDevice(device, deviceDescriptor.buildData, this.rebuiltInformation[platformData.platformNameLowerCase].packageFilePath); } const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash;