From 3574f06db0e58adfd1c8d19f6fcfd08b5f03d760 Mon Sep 17 00:00:00 2001 From: fatme Date: Wed, 7 Mar 2018 16:25:35 +0200 Subject: [PATCH 1/7] Getting started improvements When build or run command is executed, checks if the environment is properly configured for executing local builds. If it is not properly configured, {N} CLI shows a prompt and user is able to choice if to run setup script or use cloud builds. If cloud builds option is selected, {N} CLI installs the nativescript-cloud extension and prints that user can use the $ tns login command to log in with his/her account and then $ tns cloud run/build android/ios command to build his/her app in the cloud. If setup script option is selected, {N} CLI runs the setup script to try and automatically configure the environment. If the environment is not properly configured after executing the setup script, {N} CLI shows a prompt and user is able to choice if to use cloud builds or manually setup the environment. If the manually setup option is selected, {N} CLI prints: "To be able to build for android/ios, verify that your environment is configured according to the system requirements described at https://docs.nativescript.org/start/ns-setup-os-x.}" --- lib/android-tools-info.ts | 118 +---------- lib/bootstrap.ts | 1 + lib/commands/build.ts | 30 ++- lib/commands/run.ts | 5 +- lib/common | 2 +- lib/definitions/platform.d.ts | 9 + lib/services/doctor-service.ts | 48 ++++- .../platform-environment-requirements.ts | 95 +++++++++ test/android-tools-info.ts | 62 ------ .../platform-environment-requirements.ts | 189 ++++++++++++++++++ 10 files changed, 373 insertions(+), 186 deletions(-) create mode 100644 lib/services/platform-environment-requirements.ts create mode 100644 test/services/platform-environment-requirements.ts diff --git a/lib/android-tools-info.ts b/lib/android-tools-info.ts index 51e988be7d..0f86105ef5 100644 --- a/lib/android-tools-info.ts +++ b/lib/android-tools-info.ts @@ -1,8 +1,7 @@ import * as path from "path"; import * as semver from "semver"; -import { EOL } from "os"; import { cache } from "./common/decorators"; -import { appendZeroesToVersion } from './common/helpers'; +import { androidToolsInfo } from "nativescript-doctor"; export class AndroidToolsInfo implements IAndroidToolsInfo { private static ANDROID_TARGET_PREFIX = "android"; @@ -10,8 +9,6 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { private static MIN_REQUIRED_COMPILE_TARGET = 22; private static REQUIRED_BUILD_TOOLS_RANGE_PREFIX = ">=23"; private static VERSION_REGEX = /((\d+\.){2}\d+)/; - private static MIN_JAVA_VERSION = "1.8.0"; - private static MAX_JAVA_VERSION = "1.9.0"; private showWarningsAsErrors: boolean; private toolsInfo: IAndroidToolsInfoData; @@ -20,10 +17,8 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { return process.env["ANDROID_HOME"]; } - constructor(private $childProcess: IChildProcess, - private $errors: IErrors, + constructor(private $errors: IErrors, private $fs: IFileSystem, - private $hostInfo: IHostInfo, private $logger: ILogger, private $options: IOptions, protected $staticConfig: Config.IStaticConfig) { } @@ -50,39 +45,8 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { this.showWarningsAsErrors = options && options.showWarningsAsErrors; const toolsInfoData = this.getToolsInfo(); const isAndroidHomeValid = this.validateAndroidHomeEnvVariable(); - if (!toolsInfoData.compileSdkVersion) { - this.printMessage(`Cannot find a compatible Android SDK for compilation. To be able to build for Android, install Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later.`, - `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage your Android SDK versions.`); - detectedErrors = true; - } - - if (!toolsInfoData.buildToolsVersion) { - const buildToolsRange = this.getBuildToolsRange(); - const versionRangeMatches = buildToolsRange.match(/^.*?([\d\.]+)\s+.*?([\d\.]+)$/); - let message = `You can install any version in the following range: '${buildToolsRange}'.`; - - // Improve message in case buildToolsRange is something like: ">=22.0.0 <=22.0.0" - same numbers on both sides - if (versionRangeMatches && versionRangeMatches[1] && versionRangeMatches[2] && versionRangeMatches[1] === versionRangeMatches[2]) { - message = `You have to install version ${versionRangeMatches[1]}.`; - } - - let invalidBuildToolsAdditionalMsg = `Run \`\$ ${this.getPathToSdkManagementTool()}\` from your command-line to install required \`Android Build Tools\`.`; - if (!isAndroidHomeValid) { - invalidBuildToolsAdditionalMsg += ' In case you already have them installed, make sure `ANDROID_HOME` environment variable is set correctly.'; - } - this.printMessage("You need to have the Android SDK Build-tools installed on your system. " + message, invalidBuildToolsAdditionalMsg); - detectedErrors = true; - } - - if (!toolsInfoData.supportRepositoryVersion) { - let invalidSupportLibAdditionalMsg = `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage the Android Support Repository.`; - if (!isAndroidHomeValid) { - invalidSupportLibAdditionalMsg += ' In case you already have it installed, make sure `ANDROID_HOME` environment variable is set correctly.'; - } - this.printMessage(`You need to have Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later and the latest Android Support Repository installed on your system.`, invalidSupportLibAdditionalMsg); - detectedErrors = true; - } + detectedErrors = androidToolsInfo.validateInfo().map(warning => this.printMessage(warning.warning)).length > 0; if (options && options.validateTargetSdk) { const targetSdk = toolsInfoData.targetSdkVersion; @@ -104,44 +68,20 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { } public validateJavacVersion(installedJavacVersion: string, options?: { showWarningsAsErrors: boolean }): boolean { - let hasProblemWithJavaVersion = false; if (options) { this.showWarningsAsErrors = options.showWarningsAsErrors; } - const additionalMessage = "You will not be able to build your projects for Android." + EOL - + "To be able to build for Android, verify that you have installed The Java Development Kit (JDK) and configured it according to system requirements as" + EOL + - " described in " + this.$staticConfig.SYS_REQUIREMENTS_LINK; - - const matchingVersion = appendZeroesToVersion(installedJavacVersion || "", 3).match(AndroidToolsInfo.VERSION_REGEX); - const installedJavaCompilerVersion = matchingVersion && matchingVersion[1]; - if (installedJavaCompilerVersion) { - if (semver.lt(installedJavaCompilerVersion, AndroidToolsInfo.MIN_JAVA_VERSION)) { - hasProblemWithJavaVersion = true; - this.printMessage(`Javac version ${installedJavacVersion} is not supported. You have to install at least ${AndroidToolsInfo.MIN_JAVA_VERSION}.`, additionalMessage); - } else if (semver.gte(installedJavaCompilerVersion, AndroidToolsInfo.MAX_JAVA_VERSION)) { - hasProblemWithJavaVersion = true; - this.printMessage(`Javac version ${installedJavacVersion} is not supported. You have to install version ${AndroidToolsInfo.MIN_JAVA_VERSION}.`, additionalMessage); - } - } else { - hasProblemWithJavaVersion = true; - this.printMessage("Error executing command 'javac'. Make sure you have installed The Java Development Kit (JDK) and set JAVA_HOME environment variable.", additionalMessage); - } - - return hasProblemWithJavaVersion; + return androidToolsInfo.validateJavacVersion(installedJavacVersion).map(warning => this.printMessage(warning.warning)).length > 0; } public async getPathToAdbFromAndroidHome(): Promise { - if (this.androidHome) { - const pathToAdb = path.join(this.androidHome, "platform-tools", "adb"); - try { - await this.$childProcess.execFile(pathToAdb, ["help"]); - return pathToAdb; - } catch (err) { - // adb does not exist, so ANDROID_HOME is not set correctly - // try getting default adb path (included in CLI package) - this.$logger.trace(`Error while executing '${pathToAdb} help'. Error is: ${err.message}`); - } + try { + return androidToolsInfo.getPathToAdbFromAndroidHome(); + } catch (err) { + // adb does not exist, so ANDROID_HOME is not set correctly + // try getting default adb path (included in CLI package) + this.$logger.trace(`Error while executing '${path.join(this.androidHome, "platform-tools", "adb")} help'. Error is: ${err.message}`); } return null; @@ -153,43 +93,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { this.showWarningsAsErrors = options.showWarningsAsErrors; } - const expectedDirectoriesInAndroidHome = ["build-tools", "tools", "platform-tools", "extras"]; - let androidHomeValidationResult = true; - - if (!this.androidHome || !this.$fs.exists(this.androidHome)) { - this.printMessage("The ANDROID_HOME environment variable is not set or it points to a non-existent directory. You will not be able to perform any build-related operations for Android.", - "To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory."); - androidHomeValidationResult = false; - } else if (!_.some(expectedDirectoriesInAndroidHome.map(dir => this.$fs.exists(path.join(this.androidHome, dir))))) { - this.printMessage("The ANDROID_HOME environment variable points to incorrect directory. You will not be able to perform any build-related operations for Android.", - "To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory, " + - "where you will find `tools` and `platform-tools` directories."); - androidHomeValidationResult = false; - } - - return androidHomeValidationResult; - } - - @cache() - private getPathToSdkManagementTool(): string { - const sdkManagerName = "sdkmanager"; - let sdkManagementToolPath = sdkManagerName; - - const isAndroidHomeValid = this.validateAndroidHomeEnvVariable(); - - if (isAndroidHomeValid) { - // In case ANDROID_HOME is correct, check if sdkmanager exists and if not it means the SDK has not been updated. - // In this case user shoud use `android` from the command-line instead of sdkmanager. - const pathToSdkManager = path.join(this.androidHome, "tools", "bin", sdkManagerName); - const pathToAndroidExecutable = path.join(this.androidHome, "tools", "android"); - const pathToExecutable = this.$fs.exists(pathToSdkManager) ? pathToSdkManager : pathToAndroidExecutable; - - this.$logger.trace(`Path to Android SDK Management tool is: ${pathToExecutable}`); - - sdkManagementToolPath = pathToExecutable.replace(this.androidHome, this.$hostInfo.isWindows ? "%ANDROID_HOME%" : "$ANDROID_HOME"); - } - - return sdkManagementToolPath; + return androidToolsInfo.validateAndroidHomeEnvVariable().map(warning => this.printMessage(warning.warning)).length > 0; } private shouldGenerateTypings(): boolean { diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 4dd9ec2c74..a3078a83a0 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -152,3 +152,4 @@ $injector.require("subscriptionService", "./services/subscription-service"); $injector.require("terminalSpinnerService", "./services/terminal-spinner-service"); $injector.require('playgroundService', './services/playground-service'); +$injector.require("platformEnvironmentRequirements", "./services/platform-environment-requirements"); diff --git a/lib/commands/build.ts b/lib/commands/build.ts index e87fd05edb..af681adf7f 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -7,7 +7,9 @@ export class BuildCommandBase { protected $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $platformService: IPlatformService, - private $bundleValidatorHelper: IBundleValidatorHelper) { + private $bundleValidatorHelper: IBundleValidatorHelper, + private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { + super($projectData, $errors, $options); this.$projectData.initializeProjectData(); } @@ -44,7 +46,13 @@ export class BuildCommandBase { } } - protected validatePlatform(platform: string): void { + protected async canExecuteCore(platform: string): Promise { + const result = await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ platform: platform, cloudCommandName: "build" }); + this.validatePlatform(platform); + return result; + } + + private validatePlatform(platform: string): void { if (!this.$platformService.isPlatformSupportedForOS(platform, this.$projectData)) { this.$errors.fail(`Applications for platform ${platform} can not be built on this OS`); } @@ -62,17 +70,17 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService, - $bundleValidatorHelper: IBundleValidatorHelper) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper); + $bundleValidatorHelper: IBundleValidatorHelper, + $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $platformEnvironmentRequirements); } public async execute(args: string[]): Promise { return this.executeCore([this.$platformsData.availablePlatforms.iOS]); } - public canExecute(args: string[]): Promise { - super.validatePlatform(this.$devicePlatformsConstants.iOS); - return args.length === 0 && this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); + public async canExecute(args: string[]): Promise { + return await super.canExecuteCore(this.$devicePlatformsConstants.iOS) && args.length === 0 && this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } @@ -87,8 +95,9 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService, - $bundleValidatorHelper: IBundleValidatorHelper) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper); + $bundleValidatorHelper: IBundleValidatorHelper, + $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $platformEnvironmentRequirements); } public async execute(args: string[]): Promise { @@ -96,7 +105,8 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { - super.validatePlatform(this.$devicePlatformsConstants.Android); + await super.canExecuteCore(this.$devicePlatformsConstants.Android); + if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 38365c2a59..829ac9b59a 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -9,7 +9,8 @@ export class RunCommandBase implements ICommand { private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $errors: IErrors, private $hostInfo: IHostInfo, - private $liveSyncCommandHelper: ILiveSyncCommandHelper) { } + private $liveSyncCommandHelper: ILiveSyncCommandHelper, + private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { } public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { @@ -17,6 +18,8 @@ export class RunCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { + await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({platform: this.platform, cloudCommandName: "run"}); + if (args.length) { this.$errors.fail(ERROR_NO_VALID_SUBCOMMAND_FORMAT, "run"); } diff --git a/lib/common b/lib/common index 90db0aa6d9..f978eaf4bc 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 90db0aa6d98c9b105d2c9a982cbcbb53b375e32e +Subproject commit f978eaf4bcec7e6564246616dae2de809504efb2 diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 3edeb6af49..85158dc5db 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -365,3 +365,12 @@ interface IDeployPlatformInfo extends IPlatform, IAppFilesUpdaterOptionsComposit interface IUpdateAppOptions extends IOptionalFilesToSync, IOptionalFilesToRemove { beforeCopyAction: (sourceFiles: string[]) => void; } + +interface IPlatformEnvironmentRequirements { + checkEnvironmentRequirements(input: ICheckEnvironmentRequirementsInput): Promise; +} + +interface ICheckEnvironmentRequirementsInput { + platform: string; + cloudCommandName: string; +} \ No newline at end of file diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index 36679d0e03..0c146c72d8 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -53,17 +53,38 @@ class DoctorService implements IDoctorService { return hasWarnings; } + public async runSetupScript(): Promise { + this.$logger.out("Running the setup script to try and automatically configure your environment."); + + if (this.$hostInfo.isDarwin) { + return this.runSetupScriptCore(DoctorService.DarwinSetupScriptLocation, []); + } + + if (this.$hostInfo.isWindows) { + return this.runSetupScriptCore(DoctorService.WindowsSetupScriptExecutable, DoctorService.WindowsSetupScriptArguments); + } + } + + public async canExecuteLocalBuild(platform?: string): Promise { + let infos = await doctor.getInfos(); + if (platform) { + infos = this.filterInfosByPlatform(infos, platform); + } + this.printInfosCore(infos); + + const warnings = this.filterInfosByType(infos, constants.WARNING_TYPE_NAME); + return warnings.length === 0; + } + private async promptForDocs(link: string): Promise { if (await this.$prompter.confirm("Do you want to visit the official documentation?", () => helpers.isInteractive())) { this.$opener.open(link); } } - private async promptForHelpCore(link: string, commandName: string, commandArguments: string[]): Promise { - await this.promptForDocs(link); - + private async promptForSetupScript(executablePath: string, setupScriptArgs: string[]): Promise { if (await this.$prompter.confirm("Do you want to run the setup script?", () => helpers.isInteractive())) { - await this.$childProcess.spawnFromEvent(commandName, commandArguments, "close", { stdio: "inherit" }); + await this.runSetupScriptCore(executablePath, setupScriptArgs); } } @@ -77,6 +98,15 @@ class DoctorService implements IDoctorService { } } + private async promptForHelpCore(link: string, setupScriptExecutablePath: string, setupScriptArgs: string[]): Promise { + await this.promptForDocs(link); + await this.promptForSetupScript(setupScriptExecutablePath, setupScriptArgs); + } + + private async runSetupScriptCore(executablePath: string, setupScriptArgs: string[]): Promise { + return this.$childProcess.spawnFromEvent(executablePath, setupScriptArgs, "close", { stdio: "inherit" }); + } + private printPackageManagerTip() { if (this.$hostInfo.isWindows) { this.$logger.out("TIP: To avoid setting up the necessary environment variables, you can use the chocolatey package manager to install the Android SDK and its dependencies." + EOL); @@ -98,7 +128,15 @@ class DoctorService implements IDoctorService { const spinner = this.$terminalSpinnerService.createSpinner(); spinner.text = `${info.message.yellow} ${EOL} ${info.additionalInformation} ${EOL}`; spinner.fail(); - }); + }); + } + + private filterInfosByPlatform(infos: NativeScriptDoctor.IInfo[], platform: string): NativeScriptDoctor.IInfo[] { + return infos.filter(info => _.includes(info.platforms, platform)); + } + + private filterInfosByType(infos: NativeScriptDoctor.IInfo[], type: string): NativeScriptDoctor.IInfo[] { + return infos.filter(info => info.type === type); } } $injector.register("doctorService", DoctorService); diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts new file mode 100644 index 0000000000..e04cd9ee0d --- /dev/null +++ b/lib/services/platform-environment-requirements.ts @@ -0,0 +1,95 @@ +export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequirements { + constructor(private $doctorService: IDoctorService, + private $errors: IErrors, + private $extensibilityService: IExtensibilityService, + private $logger: ILogger, + private $prompter: IPrompter, + private $staticConfig: IStaticConfig) { } + + public static CLOUD_BUILDS_OPTION_NAME = "Install the nativescript-cloud extension to configure your environment for cloud builds"; + public static SETUP_SCRIPT_OPTION_NAME = "Run the setup script to try to automatically configure your environment for local builds"; + public static MANUALLY_SETUP_OPTION_NAME = "Manually setup your environment for local builds"; + public static BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME = "Install the nativescript-cloud extension and run the setup script to try to automatically configure your environment"; + public static NOT_CONFIGURED_ENV_MESSAGE = "Your environment is not configured properly and you will not be able to execute local builds. You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. To continue, choose one of the following options: "; + public static NOT_CONFIGURED_ENV_AFTER_SETUP_SCRIPT_MESSAGE = "The setup script was not able to configure your environment for local builds. To execute local builds, you have to set up your environment manually. To continue, choose one of the following options:"; + + public async checkEnvironmentRequirements(data: ICheckEnvironmentRequirementsInput): Promise { + const canExecute = await this.$doctorService.canExecuteLocalBuild(data.platform); + if (!canExecute) { + const selectedOption = await this.$prompter.promptForChoice(PlatformEnvironmentRequirements.NOT_CONFIGURED_ENV_MESSAGE, [ + PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME, + PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME, + PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME, + PlatformEnvironmentRequirements.BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME + ]); + + if (selectedOption === PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME) { + await this.processCloudBuilds(data); + } + + if (selectedOption === PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME) { + await this.$doctorService.runSetupScript(); + + if (await this.$doctorService.canExecuteLocalBuild(data.platform)) { + return true; + } + + const option = await this.$prompter.promptForChoice(PlatformEnvironmentRequirements.NOT_CONFIGURED_ENV_AFTER_SETUP_SCRIPT_MESSAGE, [ + PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME, + PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME + ]); + + if (option === PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME) { + await this.processCloudBuilds(data); + } + + if (option === PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME) { + this.processManuallySetup(data); + } + } + + if (selectedOption === PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME) { + this.processManuallySetup(data); + } + + if (selectedOption === PlatformEnvironmentRequirements.BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME) { + await this.processBothCloudBuildsAndSetupScript(data); + if (await this.$doctorService.canExecuteLocalBuild(data.platform)) { + return true; + } + + this.processManuallySetup(data); + } + } + + return true; + } + + private async processCloudBuilds(data: ICheckEnvironmentRequirementsInput): Promise { + await this.processCloudBuildsCore(data); + this.fail(`Use the $ tns login command to log in with your account and then $ tns cloud ${data.cloudCommandName.toLowerCase()} ${data.platform.toLowerCase()} command to build your app in the cloud.`); + } + + private async processCloudBuildsCore(data: ICheckEnvironmentRequirementsInput): Promise { + return this.$extensibilityService.installExtension("nativescript-cloud"); + } + + private processManuallySetup(data: ICheckEnvironmentRequirementsInput): void { + this.fail(`To be able to build for ${data.platform}, verify that your environment is configured according to the system requirements described at ${this.$staticConfig.SYS_REQUIREMENTS_LINK}`); + } + + private async processBothCloudBuildsAndSetupScript(data: ICheckEnvironmentRequirementsInput): Promise { + try { + await this.processCloudBuildsCore(data); + } catch (e) { + this.$logger.trace(`Error while installing nativescript-cloud extension. ${e.message}.`); + } + + await this.$doctorService.runSetupScript(); + } + + private fail(message: string): void { + this.$errors.fail({ formatStr: message, suppressCommandHelp: true, printOnStdout: true }); + } +} +$injector.register("platformEnvironmentRequirements", PlatformEnvironmentRequirements); diff --git a/test/android-tools-info.ts b/test/android-tools-info.ts index f548462455..d8fb69c732 100644 --- a/test/android-tools-info.ts +++ b/test/android-tools-info.ts @@ -1,24 +1,13 @@ import { Yok } from "../lib/common/yok"; import { AndroidToolsInfo } from "../lib/android-tools-info"; -import { EOL } from "os"; import { format } from "util"; import { assert } from "chai"; -interface ITestData { - javacVersion: string; - expectedResult: boolean; - warnings?: string[]; -} - describe("androidToolsInfo", () => { let loggedWarnings: string[] = []; let loggedMarkdownMessages: string[] = []; const sysRequirementsLink = ""; - const additionalMsg = "You will not be able to build your projects for Android." + EOL - + "To be able to build for Android, verify that you have installed The Java Development Kit (JDK) and configured it according to system requirements as" + EOL + - " described in " + sysRequirementsLink; - beforeEach(() => { loggedWarnings = []; loggedMarkdownMessages = []; @@ -52,57 +41,6 @@ describe("androidToolsInfo", () => { }; describe("validateJavacVersion", () => { - const testData: ITestData[] = [ - { - javacVersion: "1.8.0", - expectedResult: false - }, - { - javacVersion: "1.8.0_152", - expectedResult: false - }, - { - javacVersion: "9", - expectedResult: true, - warnings: ["Javac version 9 is not supported. You have to install version 1.8.0."] - }, - { - javacVersion: "9.0.1", - expectedResult: true, - warnings: ["Javac version 9.0.1 is not supported. You have to install version 1.8.0."] - }, - { - javacVersion: "1.7.0", - expectedResult: true, - warnings: ["Javac version 1.7.0 is not supported. You have to install at least 1.8.0."] - }, - { - javacVersion: "1.7.0_132", - expectedResult: true, - warnings: ["Javac version 1.7.0_132 is not supported. You have to install at least 1.8.0."] - }, - { - javacVersion: null, - expectedResult: true, - warnings: ["Error executing command 'javac'. Make sure you have installed The Java Development Kit (JDK) and set JAVA_HOME environment variable."] - } - ]; - - _.each(testData, ({ javacVersion, expectedResult, warnings }) => { - it(`returns ${expectedResult} when version is ${javacVersion}`, () => { - const testInjector = createTestInjector(); - const androidToolsInfo = testInjector.resolve(AndroidToolsInfo); - assert.deepEqual(androidToolsInfo.validateJavacVersion(javacVersion), expectedResult); - if (warnings && warnings.length) { - assert.deepEqual(loggedWarnings, warnings); - assert.deepEqual(loggedMarkdownMessages, [additionalMsg]); - } else { - assert.equal(loggedWarnings.length, 0); - assert.equal(loggedMarkdownMessages.length, 0); - } - }); - }); - it("throws error when passing showWarningsAsErrors to true and javac is not installed", () => { const testInjector = createTestInjector(); const androidToolsInfo = testInjector.resolve(AndroidToolsInfo); diff --git a/test/services/platform-environment-requirements.ts b/test/services/platform-environment-requirements.ts new file mode 100644 index 0000000000..80b8dc7be3 --- /dev/null +++ b/test/services/platform-environment-requirements.ts @@ -0,0 +1,189 @@ +import { Yok } from "../../lib/common/yok"; +import { PlatformEnvironmentRequirements } from '../../lib/services/platform-environment-requirements'; +import * as stubs from "../stubs"; +import { assert } from "chai"; + +const data = {platform: "android", cloudCommandName: "build"}; +const cloudBuildsErrorMessage = `Use the $ tns login command to log in with your account and then $ tns cloud ${data.cloudCommandName.toLowerCase()} ${data.platform.toLowerCase()} command to build your app in the cloud.`; +function getManuallySetupErrorMessage(testInjector: IInjector) { + const staticConfig = testInjector.resolve("staticConfig"); + return `To be able to build for ${data.platform}, verify that your environment is configured according to the system requirements described at ${staticConfig.SYS_REQUIREMENTS_LINK}`; +} + +function createTestInjector() { + const testInjector = new Yok(); + + testInjector.register("doctorService", {}); + testInjector.register("errors", stubs.ErrorsStub); + testInjector.register("extensibilityService", {}); + testInjector.register("logger", stubs.LoggerStub); + testInjector.register("prompter", {}); + testInjector.register("platformEnvironmentRequirements", PlatformEnvironmentRequirements); + testInjector.register("staticConfig", { SYS_REQUIREMENTS_LINK: "" }); + + return testInjector; +} + +describe("platformEnvironmentRequirements ", () => { + describe("checkRequirements", () => { + let testInjector: IInjector = null; + let platformEnvironmentRequirements: IPlatformEnvironmentRequirements = null; + + beforeEach(() => { + testInjector = createTestInjector(); + platformEnvironmentRequirements = testInjector.resolve("platformEnvironmentRequirements"); + }); + + it("should show prompt when environment is not configured", async () => { + const doctorService = testInjector.resolve("doctorService"); + doctorService.canExecuteLocalBuild = () => false; + + let isPromptForChoiceCalled = false; + const prompter = testInjector.resolve("prompter"); + prompter.promptForChoice = () => { + isPromptForChoiceCalled = true; + return PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME; + }; + + let isInstallExtensionCalled = false; + const extensibilityService = testInjector.resolve("extensibilityService"); + extensibilityService.installExtension = () => {isInstallExtensionCalled = true; }; + + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data)); + assert.isTrue(isPromptForChoiceCalled); + assert.isTrue(isInstallExtensionCalled); + }); + + it("should return true when environment is configured", async () => { + const doctorService = testInjector.resolve("doctorService"); + doctorService.canExecuteLocalBuild = () => true; + + const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(data); + assert.isTrue(result); + }); + + describe("when setup script option is selected ", () => { + it("should return true when env is configured after executing setup script", async () => { + let index = 0; + const doctorService = testInjector.resolve("doctorService"); + doctorService.canExecuteLocalBuild = () => { + if (index === 0) { + index++; + return false; + } + + return true; + }; + doctorService.runSetupScript = () => Promise.resolve(); + + const prompter = testInjector.resolve("prompter"); + prompter.promptForChoice = () => Promise.resolve(PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME); + + const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(data); + assert.isTrue(result); + }); + it("should prompt for choice when env is not configured after executing setup script", async () => { + const doctorService = testInjector.resolve("doctorService"); + doctorService.canExecuteLocalBuild = () => false; + doctorService.runSetupScript = () => Promise.resolve(); + + let index = 0; + let isPromptForChoiceCalled = false; + const prompter = testInjector.resolve("prompter"); + prompter.promptForChoice = () => { + if (index === 0) { + index++; + return PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME; + } + + isPromptForChoiceCalled = true; + return PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME; + }; + + let isInstallExtensionCalled = false; + const extensibilityService = testInjector.resolve("extensibilityService"); + extensibilityService.installExtension = () => {isInstallExtensionCalled = true; }; + + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data)); + assert.isTrue(isInstallExtensionCalled); + assert.isTrue(isPromptForChoiceCalled); + }); + + describe("and environment is not configured after executing setup script ", () => { + beforeEach(() => { + const doctorService = testInjector.resolve("doctorService"); + doctorService.canExecuteLocalBuild = () => false; + doctorService.runSetupScript = () => Promise.resolve(); + }); + it("should install nativescript-cloud extension when cloud builds option is selected", async () => { + let index = 0; + let isPromptForChoiceCalled = false; + const prompter = testInjector.resolve("prompter"); + prompter.promptForChoice = () => { + if (index === 0) { + index++; + return PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME; + } + + isPromptForChoiceCalled = true; + return PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME; + }; + + let isInstallExtensionCalled = false; + const extensibilityService = testInjector.resolve("extensibilityService"); + extensibilityService.installExtension = () => isInstallExtensionCalled = true; + + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), cloudBuildsErrorMessage); + assert.isTrue(isInstallExtensionCalled); + assert.isTrue(isPromptForChoiceCalled); + }); + it("should fail when manually setup option is selected", async () => { + let index = 0; + let isPromptForChoiceCalled = false; + const prompter = testInjector.resolve("prompter"); + prompter.promptForChoice = () => { + if (index === 0) { + index++; + return PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME; + } + + isPromptForChoiceCalled = true; + return PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME; + }; + + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), getManuallySetupErrorMessage(testInjector)); + assert.isTrue(isPromptForChoiceCalled); + }); + }); + }); + + describe("when cloud builds option is selected", () => { + it("should install nativescript-cloud extension when cloud builds option is selected", async () => { + const doctorService = testInjector.resolve("doctorService"); + doctorService.canExecuteLocalBuild = () => false; + + const prompter = testInjector.resolve("prompter"); + prompter.promptForChoice = () => Promise.resolve(PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME); + + let isInstallExtensionCalled = false; + const extensibilityService = testInjector.resolve("extensibilityService"); + extensibilityService.installExtension = () => isInstallExtensionCalled = true; + + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), cloudBuildsErrorMessage); + assert.isTrue(isInstallExtensionCalled); + }); + }); + + describe("when manually setup option is selected", () => { + it("should fail when manually setup option is selected", async () => { + const doctorService = testInjector.resolve("doctorService"); + doctorService.canExecuteLocalBuild = () => false; + + const prompter = testInjector.resolve("prompter"); + prompter.promptForChoice = () => PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME; + + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), getManuallySetupErrorMessage(testInjector)); + }); + }); + }); +}); From 78f97bfc6c3ecc6823b2b999c9d60db6beb62e4a Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 8 Mar 2018 09:58:31 +0200 Subject: [PATCH 2/7] Support for non interactive console --- .../platform-environment-requirements.ts | 13 +++++++- .../platform-environment-requirements.ts | 32 ++++++++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index e04cd9ee0d..489f9333b1 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -1,3 +1,6 @@ +import { isInteractive } from "../common/helpers"; +import { EOL } from "os"; + export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequirements { constructor(private $doctorService: IDoctorService, private $errors: IErrors, @@ -16,6 +19,13 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ public async checkEnvironmentRequirements(data: ICheckEnvironmentRequirementsInput): Promise { const canExecute = await this.$doctorService.canExecuteLocalBuild(data.platform); if (!canExecute) { + if (!isInteractive()) { + this.fail("Your environment is not configured properly and you will not be able to execute local builds. You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. To continue, choose one of the following options: " + EOL + + "Run $ tns setup command to run the setup script to try to automatically configure your environment for local builds." + EOL + + "Run $ tns cloud setup command to install the nativescript-cloud extension to configure your environment for cloud builds." + EOL + + `Verify that your environment is configured according to the system requirements described at ${this.$staticConfig.SYS_REQUIREMENTS_LINK}`); + } + const selectedOption = await this.$prompter.promptForChoice(PlatformEnvironmentRequirements.NOT_CONFIGURED_ENV_MESSAGE, [ PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME, PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME, @@ -67,7 +77,8 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private async processCloudBuilds(data: ICheckEnvironmentRequirementsInput): Promise { await this.processCloudBuildsCore(data); - this.fail(`Use the $ tns login command to log in with your account and then $ tns cloud ${data.cloudCommandName.toLowerCase()} ${data.platform.toLowerCase()} command to build your app in the cloud.`); + const cloudCommandName = data.platform ? `$ tns cloud ${data.cloudCommandName.toLowerCase()} ${data.platform.toLowerCase()}` : `$ tns cloud ${data.cloudCommandName.toLowerCase()}`; + this.fail(`Use the $ tns login command to log in with your account and then ${cloudCommandName} command to build your app in the cloud.`); } private async processCloudBuildsCore(data: ICheckEnvironmentRequirementsInput): Promise { diff --git a/test/services/platform-environment-requirements.ts b/test/services/platform-environment-requirements.ts index 80b8dc7be3..9837d7dc06 100644 --- a/test/services/platform-environment-requirements.ts +++ b/test/services/platform-environment-requirements.ts @@ -5,16 +5,18 @@ import { assert } from "chai"; const data = {platform: "android", cloudCommandName: "build"}; const cloudBuildsErrorMessage = `Use the $ tns login command to log in with your account and then $ tns cloud ${data.cloudCommandName.toLowerCase()} ${data.platform.toLowerCase()} command to build your app in the cloud.`; -function getManuallySetupErrorMessage(testInjector: IInjector) { - const staticConfig = testInjector.resolve("staticConfig"); - return `To be able to build for ${data.platform}, verify that your environment is configured according to the system requirements described at ${staticConfig.SYS_REQUIREMENTS_LINK}`; -} +const manuallySetupErrorMessage = `To be able to build for ${data.platform}, verify that your environment is configured according to the system requirements described at `; +const nonInteractiveConsoleErrorMessage = `Your environment is not configured properly and you will not be able to execute local builds. You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. To continue, choose one of the following options: \nRun $ tns setup command to run the setup script to try to automatically configure your environment for local builds.\nRun $ tns cloud setup command to install the nativescript-cloud extension to configure your environment for cloud builds.\nVerify that your environment is configured according to the system requirements described at `; function createTestInjector() { const testInjector = new Yok(); testInjector.register("doctorService", {}); - testInjector.register("errors", stubs.ErrorsStub); + testInjector.register("errors", { + fail: (err: any) => { + throw new Error(err.formatStr || err.message || err); + } + }); testInjector.register("extensibilityService", {}); testInjector.register("logger", stubs.LoggerStub); testInjector.register("prompter", {}); @@ -24,7 +26,7 @@ function createTestInjector() { return testInjector; } -describe("platformEnvironmentRequirements ", () => { +describe.only("platformEnvironmentRequirements ", () => { describe("checkRequirements", () => { let testInjector: IInjector = null; let platformEnvironmentRequirements: IPlatformEnvironmentRequirements = null; @@ -32,6 +34,8 @@ describe("platformEnvironmentRequirements ", () => { beforeEach(() => { testInjector = createTestInjector(); platformEnvironmentRequirements = testInjector.resolve("platformEnvironmentRequirements"); + process.stdout.isTTY = true; + process.stdin.isTTY = true; }); it("should show prompt when environment is not configured", async () => { @@ -151,7 +155,7 @@ describe("platformEnvironmentRequirements ", () => { return PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME; }; - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), getManuallySetupErrorMessage(testInjector)); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), manuallySetupErrorMessage); assert.isTrue(isPromptForChoiceCalled); }); }); @@ -182,7 +186,19 @@ describe("platformEnvironmentRequirements ", () => { const prompter = testInjector.resolve("prompter"); prompter.promptForChoice = () => PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME; - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), getManuallySetupErrorMessage(testInjector)); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), manuallySetupErrorMessage); + }); + }); + + describe("when console is not interactive", () => { + it("should fail when console is not interactive", async () => { + process.stdout.isTTY = false; + process.stdin.isTTY = false; + + const doctorService = testInjector.resolve("doctorService"); + doctorService.canExecuteLocalBuild = () => false; + + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), nonInteractiveConsoleErrorMessage); }); }); }); From dbcc05b46ee4a28fd1a2542c2f6064d047d1d704 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 8 Mar 2018 09:58:49 +0200 Subject: [PATCH 3/7] Setup and cloud setup commands --- docs/man_pages/cloud/cloud-setup.md | 18 ++++++++++++ docs/man_pages/env-configuration/setup.md | 18 ++++++++++++ lib/bootstrap.ts | 3 ++ lib/commands/build.ts | 3 +- lib/commands/setup.ts | 29 +++++++++++++++++++ .../platform-environment-requirements.ts | 2 +- 6 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 docs/man_pages/cloud/cloud-setup.md create mode 100644 docs/man_pages/env-configuration/setup.md create mode 100644 lib/commands/setup.ts diff --git a/docs/man_pages/cloud/cloud-setup.md b/docs/man_pages/cloud/cloud-setup.md new file mode 100644 index 0000000000..ef732c1f7b --- /dev/null +++ b/docs/man_pages/cloud/cloud-setup.md @@ -0,0 +1,18 @@ +<% if (isJekyll) { %>--- +title: tns cloud setup +position: 5 +---<% } %> +# tns cloud setup +========== + +Usage | Synopsis +------|------- +Install the nativescript-cloud extension | `$ tns cloud setup` + +Install the nativescript-cloud extension to configure your environment for cloud builds. + +### Related Commands + +Command | Description +----------|---------- +[setup](setup.html) | Run the setup script to try to automatically configure your environment for local builds. \ No newline at end of file diff --git a/docs/man_pages/env-configuration/setup.md b/docs/man_pages/env-configuration/setup.md new file mode 100644 index 0000000000..de90c80f85 --- /dev/null +++ b/docs/man_pages/env-configuration/setup.md @@ -0,0 +1,18 @@ +<% if (isJekyll) { %>--- +title: tns setup +position: 5 +---<% } %> +# tns setup +========== + +Usage | Synopsis +------|------- +Run the setup script | `$ tns setup` + +Run the setup script to try to automatically configure your environment for local builds. + +### Related Commands + +Command | Description +----------|---------- +[setup cloud](setup-cloud.html) | Install the nativescript-cloud extension to configure your environment for cloud builds. \ No newline at end of file diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index a3078a83a0..34ca39d6ce 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -72,6 +72,9 @@ $injector.requireCommand("appstore|upload", "./commands/appstore-upload"); $injector.requireCommand("publish|ios", "./commands/appstore-upload"); $injector.require("itmsTransporterService", "./services/itmstransporter-service"); +$injector.requireCommand("setup", "./commands/setup"); +$injector.requireCommand("cloud|setup", "./commands/setup"); + $injector.requirePublic("npm", "./node-package-manager"); $injector.require("npmInstallationManager", "./npm-installation-manager"); $injector.require("dynamicHelpProvider", "./dynamic-help-provider"); diff --git a/lib/commands/build.ts b/lib/commands/build.ts index af681adf7f..1507b29a6f 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -9,8 +9,7 @@ export class BuildCommandBase { protected $platformService: IPlatformService, private $bundleValidatorHelper: IBundleValidatorHelper, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { - super($projectData, $errors, $options); - this.$projectData.initializeProjectData(); + this.$projectData.initializeProjectData(); } public async executeCore(args: string[]): Promise { diff --git a/lib/commands/setup.ts b/lib/commands/setup.ts new file mode 100644 index 0000000000..47c397cfd5 --- /dev/null +++ b/lib/commands/setup.ts @@ -0,0 +1,29 @@ +export class SetupCommand implements ICommand { + public allowedParameters: ICommandParameter[] = []; + + constructor(private $doctorService: IDoctorService) { } + + public async execute(args: string[]): Promise { + return this.$doctorService.runSetupScript(); + } + + public async canExecute(args: string[]): Promise { + return true; + } +} +$injector.registerCommand("setup", SetupCommand); + +export class CloudSetupCommand implements ICommand { + public allowedParameters: ICommandParameter[] = []; + + constructor(private $extensibilityService: IExtensibilityService) { } + + public async execute(args: string[]): Promise { + return this.$extensibilityService.installExtension("nativescript-cloud"); + } + + public async canExecute(args: string[]): Promise { + return true; + } +} +$injector.registerCommand("cloud|setup", CloudSetupCommand); diff --git a/test/services/platform-environment-requirements.ts b/test/services/platform-environment-requirements.ts index 9837d7dc06..f306e7f9d2 100644 --- a/test/services/platform-environment-requirements.ts +++ b/test/services/platform-environment-requirements.ts @@ -26,7 +26,7 @@ function createTestInjector() { return testInjector; } -describe.only("platformEnvironmentRequirements ", () => { +describe("platformEnvironmentRequirements ", () => { describe("checkRequirements", () => { let testInjector: IInjector = null; let platformEnvironmentRequirements: IPlatformEnvironmentRequirements = null; From cdbbbbce4be2cbd1e018ef56c0fad2840202d64f Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 8 Mar 2018 15:26:48 +0200 Subject: [PATCH 4/7] Don't check twice if env is properly configured --- docs/man_pages/cloud/cloud-setup.md | 4 +- lib/android-tools-info.ts | 43 +++++---- lib/commands/build.ts | 35 +++----- lib/commands/run.ts | 5 +- lib/commands/setup.ts | 5 +- lib/declarations.d.ts | 29 ++++++- lib/definitions/platform.d.ts | 7 +- lib/services/android-project-service.ts | 13 +-- lib/services/ios-project-service.ts | 11 +-- .../platform-environment-requirements.ts | 87 ++++++++++++------- test/debug.ts | 1 + test/ios-project-service.ts | 1 + test/plugins-service.ts | 1 + .../platform-environment-requirements.ts | 31 ++++--- test/stubs.ts | 6 ++ 15 files changed, 166 insertions(+), 113 deletions(-) diff --git a/docs/man_pages/cloud/cloud-setup.md b/docs/man_pages/cloud/cloud-setup.md index ef732c1f7b..4c7225bb00 100644 --- a/docs/man_pages/cloud/cloud-setup.md +++ b/docs/man_pages/cloud/cloud-setup.md @@ -7,9 +7,9 @@ position: 5 Usage | Synopsis ------|------- -Install the nativescript-cloud extension | `$ tns cloud setup` +Install the `nativescript-cloud extension` | `$ tns cloud setup` -Install the nativescript-cloud extension to configure your environment for cloud builds. +Install the `nativescript-cloud extension` to configure your environment for cloud builds. ### Related Commands diff --git a/lib/android-tools-info.ts b/lib/android-tools-info.ts index 0f86105ef5..cd9cdbe0fd 100644 --- a/lib/android-tools-info.ts +++ b/lib/android-tools-info.ts @@ -40,34 +40,43 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { return this.toolsInfo; } - public validateInfo(options?: { showWarningsAsErrors: boolean, validateTargetSdk: boolean }): boolean { + public validateInfo(options?: IAndroidToolsInfoValidateInput): boolean { let detectedErrors = false; this.showWarningsAsErrors = options && options.showWarningsAsErrors; - const toolsInfoData = this.getToolsInfo(); const isAndroidHomeValid = this.validateAndroidHomeEnvVariable(); detectedErrors = androidToolsInfo.validateInfo().map(warning => this.printMessage(warning.warning)).length > 0; if (options && options.validateTargetSdk) { - const targetSdk = toolsInfoData.targetSdkVersion; - const newTarget = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${targetSdk}`; - if (!_.includes(AndroidToolsInfo.SUPPORTED_TARGETS, newTarget)) { - const supportedVersions = AndroidToolsInfo.SUPPORTED_TARGETS.sort(); - const minSupportedVersion = this.parseAndroidSdkString(_.first(supportedVersions)); - - if (targetSdk && (targetSdk < minSupportedVersion)) { - this.printMessage(`The selected Android target SDK ${newTarget} is not supported. You must target ${minSupportedVersion} or later.`); - detectedErrors = true; - } else if (!targetSdk || targetSdk > this.getMaxSupportedVersion()) { - this.$logger.warn(`Support for the selected Android target SDK ${newTarget} is not verified. Your Android app might not work as expected.`); - } - } + detectedErrors = this.validateTargetSdk(); } return detectedErrors || !isAndroidHomeValid; } - public validateJavacVersion(installedJavacVersion: string, options?: { showWarningsAsErrors: boolean }): boolean { + public validateTargetSdk(options?: IAndroidToolsInfoOptions): boolean { + this.showWarningsAsErrors = options && options.showWarningsAsErrors; + + const toolsInfoData = this.getToolsInfo(); + const targetSdk = toolsInfoData.targetSdkVersion; + const newTarget = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${targetSdk}`; + + if (!_.includes(AndroidToolsInfo.SUPPORTED_TARGETS, newTarget)) { + const supportedVersions = AndroidToolsInfo.SUPPORTED_TARGETS.sort(); + const minSupportedVersion = this.parseAndroidSdkString(_.first(supportedVersions)); + + if (targetSdk && (targetSdk < minSupportedVersion)) { + this.printMessage(`The selected Android target SDK ${newTarget} is not supported. You must target ${minSupportedVersion} or later.`); + return true; + } else if (!targetSdk || targetSdk > this.getMaxSupportedVersion()) { + this.$logger.warn(`Support for the selected Android target SDK ${newTarget} is not verified. Your Android app might not work as expected.`); + } + } + + return false; + } + + public validateJavacVersion(installedJavacVersion: string, options?: IAndroidToolsInfoOptions): boolean { if (options) { this.showWarningsAsErrors = options.showWarningsAsErrors; } @@ -88,7 +97,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo { } @cache() - public validateAndroidHomeEnvVariable(options?: { showWarningsAsErrors: boolean }): boolean { + public validateAndroidHomeEnvVariable(options?: IAndroidToolsInfoOptions): boolean { if (options) { this.showWarningsAsErrors = options.showWarningsAsErrors; } diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 1507b29a6f..fbee66cf3b 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -7,8 +7,7 @@ export class BuildCommandBase { protected $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $platformService: IPlatformService, - private $bundleValidatorHelper: IBundleValidatorHelper, - private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { + private $bundleValidatorHelper: IBundleValidatorHelper) { this.$projectData.initializeProjectData(); } @@ -45,18 +44,16 @@ export class BuildCommandBase { } } - protected async canExecuteCore(platform: string): Promise { - const result = await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ platform: platform, cloudCommandName: "build" }); - this.validatePlatform(platform); - return result; - } - - private validatePlatform(platform: string): void { + protected async validatePlatform(platform: string): Promise { if (!this.$platformService.isPlatformSupportedForOS(platform, this.$projectData)) { this.$errors.fail(`Applications for platform ${platform} can not be built on this OS`); } this.$bundleValidatorHelper.validate(); + + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; + await platformProjectService.validate(this.$projectData); } } @@ -69,9 +66,8 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService, - $bundleValidatorHelper: IBundleValidatorHelper, - $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $platformEnvironmentRequirements); + $bundleValidatorHelper: IBundleValidatorHelper) { + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper); } public async execute(args: string[]): Promise { @@ -79,7 +75,8 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { - return await super.canExecuteCore(this.$devicePlatformsConstants.iOS) && args.length === 0 && this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); + await super.validatePlatform(this.$devicePlatformsConstants.iOS); + return args.length === 0 && this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); } } @@ -94,9 +91,8 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { $platformsData: IPlatformsData, $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $platformService: IPlatformService, - $bundleValidatorHelper: IBundleValidatorHelper, - $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { - super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper, $platformEnvironmentRequirements); + $bundleValidatorHelper: IBundleValidatorHelper) { + super($options, $errors, $projectData, $platformsData, $devicePlatformsConstants, $platformService, $bundleValidatorHelper); } public async execute(args: string[]): Promise { @@ -104,16 +100,11 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { - await super.canExecuteCore(this.$devicePlatformsConstants.Android); - + await super.validatePlatform(this.$devicePlatformsConstants.Android); if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } - const platformData = this.$platformsData.getPlatformData(this.$devicePlatformsConstants.Android, this.$projectData); - const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); - return args.length === 0 && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); } } diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 829ac9b59a..38365c2a59 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -9,8 +9,7 @@ export class RunCommandBase implements ICommand { private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $errors: IErrors, private $hostInfo: IHostInfo, - private $liveSyncCommandHelper: ILiveSyncCommandHelper, - private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { } + private $liveSyncCommandHelper: ILiveSyncCommandHelper) { } public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { @@ -18,8 +17,6 @@ export class RunCommandBase implements ICommand { } public async canExecute(args: string[]): Promise { - await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({platform: this.platform, cloudCommandName: "run"}); - if (args.length) { this.$errors.fail(ERROR_NO_VALID_SUBCOMMAND_FORMAT, "run"); } diff --git a/lib/commands/setup.ts b/lib/commands/setup.ts index 47c397cfd5..62c586a436 100644 --- a/lib/commands/setup.ts +++ b/lib/commands/setup.ts @@ -19,7 +19,10 @@ export class CloudSetupCommand implements ICommand { constructor(private $extensibilityService: IExtensibilityService) { } public async execute(args: string[]): Promise { - return this.$extensibilityService.installExtension("nativescript-cloud"); + const installedExtensions = this.$extensibilityService.getInstalledExtensions(); + if (!installedExtensions["nativescript-cloud"]) { + return this.$extensibilityService.installExtension("nativescript-cloud"); + } } public async canExecute(args: string[]): Promise { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index be53399b4e..23ecff50a4 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -547,7 +547,7 @@ interface IAndroidToolsInfo { * @param {any} options Defines if the warning messages should treated as error and if the targetSdk value should be validated as well. * @return {boolean} True if there are detected issues, false otherwise. */ - validateInfo(options?: { showWarningsAsErrors: boolean, validateTargetSdk: boolean }): boolean; + validateInfo(options?: IAndroidToolsInfoValidateInput): boolean; /** * Validates the information about required JAVA version. @@ -555,14 +555,20 @@ interface IAndroidToolsInfo { * @param {any} options Defines if the warning messages should treated as error. * @return {boolean} True if there are detected issues, false otherwise. */ - validateJavacVersion(installedJavaVersion: string, options?: { showWarningsAsErrors: boolean }): boolean; + validateJavacVersion(installedJavaVersion: string, options?: IAndroidToolsInfoOptions): boolean; /** * Validates if ANDROID_HOME environment variable is set correctly. * @param {any} options Defines if the warning messages should treated as error. * @returns {boolean} true in case ANDROID_HOME is correctly set, false otherwise. */ - validateAndroidHomeEnvVariable(options?: { showWarningsAsErrors: boolean }): boolean; + validateAndroidHomeEnvVariable(options?: IAndroidToolsInfoOptions): boolean; + + /** + * Validates target sdk + * @returns {boolean} True if there are detected issues, false otherwise + */ + validateTargetSdk(options?: IAndroidToolsInfoOptions): boolean; /** * Gets the path to `adb` executable from ANDROID_HOME. It should be `$ANDROID_HOME/platform-tools/adb` in case it exists. @@ -607,6 +613,23 @@ interface IAndroidToolsInfoData { generateTypings: boolean; } +/** + * Describes options that can be passed to methods from IAndroidToolsInfo interface + */ +interface IAndroidToolsInfoOptions { + /** + * Defines if the warning messages should treated as error. + */ + showWarningsAsErrors: boolean; +} + +interface IAndroidToolsInfoValidateInput extends IAndroidToolsInfoOptions { + /** + * Defines if the targetSdk value should be validated. + */ + validateTargetSdk: boolean; +} + interface ISocketProxyFactory extends NodeJS.EventEmitter { createTCPSocketProxy(factory: () => Promise): Promise; createWebSocketProxy(factory: () => Promise, deviceIdentifier: string): Promise; diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 85158dc5db..2cc11f6942 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -367,10 +367,5 @@ interface IUpdateAppOptions extends IOptionalFilesToSync, IOptionalFilesToRemove } interface IPlatformEnvironmentRequirements { - checkEnvironmentRequirements(input: ICheckEnvironmentRequirementsInput): Promise; -} - -interface ICheckEnvironmentRequirementsInput { - platform: string; - cloudCommandName: string; + checkEnvironmentRequirements(platform: string): Promise; } \ No newline at end of file diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 442c40cfd7..3330b6944a 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -32,12 +32,12 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $hostInfo: IHostInfo, private $logger: ILogger, $projectDataService: IProjectDataService, - private $sysInfo: ISysInfo, private $injector: IInjector, private $pluginVariablesService: IPluginVariablesService, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $npm: INodePackageManager, - private $androidPluginBuildService: IAndroidPluginBuildService) { + private $androidPluginBuildService: IAndroidPluginBuildService, + private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { super($fs, $projectDataService); this._androidProjectPropertiesManagers = Object.create(null); this.isAndroidStudioTemplate = false; @@ -155,13 +155,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject this.validatePackageName(projectData.projectId); this.validateProjectName(projectData.projectName); - this.$androidToolsInfo.validateAndroidHomeEnvVariable({ showWarningsAsErrors: true }); - - const javaCompilerVersion = await this.$sysInfo.getJavaCompilerVersion(); - - this.$androidToolsInfo.validateJavacVersion(javaCompilerVersion, { showWarningsAsErrors: true }); - - await this.$androidToolsInfo.validateInfo({ showWarningsAsErrors: true, validateTargetSdk: true }); + await this.$platformEnvironmentRequirements.checkEnvironmentRequirements(this.getPlatformData(projectData).normalizedPlatformName); + this.$androidToolsInfo.validateTargetSdk({ showWarningsAsErrors: true }); } public async validatePlugins(): Promise { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index bc579b7143..4e92d9d7ac 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -49,9 +49,10 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $pbxprojDomXcode: IPbxprojDomXcode, private $xcode: IXcode, private $iOSEntitlementsService: IOSEntitlementsService, + private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $sysInfo: ISysInfo, private $xCConfigService: XCConfigService) { - super($fs, $projectDataService); + super($fs, $projectDataService); } private _platformsDirCache: string = null; @@ -130,16 +131,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName, "Resources"); } - public async validate(): Promise { + public async validate(projectData: IProjectData): Promise { if (!this.$hostInfo.isDarwin) { return; } - try { - await this.$childProcess.exec("which xcodebuild"); - } catch (error) { - this.$errors.fail("Xcode is not installed. Make sure you have Xcode installed and added to your PATH"); - } + await this.$platformEnvironmentRequirements.checkEnvironmentRequirements(this.getPlatformData(projectData).normalizedPlatformName); const xcodeBuildVersion = await this.getXcodeVersion(); if (helpers.versionCompare(xcodeBuildVersion, IOSProjectService.XCODEBUILD_MIN_VERSION) < 0) { diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index 489f9333b1..7f358106c6 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -2,30 +2,44 @@ import { isInteractive } from "../common/helpers"; import { EOL } from "os"; export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequirements { - constructor(private $doctorService: IDoctorService, + constructor(private $commandsService: ICommandsService, + private $doctorService: IDoctorService, private $errors: IErrors, private $extensibilityService: IExtensibilityService, private $logger: ILogger, private $prompter: IPrompter, private $staticConfig: IStaticConfig) { } - public static CLOUD_BUILDS_OPTION_NAME = "Install the nativescript-cloud extension to configure your environment for cloud builds"; - public static SETUP_SCRIPT_OPTION_NAME = "Run the setup script to try to automatically configure your environment for local builds"; - public static MANUALLY_SETUP_OPTION_NAME = "Manually setup your environment for local builds"; - public static BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME = "Install the nativescript-cloud extension and run the setup script to try to automatically configure your environment"; - public static NOT_CONFIGURED_ENV_MESSAGE = "Your environment is not configured properly and you will not be able to execute local builds. You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. To continue, choose one of the following options: "; - public static NOT_CONFIGURED_ENV_AFTER_SETUP_SCRIPT_MESSAGE = "The setup script was not able to configure your environment for local builds. To execute local builds, you have to set up your environment manually. To continue, choose one of the following options:"; - - public async checkEnvironmentRequirements(data: ICheckEnvironmentRequirementsInput): Promise { - const canExecute = await this.$doctorService.canExecuteLocalBuild(data.platform); + public static CLOUD_BUILDS_OPTION_NAME = "Configure for Cloud Builds"; + public static SETUP_SCRIPT_OPTION_NAME = "Configure for Local Builds"; + public static MANUALLY_SETUP_OPTION_NAME = "Skip Step and Configure Manually"; + private static BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME = "Configure for Both Local and Cloud Builds"; + private static NOT_CONFIGURED_ENV_MESSAGE = "To continue, choose one of the following options: "; + private static NOT_CONFIGURED_ENV_AFTER_SETUP_SCRIPT_MESSAGE = "The setup script was not able to configure your environment for local builds. To execute local builds, you have to set up your environment manually. To continue, choose one of the following options:"; + private static NATIVESCRIPT_CLOUD_EXTENSION_NAME = "nativescript-cloud"; + + private cliCommandToCloudCommandName: IStringDictionary = { + "build": "tns cloud build", + "run": "tns cloud run", + "deploy": "tns cloud deploy" + }; + + public async checkEnvironmentRequirements(platform: string): Promise { + const canExecute = await this.$doctorService.canExecuteLocalBuild(platform); if (!canExecute) { if (!isInteractive()) { - this.fail("Your environment is not configured properly and you will not be able to execute local builds. You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. To continue, choose one of the following options: " + EOL + this.fail("You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. Your environment is not configured properly and you will not be able to execute local builds. To continue, choose one of the following options: " + EOL + "Run $ tns setup command to run the setup script to try to automatically configure your environment for local builds." + EOL + "Run $ tns cloud setup command to install the nativescript-cloud extension to configure your environment for cloud builds." + EOL + `Verify that your environment is configured according to the system requirements described at ${this.$staticConfig.SYS_REQUIREMENTS_LINK}`); } + this.$logger.info("You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. Your environment is not configured properly and you will not be able to execute local builds. " + EOL + + "Select “Configure for Cloud Builds” to install the nativescript-cloud extension and automatically configure your environment for cloud builds." + EOL + + "Select “Configure for Local Builds” to run the setup script and automatically configure your environment for local builds." + + "Select “Configure for Both Local and Cloud Builds” to automatically configure your environment for both options." + + "Select “Skip Step and Configure Manually” to disregard these options and install any required components manually."); + const selectedOption = await this.$prompter.promptForChoice(PlatformEnvironmentRequirements.NOT_CONFIGURED_ENV_MESSAGE, [ PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME, PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME, @@ -34,13 +48,13 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ ]); if (selectedOption === PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME) { - await this.processCloudBuilds(data); + await this.processCloudBuilds(platform); } if (selectedOption === PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME) { await this.$doctorService.runSetupScript(); - if (await this.$doctorService.canExecuteLocalBuild(data.platform)) { + if (await this.$doctorService.canExecuteLocalBuild(platform)) { return true; } @@ -50,48 +64,63 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ ]); if (option === PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME) { - await this.processCloudBuilds(data); + await this.processCloudBuilds(platform); } if (option === PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME) { - this.processManuallySetup(data); + this.processManuallySetup(platform); } } if (selectedOption === PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME) { - this.processManuallySetup(data); + this.processManuallySetup(platform); } if (selectedOption === PlatformEnvironmentRequirements.BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME) { - await this.processBothCloudBuildsAndSetupScript(data); - if (await this.$doctorService.canExecuteLocalBuild(data.platform)) { + await this.processBothCloudBuildsAndSetupScript(platform); + if (await this.$doctorService.canExecuteLocalBuild(platform)) { return true; } - this.processManuallySetup(data); + this.processManuallySetup(platform); } } return true; } - private async processCloudBuilds(data: ICheckEnvironmentRequirementsInput): Promise { - await this.processCloudBuildsCore(data); - const cloudCommandName = data.platform ? `$ tns cloud ${data.cloudCommandName.toLowerCase()} ${data.platform.toLowerCase()}` : `$ tns cloud ${data.cloudCommandName.toLowerCase()}`; - this.fail(`Use the $ tns login command to log in with your account and then ${cloudCommandName} command to build your app in the cloud.`); + private async processCloudBuilds(platform: string): Promise { + await this.processCloudBuildsCore(platform); + this.fail(this.getCloudBuildsMessage(platform)); + } + + private async processCloudBuildsCore(platform: string): Promise { + const installedExtensions = this.$extensibilityService.getInstalledExtensions(); + if (!installedExtensions[PlatformEnvironmentRequirements.NATIVESCRIPT_CLOUD_EXTENSION_NAME]) { + return this.$extensibilityService.installExtension(PlatformEnvironmentRequirements.NATIVESCRIPT_CLOUD_EXTENSION_NAME); + } } - private async processCloudBuildsCore(data: ICheckEnvironmentRequirementsInput): Promise { - return this.$extensibilityService.installExtension("nativescript-cloud"); + private getCloudBuildsMessage(platform: string): string { + const cloudCommandName = this.cliCommandToCloudCommandName[this.$commandsService.currentCommandData.commandName]; + if (!cloudCommandName) { + return `In order to test your application use the $ tns login command to log in with your account and then $ tns cloud build command to build your app in the cloud.`; + } + + if (!platform) { + return `Use the $ tns login command to log in with your account and then $ tns cloud ${cloudCommandName.toLowerCase()} command to build your app in the cloud.`; + } + + return `Use the $ tns login command to log in with your account and then $ tns cloud ${cloudCommandName.toLowerCase()} ${platform.toLowerCase()} command to build your app in the cloud.`; } - private processManuallySetup(data: ICheckEnvironmentRequirementsInput): void { - this.fail(`To be able to build for ${data.platform}, verify that your environment is configured according to the system requirements described at ${this.$staticConfig.SYS_REQUIREMENTS_LINK}`); + private processManuallySetup(platform: string): void { + this.fail(`To be able to build for ${platform}, verify that your environment is configured according to the system requirements described at ${this.$staticConfig.SYS_REQUIREMENTS_LINK}`); } - private async processBothCloudBuildsAndSetupScript(data: ICheckEnvironmentRequirementsInput): Promise { + private async processBothCloudBuildsAndSetupScript(platform: string): Promise { try { - await this.processCloudBuildsCore(data); + await this.processCloudBuildsCore(platform); } catch (e) { this.$logger.trace(`Error while installing nativescript-cloud extension. ${e.message}.`); } diff --git a/test/debug.ts b/test/debug.ts index 067f81f580..8a892e6c7e 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -77,6 +77,7 @@ function createTestInjector(): IInjector { }); testInjector.register("settingsService", SettingsService); testInjector.register("androidPluginBuildService", stubs.AndroidPluginBuildServiceStub); + testInjector.register("platformEnvironmentRequirements", {}); return testInjector; } diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 673944c74a..3e383a35fb 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -118,6 +118,7 @@ function createTestInjector(projectPath: string, projectName: string): IInjector testInjector.register("xCConfigService", XCConfigService); testInjector.register("settingsService", SettingsService); testInjector.register("httpClient", {}); + testInjector.register("platformEnvironmentRequirements", {}); return testInjector; } diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 7329920c54..9fe3d0e1bd 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -117,6 +117,7 @@ function createTestInjector() { getPlaygroundInfo: () => Promise.resolve(null) }); + testInjector.register("platformEnvironmentRequirements", {}); return testInjector; } diff --git a/test/services/platform-environment-requirements.ts b/test/services/platform-environment-requirements.ts index f306e7f9d2..9aab41f25c 100644 --- a/test/services/platform-environment-requirements.ts +++ b/test/services/platform-environment-requirements.ts @@ -3,14 +3,15 @@ import { PlatformEnvironmentRequirements } from '../../lib/services/platform-env import * as stubs from "../stubs"; import { assert } from "chai"; -const data = {platform: "android", cloudCommandName: "build"}; -const cloudBuildsErrorMessage = `Use the $ tns login command to log in with your account and then $ tns cloud ${data.cloudCommandName.toLowerCase()} ${data.platform.toLowerCase()} command to build your app in the cloud.`; -const manuallySetupErrorMessage = `To be able to build for ${data.platform}, verify that your environment is configured according to the system requirements described at `; -const nonInteractiveConsoleErrorMessage = `Your environment is not configured properly and you will not be able to execute local builds. You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. To continue, choose one of the following options: \nRun $ tns setup command to run the setup script to try to automatically configure your environment for local builds.\nRun $ tns cloud setup command to install the nativescript-cloud extension to configure your environment for cloud builds.\nVerify that your environment is configured according to the system requirements described at `; +const platform = "android"; +const cloudBuildsErrorMessage = `In order to test your application use the $ tns login command to log in with your account and then $ tns cloud build command to build your app in the cloud.`; +const manuallySetupErrorMessage = `To be able to build for ${platform}, verify that your environment is configured according to the system requirements described at `; +const nonInteractiveConsoleErrorMessage = `You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. Your environment is not configured properly and you will not be able to execute local builds. To continue, choose one of the following options: \nRun $ tns setup command to run the setup script to try to automatically configure your environment for local builds.\nRun $ tns cloud setup command to install the nativescript-cloud extension to configure your environment for cloud builds.\nVerify that your environment is configured according to the system requirements described at `; function createTestInjector() { const testInjector = new Yok(); + testInjector.register("commandsService", {currentCommandData: {commandName: "test", commandArguments: [""]}}); testInjector.register("doctorService", {}); testInjector.register("errors", { fail: (err: any) => { @@ -52,8 +53,9 @@ describe("platformEnvironmentRequirements ", () => { let isInstallExtensionCalled = false; const extensibilityService = testInjector.resolve("extensibilityService"); extensibilityService.installExtension = () => {isInstallExtensionCalled = true; }; + extensibilityService.getInstalledExtensions = () => { return {}; }; - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data)); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); assert.isTrue(isPromptForChoiceCalled); assert.isTrue(isInstallExtensionCalled); }); @@ -62,7 +64,7 @@ describe("platformEnvironmentRequirements ", () => { const doctorService = testInjector.resolve("doctorService"); doctorService.canExecuteLocalBuild = () => true; - const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(data); + const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(platform); assert.isTrue(result); }); @@ -83,7 +85,7 @@ describe("platformEnvironmentRequirements ", () => { const prompter = testInjector.resolve("prompter"); prompter.promptForChoice = () => Promise.resolve(PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME); - const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(data); + const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(platform); assert.isTrue(result); }); it("should prompt for choice when env is not configured after executing setup script", async () => { @@ -107,8 +109,9 @@ describe("platformEnvironmentRequirements ", () => { let isInstallExtensionCalled = false; const extensibilityService = testInjector.resolve("extensibilityService"); extensibilityService.installExtension = () => {isInstallExtensionCalled = true; }; + extensibilityService.getInstalledExtensions = () => { return {}; }; - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data)); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); assert.isTrue(isInstallExtensionCalled); assert.isTrue(isPromptForChoiceCalled); }); @@ -136,8 +139,9 @@ describe("platformEnvironmentRequirements ", () => { let isInstallExtensionCalled = false; const extensibilityService = testInjector.resolve("extensibilityService"); extensibilityService.installExtension = () => isInstallExtensionCalled = true; + extensibilityService.getInstalledExtensions = () => { return {}; }; - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), cloudBuildsErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), cloudBuildsErrorMessage); assert.isTrue(isInstallExtensionCalled); assert.isTrue(isPromptForChoiceCalled); }); @@ -155,7 +159,7 @@ describe("platformEnvironmentRequirements ", () => { return PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME; }; - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), manuallySetupErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); assert.isTrue(isPromptForChoiceCalled); }); }); @@ -172,8 +176,9 @@ describe("platformEnvironmentRequirements ", () => { let isInstallExtensionCalled = false; const extensibilityService = testInjector.resolve("extensibilityService"); extensibilityService.installExtension = () => isInstallExtensionCalled = true; + extensibilityService.getInstalledExtensions = () => { return {}; }; - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), cloudBuildsErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), cloudBuildsErrorMessage); assert.isTrue(isInstallExtensionCalled); }); }); @@ -186,7 +191,7 @@ describe("platformEnvironmentRequirements ", () => { const prompter = testInjector.resolve("prompter"); prompter.promptForChoice = () => PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME; - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), manuallySetupErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); }); }); @@ -198,7 +203,7 @@ describe("platformEnvironmentRequirements ", () => { const doctorService = testInjector.resolve("doctorService"); doctorService.canExecuteLocalBuild = () => false; - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(data), nonInteractiveConsoleErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), nonInteractiveConsoleErrorMessage); }); }); }); diff --git a/test/stubs.ts b/test/stubs.ts index c1e26bf437..9e36009616 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -606,6 +606,10 @@ export class AndroidToolsInfoStub implements IAndroidToolsInfo { public validateAndroidHomeEnvVariable(options?: { showWarningsAsErrors: boolean }): boolean { return false; } + + public validateTargetSdk(options?: { showWarningsAsErrors: boolean }): boolean { + return true; + } } export class ChildProcessStub { @@ -655,6 +659,8 @@ export class ProjectChangesService implements IProjectChangesService { } export class CommandsService implements ICommandsService { + public currentCommandData = { commandName: "test", commandArguments: [""]}; + public allCommands(opts: { includeDevCommands: boolean }): string[] { return []; } From 3afca9b7c079fedaf1ab1c8b24015b4237ad3c13 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 9 Mar 2018 09:34:56 +0200 Subject: [PATCH 5/7] Handle non interactive console in doctor-service --- lib/services/doctor-service.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index 0c146c72d8..a792d50700 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -116,6 +116,16 @@ class DoctorService implements IDoctorService { } private printInfosCore(infos: NativeScriptDoctor.IInfo[]): void { + if (!helpers.isInteractive()) { + infos.map(info => { + let message = info.message; + if (info.type === constants.WARNING_TYPE_NAME) { + message = `WARNING: ${info.message.yellow} ${EOL} ${info.additionalInformation} ${EOL}`; + } + this.$logger.out(message); + }); + } + infos.filter(info => info.type === constants.INFO_TYPE_NAME) .map(info => { const spinner = this.$terminalSpinnerService.createSpinner(); From 94af8e55239918a64963ad4f61b6f39d4158e512 Mon Sep 17 00:00:00 2001 From: fatme Date: Fri, 9 Mar 2018 16:03:50 +0200 Subject: [PATCH 6/7] Fix PR comments --- lib/bootstrap.ts | 1 + lib/commands/setup.ts | 19 ++---- lib/constants.ts | 2 + lib/declarations.d.ts | 9 +++ lib/services/doctor-service.ts | 6 +- .../nativescript-cloud-extension-service.ts | 14 +++++ .../platform-environment-requirements.ts | 63 ++++++++++--------- .../platform-environment-requirements.ts | 55 ++++++---------- 8 files changed, 87 insertions(+), 82 deletions(-) create mode 100644 lib/services/nativescript-cloud-extension-service.ts diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 34ca39d6ce..cf115d556d 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -156,3 +156,4 @@ $injector.require("terminalSpinnerService", "./services/terminal-spinner-service $injector.require('playgroundService', './services/playground-service'); $injector.require("platformEnvironmentRequirements", "./services/platform-environment-requirements"); +$injector.require("nativescriptCloudExtensionService", "./services/nativescript-cloud-extension-service"); diff --git a/lib/commands/setup.ts b/lib/commands/setup.ts index 62c586a436..eb81ab1e4c 100644 --- a/lib/commands/setup.ts +++ b/lib/commands/setup.ts @@ -3,30 +3,19 @@ export class SetupCommand implements ICommand { constructor(private $doctorService: IDoctorService) { } - public async execute(args: string[]): Promise { + public execute(args: string[]): Promise { return this.$doctorService.runSetupScript(); } - - public async canExecute(args: string[]): Promise { - return true; - } } $injector.registerCommand("setup", SetupCommand); export class CloudSetupCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $extensibilityService: IExtensibilityService) { } - - public async execute(args: string[]): Promise { - const installedExtensions = this.$extensibilityService.getInstalledExtensions(); - if (!installedExtensions["nativescript-cloud"]) { - return this.$extensibilityService.installExtension("nativescript-cloud"); - } - } + constructor(private $nativescriptCloudExtensionService: INativescriptCloudExtensionService) { } - public async canExecute(args: string[]): Promise { - return true; + public execute(args: string[]): Promise { + return this.$nativescriptCloudExtensionService.install(); } } $injector.registerCommand("cloud|setup", CloudSetupCommand); diff --git a/lib/constants.ts b/lib/constants.ts index 2f4cc82782..3857c72542 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -137,3 +137,5 @@ export const enum BuildStates { Clean = "Clean", Incremental = "Incremental" } + +export const NATIVESCRIPT_CLOUD_EXTENSION_NAME = "nativescript-cloud"; diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 23ecff50a4..b3774f131f 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -566,6 +566,7 @@ interface IAndroidToolsInfo { /** * Validates target sdk + * @param {IAndroidToolsInfoOptions} options @optional Defines if the warning messages should treated as error. * @returns {boolean} True if there are detected issues, false otherwise */ validateTargetSdk(options?: IAndroidToolsInfoOptions): boolean; @@ -785,3 +786,11 @@ interface IBundleValidatorHelper { */ validate(): void; } + +interface INativescriptCloudExtensionService { + /** + * Installs nativescript-cloud extension + * @return {Promise} returns the extension data + */ + install(): Promise; +} diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index a792d50700..b77c59b289 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -53,7 +53,11 @@ class DoctorService implements IDoctorService { return hasWarnings; } - public async runSetupScript(): Promise { + public runSetupScript(): Promise { + if (this.$hostInfo.isLinux) { + return; + } + this.$logger.out("Running the setup script to try and automatically configure your environment."); if (this.$hostInfo.isDarwin) { diff --git a/lib/services/nativescript-cloud-extension-service.ts b/lib/services/nativescript-cloud-extension-service.ts new file mode 100644 index 0000000000..4eb466fcbf --- /dev/null +++ b/lib/services/nativescript-cloud-extension-service.ts @@ -0,0 +1,14 @@ +import * as constants from "../constants"; + +export class NativescriptCloudExtensionService implements INativescriptCloudExtensionService { + + constructor(private $extensibilityService: IExtensibilityService) { } + + public install(): Promise { + const installedExtensions = this.$extensibilityService.getInstalledExtensions(); + if (!installedExtensions[constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME]) { + return this.$extensibilityService.installExtension(constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME); + } + } +} +$injector.register("nativescriptCloudExtensionService", NativescriptCloudExtensionService); diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index 7f358106c6..11ae785f18 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -1,3 +1,4 @@ +import * as constants from "../constants"; import { isInteractive } from "../common/helpers"; import { EOL } from "os"; @@ -5,7 +6,7 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ constructor(private $commandsService: ICommandsService, private $doctorService: IDoctorService, private $errors: IErrors, - private $extensibilityService: IExtensibilityService, + private $nativescriptCloudExtensionService: INativescriptCloudExtensionService, private $logger: ILogger, private $prompter: IPrompter, private $staticConfig: IStaticConfig) { } @@ -16,7 +17,6 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private static BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME = "Configure for Both Local and Cloud Builds"; private static NOT_CONFIGURED_ENV_MESSAGE = "To continue, choose one of the following options: "; private static NOT_CONFIGURED_ENV_AFTER_SETUP_SCRIPT_MESSAGE = "The setup script was not able to configure your environment for local builds. To execute local builds, you have to set up your environment manually. To continue, choose one of the following options:"; - private static NATIVESCRIPT_CLOUD_EXTENSION_NAME = "nativescript-cloud"; private cliCommandToCloudCommandName: IStringDictionary = { "build": "tns cloud build", @@ -28,28 +28,28 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ const canExecute = await this.$doctorService.canExecuteLocalBuild(platform); if (!canExecute) { if (!isInteractive()) { - this.fail("You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. Your environment is not configured properly and you will not be able to execute local builds. To continue, choose one of the following options: " + EOL + this.fail(`You are missing the ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension and you will not be able to execute cloud builds. Your environment is not configured properly and you will not be able to execute local builds. To continue, choose one of the following options: ` + EOL + "Run $ tns setup command to run the setup script to try to automatically configure your environment for local builds." + EOL - + "Run $ tns cloud setup command to install the nativescript-cloud extension to configure your environment for cloud builds." + EOL + + `Run $ tns cloud setup command to install the ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension to configure your environment for cloud builds.` + EOL + `Verify that your environment is configured according to the system requirements described at ${this.$staticConfig.SYS_REQUIREMENTS_LINK}`); } - this.$logger.info("You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. Your environment is not configured properly and you will not be able to execute local builds. " + EOL - + "Select “Configure for Cloud Builds” to install the nativescript-cloud extension and automatically configure your environment for cloud builds." + EOL - + "Select “Configure for Local Builds” to run the setup script and automatically configure your environment for local builds." - + "Select “Configure for Both Local and Cloud Builds” to automatically configure your environment for both options." - + "Select “Skip Step and Configure Manually” to disregard these options and install any required components manually."); + this.$logger.info(`You are missing the ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension and you will not be able to execute cloud builds. Your environment is not configured properly and you will not be able to execute local builds. ` + EOL + + `Select "Configure for Cloud Builds" to install the ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension and automatically configure your environment for cloud builds.` + EOL + + `Select "Configure for Local Builds" to run the setup script and automatically configure your environment for local builds.` + + `Select "Configure for Both Local and Cloud Builds" to automatically configure your environment for both options.` + + `Select "Skip Step and Configure Manually" to disregard these options and install any required components manually.`); const selectedOption = await this.$prompter.promptForChoice(PlatformEnvironmentRequirements.NOT_CONFIGURED_ENV_MESSAGE, [ PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME, PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME, + PlatformEnvironmentRequirements.BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME, PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME, - PlatformEnvironmentRequirements.BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME ]); - if (selectedOption === PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME) { - await this.processCloudBuilds(platform); - } + await this.processCloudBuildsIfNeeded(platform, selectedOption); + + this.processManuallySetupIfNeeded(platform, selectedOption); if (selectedOption === PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME) { await this.$doctorService.runSetupScript(); @@ -63,17 +63,9 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME ]); - if (option === PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME) { - await this.processCloudBuilds(platform); - } + await this.processCloudBuildsIfNeeded(platform, option); - if (option === PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME) { - this.processManuallySetup(platform); - } - } - - if (selectedOption === PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME) { - this.processManuallySetup(platform); + this.processManuallySetupIfNeeded(platform, option); } if (selectedOption === PlatformEnvironmentRequirements.BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME) { @@ -89,16 +81,19 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ return true; } + private async processCloudBuildsIfNeeded(platform: string, selectedOption: string): Promise { + if (selectedOption === PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME) { + await this.processCloudBuilds(platform); + } + } + private async processCloudBuilds(platform: string): Promise { await this.processCloudBuildsCore(platform); this.fail(this.getCloudBuildsMessage(platform)); } - private async processCloudBuildsCore(platform: string): Promise { - const installedExtensions = this.$extensibilityService.getInstalledExtensions(); - if (!installedExtensions[PlatformEnvironmentRequirements.NATIVESCRIPT_CLOUD_EXTENSION_NAME]) { - return this.$extensibilityService.installExtension(PlatformEnvironmentRequirements.NATIVESCRIPT_CLOUD_EXTENSION_NAME); - } + private processCloudBuildsCore(platform: string): Promise { + return this.$nativescriptCloudExtensionService.install(); } private getCloudBuildsMessage(platform: string): string { @@ -108,10 +103,16 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ } if (!platform) { - return `Use the $ tns login command to log in with your account and then $ tns cloud ${cloudCommandName.toLowerCase()} command to build your app in the cloud.`; + return `Use the $ tns login command to log in with your account and then $ ${cloudCommandName.toLowerCase()} command.`; } - return `Use the $ tns login command to log in with your account and then $ tns cloud ${cloudCommandName.toLowerCase()} ${platform.toLowerCase()} command to build your app in the cloud.`; + return `Use the $ tns login command to log in with your account and then $ ${cloudCommandName.toLowerCase()} ${platform.toLowerCase()} command.`; + } + + private processManuallySetupIfNeeded(platform: string, selectedOption: string) { + if (selectedOption === PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME) { + this.processManuallySetup(platform); + } } private processManuallySetup(platform: string): void { @@ -122,7 +123,7 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ try { await this.processCloudBuildsCore(platform); } catch (e) { - this.$logger.trace(`Error while installing nativescript-cloud extension. ${e.message}.`); + this.$logger.trace(`Error while installing ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension. ${e.message}.`); } await this.$doctorService.runSetupScript(); diff --git a/test/services/platform-environment-requirements.ts b/test/services/platform-environment-requirements.ts index 9aab41f25c..8c52e7635e 100644 --- a/test/services/platform-environment-requirements.ts +++ b/test/services/platform-environment-requirements.ts @@ -18,11 +18,11 @@ function createTestInjector() { throw new Error(err.formatStr || err.message || err); } }); - testInjector.register("extensibilityService", {}); testInjector.register("logger", stubs.LoggerStub); testInjector.register("prompter", {}); testInjector.register("platformEnvironmentRequirements", PlatformEnvironmentRequirements); testInjector.register("staticConfig", { SYS_REQUIREMENTS_LINK: "" }); + testInjector.register("nativescriptCloudExtensionService", {}); return testInjector; } @@ -51,9 +51,8 @@ describe("platformEnvironmentRequirements ", () => { }; let isInstallExtensionCalled = false; - const extensibilityService = testInjector.resolve("extensibilityService"); - extensibilityService.installExtension = () => {isInstallExtensionCalled = true; }; - extensibilityService.getInstalledExtensions = () => { return {}; }; + const nativescriptCloudExtensionService = testInjector.resolve("nativescriptCloudExtensionService"); + nativescriptCloudExtensionService.install = () => {isInstallExtensionCalled = true; }; await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); assert.isTrue(isPromptForChoiceCalled); @@ -70,17 +69,9 @@ describe("platformEnvironmentRequirements ", () => { describe("when setup script option is selected ", () => { it("should return true when env is configured after executing setup script", async () => { - let index = 0; const doctorService = testInjector.resolve("doctorService"); - doctorService.canExecuteLocalBuild = () => { - if (index === 0) { - index++; - return false; - } - - return true; - }; - doctorService.runSetupScript = () => Promise.resolve(); + doctorService.canExecuteLocalBuild = () => false; + doctorService.runSetupScript = async () => { doctorService.canExecuteLocalBuild = () => true; }; const prompter = testInjector.resolve("prompter"); prompter.promptForChoice = () => Promise.resolve(PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME); @@ -93,12 +84,11 @@ describe("platformEnvironmentRequirements ", () => { doctorService.canExecuteLocalBuild = () => false; doctorService.runSetupScript = () => Promise.resolve(); - let index = 0; - let isPromptForChoiceCalled = false; + let isPromptForChoiceCalled = true; const prompter = testInjector.resolve("prompter"); prompter.promptForChoice = () => { - if (index === 0) { - index++; + if (isPromptForChoiceCalled) { + isPromptForChoiceCalled = false; return PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME; } @@ -107,9 +97,8 @@ describe("platformEnvironmentRequirements ", () => { }; let isInstallExtensionCalled = false; - const extensibilityService = testInjector.resolve("extensibilityService"); - extensibilityService.installExtension = () => {isInstallExtensionCalled = true; }; - extensibilityService.getInstalledExtensions = () => { return {}; }; + const nativescriptCloudExtensionService = testInjector.resolve("nativescriptCloudExtensionService"); + nativescriptCloudExtensionService.install = () => {isInstallExtensionCalled = true; }; await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); assert.isTrue(isInstallExtensionCalled); @@ -123,12 +112,11 @@ describe("platformEnvironmentRequirements ", () => { doctorService.runSetupScript = () => Promise.resolve(); }); it("should install nativescript-cloud extension when cloud builds option is selected", async () => { - let index = 0; - let isPromptForChoiceCalled = false; + let isPromptForChoiceCalled = true; const prompter = testInjector.resolve("prompter"); prompter.promptForChoice = () => { - if (index === 0) { - index++; + if (isPromptForChoiceCalled) { + isPromptForChoiceCalled = false; return PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME; } @@ -137,21 +125,19 @@ describe("platformEnvironmentRequirements ", () => { }; let isInstallExtensionCalled = false; - const extensibilityService = testInjector.resolve("extensibilityService"); - extensibilityService.installExtension = () => isInstallExtensionCalled = true; - extensibilityService.getInstalledExtensions = () => { return {}; }; + const nativescriptCloudExtensionService = testInjector.resolve("nativescriptCloudExtensionService"); + nativescriptCloudExtensionService.install = () => {isInstallExtensionCalled = true; }; await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), cloudBuildsErrorMessage); assert.isTrue(isInstallExtensionCalled); assert.isTrue(isPromptForChoiceCalled); }); it("should fail when manually setup option is selected", async () => { - let index = 0; - let isPromptForChoiceCalled = false; + let isPromptForChoiceCalled = true; const prompter = testInjector.resolve("prompter"); prompter.promptForChoice = () => { - if (index === 0) { - index++; + if (isPromptForChoiceCalled) { + isPromptForChoiceCalled = false; return PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME; } @@ -174,9 +160,8 @@ describe("platformEnvironmentRequirements ", () => { prompter.promptForChoice = () => Promise.resolve(PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME); let isInstallExtensionCalled = false; - const extensibilityService = testInjector.resolve("extensibilityService"); - extensibilityService.installExtension = () => isInstallExtensionCalled = true; - extensibilityService.getInstalledExtensions = () => { return {}; }; + const nativescriptCloudExtensionService = testInjector.resolve("nativescriptCloudExtensionService"); + nativescriptCloudExtensionService.install = () => {isInstallExtensionCalled = true; }; await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), cloudBuildsErrorMessage); assert.isTrue(isInstallExtensionCalled); From 4e51bc621570efef76dc74a66614dafd43c1fe0b Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 12 Mar 2018 09:38:42 +0200 Subject: [PATCH 7/7] Fix setup commands --- docs/man_pages/index.md | 6 ++++++ lib/bootstrap.ts | 6 +++--- lib/commands/setup.ts | 4 ++-- lib/services/nativescript-cloud-extension-service.ts | 5 ++++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/man_pages/index.md b/docs/man_pages/index.md index 0602ad3101..54c6218068 100644 --- a/docs/man_pages/index.md +++ b/docs/man_pages/index.md @@ -57,6 +57,12 @@ Command | Description [device run](device/device-run.html) | Runs the selected application on a connected device. [device list-applications](device/device-list-applications.html) | Lists the installed applications on all connected devices. +## Environment Configuration Commands +Command | Description +---|--- +[setup](env-configuration/setup.html) | Run the setup script to try to automatically configure your environment for local builds. +[setup cloud](cloud/cloud-setup.html) | Install the `nativescript-cloud extension` to configure your environment for cloud builds. + ## Global Options Option | Description -------|--------- diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index cf115d556d..6711c80bab 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -72,15 +72,15 @@ $injector.requireCommand("appstore|upload", "./commands/appstore-upload"); $injector.requireCommand("publish|ios", "./commands/appstore-upload"); $injector.require("itmsTransporterService", "./services/itmstransporter-service"); -$injector.requireCommand("setup", "./commands/setup"); -$injector.requireCommand("cloud|setup", "./commands/setup"); +$injector.requireCommand("setup|*", "./commands/setup"); +$injector.requireCommand(["setup|cloud", "cloud|setup"], "./commands/setup"); $injector.requirePublic("npm", "./node-package-manager"); $injector.require("npmInstallationManager", "./npm-installation-manager"); $injector.require("dynamicHelpProvider", "./dynamic-help-provider"); $injector.require("mobilePlatformsCapabilities", "./mobile-platforms-capabilities"); $injector.require("commandsServiceProvider", "./providers/commands-service-provider"); -$injector.require("deviceAppDataProvider", "./providers/device-app-data-provider"); +$injector.require("AppDataProvider", "./providers/device-app-data-provider"); $injector.require("deviceLogProvider", "./common/mobile/device-log-provider"); $injector.require("projectFilesProvider", "./providers/project-files-provider"); diff --git a/lib/commands/setup.ts b/lib/commands/setup.ts index eb81ab1e4c..928582fb3e 100644 --- a/lib/commands/setup.ts +++ b/lib/commands/setup.ts @@ -7,7 +7,7 @@ export class SetupCommand implements ICommand { return this.$doctorService.runSetupScript(); } } -$injector.registerCommand("setup", SetupCommand); +$injector.registerCommand("setup|*", SetupCommand); export class CloudSetupCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; @@ -18,4 +18,4 @@ export class CloudSetupCommand implements ICommand { return this.$nativescriptCloudExtensionService.install(); } } -$injector.registerCommand("cloud|setup", CloudSetupCommand); +$injector.registerCommand(["setup|cloud", "cloud|setup"], CloudSetupCommand); diff --git a/lib/services/nativescript-cloud-extension-service.ts b/lib/services/nativescript-cloud-extension-service.ts index 4eb466fcbf..ef33a8d84b 100644 --- a/lib/services/nativescript-cloud-extension-service.ts +++ b/lib/services/nativescript-cloud-extension-service.ts @@ -2,13 +2,16 @@ import * as constants from "../constants"; export class NativescriptCloudExtensionService implements INativescriptCloudExtensionService { - constructor(private $extensibilityService: IExtensibilityService) { } + constructor(private $extensibilityService: IExtensibilityService, + private $logger: ILogger) { } public install(): Promise { const installedExtensions = this.$extensibilityService.getInstalledExtensions(); if (!installedExtensions[constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME]) { return this.$extensibilityService.installExtension(constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME); } + + this.$logger.out(`Extension ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} is already installed.`); } } $injector.register("nativescriptCloudExtensionService", NativescriptCloudExtensionService);