diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 39393ad64c..507824a38f 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -232,3 +232,4 @@ $injector.requirePublicClass("initializeService", "./services/initialize-service $injector.require("npmConfigService", "./services/npm-config-service"); $injector.require("ipService", "./services/ip-service"); $injector.require("jsonFileSettingsService", "./common/services/json-file-settings-service"); +$injector.require("markingModeService", "./services/marking-mode-service"); diff --git a/lib/commands/build.ts b/lib/commands/build.ts index fd66257f21..7746a5c37d 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -97,11 +97,13 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, $buildDataService: IBuildDataService, protected $logger: ILogger, - private $migrateController: IMigrateController) { + private $migrateController: IMigrateController, + private $markingModeService: IMarkingModeService) { super($options, $errors, $projectData, platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $buildDataService, $logger); } public async execute(args: string[]): Promise { + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: this.$projectData.projectDir, skipWarnings: true }); await this.executeCore([this.$devicePlatformsConstants.Android.toLowerCase()]); if (this.$options.aab) { diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index e23d2aa9aa..3e9c35bb1e 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -157,11 +157,13 @@ export class DebugAndroidCommand implements ICommand { constructor(protected $errors: IErrors, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $injector: IInjector, - private $projectData: IProjectData) { + private $projectData: IProjectData, + private $markingModeService: IMarkingModeService) { this.$projectData.initializeProjectData(); } - public execute(args: string[]): Promise { + public async execute(args: string[]): Promise { + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: this.$projectData.projectDir, skipWarnings: true }); return this.debugPlatformCommand.execute(args); } public async canExecute(args: string[]): Promise { diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index 05e85066ca..e2b87ff1d3 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -18,31 +18,42 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement private $mobileHelper: Mobile.IMobileHelper, $platformsDataService: IPlatformsDataService, private $deployCommandHelper: DeployCommandHelper, - private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { + private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, + private $markingModeService: IMarkingModeService, + private $migrateController: IMigrateController) { super($options, $platformsDataService, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { - const platform = args[0].toLowerCase(); + const platform = args[0]; + if (this.$mobileHelper.isAndroidPlatform(platform)) { + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: this.$projectData.projectDir, skipWarnings: true }); + } + await this.$deployCommandHelper.deploy(platform); } public async canExecute(args: string[]): Promise { + const platform = args[0]; + if (!this.$options.force) { + await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms: [platform] }); + } + this.$androidBundleValidatorHelper.validateNoAab(); if (!args || !args.length || args.length > 1) { return false; } - if (!(await this.$platformCommandParameter.validate(args[0]))) { + if (!(await this.$platformCommandParameter.validate(platform))) { return false; } - if (this.$mobileHelper.isAndroidPlatform(args[0]) && this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { + if (this.$mobileHelper.isAndroidPlatform(platform) && this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.failWithHelp(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } - const result = await super.canExecuteCommandBase(args[0], { validateOptions: true }); + const result = await super.canExecuteCommandBase(platform, { validateOptions: true }); return result; } } diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 727be490bb..e59f4ccb31 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -17,13 +17,18 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm private $platformCommandParameter: ICommandParameter, $platformsDataService: IPlatformsDataService, private $prepareDataService: PrepareDataService, - private $migrateController: IMigrateController) { + private $migrateController: IMigrateController, + private $markingModeService: IMarkingModeService, + private $mobileHelper: Mobile.IMobileHelper) { super($options, $platformsDataService, $platformValidationService, $projectData); this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { const platform = args[0]; + if (this.$mobileHelper.isAndroidPlatform(platform)) { + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: this.$projectData.projectDir, skipWarnings: true }); + } const prepareData = this.$prepareDataService.getPrepareData(this.$projectData.projectDir, platform, this.$options); await this.$prepareController.prepare(prepareData); diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index 6459312f0d..381ea215de 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -13,12 +13,14 @@ export class PreviewCommand implements ICommand { private $options: IOptions, private $previewAppLogProvider: IPreviewAppLogProvider, private $previewQrCodeService: IPreviewQrCodeService, - $cleanupService: ICleanupService) { + $cleanupService: ICleanupService, + private $markingModeService: IMarkingModeService) { this.$analyticsService.setShouldDispose(false); $cleanupService.setShouldDispose(false); } public async execute(): Promise { + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: this.$projectData.projectDir, skipWarnings: true }); this.$previewAppLogProvider.on(DEVICE_LOG_EVENT_NAME, (deviceId: string, message: string) => { this.$logger.info(message); }); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 669ad67efc..cffdbe0c75 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -113,9 +113,11 @@ export class RunAndroidCommand implements ICommand { private $options: IOptions, private $platformValidationService: IPlatformValidationService, private $projectData: IProjectData, + private $markingModeService: IMarkingModeService ) { } - public execute(args: string[]): Promise { + public async execute(args: string[]): Promise { + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: this.$projectData.projectDir, skipWarnings: true }); return this.runCommand.execute(args); } diff --git a/lib/commands/test.ts b/lib/commands/test.ts index a094facd19..a6c8998ae5 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -16,7 +16,7 @@ abstract class TestCommandBase { protected abstract $devicesService: Mobile.IDevicesService; protected abstract $migrateController: IMigrateController; - async execute(args: string[]): Promise { + public async execute(args: string[]): Promise { let devices = []; if (this.$options.debugBrk) { await this.$devicesService.initialize({ @@ -102,9 +102,15 @@ class TestAndroidCommand extends TestCommandBase implements ICommand { protected $cleanupService: ICleanupService, protected $liveSyncCommandHelper: ILiveSyncCommandHelper, protected $devicesService: Mobile.IDevicesService, - protected $migrateController: IMigrateController) { + protected $migrateController: IMigrateController, + protected $markingModeService: IMarkingModeService) { super(); } + + public async execute(args: string[]): Promise { + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: this.$projectData.projectDir, skipWarnings: true }); + await super.execute(args); + } } class TestIosCommand extends TestCommandBase implements ICommand { diff --git a/lib/commands/update.ts b/lib/commands/update.ts index bcade39577..c7bb8f1ec3 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -10,11 +10,17 @@ export class UpdateCommand implements ICommand { private $options: IOptions, private $errors: IErrors, private $logger: ILogger, - private $projectData: IProjectData) { + private $projectData: IProjectData, + private $markingModeService: IMarkingModeService) { this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { + if (this.$options.markingMode) { + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: this.$projectData.projectDir, forceSwitch: true }); + return; + } + if (!await this.$updateController.shouldUpdate({ projectDir: this.$projectData.projectDir, version: args[0] })) { this.$logger.printMarkdown(`__${UpdateCommand.PROJECT_UP_TO_DATE_MESSAGE}__`); return; diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index fe0ff9460a..01c0008325 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -27,11 +27,13 @@ export class PrepareController extends EventEmitter { private $projectDataService: IProjectDataService, private $webpackCompilerService: IWebpackCompilerService, private $watchIgnoreListService: IWatchIgnoreListService, - private $analyticsService: IAnalyticsService + private $analyticsService: IAnalyticsService, + private $markingModeService: IMarkingModeService ) { super(); } public async prepare(prepareData: IPrepareData): Promise { const projectData = this.$projectDataService.getProjectData(prepareData.projectDir); + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: projectData.projectDir }); await this.trackRuntimeVersion(prepareData.platform, projectData); await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); @@ -187,8 +189,8 @@ export class PrepareController extends EventEmitter { path.join(projectData.getAppDirectoryPath(), PACKAGE_JSON_FILE_NAME), path.join(projectData.getAppResourcesRelativeDirectoryPath(), platformData.normalizedPlatformName), ] - .concat(pluginsNativeDirectories) - .concat(pluginsPackageJsonFiles); + .concat(pluginsNativeDirectories) + .concat(pluginsPackageJsonFiles); return patterns; } diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts index 79fb137586..3b14c4d05b 100644 --- a/lib/controllers/preview-app-controller.ts +++ b/lib/controllers/preview-app-controller.ts @@ -28,7 +28,8 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon private $previewQrCodeService: IPreviewQrCodeService, private $previewSdkService: IPreviewSdkService, private $prepareDataService: PrepareDataService, - private $projectDataService: IProjectDataService + private $projectDataService: IProjectDataService, + private $markingModeService: IMarkingModeService ) { super(); } public async startPreview(data: IPreviewAppLiveSyncData): Promise { @@ -57,6 +58,7 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon const projectData = this.$projectDataService.getProjectData(data.projectDir); await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); await this.$previewSdkService.initialize(data.projectDir, async (device: Device) => { + await this.$markingModeService.handleMarkingModeFullDeprecation({ projectDir: projectData.projectDir }); try { if (!device) { this.$errors.fail("Sending initial preview files without a specified device is not supported."); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index d2110fac42..3ecc2945fb 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -572,6 +572,7 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai cleanupLogFile: string; appleApplicationSpecificPassword: string; appleSessionBase64: string; + markingMode: boolean; } interface IEnvOptions { diff --git a/lib/definitions/marking-mode-service.d.ts b/lib/definitions/marking-mode-service.d.ts new file mode 100644 index 0000000000..bf07899743 --- /dev/null +++ b/lib/definitions/marking-mode-service.d.ts @@ -0,0 +1,9 @@ +interface IMarkingModeService { + handleMarkingModeFullDeprecation(options: IMarkingModeFullDeprecationOptions): Promise; +} + +interface IMarkingModeFullDeprecationOptions { + projectDir: string; + skipWarnings?: boolean; + forceSwitch?: boolean; +} diff --git a/lib/options.ts b/lib/options.ts index 9bb960f91a..cdac35ec52 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -136,6 +136,7 @@ export class Options { certificate: { type: OptionType.String, hasSensitiveValue: true }, certificatePassword: { type: OptionType.String, hasSensitiveValue: true }, release: { type: OptionType.Boolean, alias: "r", hasSensitiveValue: false }, + markingMode: { type: OptionType.Boolean, hasSensitiveValue: false }, var: { type: OptionType.Object, hasSensitiveValue: true }, default: { type: OptionType.Boolean, hasSensitiveValue: false }, count: { type: OptionType.Number, hasSensitiveValue: false }, diff --git a/lib/services/marking-mode-service.ts b/lib/services/marking-mode-service.ts new file mode 100644 index 0000000000..9dbc2cecd1 --- /dev/null +++ b/lib/services/marking-mode-service.ts @@ -0,0 +1,74 @@ +import * as helpers from "../common/helpers"; +import * as path from "path"; +import { EOL } from "os"; +import { PACKAGE_JSON_FILE_NAME, LoggerConfigData } from "../constants"; + +const enum MarkingMode { + None = "none", + Full = "full" +} + +const MARKING_MODE_PROP = "markingMode"; +const MARKING_MODE_FULL_DEPRECATION_MSG = `With the upcoming NativeScript 7.0 the "${MARKING_MODE_PROP}:${MarkingMode.None}" will become the only marking mode supported by the Android Runtime.`; +const MARKING_MODE_NONE_CONFIRM_MSG = `Do you want to switch your app to the recommended "${MARKING_MODE_PROP}:${MarkingMode.None}"? +More info about the reasons for this change can be found in the link below: +https://www.nativescript.org/blog/markingmode-none-is-official-boost-android-performance-while-avoiding-memory-issues`; + +export class MarkingModeService implements IMarkingModeService { + + constructor(private $fs: IFileSystem, + private $logger: ILogger, + private $projectDataService: IProjectDataService, + private $prompter: IPrompter + ) { + } + + public async handleMarkingModeFullDeprecation(options: IMarkingModeFullDeprecationOptions): Promise { + const { projectDir, skipWarnings, forceSwitch } = options; + const projectData = this.$projectDataService.getProjectData(projectDir); + const innerPackageJsonPath = path.join(projectData.getAppDirectoryPath(projectDir), PACKAGE_JSON_FILE_NAME); + if (!this.$fs.exists(innerPackageJsonPath)) { + return; + } + + const innerPackageJson = this.$fs.readJson(innerPackageJsonPath); + let markingModeValue = (innerPackageJson && innerPackageJson.android + && typeof (innerPackageJson.android[MARKING_MODE_PROP]) === "string" && innerPackageJson.android[MARKING_MODE_PROP]) || ""; + + if (forceSwitch) { + this.setMarkingMode(innerPackageJsonPath, innerPackageJson, MarkingMode.None); + return; + } + + if (!markingModeValue && helpers.isInteractive()) { + this.$logger.info(); + this.$logger.printMarkdown(` +__Improve your app by switching to "${MARKING_MODE_PROP}:${MarkingMode.None}".__ + +\`${MARKING_MODE_FULL_DEPRECATION_MSG}\``); + const hasSwitched = await this.$prompter.confirm(MARKING_MODE_NONE_CONFIRM_MSG, () => true); + + markingModeValue = hasSwitched ? MarkingMode.None : MarkingMode.Full; + this.setMarkingMode(innerPackageJsonPath, innerPackageJson, markingModeValue); + } + + if (!skipWarnings && markingModeValue.toLowerCase() !== MarkingMode.None) { + this.showMarkingModeFullWarning(); + } + } + + private setMarkingMode(packagePath: string, packageValue: any, newMode: string) { + packageValue = packageValue || {}; + packageValue.android = packageValue.android || {}; + packageValue.android[MARKING_MODE_PROP] = newMode; + this.$fs.writeJson(packagePath, packageValue); + } + + private showMarkingModeFullWarning() { + const markingModeFullWarning = `You are using the deprecated "${MARKING_MODE_PROP}:${MarkingMode.Full}".${EOL}${EOL}${MARKING_MODE_FULL_DEPRECATION_MSG}${EOL}${EOL}You should update your marking mode by executing 'tns update --markingMode'.`; + + this.$logger.warn(markingModeFullWarning, { [LoggerConfigData.wrapMessageWithBorders]: true }); + } +} + +$injector.register("markingModeService", MarkingModeService); diff --git a/lib/services/workflow/workflow.d.ts b/lib/services/workflow/workflow.d.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/package-installation-manager.ts b/test/package-installation-manager.ts index d4b303f4ad..19c695f87d 100644 --- a/test/package-installation-manager.ts +++ b/test/package-installation-manager.ts @@ -14,11 +14,12 @@ import * as yok from "../lib/common/yok"; import ChildProcessLib = require("../lib/common/child-process"); import { SettingsService } from "../lib/common/test/unit-tests/stubs"; import { ProjectDataService } from "../lib/services/project-data-service"; -import { ProjectDataStub } from "./stubs"; +import { MarkingModeServiceStub, ProjectDataStub } from "./stubs"; function createTestInjector(): IInjector { const testInjector = new yok.Yok(); + testInjector.register("markingModeService", MarkingModeServiceStub); testInjector.register("projectData", ProjectDataStub); testInjector.register("config", ConfigLib.Configuration); testInjector.register("logger", LoggerLib.Logger); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 3447a627a9..0832227ff7 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -21,6 +21,7 @@ import { Messages } from "../lib/common/messages/messages"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; import { PlatformValidationService } from "../lib/services/platform/platform-validation-service"; import { PlatformCommandHelper } from "../lib/helpers/platform-command-helper"; +import { MarkingModeServiceStub } from "./stubs"; let isCommandExecuted = true; @@ -103,6 +104,7 @@ function createTestInjector() { const testInjector = new yok.Yok(); testInjector.register("injector", testInjector); + testInjector.register("markingModeService", MarkingModeServiceStub); testInjector.register("hooksService", stubs.HooksServiceStub); testInjector.register("staticConfig", StaticConfigLib.StaticConfig); testInjector.register("nodeModulesDependenciesBuilder", {}); diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts index 105789a1ab..229a73143e 100644 --- a/test/services/playground/preview-app-livesync-service.ts +++ b/test/services/playground/preview-app-livesync-service.ts @@ -1,6 +1,6 @@ import { Yok } from "../../../lib/common/yok"; import * as _ from 'lodash'; -import { LoggerStub, ErrorsStub } from "../../stubs"; +import { LoggerStub, ErrorsStub, MarkingModeServiceStub } from "../../stubs"; import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk"; import * as chai from "chai"; import * as path from "path"; @@ -116,6 +116,7 @@ function createTestInjector(options?: { const injector = new Yok(); injector.register("logger", LoggerMock); + injector.register("markingModeService", MarkingModeServiceStub); injector.register("hmrStatusService", { attachToHmrStatusEvent: () => ({}) }); diff --git a/test/services/project-data-service.ts b/test/services/project-data-service.ts index 2499c4b280..35c4c919c2 100644 --- a/test/services/project-data-service.ts +++ b/test/services/project-data-service.ts @@ -1,7 +1,7 @@ import { Yok } from "../../lib/common/yok"; import { assert } from "chai"; import { ProjectDataService } from "../../lib/services/project-data-service"; -import { LoggerStub, ProjectDataStub } from "../stubs"; +import { LoggerStub, ProjectDataStub, MarkingModeServiceStub } from "../stubs"; import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, PACKAGE_JSON_FILE_NAME, CONFIG_NS_FILE_NAME, AssetConstants, ProjectTypes } from '../../lib/constants'; import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants"; import { basename, join } from "path"; @@ -74,6 +74,7 @@ const createTestInjector = (packageJsonContent?: string, nsConfigContent?: strin }); testInjector.register("logger", LoggerStub); + testInjector.register("markingModeService", MarkingModeServiceStub); testInjector.register("projectDataService", ProjectDataService); diff --git a/test/stubs.ts b/test/stubs.ts index 527f532c02..2fb547b39f 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -848,6 +848,12 @@ export class TerminalSpinnerServiceStub implements ITerminalSpinnerService { } } +export class MarkingModeServiceStub implements IMarkingModeService { + handleMarkingModeFullDeprecation(options: IMarkingModeFullDeprecationOptions): Promise { + return; + } +} + export class InjectorStub extends Yok implements IInjector { constructor() { super(); @@ -873,6 +879,7 @@ export class InjectorStub extends Yok implements IInjector { this.register('projectData', ProjectDataStub); this.register('packageInstallationManager', PackageInstallationManagerStub); this.register('packageInstallationManager', PackageInstallationManagerStub); + this.register("markingModeService", MarkingModeServiceStub); this.register("httpClient", { httpRequest: async (options: any, proxySettings?: IProxySettings): Promise => undefined }); diff --git a/test/update.ts b/test/update.ts index 8ab22c7802..71f5cf5c1e 100644 --- a/test/update.ts +++ b/test/update.ts @@ -13,6 +13,7 @@ function createTestInjector( ): IInjector { const testInjector: IInjector = new yok.Yok(); testInjector.register("logger", stubs.LoggerStub); + testInjector.register("markingModeService", stubs.MarkingModeServiceStub); testInjector.register("options", Options); testInjector.register("analyticsService", { trackException: async (): Promise => undefined,