diff --git a/CHANGELOG.md b/CHANGELOG.md index e66fde4878..7f5c42f875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ NativeScript CLI Changelog ================ +4.0.0rc (2018, March 23) +== + +### New +* [Implemented #3243](https://github.com/NativeScript/nativescript-cli/issues/3243) 'Scss' file changes trigger app refresh instead reloading the view and apply generated 'css' +* [Implemented #3248](https://github.com/NativeScript/nativescript-cli/issues/3248) Support JDK_HOME and JAVA_HOME +* [Implemented #3257](https://github.com/NativeScript/nativescript-cli/issues/3257) Make {N} project structure configurable +* [Implemented #3317](https://github.com/NativeScript/nativescript-cli/issues/3317) Support livesync with webpack +* [Implemented #3449](https://github.com/NativeScript/nativescript-cli/pull/3449) Track Vue.js project type +* [Implemented #3496](https://github.com/NativeScript/nativescript-cli/issues/3496) Generate assets for the mobile application +* [Implemented #3497](https://github.com/NativeScript/nativescript-cli/issues/3497) Command to migrate Android resources to 4.0.0 structure + +### Fixed +* [Fixed #3151](https://github.com/NativeScript/nativescript-cli/issues/3151): Install fails if user setting file is not valid json +* [Fixed #3324](https://github.com/NativeScript/nativescript-cli/issues/3324): Error when iOS simulator's window is closed +* [Fixed #3442](https://github.com/NativeScript/nativescript-cli/issues/3442): Unnecessary second build upon `tns run android` +* [Fixed #3451](https://github.com/NativeScript/nativescript-cli/issues/3451): `tns plugin remove` fails with : Cannot convert undefined or null to object + + 3.4.3 (2018, March 02) == diff --git a/docs/man_pages/project/configuration/resources/resources-generate-splashes.md b/docs/man_pages/project/configuration/resources/resources-generate-splashes.md index 5db76757dc..02655d7020 100644 --- a/docs/man_pages/project/configuration/resources/resources-generate-splashes.md +++ b/docs/man_pages/project/configuration/resources/resources-generate-splashes.md @@ -6,13 +6,16 @@ position: 12 Usage | Synopsis ------|------- -`$ tns resources generate splashes []` | Generate all splashscreens for Android and iOS based on the specified image. +`$ tns resources generate splashes [--background ]` | Generate all splashscreens for Android and iOS based on the specified image. -Generates all icons for Android and iOS platforms and places the generated images in the correct directories under `App_Resources/` directory. +Generates all splashscreens for Android and iOS platforms and places the generated images in the correct directories under `App_Resources/` directory. + +### Options +* `--background` Sets the background color of the splashscreen. Defaults to white in case it is not specified. ### Attributes * `` is a valid path to an image that will be used to generate all splashscreens. -* `` is a valid path to an image that will be used as a background of the splashscreen. Defaults to white in case it is not specified. +* `` is a valid color. It can be represented with string, like `white`, `black`, `blue`, etc. or its HEX representation, for example `#FFFFFF`, `#000000`, `#0000FF`. NOTE: As the `#` is special symbol in some terminals, make sure to place the value in quotes, for example `$ tns resources generate splashes ../myImage.png --background "#FF00FF"`. <% if(isHtml) { %> ### Related Commands diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index d1e532459f..9f4bb2094d 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -160,7 +160,7 @@ $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"); +$injector.require("nativeScriptCloudExtensionService", "./services/nativescript-cloud-extension-service"); $injector.requireCommand("resources|generate|icons", "./commands/generate-assets"); $injector.requireCommand("resources|generate|splashes", "./commands/generate-assets"); diff --git a/lib/commands/create-project.ts b/lib/commands/create-project.ts index 802f829f44..bc024dc6f5 100644 --- a/lib/commands/create-project.ts +++ b/lib/commands/create-project.ts @@ -1,10 +1,14 @@ import * as constants from "../constants"; +import * as path from "path"; export class CreateProjectCommand implements ICommand { public enableHooks = false; public allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("Project name cannot be empty.")]; + private createdProjecData: ICreateProjectData; + constructor(private $projectService: IProjectService, + private $logger: ILogger, private $errors: IErrors, private $options: IOptions, private $stringParameterBuilder: IStringParameterBuilder) { } @@ -23,7 +27,7 @@ export class CreateProjectCommand implements ICommand { selectedTemplate = this.$options.template; } - await this.$projectService.createProject({ + this.createdProjecData = await this.$projectService.createProject({ projectName: args[0], template: selectedTemplate, appId: this.$options.appid, @@ -32,6 +36,13 @@ export class CreateProjectCommand implements ICommand { ignoreScripts: this.$options.ignoreScripts }); } + + public async postCommandAction(args: string[]): Promise { + const { projectDir } = this.createdProjecData; + const relativePath = path.relative(process.cwd(), projectDir); + this.$logger.printMarkdown(`Now you can navigate to your project with \`$ cd ${relativePath}\``); + this.$logger.printMarkdown(`After that you can run it on device/emulator by executing \`$ tns run \``); + } } $injector.registerCommand("create", CreateProjectCommand); diff --git a/lib/commands/post-install.ts b/lib/commands/post-install.ts index 80db0dfc9b..75c600e6a5 100644 --- a/lib/commands/post-install.ts +++ b/lib/commands/post-install.ts @@ -18,6 +18,27 @@ export class PostInstallCliCommand extends PostInstallCommand { await this.$subscriptionService.subscribeForNewsletter(); } + + public async postCommandAction(args: string[]): Promise { + this.$logger.info("You have successfully installed NativeScript CLI."); + this.$logger.info("In order to create a new project, you can use:".green); + this.$logger.printMarkdown("`tns create `"); + this.$logger.info("To build your project locally you can use:".green); + this.$logger.printMarkdown("`tns build `"); + this.$logger.printMarkdown("NOTE: Local builds require additional setup of your environment. You can find more information here: `https://docs.nativescript.org/start/quick-setup`"); + + // Add a new line just to ensure separation between local builds and cloud builds info. + this.$logger.info(""); + this.$logger.info("To build your project in the cloud you can use:".green); + this.$logger.printMarkdown("`tns cloud build `"); + this.$logger.printMarkdown("NOTE: Cloud builds require Telerik account. You can find more information here: `https://docs.nativescript.org/sidekick/intro/requirements`"); + + this.$logger.info(""); + this.$logger.printMarkdown("In case you want to experiment quickly with NativeScript, you can try the Playground: `https://play.nativescript.org`"); + + this.$logger.info(""); + this.$logger.printMarkdown("In case you have any questions, you can check our forum: `https://forum.nativescript.org` and our public Slack channel: `https://nativescriptcommunity.slack.com/`"); + } } $injector.registerCommand("post-install-cli", PostInstallCliCommand); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 38365c2a59..f2d361d609 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -28,7 +28,7 @@ export class RunCommandBase implements ICommand { this.platform = this.$devicePlatformsConstants.Android; } - this.$liveSyncCommandHelper.validatePlatform(this.platform); + await this.$liveSyncCommandHelper.validatePlatform(this.platform); return true; } diff --git a/lib/commands/setup.ts b/lib/commands/setup.ts index 928582fb3e..cfddc687a7 100644 --- a/lib/commands/setup.ts +++ b/lib/commands/setup.ts @@ -12,10 +12,10 @@ $injector.registerCommand("setup|*", SetupCommand); export class CloudSetupCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $nativescriptCloudExtensionService: INativescriptCloudExtensionService) { } + constructor(private $nativeScriptCloudExtensionService: INativeScriptCloudExtensionService) { } public execute(args: string[]): Promise { - return this.$nativescriptCloudExtensionService.install(); + return this.$nativeScriptCloudExtensionService.install(); } } $injector.registerCommand(["setup|cloud", "cloud|setup"], CloudSetupCommand); diff --git a/lib/common b/lib/common index ea2a16a92c..0b68ca58bc 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit ea2a16a92cf65dcb6092e199744930acf545992a +Subproject commit 0b68ca58bc81c05e8e3cd6a267aaa11eb3ff61b5 diff --git a/lib/constants.ts b/lib/constants.ts index 31fa4d306f..7983e71a1a 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -135,7 +135,10 @@ export const enum TrackActionNames { CreateProject = "Create project", Debug = "Debug", Deploy = "Deploy", - LiveSync = "LiveSync" + LiveSync = "LiveSync", + RunSetupScript = "Run Setup Script", + CheckLocalBuildSetup = "Check Local Build Setup", + CheckEnvironmentRequirements = "Check Environment Requirements" } export const enum BuildStates { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index bb644633c1..ae5637ae1d 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -788,12 +788,17 @@ interface IBundleValidatorHelper { validate(): void; } -interface INativescriptCloudExtensionService { +interface INativeScriptCloudExtensionService { /** * Installs nativescript-cloud extension * @return {Promise} returns the extension data */ install(): Promise; + /** + * Checks if nativescript-cloud extension is installed + * @return {boolean} returns true in case when nativescript-cloud extension is installed, false otherwise + */ + isInstalled(): boolean } /** diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 190467ddd0..4f098575eb 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -381,5 +381,5 @@ interface IUpdateAppOptions extends IOptionalFilesToSync, IOptionalFilesToRemove } interface IPlatformEnvironmentRequirements { - checkEnvironmentRequirements(platform: string): Promise; + checkEnvironmentRequirements(platform?: string): Promise; } \ No newline at end of file diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 01132e0d95..4ec6eb1d49 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -37,13 +37,21 @@ interface IProjectSettings { ignoreScripts?: boolean; } +interface IProjectName { + projectName: string; +} + +interface ICreateProjectData extends IProjectDir, IProjectName { + +} + interface IProjectService { /** * Creates new NativeScript application. * @param {any} projectSettings Options describing new project - its name, appId, path and template from which to be created. * @returns {Promise} */ - createProject(projectSettings: IProjectSettings): Promise; + createProject(projectSettings: IProjectSettings): Promise; /** * Checks if the specified project is valid NativeScript project. @@ -58,8 +66,7 @@ interface INsConfig { appResourcesPath?: string; } -interface IProjectData extends IProjectDir { - projectName: string; +interface IProjectData extends ICreateProjectData { platformsDir: string; projectFilePath: string; projectId?: string; @@ -394,12 +401,6 @@ interface IPlatformProjectService extends NodeJS.EventEmitter { executeCommand(projectRoot: string, args: any, childProcessOpts?: any, spawnFromEventOptions?: ISpawnFromEventOptions): Promise; } -interface IAndroidProjectPropertiesManager { - getProjectReferences(): Promise; - addProjectReference(referencePath: string): Promise; - removeProjectReference(referencePath: string): Promise; -} - interface ITestExecutionService { startTestRunner(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; startKarmaServer(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; diff --git a/lib/definitions/simple-plist.d.ts b/lib/definitions/simple-plist.d.ts deleted file mode 100644 index adc559fc81..0000000000 --- a/lib/definitions/simple-plist.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "simple-plist" { - export function readFile(filePath: string, callback?:(err: Error, obj: any) => void): void; - export function readFileSync(filePath: string): any; -} diff --git a/lib/services/analytics/analytics-service.ts b/lib/services/analytics/analytics-service.ts index 3c28cd138f..5e24e81871 100644 --- a/lib/services/analytics/analytics-service.ts +++ b/lib/services/analytics/analytics-service.ts @@ -73,7 +73,7 @@ export class AnalyticsService extends AnalyticsServiceBase { // In some cases (like in case action is Build and platform is Android), we do not know if the deviceType is emulator or device. // Just exclude the device_type in this case. - if (isForDevice !== null) { + if (isForDevice !== null && isForDevice !== undefined) { const deviceType = isForDevice ? DeviceTypes.Device : (this.$mobileHelper.isAndroidPlatform(platform) ? DeviceTypes.Emulator : DeviceTypes.Simulator); label = this.addDataToLabel(label, deviceType); } diff --git a/lib/services/analytics/google-analytics-provider.ts b/lib/services/analytics/google-analytics-provider.ts index 2ce3a8284e..785e6d1c10 100644 --- a/lib/services/analytics/google-analytics-provider.ts +++ b/lib/services/analytics/google-analytics-provider.ts @@ -4,7 +4,6 @@ import { AnalyticsClients } from "../../common/constants"; export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { private static GA_TRACKING_ID = "UA-111455-44"; - private static GA_CROSS_CLIENT_TRACKING_ID = "UA-111455-51"; private currentPage: string; constructor(private clientId: string, @@ -15,15 +14,12 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { } public async trackHit(trackInfo: IGoogleAnalyticsData): Promise { - const trackingIds = [GoogleAnalyticsProvider.GA_TRACKING_ID, GoogleAnalyticsProvider.GA_CROSS_CLIENT_TRACKING_ID]; const sessionId = uuid.v4(); - for (const gaTrackingId of trackingIds) { - try { - await this.track(gaTrackingId, trackInfo, sessionId); - } catch (e) { - this.$logger.trace("Analytics exception: ", e); - } + try { + await this.track(GoogleAnalyticsProvider.GA_TRACKING_ID, trackInfo, sessionId); + } catch (e) { + this.$logger.trace("Analytics exception: ", e); } } @@ -41,14 +37,7 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { } }); - switch (gaTrackingId) { - case GoogleAnalyticsProvider.GA_CROSS_CLIENT_TRACKING_ID: - this.setCrossClientCustomDimensions(visitor, sessionId); - break; - default: - await this.setCustomDimensions(visitor, trackInfo.customDimensions, sessionId); - break; - } + await this.setCustomDimensions(visitor, trackInfo.customDimensions, sessionId); switch (trackInfo.googleAnalyticsDataType) { case GoogleAnalyticsDataType.Page: @@ -83,18 +72,6 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { }); } - private async setCrossClientCustomDimensions(visitor: ua.Visitor, sessionId: string): Promise { - const customDimensions: IStringDictionary = { - [GoogleAnalyticsCrossClientCustomDimensions.sessionId]: sessionId, - [GoogleAnalyticsCrossClientCustomDimensions.clientId]: this.clientId, - [GoogleAnalyticsCrossClientCustomDimensions.crossClientId]: this.clientId, - }; - - _.each(customDimensions, (value, key) => { - visitor.set(key, value); - }); - } - private trackEvent(visitor: ua.Visitor, trackInfo: IGoogleAnalyticsEventData): Promise { return new Promise((resolve, reject) => { visitor.event(trackInfo.category, trackInfo.action, trackInfo.label, trackInfo.value, { p: this.currentPage }, (err: Error) => { diff --git a/lib/services/android-plugin-build-service.ts b/lib/services/android-plugin-build-service.ts index 2a5ac66f87..3e71fd57d1 100644 --- a/lib/services/android-plugin-build-service.ts +++ b/lib/services/android-plugin-build-service.ts @@ -253,9 +253,11 @@ export class AndroidPluginBuildService implements IAndroidPluginBuildService { const androidToolsInfo = this.$androidToolsInfo.getToolsInfo(); const compileSdk = androidToolsInfo.compileSdkVersion; const buildToolsVersion = androidToolsInfo.buildToolsVersion; + const supportVersion = androidToolsInfo.supportRepositoryVersion; localArgs.push(`-PcompileSdk=android-${compileSdk}`); localArgs.push(`-PbuildToolsVersion=${buildToolsVersion}`); + localArgs.push(`-PsupportVersion=${supportVersion}`); try { await this.$childProcess.exec(localArgs.join(" "), { cwd: newPluginDir }); diff --git a/lib/services/android-project-properties-manager.ts b/lib/services/android-project-properties-manager.ts deleted file mode 100644 index 71a1338b61..0000000000 --- a/lib/services/android-project-properties-manager.ts +++ /dev/null @@ -1,101 +0,0 @@ -import * as path from "path"; - -export class AndroidProjectPropertiesManager implements IAndroidProjectPropertiesManager { - private _editor: IPropertiesParserEditor = null; - private filePath: string = null; - private projectReferences: ILibRef[]; - private dirty = false; - - constructor(private $propertiesParser: IPropertiesParser, - private $logger: ILogger, - directoryPath: string) { - this.filePath = path.join(directoryPath, "project.properties"); - } - - public async getProjectReferences(): Promise { - if (!this.projectReferences || this.dirty) { - const allProjectProperties = await this.getAllProjectProperties(); - const allProjectPropertiesKeys = _.keys(allProjectProperties); - this.projectReferences = _(allProjectPropertiesKeys) - .filter(key => _.startsWith(key, "android.library.reference.")) - .map(key => this.createLibraryReference(key, allProjectProperties[key])) - .value(); - } - - return this.projectReferences; - } - - public async addProjectReference(referencePath: string): Promise { - const references = await this.getProjectReferences(); - const libRefExists = _.some(references, r => path.normalize(r.path) === path.normalize(referencePath)); - if (!libRefExists) { - await this.addToPropertyList("android.library.reference", referencePath); - } - } - - public async removeProjectReference(referencePath: string): Promise { - const references = await this.getProjectReferences(); - const libRefExists = _.some(references, r => path.normalize(r.path) === path.normalize(referencePath)); - if (libRefExists) { - await this.removeFromPropertyList("android.library.reference", referencePath); - } else { - this.$logger.error(`Could not find ${referencePath}.`); - } - } - - private async createEditor(): Promise { - return this._editor || await this.$propertiesParser.createEditor(this.filePath); - } - - private buildKeyName(key: string, index: number): string { - return `${key}.${index}`; - } - - private async getAllProjectProperties(): Promise { - return this.$propertiesParser.read(this.filePath); - } - - private createLibraryReference(referenceName: string, referencePath: string): ILibRef { - return { - idx: parseInt(referenceName.split("android.library.reference.")[1]), - key: referenceName, - path: referencePath, - adjustedPath: path.join(path.dirname(this.filePath), referencePath) - }; - } - - private async addToPropertyList(key: string, value: string): Promise { - const editor = await this.createEditor(); - let i = 1; - while (editor.get(this.buildKeyName(key, i))) { - i++; - } - - editor.set(this.buildKeyName(key, i), value); - await this.$propertiesParser.saveEditor(); - this.dirty = true; - } - - private async removeFromPropertyList(key: string, value: string): Promise { - const editor = await this.createEditor(); - const valueLowerCase = value.toLowerCase(); - let i = 1; - let currentValue: any; - while (currentValue = editor.get(this.buildKeyName(key, i))) { - if (currentValue.toLowerCase() === valueLowerCase) { - while (currentValue = editor.get(this.buildKeyName(key, i + 1))) { - editor.set(this.buildKeyName(key, i), currentValue); - i++; - } - - editor.set(this.buildKeyName(key, i)); - break; - } - - i++; - } - - await this.$propertiesParser.saveEditor(); - this.dirty = true; - } -} diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index e7c36762d3..d47626112f 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -20,7 +20,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject { name: "lazy", version: "^1.0.11" } ]; - private _androidProjectPropertiesManagers: IDictionary; private isAndroidStudioTemplate: boolean; constructor(private $androidEmulatorServices: Mobile.IEmulatorPlatformServices, @@ -39,7 +38,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, private $androidResourcesMigrationService: IAndroidResourcesMigrationService) { super($fs, $projectDataService); - this._androidProjectPropertiesManagers = Object.create(null); this.isAndroidStudioTemplate = false; } diff --git a/lib/services/android-resources-migration-service.ts b/lib/services/android-resources-migration-service.ts index 612fe07558..2aac57b327 100644 --- a/lib/services/android-resources-migration-service.ts +++ b/lib/services/android-resources-migration-service.ts @@ -1,6 +1,6 @@ import * as path from "path"; -import * as shell from "shelljs"; import * as constants from "../constants"; +import { EOL } from "os"; export class AndroidResourcesMigrationService implements IAndroidResourcesMigrationService { private static ANDROID_DIR = "Android"; @@ -8,6 +8,7 @@ export class AndroidResourcesMigrationService implements IAndroidResourcesMigrat private static ANDROID_DIR_OLD = "Android-Pre-v4"; constructor(private $fs: IFileSystem, + private $errors: IErrors, private $logger: ILogger, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants) { } @@ -22,6 +23,25 @@ export class AndroidResourcesMigrationService implements IAndroidResourcesMigrat public async migrate(appResourcesDir: string): Promise { const originalAppResources = path.join(appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR); const appResourcesDestination = path.join(appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR_TEMP); + const appResourcesBackup = path.join(appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR_OLD); + + try { + await this.tryMigrate(originalAppResources, appResourcesDestination, appResourcesBackup); + this.$logger.out(`Successfully updated your project's application resources '/Android' directory structure.${EOL}The previous version of your Android application resources has been renamed to '/${AndroidResourcesMigrationService.ANDROID_DIR_OLD}'`); + } catch (error) { + try { + this.recover(originalAppResources, appResourcesDestination, appResourcesBackup); + this.$logger.out("Failed to update resources. They should be in their initial state."); + } catch (err) { + this.$logger.trace(err); + this.$logger.out(`Failed to update resources.${EOL} Backup of original content is inside "${appResourcesBackup}".${EOL}If "${originalAppResources} is missing copy from backup folder."`); + } finally { + this.$errors.failWithoutHelp(error.message); + } + } + } + + private async tryMigrate(originalAppResources: string, appResourcesDestination: string, appResourcesBackup: string): Promise { const appMainSourceSet = path.join(appResourcesDestination, constants.SRC_DIR, constants.MAIN_DIR); const appResourcesMainSourceSetResourcesDestination = path.join(appMainSourceSet, constants.RESOURCES_DIR); @@ -64,11 +84,20 @@ export class AndroidResourcesMigrationService implements IAndroidResourcesMigrat this.$fs.copyFile(path.join(originalAppResources, constants.MANIFEST_FILE_NAME), path.join(appMainSourceSet, constants.MANIFEST_FILE_NAME)); // rename the legacy app_resources to ANDROID_DIR_OLD - shell.mv(originalAppResources, path.join(appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR_OLD)); + this.$fs.rename(originalAppResources, appResourcesBackup); + // move the new, updated app_resources to App_Resources/Android, as the de facto resources - shell.mv(appResourcesDestination, originalAppResources); + this.$fs.rename(appResourcesDestination, originalAppResources); + } + + private recover(originalAppResources: string, appResourcesDestination: string, appResourcesBackup: string): void { + if (!this.$fs.exists(originalAppResources)) { + this.$fs.rename(appResourcesBackup, originalAppResources); + } - this.$logger.out(`Successfully updated your project's application resources '/Android' directory structure.\nThe previous version of your Android application resources has been renamed to '/${AndroidResourcesMigrationService.ANDROID_DIR_OLD}'`); + if (this.$fs.exists(appResourcesDestination)) { + this.$fs.deleteDirectory(appResourcesDestination); + } } } diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index 4b0b95580b..514614833c 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -1,22 +1,19 @@ import { EOL } from "os"; import * as path from "path"; import * as helpers from "../common/helpers"; +import { TrackActionNames } from "../constants"; import { doctor, constants } from "nativescript-doctor"; class DoctorService implements IDoctorService { private static DarwinSetupScriptLocation = path.join(__dirname, "..", "..", "setup", "mac-startup-shell-script.sh"); - private static DarwinSetupDocsLink = "https://docs.nativescript.org/start/ns-setup-os-x"; private static WindowsSetupScriptExecutable = "powershell.exe"; private static WindowsSetupScriptArguments = ["start-process", "-FilePath", "PowerShell.exe", "-NoNewWindow", "-Wait", "-ArgumentList", '"-NoProfile -ExecutionPolicy Bypass -Command iex ((new-object net.webclient).DownloadString(\'https://www.nativescript.org/setup/win\'))"']; - private static WindowsSetupDocsLink = "https://docs.nativescript.org/start/ns-setup-win"; - private static LinuxSetupDocsLink = "https://docs.nativescript.org/start/ns-setup-linux"; constructor(private $analyticsService: IAnalyticsService, private $hostInfo: IHostInfo, private $logger: ILogger, private $childProcess: IChildProcess, - private $opener: IOpener, - private $prompter: IPrompter, + private $injector: IInjector, private $terminalSpinnerService: ITerminalSpinnerService, private $versionsService: IVersionsService) { } @@ -41,7 +38,6 @@ class DoctorService implements IDoctorService { if (hasWarnings) { this.$logger.info("There seem to be issues with your configuration."); - await this.promptForHelp(); } else { this.$logger.out("No issues were detected.".bold); } @@ -51,61 +47,65 @@ class DoctorService implements IDoctorService { } catch (err) { this.$logger.error("Cannot get the latest versions information from npm. Please try again later."); } + + await this.$injector.resolve("platformEnvironmentRequirements").checkEnvironmentRequirements(null); } - public runSetupScript(): Promise { + public async runSetupScript(): Promise { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.RunSetupScript, + additionalData: "Starting", + }); + if (this.$hostInfo.isLinux) { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.RunSetupScript, + additionalData: "Skipped as OS is Linux", + }); return; } this.$logger.out("Running the setup script to try and automatically configure your environment."); if (this.$hostInfo.isDarwin) { - return this.runSetupScriptCore(DoctorService.DarwinSetupScriptLocation, []); + await this.runSetupScriptCore(DoctorService.DarwinSetupScriptLocation, []); } if (this.$hostInfo.isWindows) { - return this.runSetupScriptCore(DoctorService.WindowsSetupScriptExecutable, DoctorService.WindowsSetupScriptArguments); + await this.runSetupScriptCore(DoctorService.WindowsSetupScriptExecutable, DoctorService.WindowsSetupScriptArguments); } + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.RunSetupScript, + additionalData: "Finished", + }); } public async canExecuteLocalBuild(platform?: string): Promise { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.CheckLocalBuildSetup, + additionalData: "Starting", + }); const infos = await doctor.getInfos({ platform }); const warnings = this.filterInfosByType(infos, constants.WARNING_TYPE_NAME); - if (warnings.length > 0) { + const hasWarnings = warnings.length > 0; + if (hasWarnings) { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.CheckLocalBuildSetup, + additionalData: `Warnings:${warnings.map(w => w.message).join("__")}`, + }); this.printInfosCore(infos); } else { infos.map(info => this.$logger.trace(info.message)); } - 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 promptForSetupScript(executablePath: string, setupScriptArgs: string[]): Promise { - if (await this.$prompter.confirm("Do you want to run the setup script?", () => helpers.isInteractive())) { - await this.runSetupScriptCore(executablePath, setupScriptArgs); - } - } - private async promptForHelp(): Promise { - if (this.$hostInfo.isDarwin) { - await this.promptForHelpCore(DoctorService.DarwinSetupDocsLink, DoctorService.DarwinSetupScriptLocation, []); - } else if (this.$hostInfo.isWindows) { - await this.promptForHelpCore(DoctorService.WindowsSetupDocsLink, DoctorService.WindowsSetupScriptExecutable, DoctorService.WindowsSetupScriptArguments); - } else { - await this.promptForDocs(DoctorService.LinuxSetupDocsLink); - } - } + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.CheckLocalBuildSetup, + additionalData: `Finished: Is setup correct: ${!hasWarnings}`, + }); - private async promptForHelpCore(link: string, setupScriptExecutablePath: string, setupScriptArgs: string[]): Promise { - await this.promptForDocs(link); - await this.promptForSetupScript(setupScriptExecutablePath, setupScriptArgs); + return !hasWarnings; } private async runSetupScriptCore(executablePath: string, setupScriptArgs: string[]): Promise { diff --git a/lib/services/ios-entitlements-service.ts b/lib/services/ios-entitlements-service.ts index 71d2cde947..29012eb9b1 100644 --- a/lib/services/ios-entitlements-service.ts +++ b/lib/services/ios-entitlements-service.ts @@ -56,10 +56,11 @@ export class IOSEntitlementsService { makePatch(appEntitlementsPath); } - const plistContent = session.build(); - this.$logger.trace("App.entitlements: Write to: " + this.getPlatformsEntitlementsPath(projectData)); - this.$fs.writeFile(this.getPlatformsEntitlementsPath(projectData), plistContent); - return; + if ((session).patches && (session).patches.length > 0) { + const plistContent = session.build(); + this.$logger.trace("App.entitlements: Write to: " + this.getPlatformsEntitlementsPath(projectData)); + this.$fs.writeFile(this.getPlatformsEntitlementsPath(projectData), plistContent); + } } private getAllInstalledPlugins(projectData: IProjectData): Promise { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 365aa08448..4191bee724 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -13,7 +13,6 @@ import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; import { XCConfigService } from "./xcconfig-service"; -import * as simplePlist from "simple-plist"; import * as mobileprovision from "ios-mobileprovision-finder"; import { SpawnOptions } from "child_process"; import { BUILD_XCCONFIG_FILE_NAME } from "../constants"; @@ -51,6 +50,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $xcode: IXcode, private $iOSEntitlementsService: IOSEntitlementsService, private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, + private $plistParser: IPlistParser, private $sysInfo: ISysInfo, private $xCConfigService: XCConfigService) { super($fs, $projectDataService); @@ -1045,7 +1045,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.$errors.failWithoutHelp("The bundle at %s does not contain an Info.plist file.", libraryPath); } - const plistJson = simplePlist.readFileSync(infoPlistPath); + const plistJson = this.$plistParser.parseFileSync(infoPlistPath); const packageType = plistJson["CFBundlePackageType"]; if (packageType !== "FMWK") { @@ -1241,7 +1241,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f // Set Entitlements Property to point to default file if not set explicitly by the user. const entitlementsPropertyValue = this.$xCConfigService.readPropertyValue(pluginsXcconfigFilePath, constants.CODE_SIGN_ENTITLEMENTS); - if (entitlementsPropertyValue === null) { + if (entitlementsPropertyValue === null && this.$fs.exists(this.$iOSEntitlementsService.getPlatformsEntitlementsPath(projectData))) { temp.track(); const tempEntitlementsDir = temp.mkdirSync("entitlements"); const tempEntitlementsFilePath = path.join(tempEntitlementsDir, "set-entitlements.xcconfig"); diff --git a/lib/services/itmstransporter-service.ts b/lib/services/itmstransporter-service.ts index e60b23da2c..474aea69c0 100644 --- a/lib/services/itmstransporter-service.ts +++ b/lib/services/itmstransporter-service.ts @@ -10,7 +10,7 @@ export class ITMSTransporterService implements IITMSTransporterService { private _itunesConnectApplications: IiTunesConnectApplication[] = null; private _bundleIdentifier: string = null; - constructor(private $bplistParser: IBinaryPlistParser, + constructor(private $plistParser: IPlistParser, private $childProcess: IChildProcess, private $errors: IErrors, private $fs: IFileSystem, @@ -135,8 +135,8 @@ export class ITMSTransporterService implements IITMSTransporterService { } const appFile = path.join(payloadDir, allApps[0]); - const plistObject = await this.$bplistParser.parseFile(path.join(appFile, INFO_PLIST_FILE_NAME)); - const bundleId = plistObject && plistObject[0] && plistObject[0].CFBundleIdentifier; + const plistObject = await this.$plistParser.parseFile(path.join(appFile, INFO_PLIST_FILE_NAME)); + const bundleId = plistObject && plistObject.CFBundleIdentifier; if (!bundleId) { this.$errors.failWithoutHelp(`Unable to determine bundle identifier from ${ipaFileFullPath}.`); } diff --git a/lib/services/nativescript-cloud-extension-service.ts b/lib/services/nativescript-cloud-extension-service.ts index 14eeacd46c..8b6a0f5889 100644 --- a/lib/services/nativescript-cloud-extension-service.ts +++ b/lib/services/nativescript-cloud-extension-service.ts @@ -1,17 +1,35 @@ import * as constants from "../constants"; +import * as semver from "semver"; -export class NativescriptCloudExtensionService implements INativescriptCloudExtensionService { - +export class NativeScriptCloudExtensionService implements INativeScriptCloudExtensionService { constructor(private $extensibilityService: IExtensibilityService, - private $logger: ILogger) { } + private $logger: ILogger, + private $npmInstallationManager: INpmInstallationManager) { } public install(): Promise { - const installedExtensions = this.$extensibilityService.getInstalledExtensions() || {}; - if (!installedExtensions[constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME]) { + if (!this.isInstalled()) { return this.$extensibilityService.installExtension(constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME); } this.$logger.out(`Extension ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} is already installed.`); } + + public isInstalled(): boolean { + return !!this.getExtensionData(); + } + + public async isLatestVersionInstalled(): Promise { + const extensionData = this.getExtensionData(); + if (extensionData) { + const latestVersion = await this.$npmInstallationManager.getLatestVersion(constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME); + return semver.eq(latestVersion, extensionData.version); + } + + return false; + } + + private getExtensionData(): IExtensionData { + return _.find(this.$extensibilityService.getInstalledExtensionsData(), extensionData => extensionData.extensionName === constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME); + } } -$injector.register("nativescriptCloudExtensionService", NativescriptCloudExtensionService); +$injector.register("nativeScriptCloudExtensionService", NativeScriptCloudExtensionService); diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index 0ae315c8dd..cfc1213c2f 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -1,4 +1,4 @@ -import * as constants from "../constants"; +import { NATIVESCRIPT_CLOUD_EXTENSION_NAME, TrackActionNames } from "../constants"; import { isInteractive } from "../common/helpers"; import { EOL } from "os"; @@ -6,17 +6,23 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ constructor(private $commandsService: ICommandsService, private $doctorService: IDoctorService, private $errors: IErrors, - private $nativescriptCloudExtensionService: INativescriptCloudExtensionService, private $logger: ILogger, + private $nativeScriptCloudExtensionService: INativeScriptCloudExtensionService, private $prompter: IPrompter, - private $staticConfig: IStaticConfig) { } + private $staticConfig: IStaticConfig, + private $analyticsService: IAnalyticsService) { } - public static CLOUD_BUILDS_OPTION_NAME = "Configure for Cloud Builds"; - public static SETUP_SCRIPT_OPTION_NAME = "Configure for Local Builds"; + public static CLOUD_SETUP_OPTION_NAME = "Configure for Cloud Builds"; + public static LOCAL_SETUP_OPTION_NAME = "Configure for Local Builds"; + public static TRY_CLOUD_OPERATION_OPTION_NAME = "Try Cloud Operation"; 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 BOTH_CLOUD_SETUP_AND_LOCAL_SETUP_OPTION_NAME = "Configure for Both Local and Cloud Builds"; + private static CHOOSE_OPTIONS_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. In case you have any questions, you can check our forum: 'http://forum.nativescript.org' and our public Slack channel: 'https://nativescriptcommunity.slack.com/'.`; + private static MISSING_LOCAL_SETUP_MESSAGE = "Your environment is not configured properly and you will not be able to execute local builds."; + private static MISSING_LOCAL_AND_CLOUD_SETUP_MESSAGE = `You are missing the ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension and you will not be able to execute cloud builds. ${PlatformEnvironmentRequirements.MISSING_LOCAL_SETUP_MESSAGE} ${PlatformEnvironmentRequirements.CHOOSE_OPTIONS_MESSAGE} `; + private static MISSING_LOCAL_BUT_CLOUD_SETUP_MESSAGE = `You have ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension installed, so you can execute cloud builds, but ${_.lowerFirst(PlatformEnvironmentRequirements.MISSING_LOCAL_SETUP_MESSAGE)}`; + private static RUN_TNS_SETUP_MESSAGE = 'Run $ tns setup command to run the setup script to try to automatically configure your environment for local builds.'; private cliCommandToCloudCommandName: IStringDictionary = { "build": "tns cloud build", @@ -24,83 +30,107 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ "deploy": "tns cloud deploy" }; - public async checkEnvironmentRequirements(platform: string): Promise { + public async checkEnvironmentRequirements(platform?: string): Promise { if (process.env.NS_SKIP_ENV_CHECK) { + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.CheckEnvironmentRequirements, + additionalData: "Skipped: NS_SKIP_ENV_CHECK is set" + }); return true; } const canExecute = await this.$doctorService.canExecuteLocalBuild(platform); if (!canExecute) { if (!isInteractive()) { - 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 ${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}`); + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.CheckEnvironmentRequirements, + additionalData: "Non-interactive terminal, unable to execute local builds." + }); + this.fail(this.getNonInteractiveConsoleMessage(platform)); } - 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 infoMessage = this.getInteractiveConsoleMessage(platform); + this.$logger.info(infoMessage); - 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, + const choices = this.$nativeScriptCloudExtensionService.isInstalled() ? [ + PlatformEnvironmentRequirements.TRY_CLOUD_OPERATION_OPTION_NAME, + PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME, - ]); + ] : [ + PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME, + PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, + PlatformEnvironmentRequirements.BOTH_CLOUD_SETUP_AND_LOCAL_SETUP_OPTION_NAME, + PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME, + ]; - await this.processCloudBuildsIfNeeded(platform, selectedOption); + const selectedOption = await this.promptForChoice({ infoMessage, choices }); - this.processManuallySetupIfNeeded(platform, selectedOption); + await this.processCloudBuildsIfNeeded(selectedOption, platform); + this.processManuallySetupIfNeeded(selectedOption, platform); - if (selectedOption === PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME) { + if (selectedOption === PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME) { await this.$doctorService.runSetupScript(); if (await this.$doctorService.canExecuteLocalBuild(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 - ]); - - await this.processCloudBuildsIfNeeded(platform, option); - - this.processManuallySetupIfNeeded(platform, option); + if (this.$nativeScriptCloudExtensionService.isInstalled()) { + const option = await this.promptForChoice({ + infoMessage: PlatformEnvironmentRequirements.NOT_CONFIGURED_ENV_AFTER_SETUP_SCRIPT_MESSAGE, + choices: [ + PlatformEnvironmentRequirements.TRY_CLOUD_OPERATION_OPTION_NAME, + PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME + ] + }); + + this.processTryCloudSetupIfNeeded(option, platform); + this.processManuallySetupIfNeeded(option, platform); + } else { + const option = await this.promptForChoice({ + infoMessage: PlatformEnvironmentRequirements.NOT_CONFIGURED_ENV_AFTER_SETUP_SCRIPT_MESSAGE, + choices: [ + PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME, + PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME + ] + }); + + await this.processCloudBuildsIfNeeded(option, platform); + this.processManuallySetupIfNeeded(option, platform); + } } - if (selectedOption === PlatformEnvironmentRequirements.BOTH_CLOUD_BUILDS_AND_SETUP_SCRIPT_OPTION_NAME) { - await this.processBothCloudBuildsAndSetupScript(platform); + if (selectedOption === PlatformEnvironmentRequirements.BOTH_CLOUD_SETUP_AND_LOCAL_SETUP_OPTION_NAME) { + await this.processBothCloudBuildsAndSetupScript(); if (await this.$doctorService.canExecuteLocalBuild(platform)) { return true; } this.processManuallySetup(platform); } + + this.processTryCloudSetupIfNeeded(selectedOption, platform); } return true; } - private async processCloudBuildsIfNeeded(platform: string, selectedOption: string): Promise { - if (selectedOption === PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME) { + private async processCloudBuildsIfNeeded(selectedOption: string, platform?: string): Promise { + if (selectedOption === PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME) { await this.processCloudBuilds(platform); } } private async processCloudBuilds(platform: string): Promise { - await this.processCloudBuildsCore(platform); + await this.processCloudBuildsCore(); this.fail(this.getCloudBuildsMessage(platform)); } - private processCloudBuildsCore(platform: string): Promise { - return this.$nativescriptCloudExtensionService.install(); + private processCloudBuildsCore(): Promise { + return this.$nativeScriptCloudExtensionService.install(); } - private getCloudBuildsMessage(platform: string): string { + 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.`; @@ -113,21 +143,27 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ 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) { + private processTryCloudSetupIfNeeded(selectedOption: string, platform?: string) { + if (selectedOption === PlatformEnvironmentRequirements.TRY_CLOUD_OPERATION_OPTION_NAME) { + this.fail(this.getCloudBuildsMessage(platform)); + } + } + + private processManuallySetupIfNeeded(selectedOption: string, platform?: string) { if (selectedOption === PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME) { this.processManuallySetup(platform); } } - 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 processManuallySetup(platform?: string): void { + this.fail(`To be able to ${platform ? `build for ${platform}` : 'build'}, verify that your environment is configured according to the system requirements described at ${this.$staticConfig.SYS_REQUIREMENTS_LINK}. In case you have any questions, you can check our forum: 'http://forum.nativescript.org' and our public Slack channel: 'https://nativescriptcommunity.slack.com/'.`); } - private async processBothCloudBuildsAndSetupScript(platform: string): Promise { + private async processBothCloudBuildsAndSetupScript(): Promise { try { - await this.processCloudBuildsCore(platform); + await this.processCloudBuildsCore(); } catch (e) { - this.$logger.trace(`Error while installing ${constants.NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension. ${e.message}.`); + this.$logger.trace(`Error while installing ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension. ${e.message}.`); } await this.$doctorService.runSetupScript(); @@ -136,5 +172,63 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private fail(message: string): void { this.$errors.fail({ formatStr: message, suppressCommandHelp: true, printOnStdout: true }); } + + private getNonInteractiveConsoleMessage(platform: string) { + return this.$nativeScriptCloudExtensionService.isInstalled() ? + this.buildMultilineMessage([ + `${PlatformEnvironmentRequirements.MISSING_LOCAL_SETUP_MESSAGE} ${PlatformEnvironmentRequirements.CHOOSE_OPTIONS_MESSAGE}`, + PlatformEnvironmentRequirements.RUN_TNS_SETUP_MESSAGE, + this.getCloudBuildsMessage(platform), + this.getEnvVerificationMessage() + ]) : + this.buildMultilineMessage([ + PlatformEnvironmentRequirements.MISSING_LOCAL_AND_CLOUD_SETUP_MESSAGE, + PlatformEnvironmentRequirements.RUN_TNS_SETUP_MESSAGE, + `Run $ tns cloud setup command to install the ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension to configure your environment for cloud builds.`, + this.getEnvVerificationMessage() + ]); + } + + private getInteractiveConsoleMessage(platform: string) { + return this.$nativeScriptCloudExtensionService.isInstalled() ? + this.buildMultilineMessage([ + `${PlatformEnvironmentRequirements.MISSING_LOCAL_BUT_CLOUD_SETUP_MESSAGE} ${PlatformEnvironmentRequirements.CHOOSE_OPTIONS_MESSAGE}`, + `Select "Configure for Local Builds" to run the setup script and automatically configure your environment for local builds.`, + `Select "Skip Step and Configure Manually" to disregard this option and install any required components manually.` + ]) : + this.buildMultilineMessage([ + PlatformEnvironmentRequirements.MISSING_LOCAL_AND_CLOUD_SETUP_MESSAGE, + `Select "Configure for Cloud Builds" to install the ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension and automatically configure your environment for cloud builds.`, + `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 "Configure for Both Local and Cloud Builds" to automatically configure your environment for both options.` + ]); + } + + private async promptForChoice(opts: { infoMessage: string, choices: string[], }): Promise { + this.$logger.info(opts.infoMessage); + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.CheckEnvironmentRequirements, + additionalData: `User should select: ${opts.infoMessage}` + }); + + const selection = await this.$prompter.promptForChoice(PlatformEnvironmentRequirements.CHOOSE_OPTIONS_MESSAGE, opts.choices); + + await this.$analyticsService.trackEventActionInGoogleAnalytics({ + action: TrackActionNames.CheckEnvironmentRequirements, + additionalData: `User selected: ${selection}` + }); + + return selection; + } + + private getEnvVerificationMessage() { + return `Verify that your environment is configured according to the system requirements described at ${this.$staticConfig.SYS_REQUIREMENTS_LINK}.`; + } + + private buildMultilineMessage(parts: string[]): string { + return parts.join(EOL); + } } $injector.register("platformEnvironmentRequirements", PlatformEnvironmentRequirements); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 94cf094632..94fff61155 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -341,7 +341,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { } const platformData = this.$platformsData.getPlatformData(platform, projectData); - const forDevice = !buildConfig || buildConfig.buildForDevice; + const forDevice = !buildConfig || !!buildConfig.buildForDevice; outputPath = outputPath || (forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath || platformData.deviceBuildOutputPath); if (!this.$fs.exists(outputPath)) { return true; @@ -603,7 +603,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { @helpers.hook('cleanApp') public async cleanDestinationApp(platformInfo: IPreparePlatformInfo): Promise { - await this.ensurePlatformInstalled(platformInfo.platform, platformInfo.platformTemplate, platformInfo.projectData, platformInfo.config); + await this.ensurePlatformInstalled(platformInfo.platform, platformInfo.platformTemplate, platformInfo.projectData, platformInfo.config, platformInfo.nativePrepare); const platformData = this.$platformsData.getPlatformData(platformInfo.platform, platformInfo.projectData); const appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME); @@ -788,9 +788,13 @@ export class PlatformService extends EventEmitter implements IPlatformService { private getLatestApplicationPackage(buildOutputPath: string, validBuildOutputData: IValidBuildOutputData): IApplicationPackage { let packages = this.getApplicationPackages(buildOutputPath, validBuildOutputData); + const packageExtName = path.extname(validBuildOutputData.packageNames[0]); if (packages.length === 0) { - const packageExtName = path.extname(validBuildOutputData.packageNames[0]); - this.$errors.fail("No %s found in %s directory", packageExtName, buildOutputPath); + this.$errors.fail(`No ${packageExtName} found in ${buildOutputPath} directory.`); + } + + if (packages.length > 1) { + this.$logger.warn(`More than one ${packageExtName} found in ${buildOutputPath} directory. Using the last one produced from build.`); } packages = _.sortBy(packages, pkg => pkg.time).reverse(); // We need to reverse because sortBy always sorts in ascending order diff --git a/lib/services/project-service.ts b/lib/services/project-service.ts index ecabbc9b5e..2a92ea0a0c 100644 --- a/lib/services/project-service.ts +++ b/lib/services/project-service.ts @@ -18,7 +18,7 @@ export class ProjectService implements IProjectService { private $npmInstallationManager: INpmInstallationManager) { } @exported("projectService") - public async createProject(projectOptions: IProjectSettings): Promise { + public async createProject(projectOptions: IProjectSettings): Promise { let projectName = projectOptions.projectName; let selectedTemplate = projectOptions.template; @@ -74,6 +74,7 @@ export class ProjectService implements IProjectService { } this.$logger.printMarkdown("Project `%s` was successfully created.", projectName); + return { projectName, projectDir }; } @exported("projectService") diff --git a/lib/tools/node-modules/node-modules-dependencies-builder.ts b/lib/tools/node-modules/node-modules-dependencies-builder.ts index f2482819b5..53dd1bcfb1 100644 --- a/lib/tools/node-modules/node-modules-dependencies-builder.ts +++ b/lib/tools/node-modules/node-modules-dependencies-builder.ts @@ -2,6 +2,7 @@ import * as path from "path"; import { NODE_MODULES_FOLDER_NAME, PACKAGE_JSON_FILE_NAME } from "../../constants"; interface IDependencyDescription { + parent: IDependencyDescription; parentDir: string; name: string; depth: number; @@ -20,6 +21,7 @@ export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesB const queue: IDependencyDescription[] = _.keys(dependencies) .map(dependencyName => ({ + parent: null, parentDir: projectPath, name: dependencyName, depth: 0 @@ -27,13 +29,14 @@ export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesB while (queue.length) { const currentModule = queue.shift(); - const resolvedDependency = this.findModule(rootNodeModulesPath, currentModule.parentDir, currentModule.name, currentModule.depth, resolvedDependencies); + const resolvedDependency = this.findModule(rootNodeModulesPath, currentModule, resolvedDependencies); if (resolvedDependency && !_.some(resolvedDependencies, r => r.directory === resolvedDependency.directory)) { _.each(resolvedDependency.dependencies, d => { - const dependency: IDependencyDescription = { name: d, parentDir: resolvedDependency.directory, depth: resolvedDependency.depth + 1 }; + const dependency: IDependencyDescription = { parent: currentModule, name: d, parentDir: resolvedDependency.directory, depth: resolvedDependency.depth + 1 }; const shouldAdd = !_.some(queue, element => + element.parent === dependency.parent && element.name === dependency.name && element.parentDir === dependency.parentDir && element.depth === dependency.depth); @@ -50,26 +53,40 @@ export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesB return resolvedDependencies; } - private findModule(rootNodeModulesPath: string, parentModulePath: string, name: string, depth: number, resolvedDependencies: IDependencyData[]): IDependencyData { - let modulePath = path.join(parentModulePath, NODE_MODULES_FOLDER_NAME, name); // node_modules/parent/node_modules/ - const rootModulesPath = path.join(rootNodeModulesPath, name); - let depthInNodeModules = depth; + private findModule(rootNodeModulesPath: string, depDescription: IDependencyDescription, resolvedDependencies: IDependencyData[]): IDependencyData { + let modulePath = path.join(depDescription.parentDir, NODE_MODULES_FOLDER_NAME, depDescription.name); // node_modules/parent/node_modules/ + const rootModulesPath = path.join(rootNodeModulesPath, depDescription.name); + let depthInNodeModules = depDescription.depth; if (!this.moduleExists(modulePath)) { - modulePath = rootModulesPath; // /node_modules/ - if (!this.moduleExists(modulePath)) { - return null; + + let moduleExists = false; + let parent = depDescription.parent; + + while (parent && !moduleExists) { + modulePath = path.join(depDescription.parent.parentDir, NODE_MODULES_FOLDER_NAME, depDescription.name); + moduleExists = this.moduleExists(modulePath); + if (!moduleExists) { + parent = parent.parent; + } + } + + if (!moduleExists) { + modulePath = rootModulesPath; // /node_modules/ + if (!this.moduleExists(modulePath)) { + return null; + } } depthInNodeModules = 0; } - if (_.some(resolvedDependencies, r => r.name === name && r.directory === modulePath)) { + if (_.some(resolvedDependencies, r => r.name === depDescription.name && r.directory === modulePath)) { return null; } - return this.getDependencyData(name, modulePath, depthInNodeModules); + return this.getDependencyData(depDescription.name, modulePath, depthInNodeModules); } private getDependencyData(name: string, directory: string, depth: number): IDependencyData { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 36274a44ee..af45026957 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -31,7 +31,7 @@ "@types/color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.0.tgz", - "integrity": "sha512-5qqtNia+m2I0/85+pd2YzAXaTyKO8j+svirO5aN+XaQJ5+eZ8nx0jPtEWZLxCi50xwYsX10xUHetFzfb1WEs4Q==", + "integrity": "sha1-QPimvy/YbpaYdrM5qDfY/xsKbjA=", "dev": true, "requires": { "@types/color-convert": "1.9.0" @@ -40,7 +40,7 @@ "@types/color-convert": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg==", + "integrity": "sha1-v6ggPkHnxlRx6YQdfjBqfNi1Fy0=", "dev": true, "requires": { "@types/color-name": "1.1.0" @@ -49,7 +49,7 @@ "@types/color-name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.0.tgz", - "integrity": "sha512-gZ/Rb+MFXF0pXSEQxdRoPMm5jeO3TycjOdvbpbcpHX/B+n9AqaHFe5q6Ga9CsZ7ir/UgIWPfrBzUzn3F19VH/w==", + "integrity": "sha1-km929+ZvScxZrYgLsVsDCrvwtm0=", "dev": true }, "@types/events": { @@ -82,7 +82,7 @@ "@types/ora": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@types/ora/-/ora-1.3.3.tgz", - "integrity": "sha512-XaSVRyCfnGq1xGlb6iuoxnomMXPIlZnvIIkKiGNMTCeVOg7G1Si+FA9N1lPrykPEfiRHwbuZXuTCSoYcHyjcdg==", + "integrity": "sha1-0xhkGMPPN6gxeZs3a+ykqOG/yi0=", "dev": true, "requires": { "@types/node": "6.0.61" @@ -116,7 +116,7 @@ "@types/sinon": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.0.0.tgz", - "integrity": "sha512-cuK4xM8Lg2wd8cxshcQa8RG4IK/xfyB6TNE6tNVvkrShR4xdrYgsV04q6Dp6v1Lp6biEFdzD8k8zg/ujQeiw+A==", + "integrity": "sha1-mpP/pO4TKehRZieKXtmfgdxMg2I=", "dev": true }, "@types/source-map": { @@ -144,7 +144,7 @@ "@types/xml2js": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.2.tgz", - "integrity": "sha512-8aKUBSj3oGcnuiBmDLm3BIk09RYg01mz9HlQ2u4aS17oJ25DxjQrEUVGFSBVNOfM45pQW4OjcBPplq6r/exJdA==", + "integrity": "sha1-pLhLOHn/1HEJU/2Syr/emopOhFY=", "dev": true, "requires": { "@types/node": "6.0.61" @@ -262,7 +262,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=" }, "array-find-index": { "version": "1.0.2", @@ -459,9 +459,11 @@ } }, "bplist-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.0.tgz", - "integrity": "sha1-Ywgj8gVkN9Tb78IOhAF/i6xI4Ag=" + "version": "https://github.com/telerik/node-bplist-parser/tarball/master", + "integrity": "sha512-B7Agq6ELA8hOERzvUhhgQPFe1Hcx6HCAtdaxHv/YS/PAUdxhxtCYzWFJ3zDfGIWXT1X2Nt2Q8lqeEASKoY3Wfw==", + "requires": { + "big-integer": "1.6.23" + } }, "brace-expansion": { "version": "1.1.8", @@ -728,7 +730,7 @@ "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "integrity": "sha1-2SC0Mo1TSjrIKV1o971LpsQnvpo=", "requires": { "color-convert": "1.9.1", "color-string": "1.5.2" @@ -737,7 +739,7 @@ "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=", "requires": { "color-name": "1.1.3" } @@ -1492,11 +1494,6 @@ "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" }, - "filesize": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.1.2.tgz", - "integrity": "sha1-jB0EdXYIY3CZmyPDLyF8TVGr8oo=" - }, "fill-range": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", @@ -3118,9 +3115,9 @@ } }, "ios-sim-portable": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.3.0.tgz", - "integrity": "sha1-GttoCZqUoskHDY3Mh0bpWO6XFlA=", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.3.1.tgz", + "integrity": "sha512-4mo/RKy33jQJX+BcbgB5Bq2Y6+yTe7OOo/vEpX5wVurblAhRBdncOtpjUcvkDkTTKANYwUu7lmNP5IpHX53QVw==", "requires": { "bplist-parser": "https://github.com/telerik/node-bplist-parser/tarball/master", "colors": "0.6.2", @@ -3131,13 +3128,6 @@ "yargs": "4.7.1" }, "dependencies": { - "bplist-parser": { - "version": "https://github.com/telerik/node-bplist-parser/tarball/master", - "integrity": "sha1-X7BVlo1KqtkGi+pkMyFRiYKHtFY=", - "requires": { - "big-integer": "1.6.23" - } - }, "colors": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", @@ -3580,7 +3570,7 @@ "just-extend": { "version": "1.1.27", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", - "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "integrity": "sha1-7G55QQ/5FORyZSq/oOYDwD1g6QU=", "dev": true }, "kind-of": { @@ -3757,7 +3747,7 @@ "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "integrity": "sha1-V0Dhxdbw39pK2TI7UzIQfva0xAo=", "requires": { "chalk": "2.3.2" }, @@ -3765,7 +3755,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "requires": { "color-convert": "1.9.1" } @@ -3773,7 +3763,7 @@ "chalk": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "integrity": "sha1-JQ3JawdJG/1gHmSNZt319gx6XGU=", "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", @@ -3788,7 +3778,7 @@ "supports-color": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "integrity": "sha1-WySsFduA+pJ89SJ6SjP9PEx2dsA=", "requires": { "has-flag": "3.0.0" } @@ -3808,7 +3798,7 @@ "lolex": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.1.tgz", - "integrity": "sha512-mQuW55GhduF3ppo+ZRUTz1PRjEh1hS5BbqU7d8D0ez2OKxHDod7StPPeAVKisZR5aLkHZjdGWSL42LSONUJsZw==", + "integrity": "sha1-PSMZiURx6glQ72RpLq0qUxjP82I=", "dev": true }, "longest": { @@ -3836,7 +3826,7 @@ "marked": { "version": "0.3.12", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz", - "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==" + "integrity": "sha1-fPJf8iUmMvP+JAa94ljpTu6SdRk=" }, "marked-terminal": { "version": "2.0.0", @@ -3951,7 +3941,7 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=" }, "mime-db": { "version": "1.27.0", @@ -3969,7 +3959,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=" }, "min-document": { "version": "2.19.0", @@ -4078,12 +4068,12 @@ "natives": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.2.tgz", - "integrity": "sha512-5bRASydE1gu6zPOenLN043++J8xj1Ob7ArkfdYO3JN4DF5rDmG7bMoiybkTyD+GnXQEMixVeDHMzuqm6kpBmiA==" + "integrity": "sha1-RDfKHtin8EdTHM368nkoU99O+hw=" }, "nativescript-doctor": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/nativescript-doctor/-/nativescript-doctor-0.12.0.tgz", - "integrity": "sha512-eGEx9MFagJ7i2mtFZv5Jbsg6YQqQsVLxLhmg2uOOjLC253daAkGUBFbPnpEajrqhPZX3PBmoYIh+oHfc9/trjA==", + "integrity": "sha1-bC5i5OUOlX4jZKcIRTaKrmiywiY=", "requires": { "osenv": "0.1.3", "semver": "5.3.0", @@ -4114,7 +4104,7 @@ "nise": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.0.tgz", - "integrity": "sha512-q9jXh3UNsMV28KeqI43ILz5+c3l+RiNW8mhurEwCKckuHQbL+hTJIKKTiUlCPKlgQ/OukFvSnKB/Jk3+sFbkGA==", + "integrity": "sha1-B51srbvLErow448cmZ82rU1rqlM=", "dev": true, "requires": { "formatio": "1.2.0", @@ -4198,11 +4188,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" - }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", @@ -4275,7 +4260,7 @@ "ora": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ora/-/ora-2.0.0.tgz", - "integrity": "sha512-g+IR0nMUXq1k4nE3gkENbN4wkF0XsVZFyxznTF6CdmwQ9qeTGONGpSR9LM5//1l0TVvJoJF3MkMtJp6slUsWFg==", + "integrity": "sha1-jsOjf6e/+1SjoMGIofZ5jn4YJ80=", "requires": { "chalk": "2.3.2", "cli-cursor": "2.1.0", @@ -4293,7 +4278,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "requires": { "color-convert": "1.9.1" } @@ -4301,7 +4286,7 @@ "chalk": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "integrity": "sha1-JQ3JawdJG/1gHmSNZt319gx6XGU=", "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", @@ -4349,7 +4334,7 @@ "supports-color": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "integrity": "sha1-WySsFduA+pJ89SJ6SjP9PEx2dsA=", "requires": { "has-flag": "3.0.0" } @@ -4622,7 +4607,7 @@ "pngjs": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.2.tgz", - "integrity": "sha512-bVNd3LMXRzdo6s4ehr4XW2wFMu9cb40nPgHEjSSppm8/++Xc+g0b2QQb+SeDesgfANXbjydOr1or9YQ+pcCZPQ==" + "integrity": "sha1-CXw8KnX+siPq3d6mvJ8AUM+DC8M=" }, "prelude-ls": { "version": "1.1.2", @@ -4651,25 +4636,10 @@ "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true }, - "progress-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.1.1.tgz", - "integrity": "sha1-nsvxh5MsSUHVUCGRkNdN7ArEX1Q=", - "requires": { - "single-line-log": "0.3.1", - "speedometer": "0.1.4", - "through2": "0.2.3" - } - }, - "properties-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/properties-parser/-/properties-parser-0.2.3.tgz", - "integrity": "sha1-91kSVfcHq7/yJ8e1a2N9uwNzoQ8=" - }, "proxy-lib": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/proxy-lib/-/proxy-lib-0.4.0.tgz", - "integrity": "sha512-oUDDpf0NTtKPyXjBNUcKzwZhA9GjEdu8Z47GsxGv5rZvKyCqsSrHurJtlL1yp7uVzA2NOmxd4aX7qmB1ZOdCwQ==", + "integrity": "sha1-Dt7+MSUKacPMU8xJ7RR9zk8zbXs=", "requires": { "osenv": "0.1.4" }, @@ -4737,7 +4707,7 @@ "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", "requires": { "is-number": "3.0.0", "kind-of": "4.0.0" @@ -5069,7 +5039,7 @@ "samsam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "integrity": "sha1-jR2TUOJWItow3j5EumkrUiGrfFA=", "dev": true }, "sax": { @@ -5197,15 +5167,10 @@ } } }, - "single-line-log": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.3.1.tgz", - "integrity": "sha1-p61lB/IYzl3+FsS/LWWSRkGeegY=" - }, "sinon": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.1.2.tgz", - "integrity": "sha512-5uLBZPdCWl59Lpbf45ygKj7Z0LVol+ftBe7RDIXOQV/sF58pcFmbK8raA7bt6eljNuGnvBP+/ZxlicVn0emDjA==", + "integrity": "sha1-ZWEFIdkm+1N0LdhM1ZnwuJqC9EA=", "dev": true, "requires": { "diff": "3.4.0", @@ -5220,7 +5185,7 @@ "diff": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", - "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", + "integrity": "sha1-sdhVB9rzlkgo3lSzfQ1zumfdpWw=", "dev": true }, "has-flag": { @@ -5293,7 +5258,7 @@ "source-map-support": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.3.tgz", - "integrity": "sha512-eKkTgWYeBOQqFGXRfKabMFdnWepo51vWqEdoeikaEPFiJC7MCU5j2h4+6Q8npkZTeLGbSyecZvRxiSoWl3rh+w==", + "integrity": "sha1-Kz1f/ymM+k0a/X1DUtVp6aAVjnY=", "dev": true, "requires": { "source-map": "0.6.1" @@ -5302,7 +5267,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -5325,11 +5290,6 @@ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" }, - "speedometer": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", - "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=" - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -5574,38 +5534,6 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, - "through2": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", - "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", - "requires": { - "readable-stream": "1.1.14", - "xtend": "2.1.2" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, "tiny-lr": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", @@ -6058,7 +5986,7 @@ "xhr": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.1.tgz", - "integrity": "sha512-pAIU5vBr9Hiy5cpFIbPnwf0C18ZF86DBsZKrlsf87N5De/JbA6RJ83UP/cv+aljl4S40iRVMqP4pr4sF9Dnj0A==", + "integrity": "sha1-upgsztIFrl7sOHFprJ3HfKSFPTg=", "requires": { "global": "4.3.2", "is-function": "1.0.1", @@ -6081,7 +6009,7 @@ "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "integrity": "sha1-aGwg8hMgnpSr8NG88e+qKRx4J6c=", "requires": { "sax": "1.2.4", "xmlbuilder": "9.0.7" @@ -6108,14 +6036,6 @@ "version": "https://github.com/telerik/node-XMLHttpRequest/tarball/master", "integrity": "sha1-gyu8L8J4DhCCCmdOlAQRCpQ0M6c=" }, - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "requires": { - "object-keys": "0.4.0" - } - }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", diff --git a/package.json b/package.json index b694dc4e64..03a98fe181 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,6 @@ "mobile" ], "dependencies": { - "bplist-parser": "0.1.0", - "bufferpack": "0.0.6", "byline": "4.2.1", "chalk": "1.1.0", "chokidar": "1.7.0", @@ -40,14 +38,12 @@ "colors": "1.1.2", "email-validator": "1.0.4", "esprima": "2.7.0", - "filesize": "3.1.2", "gaze": "1.1.0", - "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", "ios-device-lib": "0.4.10", "ios-mobileprovision-finder": "1.0.10", - "ios-sim-portable": "3.3.0", + "ios-sim-portable": "3.3.1", "jimp": "0.2.28", "lockfile": "1.0.3", "lodash": "4.13.1", @@ -64,8 +60,6 @@ "pbxproj-dom": "1.0.11", "plist": "1.1.0", "plist-merge-patch": "0.1.1", - "progress-stream": "1.1.1", - "properties-parser": "0.2.3", "proxy-lib": "0.4.0", "qr-image": "3.2.0", "request": "2.85.0", diff --git a/test/android-project-properties-manager.ts b/test/android-project-properties-manager.ts deleted file mode 100644 index 1c51a3a75b..0000000000 --- a/test/android-project-properties-manager.ts +++ /dev/null @@ -1,145 +0,0 @@ -import * as ProjectPropertiesParserLib from "../lib/common/properties-parser"; -import * as FsLib from "../lib/common/file-system"; -import * as ProjectPropertiesManagerLib from "../lib/services/android-project-properties-manager"; -import * as HostInfoLib from "../lib/common/host-info"; -import * as StaticConfigLib from "../lib/config"; -import * as ErrorsLib from "../lib/common/errors"; -import * as LoggerLib from "../lib/common/logger"; -import * as ConfigLib from "../lib/config"; -import * as OptionsLib from "../lib/options"; -import * as yok from "../lib/common/yok"; -import { SettingsService } from "../lib/common/test/unit-tests/stubs"; -import * as path from "path"; -import temp = require("temp"); -temp.track(); -import { assert } from "chai"; - -function createTestInjector(): IInjector { - const testInjector = new yok.Yok(); - testInjector.register("propertiesParser", ProjectPropertiesParserLib.PropertiesParser); - testInjector.register("fs", FsLib.FileSystem); - testInjector.register("hostInfo", HostInfoLib.HostInfo); - testInjector.register("staticConfig", StaticConfigLib.StaticConfig); - testInjector.register("errors", ErrorsLib.Errors); - testInjector.register("logger", LoggerLib.Logger); - testInjector.register("config", ConfigLib.Configuration); - testInjector.register("options", OptionsLib.Options); - testInjector.register("settingsService", SettingsService); - - return testInjector; -} - -describe("Android project properties parser tests", () => { - it("adds project reference", async () => { - const testInjector = createTestInjector(); - const fs = testInjector.resolve("fs"); - - const projectPropertiesFileContent = 'target=android-21'; - const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); - fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - - const projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( - ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); - await projectPropertiesManager.addProjectReference("testValue"); - - const expectedContent = 'target=android-21' + '\n' + - 'android.library.reference.1=testValue'; - const actualContent = fs.readText(path.join(tempFolder, "project.properties")); - - assert.equal(expectedContent, actualContent); - assert.equal(1, _.keys(await projectPropertiesManager.getProjectReferences()).length); - }); - - it("adds project reference if another referencence already exists in project.properties file", async () => { - const testInjector = createTestInjector(); - const fs = testInjector.resolve("fs"); - - const projectPropertiesFileContent = 'target=android-21' + '\n' + - 'android.library.reference.1=someValue'; - const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); - fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - - const projectPropertiesManager = testInjector.resolve( - ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); - await projectPropertiesManager.addProjectReference("testValue"); - - const expectedContent = ['target=android-21', - 'android.library.reference.1=someValue', - 'android.library.reference.2=testValue'].join('\n'); - const actualContent = fs.readText(path.join(tempFolder, "project.properties")); - - assert.equal(expectedContent, actualContent); - assert.equal(2, _.keys(await projectPropertiesManager.getProjectReferences()).length); - }); - it("adds project reference if more than one references exist in project.properties file", async () => { - const testInjector = createTestInjector(); - const fs = testInjector.resolve("fs"); - - const projectPropertiesFileContent = ['target=android-21', - 'android.library.reference.1=value1', - 'android.library.reference.2=value2', - 'android.library.reference.3=value3', - 'android.library.reference.4=value4', - 'android.library.reference.5=value5'].join('\n'); - const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); - fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - - const projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( - ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); - await projectPropertiesManager.addProjectReference("testValue"); - - const expectedContent = projectPropertiesFileContent + '\n' + - 'android.library.reference.6=testValue'; - - const actualContent = fs.readText(path.join(tempFolder, "project.properties")); - - assert.equal(expectedContent, actualContent); - assert.equal(6, _.keys(await projectPropertiesManager.getProjectReferences()).length); - }); - it("removes project reference if only one reference exists", async () => { - const testInjector = createTestInjector(); - const fs = testInjector.resolve("fs"); - - const projectPropertiesFileContent = 'android.library.reference.1=value1' + '\n' + - 'target=android-21'; - const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); - fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - - const projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( - ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); - await projectPropertiesManager.removeProjectReference("value1"); - - const expectedContent = 'target=android-21'; - const actualContent = fs.readText(path.join(tempFolder, "project.properties")); - - assert.equal(expectedContent, actualContent); - assert.equal(0, _.keys(await projectPropertiesManager.getProjectReferences()).length); - }); - it("removes project reference when another references exist before and after the specified reference", async () => { - const testInjector = createTestInjector(); - const fs = testInjector.resolve("fs"); - - const projectPropertiesFileContent = ['target=android-17', - 'android.library.reference.1=value1', - 'android.library.reference.2=value2', - 'android.library.reference.3=value3', - 'android.library.reference.4=value4', - 'android.library.reference.5=value5'].join('\n'); - const tempFolder = temp.mkdirSync("AndroidProjectPropertiesManager"); - fs.writeFile(path.join(tempFolder, "project.properties"), projectPropertiesFileContent); - - const projectPropertiesManager: IAndroidProjectPropertiesManager = testInjector.resolve( - ProjectPropertiesManagerLib.AndroidProjectPropertiesManager, { directoryPath: tempFolder }); - await projectPropertiesManager.removeProjectReference("value3"); - - const expectedContent = ['target=android-17', - 'android.library.reference.1=value1', - 'android.library.reference.2=value2', - 'android.library.reference.3=value4', - 'android.library.reference.4=value5'].join('\n') + '\n'; - const actualContent = fs.readText(path.join(tempFolder, "project.properties")); - - assert.equal(expectedContent, actualContent); - assert.equal(4, _.keys(await projectPropertiesManager.getProjectReferences()).length); - }); -}); diff --git a/test/ios-entitlements-service.ts b/test/ios-entitlements-service.ts index 9d9f3fc6bf..18546ec311 100644 --- a/test/ios-entitlements-service.ts +++ b/test/ios-entitlements-service.ts @@ -76,11 +76,6 @@ describe("IOSEntitlements Service Tests", () => { }); describe("Merge", () => { - const defaultPlistContent = ` - - - -`; const defaultAppResourcesEntitlementsContent = ` @@ -123,13 +118,12 @@ describe("IOSEntitlements Service Tests", () => { assert.equal(strip(actual), strip(expected)); } - it("Merge creates a default entitlements file.", async () => { + it("Merge does not create a default entitlements file.", async () => { // act await iOSEntitlementsService.merge(projectData); // assert - const actual = fs.readText(destinationFilePath); - assertContent(actual, defaultPlistContent); + assert.isFalse(fs.exists(destinationFilePath)); }); it("Merge uses the entitlements from App_Resources folder", async () => { diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 8e9a09626c..48d17b7bea 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -120,6 +120,7 @@ function createTestInjector(projectPath: string, projectName: string): IInjector testInjector.register("settingsService", SettingsService); testInjector.register("httpClient", {}); testInjector.register("platformEnvironmentRequirements", {}); + testInjector.register("plistParser", {}); return testInjector; } @@ -839,6 +840,16 @@ describe("Merge Project XCConfig files", () => { it("Adds the entitlements property if not set by the user", async () => { for (const release in [true, false]) { + const realExistsFunction = testInjector.resolve("fs").exists; + + testInjector.resolve("fs").exists = (filePath: string) => { + if (iOSEntitlementsService.getPlatformsEntitlementsPath(projectData) === filePath) { + return true; + } + + return realExistsFunction(filePath); + }; + await (iOSProjectService).mergeProjectXcconfigFiles(release, projectData); const destinationFilePath = release ? (iOSProjectService).getPluginsReleaseXcconfigFilePath(projectData) diff --git a/test/project-commands.ts b/test/project-commands.ts index d5ddccaa41..20722bceb8 100644 --- a/test/project-commands.ts +++ b/test/project-commands.ts @@ -10,9 +10,10 @@ let isProjectCreated: boolean; const dummyArgs = ["dummyArgsString"]; class ProjectServiceMock implements IProjectService { - async createProject(projectOptions: IProjectSettings): Promise { + async createProject(projectOptions: IProjectSettings): Promise { selectedTemplateName = projectOptions.template; isProjectCreated = true; + return null; } isValidNativeScriptProject(pathToProject?: string): boolean { diff --git a/test/project-service.ts b/test/project-service.ts index 027598df2d..3b54318e80 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -59,7 +59,7 @@ class ProjectIntegrationTest { this.createTestInjector(); } - public async createProject(projectOptions: IProjectSettings): Promise { + public async createProject(projectOptions: IProjectSettings): Promise { const projectService: IProjectService = this.testInjector.resolve("projectService"); if (!projectOptions.template) { projectOptions.template = constants.RESERVED_TEMPLATE_NAMES["default"]; diff --git a/test/services/platform-environment-requirements.ts b/test/services/platform-environment-requirements.ts index 8c52e7635e..28d577b120 100644 --- a/test/services/platform-environment-requirements.ts +++ b/test/services/platform-environment-requirements.ts @@ -2,15 +2,20 @@ import { Yok } from "../../lib/common/yok"; import { PlatformEnvironmentRequirements } from '../../lib/services/platform-environment-requirements'; import * as stubs from "../stubs"; import { assert } from "chai"; +import { EOL } from "os"; 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 `; +const nonInteractiveConsoleMessageWhenExtensionIsNotInstalled = `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 `; +const nonInteractiveConsoleMessageWhenExtensionIsInstalled = `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}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.${EOL}Verify that your environment is configured according to the system requirements described at .`; function createTestInjector() { const testInjector = new Yok(); + testInjector.register("analyticsService", { + trackEventActionInGoogleAnalytics: () => ({}) + }); testInjector.register("commandsService", {currentCommandData: {commandName: "test", commandArguments: [""]}}); testInjector.register("doctorService", {}); testInjector.register("errors", { @@ -22,7 +27,7 @@ function createTestInjector() { testInjector.register("prompter", {}); testInjector.register("platformEnvironmentRequirements", PlatformEnvironmentRequirements); testInjector.register("staticConfig", { SYS_REQUIREMENTS_LINK: "" }); - testInjector.register("nativescriptCloudExtensionService", {}); + testInjector.register("nativeScriptCloudExtensionService", {}); return testInjector; } @@ -31,6 +36,37 @@ describe("platformEnvironmentRequirements ", () => { describe("checkRequirements", () => { let testInjector: IInjector = null; let platformEnvironmentRequirements: IPlatformEnvironmentRequirements = null; + let promptForChoiceData: {message: string, choices: string[]}[] = []; + let isExtensionInstallCalled = false; + + function mockDoctorService(data: {canExecuteLocalBuild: boolean, mockSetupScript?: boolean}) { + const doctorService = testInjector.resolve("doctorService"); + doctorService.canExecuteLocalBuild = () => data.canExecuteLocalBuild; + if (data.mockSetupScript) { + doctorService.runSetupScript = () => Promise.resolve(); + } + } + + function mockPrompter(data: {firstCallOptionName: string, secondCallOptionName?: string}) { + const prompter = testInjector.resolve("prompter"); + prompter.promptForChoice = (message: string, choices: string[]) => { + promptForChoiceData.push({message: message, choices: choices}); + + if (promptForChoiceData.length === 1) { + return Promise.resolve(data.firstCallOptionName); + } + + if (data.secondCallOptionName) { + return Promise.resolve(data.secondCallOptionName); + } + }; + } + + function mockNativeScriptCloudExtensionService(data: {isInstalled: boolean}) { + const nativeScriptCloudExtensionService = testInjector.resolve("nativeScriptCloudExtensionService"); + nativeScriptCloudExtensionService.isInstalled = () => data.isInstalled; + nativeScriptCloudExtensionService.install = () => { isExtensionInstallCalled = true; }; + } beforeEach(() => { testInjector = createTestInjector(); @@ -39,156 +75,138 @@ describe("platformEnvironmentRequirements ", () => { process.stdin.isTTY = true; }); - 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; - }; + afterEach(() => { + promptForChoiceData = []; + isExtensionInstallCalled = false; + delete process.env.NS_SKIP_ENV_CHECK; + }); - let isInstallExtensionCalled = false; - const nativescriptCloudExtensionService = testInjector.resolve("nativescriptCloudExtensionService"); - nativescriptCloudExtensionService.install = () => {isInstallExtensionCalled = true; }; + it("should return true when environment is configured", async () => { + mockDoctorService({ canExecuteLocalBuild: true }); + const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(platform); + assert.isTrue(result); + assert.isTrue(promptForChoiceData.length === 0); + }); + it("should show prompt when environment is not configured and nativescript-cloud extension is not installed", async () => { + mockDoctorService({ canExecuteLocalBuild: false }); + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); + mockNativeScriptCloudExtensionService({ isInstalled: false }); await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); - assert.isTrue(isPromptForChoiceCalled); - assert.isTrue(isInstallExtensionCalled); + assert.isTrue(promptForChoiceData.length === 1); + assert.isTrue(isExtensionInstallCalled); + assert.deepEqual("To continue, choose one of the following options: ", promptForChoiceData[0].message); + assert.deepEqual(['Configure for Cloud Builds', 'Configure for Local Builds', 'Configure for Both Local and Cloud Builds', 'Skip Step and Configure Manually'], promptForChoiceData[0].choices); }); + it("should show prompt when environment is not configured and nativescript-cloud extension is installed", async () => { + mockDoctorService({ canExecuteLocalBuild: false }); + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); + mockNativeScriptCloudExtensionService({ isInstalled: true }); - it("should return true when environment is configured", async () => { - const doctorService = testInjector.resolve("doctorService"); - doctorService.canExecuteLocalBuild = () => true; + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); + assert.isTrue(promptForChoiceData.length === 1); + assert.deepEqual("To continue, choose one of the following options: ", promptForChoiceData[0].message); + assert.deepEqual(['Try Cloud Operation', 'Configure for Local Builds', 'Skip Step and Configure Manually'], promptForChoiceData[0].choices); + }); + it("should skip env chech when NS_SKIP_ENV_CHECK environment variable is passed", async() => { + process.env.NS_SKIP_ENV_CHECK = true; - const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(platform); - assert.isTrue(result); + assert.isTrue(await platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); + assert.isFalse(isExtensionInstallCalled); + assert.isTrue(promptForChoiceData.length === 0); }); - describe("when setup script option is selected ", () => { + describe("when local setup option is selected", () => { + beforeEach(() => { + mockPrompter( {firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME}); + }); + it("should return true when env is configured after executing setup script", async () => { const doctorService = testInjector.resolve("doctorService"); doctorService.canExecuteLocalBuild = () => false; doctorService.runSetupScript = async () => { doctorService.canExecuteLocalBuild = () => true; }; - const prompter = testInjector.resolve("prompter"); - prompter.promptForChoice = () => Promise.resolve(PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME); - - const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(platform); - 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(); + mockNativeScriptCloudExtensionService({ isInstalled: null }); - let isPromptForChoiceCalled = true; - const prompter = testInjector.resolve("prompter"); - prompter.promptForChoice = () => { - if (isPromptForChoiceCalled) { - isPromptForChoiceCalled = false; - return PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME; - } - - isPromptForChoiceCalled = true; - return PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME; - }; - - let isInstallExtensionCalled = false; - const nativescriptCloudExtensionService = testInjector.resolve("nativescriptCloudExtensionService"); - nativescriptCloudExtensionService.install = () => {isInstallExtensionCalled = true; }; - - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); - assert.isTrue(isInstallExtensionCalled); - assert.isTrue(isPromptForChoiceCalled); + assert.isTrue(await platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); }); - 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 isPromptForChoiceCalled = true; - const prompter = testInjector.resolve("prompter"); - prompter.promptForChoice = () => { - if (isPromptForChoiceCalled) { - isPromptForChoiceCalled = false; - return PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME; - } - - isPromptForChoiceCalled = true; - return PlatformEnvironmentRequirements.CLOUD_BUILDS_OPTION_NAME; - }; - - let isInstallExtensionCalled = false; - 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 isPromptForChoiceCalled = true; - const prompter = testInjector.resolve("prompter"); - prompter.promptForChoice = () => { - if (isPromptForChoiceCalled) { - isPromptForChoiceCalled = false; - return PlatformEnvironmentRequirements.SETUP_SCRIPT_OPTION_NAME; - } - - isPromptForChoiceCalled = true; - return PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME; - }; + describe("and env is not configured after executing setup script", () => { + it("should setup manually when cloud extension is installed", async () => { + mockDoctorService( { canExecuteLocalBuild: false, mockSetupScript: true }); + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, secondCallOptionName: PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME }); + mockNativeScriptCloudExtensionService({ isInstalled: true }); await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); - assert.isTrue(isPromptForChoiceCalled); + }); + describe("and cloud extension is not installed", () => { + beforeEach(() => { + mockDoctorService({ canExecuteLocalBuild: false, mockSetupScript: true }); + mockNativeScriptCloudExtensionService({ isInstalled: false }); + }); + it("should list 2 posibile options to select", async () => { + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME }); + + await platformEnvironmentRequirements.checkEnvironmentRequirements(platform); + assert.deepEqual(promptForChoiceData[1].choices, ['Configure for Cloud Builds', 'Skip Step and Configure Manually']); + }); + it("should install nativescript-cloud extension when 'Configure for Cloud Builds' option is selected", async () => { + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, secondCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); + + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), cloudBuildsErrorMessage); + assert.deepEqual(isExtensionInstallCalled, true); + }); + it("should setup manually when 'Skip Step and Configure Manually' option is selected", async () => { + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, secondCallOptionName: PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME }); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); + }); }); }); }); - 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 nativescriptCloudExtensionService = testInjector.resolve("nativescriptCloudExtensionService"); - nativescriptCloudExtensionService.install = () => {isInstallExtensionCalled = true; }; + describe("when cloud setup option is selected", () => { + beforeEach(() => { + mockDoctorService({ canExecuteLocalBuild: false }); + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); + }); + it("should install nativescript-cloud extension when it is not installed", async () => { + mockNativeScriptCloudExtensionService({ isInstalled: false }); await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), cloudBuildsErrorMessage); - assert.isTrue(isInstallExtensionCalled); + assert.isTrue(isExtensionInstallCalled); }); }); 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; + beforeEach(() => { + mockDoctorService({ canExecuteLocalBuild: false }); + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME }); + }); + it("should fail when nativescript-cloud extension is installed", async () => { + mockNativeScriptCloudExtensionService({ isInstalled: true }); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); + }); + it("should fail when nativescript-cloud extension is not installed", async () => { + mockNativeScriptCloudExtensionService({ isInstalled: false }); await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); }); }); - describe("when console is not interactive", () => { - it("should fail when console is not interactive", async () => { + describe("when console is non interactive", () => { + beforeEach(() => { process.stdout.isTTY = false; process.stdin.isTTY = false; + mockDoctorService({ canExecuteLocalBuild: false }); + }); - const doctorService = testInjector.resolve("doctorService"); - doctorService.canExecuteLocalBuild = () => false; - - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), nonInteractiveConsoleErrorMessage); + it("should fail when nativescript-cloud extension is installed", async () => { + mockNativeScriptCloudExtensionService({ isInstalled: true }); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), nonInteractiveConsoleMessageWhenExtensionIsInstalled); + }); + it("should fail when nativescript-cloud extension is not installed", async () => { + mockNativeScriptCloudExtensionService({ isInstalled: false }); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), nonInteractiveConsoleMessageWhenExtensionIsNotInstalled); }); }); }); diff --git a/vendor/gradle-plugin/build.gradle b/vendor/gradle-plugin/build.gradle index 5171a2e3cc..3a4a89a63d 100644 --- a/vendor/gradle-plugin/build.gradle +++ b/vendor/gradle-plugin/build.gradle @@ -2,9 +2,10 @@ buildscript { repositories { jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:3.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -40,3 +41,12 @@ android { versionName "1.0" } } + +dependencies { + def supportVer = "27.0.1" + if (project.hasProperty("supportVersion")) { + supportVer = supportVersion + } + compileOnly "com.android.support:support-v4:$supportVer" + compileOnly "com.android.support:appcompat-v7:$supportVer" +} diff --git a/vendor/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/vendor/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 55a7a86c65..aa471e9281 100644 --- a/vendor/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/vendor/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip