diff --git a/docs/man_pages/general/migrate.md b/docs/man_pages/general/migrate.md new file mode 100644 index 0000000000..b33a19f87a --- /dev/null +++ b/docs/man_pages/general/migrate.md @@ -0,0 +1,63 @@ +<% if (isJekyll) { %>--- +title: tns migrate +position: 15 +---<% } %> + +# tns migrate + +### Description + +Migrates the app dependencies to a form compatible with NativeScript 6.0. Running this command will not affect the codebase of the application and you might need to do additional changes manually. + +The migrate command will update **"webpack.config.js"**, **"karma.conf.js"**, **"tsconfig.tns.json"**(not for code sharing projects) and **"package-lock.json"**. The original files will be moved to **".migration_backup"** folder. +The following folders will be removed: **"hooks"**, **"platforms"** and **"node_modules"**. The **"hooks"** folder will also be backed up in **".migration_backup"** folder. + +The **"nativescript-dev-sass"** and **"nativescript-dev-typescript"** dependencies will be replaced with **"node-sass"** and **"typescript"** respectively. +The **"nativescript-dev-less"** dependency will be removed, but to enable LESS CSS support the user should follow the steps in this<% if(isConsole) { %> feature request: https://github.com/NativeScript/nativescript-dev-webpack/issues/967.<% } %><% if(isHtml) { %> [feature request](https://github.com/NativeScript/nativescript-dev-webpack/issues/967).<% } %> + +The following dependencies will be updated if needed: +* tns-core-modules +* tns-core-modules-widgets +* tns-platform-declarations +* nativescript-dev-webpack +* nativescript-camera +* nativescript-geolocation +* nativescript-imagepicker +* nativescript-permissions +* nativescript-social-share +* nativescript-ui-chart +* nativescript-ui-dataform +* nativescript-ui-gauge +* nativescript-ui-listview +* nativescript-ui-sidedrawer +* nativescript-ui-calendar +* nativescript-ui-autocomplete +* nativescript-cardview +* nativescript-datetimepicker +* kinvey-nativescript-sdk +* nativescript-plugin-firebase +* nativescript-vue +* nativescript-unit-test-runner +* karma-webpack +* karma-jasmine +* karma-mocha +* karma-chai +* karma-qunit +* karma + +### Commands + +Usage | Synopsis +------|------- +General | `$ tns migrate` + +<% if(isHtml) { %> + +### Related Commands + +Command | Description +----------|---------- +[update](update.html) | Updates the project with the latest versions of iOS/Android runtimes and cross-platform modules. +[help](help.html) | Lists the available commands or shows information about the selected command. +[doctor](doctor.html) | Checks your system for configuration problems which might prevent the NativeScript CLI from working properly. +<% } %> diff --git a/docs/man_pages/general/package-manager-get.md b/docs/man_pages/general/package-manager-get.md index e1fd3de934..ba516da2a3 100644 --- a/docs/man_pages/general/package-manager-get.md +++ b/docs/man_pages/general/package-manager-get.md @@ -1,6 +1,6 @@ <% if (isJekyll) { %>--- title: tns package-manager get -position: 18 +position: 19 ---<% } %> # tns package-manager get diff --git a/docs/man_pages/general/package-manager-set.md b/docs/man_pages/general/package-manager-set.md index 38bf182674..333ae3c93a 100644 --- a/docs/man_pages/general/package-manager-set.md +++ b/docs/man_pages/general/package-manager-set.md @@ -1,6 +1,6 @@ <% if (isJekyll) { %>--- title: tns package-manager set -position: 17 +position: 18 ---<% } %> # tns package-manager set diff --git a/docs/man_pages/general/update.md b/docs/man_pages/general/update.md index c4849220a5..53bf2bef1d 100644 --- a/docs/man_pages/general/update.md +++ b/docs/man_pages/general/update.md @@ -1,19 +1,21 @@ <% if (isJekyll) { %>--- title: tns update -position: 15 +position: 16 ---<% } %> # tns update ### Description -Updates the project with the latest versions of iOS/Android runtimes and cross-platform modules. +Updates the project with the latest versions of iOS/Android runtimes, cross-platform modules and "nativescript-dev-webpack". In order to get the latest development release instead, pass `next` as argument: `tns update next` You can also switch to specific version by passing it to the command: `tns update 5.0.0` +**NOTE:** The provided version should be an existing version of the project template for this project type. + ### Commands Usage | Synopsis diff --git a/docs/man_pages/general/usage-reporting.md b/docs/man_pages/general/usage-reporting.md index 1b11115748..5edf50dca4 100644 --- a/docs/man_pages/general/usage-reporting.md +++ b/docs/man_pages/general/usage-reporting.md @@ -1,6 +1,6 @@ <% if (isJekyll) { %>--- title: tns usage-reporting -position: 16 +position: 17 ---<% } %> # tns usage-reporting diff --git a/docs/man_pages/index.md b/docs/man_pages/index.md index 542395e2ea..0d86ade439 100644 --- a/docs/man_pages/index.md +++ b/docs/man_pages/index.md @@ -21,6 +21,7 @@ Command | Description [doctor](general/doctor.html) | Checks your system for configuration problems which might prevent the NativeScript CLI from working properly. [info](general/info.html) | Displays version information about the NativeScript CLI, core modules, and runtimes. [proxy](general/proxy.html) | Displays proxy settings. +[migrate](general/migrate.html) | Migrates the app dependencies to a form compatible with NativeScript 6.0. [update](general/update.html) | Updates the project with the latest versions of iOS/Android runtimes and cross-platform modules. ## Project Development Commands diff --git a/docs/man_pages/project/configuration/update.md b/docs/man_pages/project/configuration/update.md deleted file mode 100644 index a59fa8720b..0000000000 --- a/docs/man_pages/project/configuration/update.md +++ /dev/null @@ -1,29 +0,0 @@ -<% if (isJekyll) { %>--- -title: tns update -position: 8 ----<% } %> - -# tns update - -### Description - -Updates a NativeScript to the latest compatible combination of NativeScript dependencies. The combination of dependencies is taken from the latest(or specified) version of the template for the type of the target project (Angular, Vue.js and etc.). - -### Commands - -Usage | Synopsis -------|------- -Update with the latest version |`$ tns update` -Update with specific version | `$ tns update ` - -### Related Commands - -Command | Description -----------|---------- -[install](install.html) | Installs all platforms and dependencies described in the `package.json` file in the current directory. -[platform add](platform-add.html) | Configures the current project to target the selected platform. -[platform remove](platform-remove.html) | Removes the selected platform from the platforms that the project currently targets. -[platform](platform.html) | Lists all platforms that the project currently targets. -[prepare](prepare.html) | Copies common and relevant platform-specific content from the app directory to the subdirectory for the selected target platform in the platforms directory. -[platform update](platform-update.html) | Updates the NativeScript runtime for the specified platform. -[resources update android](resources-update.html) | Updates the App_Resources/Android directory to the new v4.0 directory structure \ No newline at end of file diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index c66924ab9f..00cbc57b25 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -144,7 +144,6 @@ $injector.require("devicePathProvider", "./device-path-provider"); $injector.requireCommand("platform|clean", "./commands/platform-clean"); -$injector.require("bundleValidatorHelper", "./helpers/bundle-validator-helper"); $injector.require("androidBundleValidatorHelper", "./helpers/android-bundle-validator-helper"); $injector.require("liveSyncCommandHelper", "./helpers/livesync-command-helper"); $injector.require("deployCommandHelper", "./helpers/deploy-command-helper"); diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 6acdb2c8f0..86686e4688 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -9,11 +9,10 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, protected $buildController: IBuildController, $platformValidationService: IPlatformValidationService, - private $bundleValidatorHelper: IBundleValidatorHelper, private $buildDataService: IBuildDataService, protected $logger: ILogger) { - super($options, $platformsDataService, $platformValidationService, $projectData); - this.$projectData.initializeProjectData(); + super($options, $platformsDataService, $platformValidationService, $projectData); + this.$projectData.initializeProjectData(); } public dashedOptions = { @@ -33,8 +32,6 @@ export abstract class BuildCommandBase extends ValidatePlatformCommandBase { if (!this.$platformValidationService.isPlatformSupportedForOS(platform, this.$projectData)) { this.$errors.fail(`Applications for platform ${platform} can not be built on this OS`); } - - this.$bundleValidatorHelper.validate(this.$projectData); } protected async validateArgs(args: string[], platform: string): Promise { @@ -65,10 +62,10 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $buildController: IBuildController, $platformValidationService: IPlatformValidationService, - $bundleValidatorHelper: IBundleValidatorHelper, $logger: ILogger, - $buildDataService: IBuildDataService) { - super($options, $errors, $projectData, $platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); + $buildDataService: IBuildDataService, + private $migrateController: IMigrateController) { + super($options, $errors, $projectData, $platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $buildDataService, $logger); } public async execute(args: string[]): Promise { @@ -77,6 +74,9 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { public async canExecute(args: string[]): Promise { const platform = this.$devicePlatformsConstants.iOS; + if (!this.$options.force) { + await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms: [platform] }); + } super.validatePlatform(platform); @@ -101,11 +101,11 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $buildController: IBuildController, $platformValidationService: IPlatformValidationService, - $bundleValidatorHelper: IBundleValidatorHelper, protected $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, $buildDataService: IBuildDataService, - protected $logger: ILogger) { - super($options, $errors, $projectData, platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $bundleValidatorHelper, $buildDataService, $logger); + protected $logger: ILogger, + private $migrateController: IMigrateController) { + super($options, $errors, $projectData, platformsDataService, $devicePlatformsConstants, $buildController, $platformValidationService, $buildDataService, $logger); } public async execute(args: string[]): Promise { @@ -122,7 +122,9 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { public async canExecute(args: string[]): Promise { const platform = this.$devicePlatformsConstants.Android; - super.validatePlatform(platform); + if (!this.$options.force) { + await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms: [platform] }); + } this.$androidBundleValidatorHelper.validateRuntimeVersion(this.$projectData); let result = await super.canExecuteCommandBase(platform, { notConfiguredEnvOptions: { hideSyncToPreviewAppOption: true } }); if (result.canExecute) { diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 3317b632df..1bdc9c50b3 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -1,12 +1,10 @@ import { cache } from "../common/decorators"; import { ValidatePlatformCommandBase } from "./command-base"; -import { LiveSyncCommandHelper } from "../helpers/livesync-command-helper"; export class DebugPlatformCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor(private platform: string, - private $bundleValidatorHelper: IBundleValidatorHelper, protected $devicesService: Mobile.IDevicesService, $platformValidationService: IPlatformValidationService, $projectData: IProjectData, @@ -18,7 +16,8 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements private $debugDataService: IDebugDataService, private $debugController: IDebugController, private $liveSyncCommandHelper: ILiveSyncCommandHelper, - private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { + private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper, + private $migrateController: IMigrateController) { super($options, $platformsDataService, $platformValidationService, $projectData); $cleanupService.setShouldDispose(false); } @@ -54,6 +53,10 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements } public async canExecute(args: string[]): Promise { + if (!this.$options.force) { + await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms: [this.platform] }); + } + this.$androidBundleValidatorHelper.validateNoAab(); if (!this.$platformValidationService.isPlatformSupportedForOS(this.platform, this.$projectData)) { @@ -64,9 +67,6 @@ export class DebugPlatformCommand extends ValidatePlatformCommandBase implements this.$errors.fail("--release flag is not applicable to this command"); } - const minSupportedWebpackVersion = this.$options.hmr ? LiveSyncCommandHelper.MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR : null; - this.$bundleValidatorHelper.validate(this.$projectData, minSupportedWebpackVersion); - const result = await super.canExecuteCommandBase(this.platform, { validateOptions: true, notConfiguredEnvOptions: { hideCloudBuildOption: true, hideSyncToPreviewAppOption: true } }); return result; } diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index aa7f613e95..ae414976c6 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -17,7 +17,6 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement private $errors: IErrors, private $mobileHelper: Mobile.IMobileHelper, $platformsDataService: IPlatformsDataService, - private $bundleValidatorHelper: IBundleValidatorHelper, private $deployCommandHelper: DeployCommandHelper, private $androidBundleValidatorHelper: IAndroidBundleValidatorHelper) { super($options, $platformsDataService, $platformValidationService, $projectData); @@ -31,7 +30,6 @@ export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implement public async canExecute(args: string[]): Promise { this.$androidBundleValidatorHelper.validateNoAab(); - this.$bundleValidatorHelper.validate(this.$projectData); if (!args || !args.length || args.length > 1) { return false; } diff --git a/lib/commands/migrate.ts b/lib/commands/migrate.ts index e9cdf19cac..e40cfd8651 100644 --- a/lib/commands/migrate.ts +++ b/lib/commands/migrate.ts @@ -2,6 +2,7 @@ export class MigrateCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor( + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $migrateController: IMigrateController, private $projectData: IProjectData, private $errors: IErrors) { @@ -9,11 +10,19 @@ export class MigrateCommand implements ICommand { } public async execute(args: string[]): Promise { - await this.$migrateController.migrate({projectDir: this.$projectData.projectDir}); + await this.$migrateController.migrate({ + projectDir: this.$projectData.projectDir, + platforms: [this.$devicePlatformsConstants.Android, this.$devicePlatformsConstants.iOS] + }); } public async canExecute(args: string[]): Promise { - if (!await this.$migrateController.shouldMigrate({ projectDir: this.$projectData.projectDir })) { + const shouldMigrateResult = await this.$migrateController.shouldMigrate({ + projectDir: this.$projectData.projectDir, + platforms: [this.$devicePlatformsConstants.Android, this.$devicePlatformsConstants.iOS] + }); + + if (!shouldMigrateResult) { this.$errors.failWithoutHelp('Project is compatible with NativeScript "v6.0.0". To get the latest NativesScript packages execute "tns update".'); } diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index 71f6e6fdd3..0e42bdd1aa 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -16,9 +16,10 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm $projectData: IProjectData, private $platformCommandParameter: ICommandParameter, $platformsDataService: IPlatformsDataService, - private $prepareDataService: PrepareDataService) { - super($options, $platformsDataService, $platformValidationService, $projectData); - this.$projectData.initializeProjectData(); + private $prepareDataService: PrepareDataService, + private $migrateController: IMigrateController) { + super($options, $platformsDataService, $platformValidationService, $projectData); + this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { @@ -32,6 +33,11 @@ export class PrepareCommand extends ValidatePlatformCommandBase implements IComm const platform = args[0]; const result = await this.$platformCommandParameter.validate(platform) && await this.$platformValidationService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); + + if (!this.$options.force) { + await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms: [platform] }); + } + if (!result) { return false; } diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts index a8bffb0bc4..21249fec62 100644 --- a/lib/commands/preview.ts +++ b/lib/commands/preview.ts @@ -2,12 +2,11 @@ import { DEVICE_LOG_EVENT_NAME } from "../common/constants"; export class PreviewCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - private static MIN_SUPPORTED_WEBPACK_VERSION = "0.17.0"; constructor(private $analyticsService: IAnalyticsService, - private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, private $logger: ILogger, + private $migrateController: IMigrateController, private $previewAppController: IPreviewAppController, private $networkConnectivityValidator: INetworkConnectivityValidator, private $projectData: IProjectData, @@ -42,8 +41,11 @@ export class PreviewCommand implements ICommand { this.$errors.fail(`The arguments '${args.join(" ")}' are not valid for the preview command.`); } + if (!this.$options.force) { + await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms: [] }); + } + await this.$networkConnectivityValidator.validate(); - this.$bundleValidatorHelper.validate(this.$projectData, PreviewCommand.MIN_SUPPORTED_WEBPACK_VERSION); return true; } } diff --git a/lib/commands/run.ts b/lib/commands/run.ts index e8fcda7cdc..7348c63c58 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -13,6 +13,8 @@ export class RunCommandBase implements ICommand { private $errors: IErrors, private $hostInfo: IHostInfo, private $liveSyncCommandHelper: ILiveSyncCommandHelper, + private $migrateController: IMigrateController, + private $options: IOptions, private $projectData: IProjectData ) { } @@ -27,15 +29,19 @@ export class RunCommandBase implements ICommand { this.$errors.fail(ERROR_NO_VALID_SUBCOMMAND_FORMAT, "run"); } - this.$androidBundleValidatorHelper.validateNoAab(); - - this.$projectData.initializeProjectData(); this.platform = args[0] || this.platform; - if (!this.platform && !this.$hostInfo.isDarwin) { this.platform = this.$devicePlatformsConstants.Android; } + this.$androidBundleValidatorHelper.validateNoAab(); + this.$projectData.initializeProjectData(); + const platforms = this.platform ? [this.platform] : [this.$devicePlatformsConstants.Android, this.$devicePlatformsConstants.iOS]; + + if (!this.$options.force) { + await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms }); + } + await this.$liveSyncCommandHelper.validatePlatform(this.platform); return true; diff --git a/lib/commands/test.ts b/lib/commands/test.ts index 1641227d28..e412e72ab0 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -10,6 +10,7 @@ abstract class TestCommandBase { protected abstract $cleanupService: ICleanupService; protected abstract $liveSyncCommandHelper: ILiveSyncCommandHelper; protected abstract $devicesService: Mobile.IDevicesService; + protected abstract $migrateController: IMigrateController; async execute(args: string[]): Promise { let devices = []; @@ -34,7 +35,7 @@ abstract class TestCommandBase { devices = await this.$liveSyncCommandHelper.getDeviceInstances(this.platform); } - if (!this.$options.env) { this.$options.env = { }; } + if (!this.$options.env) { this.$options.env = {}; } this.$options.env.unitTesting = true; const liveSyncInfo = this.$liveSyncCommandHelper.getLiveSyncData(this.$projectData.projectDir); @@ -48,6 +49,10 @@ abstract class TestCommandBase { } async canExecute(args: string[]): Promise { + if (!this.$options.force) { + await this.$migrateController.validate({ projectDir: this.$projectData.projectDir, platforms: [this.platform] }); + } + this.$projectData.initializeProjectData(); this.$analyticsService.setShouldDispose(this.$options.justlaunch || !this.$options.watch); this.$cleanupService.setShouldDispose(this.$options.justlaunch || !this.$options.watch); @@ -86,7 +91,8 @@ class TestAndroidCommand extends TestCommandBase implements ICommand { protected $errors: IErrors, protected $cleanupService: ICleanupService, protected $liveSyncCommandHelper: ILiveSyncCommandHelper, - protected $devicesService: Mobile.IDevicesService) { + protected $devicesService: Mobile.IDevicesService, + protected $migrateController: IMigrateController) { super(); } } @@ -102,7 +108,8 @@ class TestIosCommand extends TestCommandBase implements ICommand { protected $errors: IErrors, protected $cleanupService: ICleanupService, protected $liveSyncCommandHelper: ILiveSyncCommandHelper, - protected $devicesService: Mobile.IDevicesService) { + protected $devicesService: Mobile.IDevicesService, + protected $migrateController: IMigrateController) { super(); } diff --git a/lib/commands/update.ts b/lib/commands/update.ts index 1a1c7f712e..0dddf6a07f 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -4,6 +4,7 @@ export class UpdateCommand implements ICommand { public static readonly PROJECT_UP_TO_DATE_MESSAGE = 'This project is up to date.'; constructor( + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $updateController: IUpdateController, private $migrateController: IMigrateController, private $options: IOptions, @@ -13,15 +14,20 @@ export class UpdateCommand implements ICommand { } public async execute(args: string[]): Promise { - await this.$updateController.update({projectDir: this.$projectData.projectDir, version: args[0], frameworkPath: this.$options.frameworkPath}); + await this.$updateController.update({ projectDir: this.$projectData.projectDir, version: args[0], frameworkPath: this.$options.frameworkPath }); } public async canExecute(args: string[]): Promise { - if (await this.$migrateController.shouldMigrate({projectDir: this.$projectData.projectDir})) { + const shouldMigrate = await this.$migrateController.shouldMigrate({ + projectDir: this.$projectData.projectDir, + platforms: [this.$devicePlatformsConstants.Android, this.$devicePlatformsConstants.iOS] + }); + + if (shouldMigrate) { this.$errors.failWithoutHelp(UpdateCommand.SHOULD_MIGRATE_PROJECT_MESSAGE); } - if (!await this.$updateController.shouldUpdate({projectDir:this.$projectData.projectDir, version: args[0]})) { + if (!await this.$updateController.shouldUpdate({ projectDir: this.$projectData.projectDir, version: args[0] })) { this.$errors.failWithoutHelp(UpdateCommand.PROJECT_UP_TO_DATE_MESSAGE); } diff --git a/lib/common/mobile/ios/device/ios-application-manager.ts b/lib/common/mobile/ios/device/ios-application-manager.ts index 5c757de159..4e7d0bc44e 100644 --- a/lib/common/mobile/ios/device/ios-application-manager.ts +++ b/lib/common/mobile/ios/device/ios-application-manager.ts @@ -102,7 +102,7 @@ export class IOSApplicationManager extends ApplicationManagerBase { } private async runApplicationCore(appData: Mobile.IStartApplicationData): Promise { - const waitForDebugger = appData.waitForDebugger && appData.waitForDebugger.toString(); + const waitForDebugger = (!!appData.waitForDebugger).toString(); await this.$iosDeviceOperations.start([{ deviceId: this.device.deviceInfo.identifier, appId: appData.appId, ddi: this.$options.ddi, waitForDebugger }]); } diff --git a/lib/common/mobile/mobile-core/devices-service.ts b/lib/common/mobile/mobile-core/devices-service.ts index 4a139363a6..ea8a843657 100644 --- a/lib/common/mobile/mobile-core/devices-service.ts +++ b/lib/common/mobile/mobile-core/devices-service.ts @@ -444,6 +444,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi } } catch (err) { err.deviceIdentifier = device.deviceInfo.identifier; + this.$logger.trace(`Error while executing action on device ${device.deviceInfo.identifier}. The error is`, err); errors.push(err); } } @@ -454,9 +455,9 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi preErrorMsg = "Multiple errors were thrown:" + EOL; } - const singleError = (new Error(`${preErrorMsg}${errors.map(e => e.message || e).join(EOL)}`)); - singleError.allErrors = errors; - throw singleError; + const errorMessage = `${preErrorMsg}${errors.map(e => e.message || e).join(EOL)}`; + const suppressCommandHelp = _.some(errors, (e: any) => e.suppressCommandHelp); + this.$errors.fail({ formatStr: errorMessage, suppressCommandHelp }); } return result; diff --git a/lib/common/services/hooks-service.ts b/lib/common/services/hooks-service.ts index 1362aa9a64..a455e1cdda 100644 --- a/lib/common/services/hooks-service.ts +++ b/lib/common/services/hooks-service.ts @@ -117,7 +117,7 @@ export class HooksService implements IHooksService { this.$logger.trace(`Validating ${hookName} arguments.`); - const invalidArguments = this.validateHookArguments(hookEntryPoint); + const invalidArguments = this.validateHookArguments(hookEntryPoint, hook.fullPath); if (invalidArguments.length) { this.$logger.warn(`${hook.fullPath} will NOT be executed because it has invalid arguments - ${invalidArguments.join(", ").grey}.`); @@ -144,6 +144,8 @@ export class HooksService implements IHooksService { if (err && _.isBoolean(err.stopExecution) && err.errorAsWarning === true) { this.$logger.warn(err.message || err); } else { + // Print the actual error with its callstack, so it is easy to find out which hooks is causing troubles. + this.$logger.error(err); throw err || new Error(`Failed to execute hook: ${hook.fullPath}.`); } } @@ -266,7 +268,7 @@ export class HooksService implements IHooksService { } } - private validateHookArguments(hookConstructor: Function): string[] { + private validateHookArguments(hookConstructor: Function, hookFullPath: string): string[] { const invalidArguments: string[] = []; // We need to annotate the hook in order to have the arguments of the constructor. @@ -278,7 +280,7 @@ export class HooksService implements IHooksService { this.$injector.resolve(argument); } } catch (err) { - this.$logger.trace(`Cannot resolve ${argument}, reason: ${err}`); + this.$logger.trace(`Cannot resolve ${argument} of hook ${hookFullPath}, reason: ${err}`); invalidArguments.push(argument); } }); diff --git a/lib/controllers/build-controller.ts b/lib/controllers/build-controller.ts index a5e0bbf2f2..56f6fb16b7 100644 --- a/lib/controllers/build-controller.ts +++ b/lib/controllers/build-controller.ts @@ -66,7 +66,8 @@ export class BuildController extends EventEmitter implements IBuildController { if (buildData.copyTo) { this.$buildArtefactsService.copyLatestAppPackage(buildData.copyTo, platformData, buildData); - this.$logger.info(`The build result is located at: ${buildInfoFileDir}`); + } else { + this.$logger.info(`The build result is located at: ${result}`); } return result; @@ -87,12 +88,8 @@ export class BuildController extends EventEmitter implements IBuildController { const projectData = this.$projectDataService.getProjectData(buildData.projectDir); const platformData = this.$platformsDataService.getPlatformData(buildData.platform, projectData); const outputPath = buildData.outputPath || platformData.getBuildOutputPath(buildData); - - if (buildData.release && this.$projectChangesService.currentChanges.hasChanges) { - return true; - } - const changesInfo = this.$projectChangesService.currentChanges || await this.$projectChangesService.checkForChanges(platformData, projectData, buildData); + if (changesInfo.changesRequireBuild) { return true; } diff --git a/lib/controllers/deploy-controller.ts b/lib/controllers/deploy-controller.ts index 1574926ea4..8c51ac20c3 100644 --- a/lib/controllers/deploy-controller.ts +++ b/lib/controllers/deploy-controller.ts @@ -1,17 +1,19 @@ export class DeployController { constructor( - private $buildController: IBuildController, private $deviceInstallAppService: IDeviceInstallAppService, - private $devicesService: Mobile.IDevicesService + private $devicesService: Mobile.IDevicesService, + private $prepareController: IPrepareController ) { } public async deploy(data: IDeployData): Promise { - const { buildData, deviceDescriptors } = data; + const { deviceDescriptors } = data; const executeAction = async (device: Mobile.IDevice) => { - const packageFilePath = await this.$buildController.prepareAndBuild({ ...buildData, buildForDevice: !device.isEmulator }); - await this.$deviceInstallAppService.installOnDevice(device, { ...buildData, buildForDevice: !device.isEmulator }, packageFilePath); + const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + await this.$prepareController.prepare(deviceDescriptor.buildData); + const packageFilePath = await deviceDescriptor.buildAction(); + await this.$deviceInstallAppService.installOnDevice(device, { ...deviceDescriptor.buildData, buildForDevice: !device.isEmulator }, packageFilePath); }; await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)); diff --git a/lib/controllers/migrate-controller.ts b/lib/controllers/migrate-controller.ts index 67da41c784..49c00bcd52 100644 --- a/lib/controllers/migrate-controller.ts +++ b/lib/controllers/migrate-controller.ts @@ -6,22 +6,32 @@ import { UpdateControllerBase } from "./update-controller-base"; import { fromWindowsRelativePathToUnix } from "../common/helpers"; export class MigrateController extends UpdateControllerBase implements IMigrateController { + private static COMMON_MIGRATE_MESSAGE = "not affect the codebase of the application and you might need to do additional changes manually – for more information, refer to the instructions in the following blog post: https://www.nativescript.org/blog/nativescript-6.0-application-migration"; + private static UNABLE_TO_MIGRATE_APP_ERROR = `The current application is not compatible with NativeScript CLI 6.0. +Use the \`tns migrate\` command to migrate the app dependencies to a form compatible with NativeScript 6.0. +Running this command will ${MigrateController.COMMON_MIGRATE_MESSAGE}`; + private static MIGRATE_FINISH_MESSAGE = `The \`tns migrate\` command does ${MigrateController.COMMON_MIGRATE_MESSAGE}`; + constructor( protected $fs: IFileSystem, protected $platformCommandHelper: IPlatformCommandHelper, protected $platformsDataService: IPlatformsDataService, protected $packageInstallationManager: IPackageInstallationManager, protected $packageManager: IPackageManager, + protected $pacoteService: IPacoteService, + private $androidResourcesMigrationService: IAndroidResourcesMigrationService, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $logger: ILogger, private $errors: IErrors, private $addPlatformService: IAddPlatformService, private $pluginsService: IPluginsService, private $projectDataService: IProjectDataService, + private $platformValidationService: IPlatformValidationService, private $resources: IResourceLoader) { - super($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager); + super($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager, $pacoteService); } + static readonly typescriptPackageName: string = "typescript"; static readonly backupFolder: string = ".migration_backup"; static readonly migrateFailMessage: string = "Could not migrate the project!"; static readonly backupFailMessage: string = "Could not backup project folders!"; @@ -37,51 +47,54 @@ export class MigrateController extends UpdateControllerBase implements IMigrateC ]; private migrationDependencies: IMigrationDependency[] = [ - { packageName: constants.TNS_CORE_MODULES_NAME, verifiedVersion: "6.0.0-rc-2019-06-28-175837-02" }, - { packageName: constants.TNS_CORE_MODULES_WIDGETS_NAME, verifiedVersion: "6.0.0" }, - { packageName: "tns-platform-declarations", isDev: true, verifiedVersion: "6.0.0-rc-2019-06-28-175837-02" }, + { packageName: constants.TNS_CORE_MODULES_NAME, verifiedVersion: "6.0.1" }, + { packageName: constants.TNS_CORE_MODULES_WIDGETS_NAME, verifiedVersion: "6.0.1" }, + { packageName: "tns-platform-declarations", isDev: true, verifiedVersion: "6.0.1" }, { packageName: "node-sass", isDev: true, verifiedVersion: "4.12.0" }, - { packageName: "typescript", isDev: true, verifiedVersion: "3.4.1" }, - { packageName: "less", isDev: true, verifiedVersion: "3.9.0" }, { packageName: "nativescript-dev-sass", isDev: true, replaceWith: "node-sass" }, - { packageName: "nativescript-dev-typescript", isDev: true, replaceWith: "typescript" }, - { packageName: "nativescript-dev-less", isDev: true, replaceWith: "less" }, - { packageName: constants.WEBPACK_PLUGIN_NAME, isDev: true, shouldAddIfMissing: true, verifiedVersion: "1.0.0-rc-2019-07-02-161545-02" }, + { packageName: "nativescript-dev-typescript", isDev: true, replaceWith: MigrateController.typescriptPackageName }, + { packageName: "nativescript-dev-less", isDev: true, shouldRemove: true, warning: "LESS CSS is not supported out of the box. In order to enable it, follow the steps in this feature request: https://github.com/NativeScript/nativescript-dev-webpack/issues/967" }, + { packageName: constants.WEBPACK_PLUGIN_NAME, isDev: true, shouldAddIfMissing: true, verifiedVersion: "1.0.0" }, { packageName: "nativescript-camera", verifiedVersion: "4.5.0" }, { packageName: "nativescript-geolocation", verifiedVersion: "5.1.0" }, { packageName: "nativescript-imagepicker", verifiedVersion: "6.2.0" }, { packageName: "nativescript-social-share", verifiedVersion: "1.5.2" }, - { packageName: "nativescript-ui-chart", verifiedVersion: "5.0.0-androidx-110619" }, - { packageName: "nativescript-ui-dataform", verifiedVersion: "5.0.0-androidx-110619" }, - { packageName: "nativescript-ui-gauge", verifiedVersion: "5.0.0-androidx" }, - { packageName: "nativescript-ui-listview", verifiedVersion: "7.0.0-androidx-110619" }, - { packageName: "nativescript-ui-sidedrawer", verifiedVersion: "7.0.0-androidx-110619" }, - { packageName: "nativescript-ui-calendar", verifiedVersion: "5.0.0-androidx-110619-2" }, - { packageName: "nativescript-ui-autocomplete", verifiedVersion: "5.0.0-androidx-110619" }, + { packageName: "nativescript-ui-chart", verifiedVersion: "5.0.0" }, + { packageName: "nativescript-ui-dataform", verifiedVersion: "5.0.0" }, + { packageName: "nativescript-ui-gauge", verifiedVersion: "5.0.0" }, + { packageName: "nativescript-ui-listview", verifiedVersion: "7.0.0" }, + { packageName: "nativescript-ui-sidedrawer", verifiedVersion: "7.0.0" }, + { packageName: "nativescript-ui-calendar", verifiedVersion: "5.0.0" }, + { packageName: "nativescript-ui-autocomplete", verifiedVersion: "5.0.0" }, { packageName: "nativescript-datetimepicker", verifiedVersion: "1.1.0" }, - //TODO update with compatible with webpack only hooks { packageName: "kinvey-nativescript-sdk", verifiedVersion: "4.2.1" }, - //TODO update with compatible with webpack only hooks - { packageName: "nativescript-plugin-firebase", verifiedVersion: "9.0.1" }, - //TODO update with no prerelease version compatible with webpack only hooks - { packageName: "nativescript-vue", verifiedVersion: "2.3.0-rc.1" }, + { packageName: "nativescript-plugin-firebase", verifiedVersion: "9.0.2" }, + { packageName: "nativescript-vue", verifiedVersion: "2.3.0" }, { packageName: "nativescript-permissions", verifiedVersion: "1.3.0" }, { packageName: "nativescript-cardview", verifiedVersion: "3.2.0" }, { - packageName: "nativescript-unit-test-runner", verifiedVersion: "0.6.3", - shouldMigrateAction: (projectData: IProjectData) => this.hasDependency({ packageName: "nativescript-unit-test-runner", isDev: false }, projectData), + packageName: "nativescript-unit-test-runner", verifiedVersion: "0.6.4", + shouldMigrateAction: async (projectData: IProjectData, allowInvalidVersions: boolean) => { + const dependency = { packageName: "nativescript-unit-test-runner", verifiedVersion: "0.6.4", isDev: false }; + const result = this.hasDependency(dependency, projectData) && await this.shouldMigrateDependencyVersion(dependency, projectData, allowInvalidVersions); + return result; + }, migrateAction: this.migrateUnitTestRunner.bind(this) - } + }, + { packageName: MigrateController.typescriptPackageName, isDev: true, getVerifiedVersion: this.getAngularTypeScriptVersion.bind(this) }, + { packageName: "nativescript-localize", verifiedVersion: "4.2.0" }, + { packageName: "nativescript-dev-babel", verifiedVersion: "0.2.1" }, + { packageName: "nativescript-nfc", verifiedVersion: "4.0.1" } ]; get verifiedPlatformVersions(): IDictionary { return { - [this.$devicePlatformsConstants.Android.toLowerCase()]: "6.0.0-rc-2019-06-27-172817-03", - [this.$devicePlatformsConstants.iOS.toLowerCase()]: "6.0.0-rc-2019-06-28-105002-01" + [this.$devicePlatformsConstants.Android.toLowerCase()]: "6.0.0", + [this.$devicePlatformsConstants.iOS.toLowerCase()]: "6.0.0" }; } - public async migrate({ projectDir }: { projectDir: string }): Promise { + public async migrate({ projectDir, platforms, allowInvalidVersions = false }: IMigrationData): Promise { const projectData = this.$projectDataService.getProjectData(projectDir); const backupDir = path.join(projectDir, MigrateController.backupFolder); @@ -96,56 +109,116 @@ export class MigrateController extends UpdateControllerBase implements IMigrateC } try { - this.$logger.info("Backup auto-generated files."); + this.$logger.info("Clean auto-generated files."); this.handleAutoGeneratedFiles(backupDir, projectData); - this.$logger.info("Backup auto-generated files complete."); + this.$logger.info("Clean auto-generated files complete."); } catch (error) { this.$logger.trace(`Error during auto-generated files handling. ${(error && error.message) || error}`); } + await this.migrateOldAndroidAppResources(projectData, backupDir); + try { await this.cleanUpProject(projectData); - await this.migrateDependencies(projectData); + await this.migrateDependencies(projectData, platforms, allowInvalidVersions); } catch (error) { this.restoreBackup(MigrateController.folders, backupDir, projectData.projectDir); - this.$logger.error(MigrateController.migrateFailMessage); + this.$errors.failWithoutHelp(`${MigrateController.migrateFailMessage} The error is: ${error}`); } + + this.$logger.info(MigrateController.MIGRATE_FINISH_MESSAGE); } - public async shouldMigrate({ projectDir }: IProjectDir): Promise { + public async shouldMigrate({ projectDir, platforms, allowInvalidVersions = false }: IMigrationData): Promise { const projectData = this.$projectDataService.getProjectData(projectDir); + const shouldMigrateCommonMessage = "The app is not compatible with this CLI version and it should be migrated. Reason: "; for (let i = 0; i < this.migrationDependencies.length; i++) { const dependency = this.migrationDependencies[i]; const hasDependency = this.hasDependency(dependency, projectData); - if (hasDependency && dependency.shouldMigrateAction && dependency.shouldMigrateAction(projectData)) { + if (hasDependency && dependency.shouldMigrateAction && await dependency.shouldMigrateAction(projectData, allowInvalidVersions)) { + this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' requires an update.`); return true; } - if (hasDependency && dependency.replaceWith) { + if (hasDependency && (dependency.replaceWith || dependency.shouldRemove)) { + this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' is deprecated.`); return true; } - if (hasDependency && await this.shouldMigrateDependencyVersion(dependency, projectData)) { + if (hasDependency && await this.shouldMigrateDependencyVersion(dependency, projectData, allowInvalidVersions)) { + this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' should be updated.`); return true; } if (!hasDependency && dependency.shouldAddIfMissing) { + this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' is missing.`); return true; } } - for (const platform in this.$devicePlatformsConstants) { + for (let platform of platforms) { + platform = platform && platform.toLowerCase(); + if (!this.$platformValidationService.isValidPlatform(platform, projectData)) { + continue; + } + const hasRuntimeDependency = this.hasRuntimeDependency({ platform, projectData }); - if (!hasRuntimeDependency || await this.shouldUpdateRuntimeVersion({ targetVersion: this.verifiedPlatformVersions[platform.toLowerCase()], platform, projectData })) { + if (hasRuntimeDependency && await this.shouldUpdateRuntimeVersion(this.verifiedPlatformVersions[platform.toLowerCase()], platform, projectData, allowInvalidVersions)) { + this.$logger.trace(`${shouldMigrateCommonMessage}Platform '${platform}' should be updated.`); return true; } } } + public async validate({ projectDir, platforms, allowInvalidVersions = true }: IMigrationData): Promise { + const shouldMigrate = await this.shouldMigrate({ projectDir, platforms, allowInvalidVersions }); + if (shouldMigrate) { + this.$errors.failWithoutHelp(MigrateController.UNABLE_TO_MIGRATE_APP_ERROR); + } + } + + private async getAngularTypeScriptVersion(projectData: IProjectData): Promise { + let verifiedVersion = "3.4.1"; + try { + const ngcPackageName = "@angular/compiler-cli"; + // e.g. ~8.0.0 + let ngcVersion = projectData.dependencies[ngcPackageName] || projectData.devDependencies[ngcPackageName]; + if (ngcVersion) { + // e.g. 8.0.3 + ngcVersion = await this.$packageInstallationManager.maxSatisfyingVersion(ngcPackageName, ngcVersion); + const ngcManifest = await this.getPackageManifest(ngcPackageName, ngcVersion); + // e.g. >=3.4 <3.5 + verifiedVersion = (ngcManifest && ngcManifest.peerDependencies && + ngcManifest.peerDependencies[MigrateController.typescriptPackageName]) || verifiedVersion; + + // e.g. 3.4.4 + verifiedVersion = await this.$packageInstallationManager.maxSatisfyingVersion( + MigrateController.typescriptPackageName, verifiedVersion); + } + } catch (error) { + this.$logger.warn(`Unable to determine the TypeScript version based on the Angular packages. Error is: '${error}'.`); + } + + return verifiedVersion; + } + + private async migrateOldAndroidAppResources(projectData: IProjectData, backupDir: string) { + const appResourcesPath = projectData.getAppResourcesDirectoryPath(); + if (!this.$androidResourcesMigrationService.hasMigrated(appResourcesPath)) { + this.$logger.info("Migrate old Android App_Resources structure."); + try { + await this.$androidResourcesMigrationService.migrate(appResourcesPath, backupDir); + } catch (error) { + this.$logger.warn("Migrate old Android App_Resources structure failed: ", error.message); + } + } + } + private async cleanUpProject(projectData: IProjectData): Promise { this.$logger.info("Clean old project artefacts."); + this.$projectDataService.removeNSConfigProperty(projectData.projectDir, "useLegacyWorkflow"); this.$fs.deleteDirectory(path.join(projectData.projectDir, constants.HOOKS_DIR_NAME)); this.$fs.deleteDirectory(path.join(projectData.projectDir, constants.PLATFORMS_DIR_NAME)); this.$fs.deleteDirectory(path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME)); @@ -204,26 +277,26 @@ export class MigrateController extends UpdateControllerBase implements IMigrateC return autoGeneratedFiles; } - private async migrateDependencies(projectData: IProjectData): Promise { + private async migrateDependencies(projectData: IProjectData, platforms: string[], allowInvalidVersions: boolean): Promise { this.$logger.info("Start dependencies migration."); for (let i = 0; i < this.migrationDependencies.length; i++) { const dependency = this.migrationDependencies[i]; const hasDependency = this.hasDependency(dependency, projectData); - if (hasDependency && dependency.migrateAction && dependency.shouldMigrateAction(projectData)) { + if (hasDependency && dependency.migrateAction && await dependency.shouldMigrateAction(projectData, allowInvalidVersions)) { const newDependencies = await dependency.migrateAction(projectData, path.join(projectData.projectDir, MigrateController.backupFolder)); for (const newDependency of newDependencies) { - await this.migrateDependency(newDependency, projectData); + await this.migrateDependency(newDependency, projectData, allowInvalidVersions); } } - await this.migrateDependency(dependency, projectData); + await this.migrateDependency(dependency, projectData, allowInvalidVersions); } - for (const platform in this.$devicePlatformsConstants) { + for (const platform of platforms) { const lowercasePlatform = platform.toLowerCase(); const hasRuntimeDependency = this.hasRuntimeDependency({ platform, projectData }); - if (!hasRuntimeDependency || await this.shouldUpdateRuntimeVersion({ targetVersion: this.verifiedPlatformVersions[lowercasePlatform], platform, projectData })) { + if (hasRuntimeDependency && await this.shouldUpdateRuntimeVersion(this.verifiedPlatformVersions[lowercasePlatform], platform, projectData, allowInvalidVersions)) { const verifiedPlatformVersion = this.verifiedPlatformVersions[lowercasePlatform]; const platformData = this.$platformsDataService.getPlatformData(lowercasePlatform, projectData); this.$logger.info(`Updating ${platform} platform to version '${verifiedPlatformVersion}'.`); @@ -242,67 +315,92 @@ export class MigrateController extends UpdateControllerBase implements IMigrateC this.$logger.info("Migration complete."); } - private async migrateDependency(dependency: IMigrationDependency, projectData: IProjectData): Promise { + private async migrateDependency(dependency: IMigrationDependency, projectData: IProjectData, allowInvalidVersions: boolean): Promise { const hasDependency = this.hasDependency(dependency, projectData); + if (hasDependency && dependency.warning) { + this.$logger.warn(dependency.warning); + } - if (hasDependency && dependency.replaceWith) { + if (hasDependency && (dependency.replaceWith || dependency.shouldRemove)) { this.$pluginsService.removeFromPackageJson(dependency.packageName, projectData.projectDir); - const replacementDep = _.find(this.migrationDependencies, migrationPackage => migrationPackage.packageName === dependency.replaceWith); - if (!replacementDep) { - this.$errors.failWithoutHelp("Failed to find replacement dependency."); + if (dependency.replaceWith) { + const replacementDep = _.find(this.migrationDependencies, migrationPackage => migrationPackage.packageName === dependency.replaceWith); + if (!replacementDep) { + this.$errors.failWithoutHelp("Failed to find replacement dependency."); + } + + const replacementDepVersion = await this.getDependencyVerifiedVersion(replacementDep, projectData); + this.$logger.info(`Replacing '${dependency.packageName}' with '${replacementDep.packageName}'.`); + this.$pluginsService.addToPackageJson(replacementDep.packageName, replacementDepVersion, replacementDep.isDev, projectData.projectDir); } - this.$logger.info(`Replacing '${dependency.packageName}' with '${replacementDep.packageName}'.`); - this.$pluginsService.addToPackageJson(replacementDep.packageName, replacementDep.verifiedVersion, replacementDep.isDev, projectData.projectDir); + return; } - if (hasDependency && await this.shouldMigrateDependencyVersion(dependency, projectData)) { - this.$logger.info(`Updating '${dependency.packageName}' to compatible version '${dependency.verifiedVersion}'`); - this.$pluginsService.addToPackageJson(dependency.packageName, dependency.verifiedVersion, dependency.isDev, projectData.projectDir); + const dependencyVersion = await this.getDependencyVerifiedVersion(dependency, projectData); + if (hasDependency && await this.shouldMigrateDependencyVersion(dependency, projectData, allowInvalidVersions)) { + this.$logger.info(`Updating '${dependency.packageName}' to compatible version '${dependencyVersion}'`); + this.$pluginsService.addToPackageJson(dependency.packageName, dependencyVersion, dependency.isDev, projectData.projectDir); return; } if (!hasDependency && dependency.shouldAddIfMissing) { - this.$logger.info(`Adding '${dependency.packageName}' with version '${dependency.verifiedVersion}'`); - this.$pluginsService.addToPackageJson(dependency.packageName, dependency.verifiedVersion, dependency.isDev, projectData.projectDir); + this.$logger.info(`Adding '${dependency.packageName}' with version '${dependencyVersion}'`); + this.$pluginsService.addToPackageJson(dependency.packageName, dependencyVersion, dependency.isDev, projectData.projectDir); } } - private async shouldMigrateDependencyVersion(dependency: IMigrationDependency, projectData: IProjectData): Promise { + private async getDependencyVerifiedVersion(dependency: IMigrationDependency, projectData: IProjectData): Promise { + if (!dependency.verifiedVersion && dependency.getVerifiedVersion) { + dependency.verifiedVersion = await dependency.getVerifiedVersion(projectData); + } + + return dependency.verifiedVersion; + } + + private async shouldMigrateDependencyVersion(dependency: IMigrationDependency, projectData: IProjectData, allowInvalidVersions: boolean): Promise { const devDependencies = projectData.devDependencies || {}; const dependencies = projectData.dependencies || {}; const packageName = dependency.packageName; - const version = dependencies[packageName] || devDependencies[packageName]; - const maxSatisfyingVersion = await this.getMaxDependencyVersion(dependency.packageName, version); + const referencedVersion = dependencies[packageName] || devDependencies[packageName]; + const installedVersion = await this.getMaxDependencyVersion(dependency.packageName, referencedVersion); + const requiredVersion = await this.getDependencyVerifiedVersion(dependency, projectData); - return !(maxSatisfyingVersion && semver.gte(maxSatisfyingVersion, dependency.verifiedVersion)); + return this.isOutdatedVersion(installedVersion, requiredVersion, allowInvalidVersions); } - protected async shouldUpdateRuntimeVersion({ targetVersion, platform, projectData }: { targetVersion: string, platform: string, projectData: IProjectData }): Promise { - const maxRuntimeVersion = await this.getMaxRuntimeVersion({ platform, projectData }); + private async shouldUpdateRuntimeVersion(targetVersion: string, platform: string, projectData: IProjectData, allowInvalidVersions: boolean): Promise { + const installedVersion = await this.getMaxRuntimeVersion({ platform, projectData }); - return !(maxRuntimeVersion && semver.gte(maxRuntimeVersion, targetVersion)); + return this.isOutdatedVersion(installedVersion, targetVersion, allowInvalidVersions); + } + + private isOutdatedVersion(version: string, targetVersion: string, allowInvalidVersions: boolean): boolean { + return !!version ? semver.lt(version, targetVersion) : !allowInvalidVersions; } private async migrateUnitTestRunner(projectData: IProjectData, migrationBackupDirPath: string): Promise { // Migrate karma.conf.js - const oldKarmaContent = this.$fs.readText(path.join(migrationBackupDirPath, constants.KARMA_CONFIG_NAME)); + const pathToKarmaConfig = path.join(migrationBackupDirPath, constants.KARMA_CONFIG_NAME); + if (this.$fs.exists(pathToKarmaConfig)) { + const oldKarmaContent = this.$fs.readText(pathToKarmaConfig); - const regExp = /frameworks:\s+\[([\S\s]*?)\]/g; - const matches = regExp.exec(oldKarmaContent); - const frameworks = (matches && matches[1] && matches[1].trim()) || '["jasmine"]'; + const regExp = /frameworks:\s+\[([\S\s]*?)\]/g; + const matches = regExp.exec(oldKarmaContent); + const frameworks = (matches && matches[1] && matches[1].trim()) || '["jasmine"]'; - const testsDir = path.join(projectData.appDirectoryPath, 'tests'); - const relativeTestsDir = path.relative(projectData.projectDir, testsDir); - const testFiles = `'${fromWindowsRelativePathToUnix(relativeTestsDir)}/**/*.*'`; + const testsDir = path.join(projectData.appDirectoryPath, 'tests'); + const relativeTestsDir = path.relative(projectData.projectDir, testsDir); + const testFiles = `'${fromWindowsRelativePathToUnix(relativeTestsDir)}/**/*.*'`; - const karmaConfTemplate = this.$resources.readText('test/karma.conf.js'); - const karmaConf = _.template(karmaConfTemplate)({ frameworks, testFiles }); - this.$fs.writeFile(path.join(projectData.projectDir, constants.KARMA_CONFIG_NAME), karmaConf); + const karmaConfTemplate = this.$resources.readText('test/karma.conf.js'); + const karmaConf = _.template(karmaConfTemplate)({ frameworks, testFiles }); + this.$fs.writeFile(path.join(projectData.projectDir, constants.KARMA_CONFIG_NAME), karmaConf); + } // Dependencies to migrate const dependencies = [ - { packageName: "karma-webpack", verifiedVersion: "3.0.5", isDev: true, shouldAddIfMissing: !this.hasDependency({ packageName: "karma-webpack", isDev: true }, projectData) }, + { packageName: "karma-webpack", verifiedVersion: "3.0.5", isDev: true, shouldAddIfMissing: true }, { packageName: "karma-jasmine", verifiedVersion: "2.0.1", isDev: true }, { packageName: "karma-mocha", verifiedVersion: "1.3.0", isDev: true }, { packageName: "karma-chai", verifiedVersion: "0.1.0", isDev: true }, diff --git a/lib/controllers/prepare-controller.ts b/lib/controllers/prepare-controller.ts index 29b9be2b2a..86c871027d 100644 --- a/lib/controllers/prepare-controller.ts +++ b/lib/controllers/prepare-controller.ts @@ -1,4 +1,3 @@ -import * as child_process from "child_process"; import * as choki from "chokidar"; import { hook } from "../common/helpers"; import { performanceLog } from "../common/decorators"; @@ -7,7 +6,7 @@ import * as path from "path"; import { PREPARE_READY_EVENT_NAME, WEBPACK_COMPILATION_COMPLETE, PACKAGE_JSON_FILE_NAME, PLATFORMS_DIR_NAME } from "../constants"; interface IPlatformWatcherData { - webpackCompilerProcess: child_process.ChildProcess; + hasWebpackCompilerProcess: boolean; nativeFilesWatcher: choki.FSWatcher; } @@ -22,6 +21,7 @@ export class PrepareController extends EventEmitter { private $logger: ILogger, private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, private $platformsDataService: IPlatformsDataService, + private $pluginsService: IPluginsService, private $prepareNativePlatformService: IPrepareNativePlatformService, private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService, @@ -29,15 +29,36 @@ export class PrepareController extends EventEmitter { private $watchIgnoreListService: IWatchIgnoreListService ) { super(); } + public async prepare(prepareData: IPrepareData): Promise { + const projectData = this.$projectDataService.getProjectData(prepareData.projectDir); + + await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); + + return this.prepareCore(prepareData, projectData); + } + + public async stopWatchers(projectDir: string, platform: string): Promise { + const platformLowerCase = platform.toLowerCase(); + + if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) { + this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher.close(); + this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher = null; + } + + if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].hasWebpackCompilerProcess) { + await this.$webpackCompilerService.stopWebpackCompiler(platform); + this.watchersData[projectDir][platformLowerCase].hasWebpackCompilerProcess = false; + } + } + @performanceLog() @hook("prepare") - public async prepare(prepareData: IPrepareData): Promise { + private async prepareCore(prepareData: IPrepareData, projectData: IProjectData): Promise { await this.$platformController.addPlatformIfNeeded(prepareData); this.$logger.info("Preparing project..."); let result = null; - const projectData = this.$projectDataService.getProjectData(prepareData.projectDir); const platformData = this.$platformsDataService.getPlatformData(prepareData.platform, projectData); if (prepareData.watch) { @@ -55,20 +76,6 @@ export class PrepareController extends EventEmitter { return result; } - public async stopWatchers(projectDir: string, platform: string): Promise { - const platformLowerCase = platform.toLowerCase(); - - if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher) { - this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher.close(); - this.watchersData[projectDir][platformLowerCase].nativeFilesWatcher = null; - } - - if (this.watchersData && this.watchersData[projectDir] && this.watchersData[projectDir][platformLowerCase] && this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess) { - await this.$webpackCompilerService.stopWebpackCompiler(platform); - this.watchersData[projectDir][platformLowerCase].webpackCompilerProcess = null; - } - } - @hook("watch") private async startWatchersWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { if (!this.watchersData[projectData.projectDir]) { @@ -78,38 +85,39 @@ export class PrepareController extends EventEmitter { if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase]) { this.watchersData[projectData.projectDir][platformData.platformNameLowerCase] = { nativeFilesWatcher: null, - webpackCompilerProcess: null + hasWebpackCompilerProcess: false }; - await this.startJSWatcherWithPrepare(platformData, projectData, prepareData); // -> start watcher + initial compilation - const hasNativeChanges = await this.startNativeWatcherWithPrepare(platformData, projectData, prepareData); // -> start watcher + initial prepare - const result = { platform: platformData.platformNameLowerCase, hasNativeChanges }; + } - const hasPersistedDataWithNativeChanges = this.persistedData.find(data => data.platform === result.platform && data.hasNativeChanges); - if (hasPersistedDataWithNativeChanges) { - result.hasNativeChanges = true; - } + await this.startJSWatcherWithPrepare(platformData, projectData, prepareData); // -> start watcher + initial compilation + const hasNativeChanges = await this.startNativeWatcherWithPrepare(platformData, projectData, prepareData); // -> start watcher + initial prepare + const result = { platform: platformData.platformNameLowerCase, hasNativeChanges }; - // TODO: Do not persist this in `this` context. Also it should be per platform. - this.isInitialPrepareReady = true; + const hasPersistedDataWithNativeChanges = this.persistedData.find(data => data.platform === result.platform && data.hasNativeChanges); + if (hasPersistedDataWithNativeChanges) { + result.hasNativeChanges = true; + } - if (this.persistedData && this.persistedData.length) { - this.emitPrepareEvent({ files: [], hasOnlyHotUpdateFiles: false, hasNativeChanges: result.hasNativeChanges, hmrData: null, platform: platformData.platformNameLowerCase }); - } + // TODO: Do not persist this in `this` context. Also it should be per platform. + this.isInitialPrepareReady = true; - return result; + if (this.persistedData && this.persistedData.length) { + this.emitPrepareEvent({ files: [], hasOnlyHotUpdateFiles: false, hasNativeChanges: result.hasNativeChanges, hmrData: null, platform: platformData.platformNameLowerCase }); } + + return result; } private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { - if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) { + if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].hasWebpackCompilerProcess) { this.$webpackCompilerService.on(WEBPACK_COMPILATION_COMPLETE, data => { if (data.platform.toLowerCase() === platformData.platformNameLowerCase) { this.emitPrepareEvent({ ...data, hasNativeChanges: false }); } }); - const childProcess = await this.$webpackCompilerService.compileWithWatch(platformData, projectData, prepareData); - this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess = childProcess; + this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].hasWebpackCompilerProcess = true; + await this.$webpackCompilerService.compileWithWatch(platformData, projectData, prepareData); } } diff --git a/lib/controllers/preview-app-controller.ts b/lib/controllers/preview-app-controller.ts index c1883a76bf..bedfde8326 100644 --- a/lib/controllers/preview-app-controller.ts +++ b/lib/controllers/preview-app-controller.ts @@ -9,8 +9,8 @@ import { PrepareDataService } from "../services/prepare-data-service"; import { PreviewAppLiveSyncEvents } from "../services/livesync/playground/preview-app-constants"; export class PreviewAppController extends EventEmitter implements IPreviewAppController { + private prepareReadyEventHandler: any = null; private deviceInitializationPromise: IDictionary = {}; - private platformPrepareHandlers: IDictionary = {}; private promise = Promise.resolve(); constructor( @@ -25,7 +25,8 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon private $previewDevicesService: IPreviewDevicesService, private $previewQrCodeService: IPreviewQrCodeService, private $previewSdkService: IPreviewSdkService, - private $prepareDataService: PrepareDataService + private $prepareDataService: PrepareDataService, + private $projectDataService: IProjectDataService ) { super(); } public async startPreview(data: IPreviewAppLiveSyncData): Promise { @@ -40,6 +41,10 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon public async stopPreview(): Promise { this.$previewSdkService.stop(); this.$previewDevicesService.updateConnectedDevices([]); + if (this.prepareReadyEventHandler) { + this.removeListener(PREPARE_READY_EVENT_NAME, this.prepareReadyEventHandler); + this.prepareReadyEventHandler = null; + } } private async previewCore(data: IPreviewAppLiveSyncData): Promise { @@ -66,7 +71,8 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon }); } - await this.$hooksService.executeBeforeHooks("preview-sync", { ...data, platform: device.platform }); + const projectData = this.$projectDataService.getProjectData(data.projectDir); + await this.$hooksService.executeBeforeHooks("preview-sync", { hookArgs: { ...data, platform: device.platform, projectData } }); if (data.useHotModuleReload) { this.$hmrStatusService.attachToHmrStatusEvent(); @@ -74,15 +80,11 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon await this.$previewAppPluginsService.comparePluginsOnDevice(data, device); - if (!this.platformPrepareHandlers[device.platform]) { - // TODO: Unset this property once the preview operation for this platform is stopped - this.platformPrepareHandlers[device.platform] = true; - - // TODO: Remove the handler once the preview operation for this platform is stopped - this.$prepareController.on(PREPARE_READY_EVENT_NAME, async currentPrepareData => { - await this.handlePrepareReadyEvent(data, currentPrepareData.hmrData, currentPrepareData.files, device.platform); - }); - + if (!this.prepareReadyEventHandler) { + this.prepareReadyEventHandler = async (currentPrepareData: IFilesChangeEventData) => { + await this.handlePrepareReadyEvent(data, currentPrepareData); + }; + this.$prepareController.on(PREPARE_READY_EVENT_NAME, this.prepareReadyEventHandler.bind(this)); } data.env = data.env || {}; @@ -111,9 +113,10 @@ export class PreviewAppController extends EventEmitter implements IPreviewAppCon } @performanceLog() - private async handlePrepareReadyEvent(data: IPreviewAppLiveSyncData, hmrData: IPlatformHmrData, files: string[], platform: string) { + private async handlePrepareReadyEvent(data: IPreviewAppLiveSyncData, currentPrepareData: IFilesChangeEventData) { await this.promise .then(async () => { + const { hmrData, files, platform } = currentPrepareData; const platformHmrData = _.cloneDeep(hmrData); this.promise = this.syncFilesForPlatformSafe(data, { filesToSync: files }, platform); diff --git a/lib/controllers/run-controller.ts b/lib/controllers/run-controller.ts index 1237555a06..259a44b99f 100644 --- a/lib/controllers/run-controller.ts +++ b/lib/controllers/run-controller.ts @@ -4,6 +4,8 @@ import { cache, performanceLog } from "../common/decorators"; import { EventEmitter } from "events"; export class RunController extends EventEmitter implements IRunController { + private prepareReadyEventHandler: any = null; + constructor( protected $analyticsService: IAnalyticsService, private $buildController: IBuildController, @@ -23,6 +25,7 @@ export class RunController extends EventEmitter implements IRunController { private $prepareController: IPrepareController, private $prepareDataService: IPrepareDataService, private $prepareNativePlatformService: IPrepareNativePlatformService, + private $projectChangesService: IProjectChangesService, protected $projectDataService: IProjectDataService ) { super(); @@ -35,8 +38,10 @@ export class RunController extends EventEmitter implements IRunController { const projectData = this.$projectDataService.getProjectData(projectDir); await this.initializeSetup(projectData); - const platforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); const deviceDescriptorsForInitialSync = this.getDeviceDescriptorsForInitialSync(projectDir, deviceDescriptors); + const newPlatforms = this.$devicesService.getPlatformsFromDeviceDescriptors(deviceDescriptors); + const oldPlatforms = this.$liveSyncProcessDataService.getPlatforms(projectDir); + const platforms = _.uniq(_.concat(newPlatforms, oldPlatforms)); this.$liveSyncProcessDataService.persistData(projectDir, deviceDescriptors, platforms); @@ -45,9 +50,21 @@ export class RunController extends EventEmitter implements IRunController { this.$hmrStatusService.attachToHmrStatusEvent(); } - this.$prepareController.on(PREPARE_READY_EVENT_NAME, async data => { - await this.syncChangedDataOnDevices(data, projectData, liveSyncInfo, deviceDescriptors); - }); + if (!this.prepareReadyEventHandler) { + this.prepareReadyEventHandler = async (data: IFilesChangeEventData) => { + if (data.hasNativeChanges) { + const platformData = this.$platformsDataService.getPlatformData(data.platform, projectData); + const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, data.platform, { ...liveSyncInfo, watch: !liveSyncInfo.skipWatcher }); + const changesInfo = await this.$projectChangesService.checkForChanges(platformData, projectData, prepareData); + if (changesInfo.hasChanges) { + await this.syncChangedDataOnDevices(data, projectData, liveSyncInfo); + } + } else { + await this.syncChangedDataOnDevices(data, projectData, liveSyncInfo); + } + }; + this.$prepareController.on(PREPARE_READY_EVENT_NAME, this.prepareReadyEventHandler.bind(this)); + } await this.syncInitialDataOnDevices(projectData, liveSyncInfo, deviceDescriptorsForInitialSync); @@ -58,6 +75,7 @@ export class RunController extends EventEmitter implements IRunController { const { projectDir, deviceIdentifiers, stopOptions } = data; const liveSyncProcessInfo = this.$liveSyncProcessDataService.getPersistedData(projectDir); if (liveSyncProcessInfo && !liveSyncProcessInfo.isStopped) { + // In case we are coming from error during livesync, the current action is the one that erred (but we are still executing it), // so we cannot await it as this will cause infinite loop. const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; @@ -94,6 +112,11 @@ export class RunController extends EventEmitter implements IRunController { liveSyncProcessInfo.deviceDescriptors = []; + if (this.prepareReadyEventHandler) { + this.removeListener(PREPARE_READY_EVENT_NAME, this.prepareReadyEventHandler); + this.prepareReadyEventHandler = null; + } + const projectData = this.$projectDataService.getProjectData(projectDir); await this.$hooksService.executeAfterHooks('watch', { hookArgs: { @@ -313,10 +336,11 @@ export class RunController extends EventEmitter implements IRunController { await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier))); } - private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { + private async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise { const rebuiltInformation: IDictionary<{ packageFilePath: string, platform: string, isEmulator: boolean }> = { }; const deviceAction = async (device: Mobile.IDevice) => { + const deviceDescriptors = this.$liveSyncProcessDataService.getDeviceDescriptors(projectData.projectDir); const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); const platformData = this.$platformsDataService.getPlatformData(data.platform, projectData); const prepareData = this.$prepareDataService.getPrepareData(liveSyncInfo.projectDir, device.deviceInfo.platform, diff --git a/lib/controllers/update-controller-base.ts b/lib/controllers/update-controller-base.ts index ea0c591d39..5451b9d822 100644 --- a/lib/controllers/update-controller-base.ts +++ b/lib/controllers/update-controller-base.ts @@ -2,11 +2,17 @@ import * as path from "path"; import * as semver from "semver"; export class UpdateControllerBase { + protected getPackageManifest: Function; + constructor(protected $fs: IFileSystem, protected $platformCommandHelper: IPlatformCommandHelper, protected $platformsDataService: IPlatformsDataService, protected $packageInstallationManager: IPackageInstallationManager, - protected $packageManager: IPackageManager) { + protected $packageManager: IPackageManager, + protected $pacoteService: IPacoteService) { + this.getPackageManifest = _.memoize(this._getPackageManifest, (...args) => { + return args.join("@"); + }); } protected restoreBackup(folders: string[], backupDir: string, projectDir: string): void { @@ -39,13 +45,13 @@ export class UpdateControllerBase { return (dependencies && dependencies[dependency.packageName]) || (devDependencies && devDependencies[dependency.packageName]); } - protected hasRuntimeDependency({platform, projectData}: {platform: string, projectData: IProjectData}): boolean { + protected hasRuntimeDependency({ platform, projectData }: { platform: string, projectData: IProjectData }): boolean { const lowercasePlatform = platform.toLowerCase(); const currentPlatformVersion = this.$platformCommandHelper.getCurrentPlatformVersion(lowercasePlatform, projectData); return !!currentPlatformVersion; } - protected async getMaxRuntimeVersion({platform, projectData}: {platform: string, projectData: IProjectData}) { + protected async getMaxRuntimeVersion({ platform, projectData }: { platform: string, projectData: IProjectData }) { const lowercasePlatform = platform.toLowerCase(); const currentPlatformVersion = this.$platformCommandHelper.getCurrentPlatformVersion(lowercasePlatform, projectData); const platformData = this.$platformsDataService.getPlatformData(lowercasePlatform, projectData); @@ -66,4 +72,14 @@ export class UpdateControllerBase { return maxDependencyVersion; } + + private async _getPackageManifest(templateName: string, version: string): Promise { + const packageVersion = semver.valid(version) || await this.$packageManager.getTagVersion(templateName, version); + + if (packageVersion && semver.valid(packageVersion)) { + return await this.$pacoteService.manifest(`${templateName}@${packageVersion}`, { fullMetadata: true }); + } else { + throw new Error(`Failed to get information for package: ${templateName}@${version}`); + } + } } diff --git a/lib/controllers/update-controller.ts b/lib/controllers/update-controller.ts index eefccb519f..873451cca0 100644 --- a/lib/controllers/update-controller.ts +++ b/lib/controllers/update-controller.ts @@ -4,6 +4,23 @@ import * as constants from "../constants"; import { UpdateControllerBase } from "./update-controller-base"; export class UpdateController extends UpdateControllerBase implements IUpdateController { + static readonly updatableDependencies: string[] = [ + constants.TNS_CORE_MODULES_NAME, + constants.TNS_CORE_MODULES_WIDGETS_NAME, + constants.WEBPACK_PLUGIN_NAME]; + static readonly folders: string[] = [ + constants.LIB_DIR_NAME, + constants.HOOKS_DIR_NAME, + constants.WEBPACK_CONFIG_NAME, + constants.PACKAGE_JSON_FILE_NAME, + constants.PACKAGE_LOCK_JSON_FILE_NAME + ]; + + static readonly backupFolder: string = ".update_backup"; + static readonly updateFailMessage: string = "Could not update the project!"; + static readonly backupFailMessage: string = "Could not backup project folders!"; + static readonly failedToGetTemplateManifestMessage = "Failed to get template information for the specified version. Original error: %s"; + constructor( protected $fs: IFileSystem, protected $platformsDataService: IPlatformsDataService, @@ -13,26 +30,12 @@ export class UpdateController extends UpdateControllerBase implements IUpdateCon private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $addPlatformService: IAddPlatformService, private $logger: ILogger, + private $errors: IErrors, private $pluginsService: IPluginsService, - private $pacoteService: IPacoteService, + protected $pacoteService: IPacoteService, private $projectDataService: IProjectDataService) { - super($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager); - this.getTemplateManifest = _.memoize(this._getTemplateManifest, (...args) => { - return args.join("@"); - }); + super($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager, $pacoteService); } - private getTemplateManifest: Function; - static readonly folders: string[] = [ - constants.LIB_DIR_NAME, - constants.HOOKS_DIR_NAME, - constants.WEBPACK_CONFIG_NAME, - constants.PACKAGE_JSON_FILE_NAME, - constants.PACKAGE_LOCK_JSON_FILE_NAME - ]; - - static readonly backupFolder: string = ".update_backup"; - static readonly updateFailMessage: string = "Could not update the project!"; - static readonly backupFailMessage: string = "Could not backup project folders!"; public async update(updateOptions: IUpdateOptions): Promise { const projectData = this.$projectDataService.getProjectData(updateOptions.projectDir); @@ -55,16 +58,15 @@ export class UpdateController extends UpdateControllerBase implements IUpdateCon } } - public async shouldUpdate({projectDir, version}: {projectDir: string, version?: string}): Promise { + public async shouldUpdate({ projectDir, version }: { projectDir: string, version?: string }): Promise { const projectData = this.$projectDataService.getProjectData(projectDir); - const templateName = this.getTemplateName(projectData); - const templateManifest = await this.getTemplateManifest(templateName, version); - const dependencies = templateManifest.dependencies; - const devDependencies = templateManifest.devDependencies; + const templateManifest = await this.getTemplateManifest(projectData, version); + const dependencies = this.getUpdatableDependencies(templateManifest.dependencies); + const devDependencies = this.getUpdatableDependencies(templateManifest.devDependencies); if ( - await this.hasDependenciesToUpdate({dependencies, areDev: false, projectData}) || - await this.hasDependenciesToUpdate({dependencies: devDependencies, areDev: true, projectData}) + await this.hasDependenciesToUpdate({ dependencies, areDev: false, projectData }) || + await this.hasDependenciesToUpdate({ dependencies: devDependencies, areDev: true, projectData }) ) { return true; } @@ -91,14 +93,15 @@ export class UpdateController extends UpdateControllerBase implements IUpdateCon } private async updateProject(projectData: IProjectData, version: string): Promise { - const templateName = this.getTemplateName(projectData); - const templateManifest = await this.getTemplateManifest(templateName, version); + const templateManifest = await this.getTemplateManifest(projectData, version); + const dependencies = this.getUpdatableDependencies(templateManifest.dependencies); + const devDependencies = this.getUpdatableDependencies(templateManifest.devDependencies); this.$logger.info("Start updating dependencies."); - await this.updateDependencies({ dependencies: templateManifest.dependencies, areDev: false, projectData}); + await this.updateDependencies({ dependencies, areDev: false, projectData }); this.$logger.info("Finished updating dependencies."); this.$logger.info("Start updating devDependencies."); - await this.updateDependencies({ dependencies: templateManifest.devDependencies, areDev: true, projectData}); + await this.updateDependencies({ dependencies: devDependencies, areDev: true, projectData }); this.$logger.info("Finished updating devDependencies."); this.$logger.info("Start updating runtimes."); @@ -114,7 +117,7 @@ export class UpdateController extends UpdateControllerBase implements IUpdateCon }); } - private async updateDependencies( {dependencies, areDev, projectData} : {dependencies: IDictionary, areDev: boolean, projectData: IProjectData}) { + private async updateDependencies({ dependencies, areDev, projectData }: { dependencies: IDictionary, areDev: boolean, projectData: IProjectData }) { for (const dependency in dependencies) { const templateVersion = dependencies[dependency]; if (!this.hasDependency({ packageName: dependency, isDev: areDev }, projectData)) { @@ -134,11 +137,10 @@ export class UpdateController extends UpdateControllerBase implements IUpdateCon const projectVersion = dependencies[dependency] || devDependencies[dependency]; const maxSatisfyingTargetVersion = await this.getMaxDependencyVersion(dependency, targetVersion); const maxSatisfyingProjectVersion = await this.getMaxDependencyVersion(dependency, projectVersion); - return maxSatisfyingProjectVersion && maxSatisfyingTargetVersion && semver.gt(maxSatisfyingTargetVersion, maxSatisfyingProjectVersion); } - private async hasDependenciesToUpdate({dependencies, areDev, projectData}: {dependencies: IDictionary, areDev: boolean, projectData:IProjectData}) { + private async hasDependenciesToUpdate({ dependencies, areDev, projectData }: { dependencies: IDictionary, areDev: boolean, projectData: IProjectData }) { for (const dependency in dependencies) { const templateVersion = dependencies[dependency]; if (!this.hasDependency({ packageName: dependency, isDev: areDev }, projectData)) { @@ -165,24 +167,22 @@ export class UpdateController extends UpdateControllerBase implements IUpdateCon } private async shouldUpdateRuntimeVersion(templateRuntimeVersion: string, frameworkPackageName: string, platform: string, projectData: IProjectData): Promise { - const hasRuntimeDependency = this.hasRuntimeDependency({platform, projectData}); + const hasRuntimeDependency = this.hasRuntimeDependency({ platform, projectData }); if (!hasRuntimeDependency) { return false; } const maxTemplateRuntimeVersion = await this.getMaxDependencyVersion(frameworkPackageName, templateRuntimeVersion); - const maxRuntimeVersion = await this.getMaxRuntimeVersion({platform, projectData}); + const maxRuntimeVersion = await this.getMaxRuntimeVersion({ platform, projectData }); return maxTemplateRuntimeVersion && maxRuntimeVersion && semver.gt(maxTemplateRuntimeVersion, maxRuntimeVersion); } - private async _getTemplateManifest(templateName: string, version: string) { - let packageVersion = version ? version : await this.$packageInstallationManager.getLatestCompatibleVersionSafe(templateName); - packageVersion = semver.valid(version) ? version : await this.$packageManager.getTagVersion(templateName, packageVersion); - packageVersion = packageVersion ? packageVersion : await this.$packageInstallationManager.getLatestCompatibleVersionSafe(templateName); - - return await this.$pacoteService.manifest(`${templateName}@${packageVersion}`, { fullMetadata: true }); + private getUpdatableDependencies(dependencies: IDictionary): IDictionary { + return _.pickBy(dependencies, (value, key) => { + return UpdateController.updatableDependencies.indexOf(key) > -1; + }); } private getTemplateName(projectData: IProjectData) { @@ -208,6 +208,19 @@ export class UpdateController extends UpdateControllerBase implements IUpdateCon return template; } + + private async getTemplateManifest(projectData: IProjectData, version: string): Promise { + let templateManifest; + const templateName = this.getTemplateName(projectData); + version = version || await this.$packageInstallationManager.getLatestCompatibleVersionSafe(templateName); + try { + templateManifest = await this.getPackageManifest(templateName, version); + } catch (err) { + this.$errors.fail(UpdateController.failedToGetTemplateManifestMessage, err.message); + } + + return templateManifest; + } } $injector.register("updateController", UpdateController); diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index eed7ccdcf7..389e2c17b7 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -603,7 +603,7 @@ interface IInfoService { interface IAndroidResourcesMigrationService { canMigrate(platformString: string): boolean; hasMigrated(appResourcesDir: string): boolean; - migrate(appResourcesDir: string): Promise; + migrate(appResourcesDir: string, backupLocation?: string): Promise; } /** @@ -1021,6 +1021,11 @@ interface IPlatformValidationService { */ validatePlatform(platform: string, projectData: IProjectData): void; + /** + * Returns whether the passed platform is a valid one (from the supported ones) + */ + isValidPlatform(platform: string, projectData: IProjectData): boolean; + /** * Gets first chance to validate the options provided as command line arguments. * If no platform is provided or a falsy (null, undefined, "", false...) platform is provided, diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index b692609ccb..e4ac2c0d99 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -492,5 +492,6 @@ declare global { getAllPersistedData(): IDictionary; persistData(projectDir: string, deviceDescriptors: ILiveSyncDeviceDescriptor[], platforms: string[]): void; hasDeviceDescriptors(projectDir: string): boolean; + getPlatforms(projectDir: string): string[]; } } diff --git a/lib/definitions/migrate.d.ts b/lib/definitions/migrate.d.ts index 418364c9b8..1931d4fc88 100644 --- a/lib/definitions/migrate.d.ts +++ b/lib/definitions/migrate.d.ts @@ -1,6 +1,12 @@ interface IMigrateController { - migrate(migrateData: IProjectDir): Promise; - shouldMigrate(data: IProjectDir): Promise; + migrate(data: IMigrationData): Promise; + shouldMigrate(data: IMigrationData): Promise; + validate(data: IMigrationData): Promise; +} + +interface IMigrationData extends IProjectDir { + platforms: string[]; + allowInvalidVersions?: boolean; } interface IDependency { @@ -11,8 +17,10 @@ interface IDependency { interface IMigrationDependency extends IDependency { shouldRemove?: boolean; replaceWith?: string; + warning?: string; verifiedVersion?: string; + getVerifiedVersion?: (projectData: IProjectData) => Promise; shouldAddIfMissing?: boolean; - shouldMigrateAction?: (projectData: IProjectData) => boolean; + shouldMigrateAction?: (projectData: IProjectData, allowInvalidVersions: boolean) => Promise; migrateAction?: (projectData: IProjectData, migrationBackupDirPath: string) => Promise; } \ No newline at end of file diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 601017812b..56c9e23ee4 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -74,7 +74,6 @@ interface INsConfig { appPath?: string; appResourcesPath?: string; shared?: boolean; - useLegacyWorkflow?: boolean; previewAppSchema?: string; } @@ -101,11 +100,6 @@ interface IProjectData extends ICreateProjectData { */ isShared: boolean; - /** - * Defines if the project has hmr enabled by default - */ - useLegacyWorkflow: boolean; - /** * Defines the schema for the preview app */ @@ -150,6 +144,14 @@ interface IProjectDataService { */ removeNSProperty(projectDir: string, propertyName: string): void; + /** + * Removes a property from `nsconfig.json`. + * @param {string} projectDir The project directory - the place where the `nsconfig.json` is located. + * @param {string} propertyName The name of the property to be removed. + * @returns {void} + */ + removeNSConfigProperty(projectDir: string, propertyName: string): void; + /** * Removes dependency from package.json * @param {string} projectDir The project directory - the place where the root package.json is located. @@ -192,12 +194,12 @@ interface IProjectDataService { */ getAppExecutableFiles(projectDir: string): string[]; - /** - * Returns a value from `nativescript` key in project's package.json. - * @param {string} jsonData The project directory - the place where the root package.json is located. - * @param {string} propertyName The name of the property to be checked in `nativescript` key. - * @returns {any} The value of the property. - */ + /** + * Returns a value from `nativescript` key in project's package.json. + * @param {string} jsonData The project directory - the place where the root package.json is located. + * @param {string} propertyName The name of the property to be checked in `nativescript` key. + * @returns {any} The value of the property. + */ getNSValueFromContent(jsonData: Object, propertyName: string): any; } @@ -496,7 +498,7 @@ interface IRemoveExtensionsOptions { pbxProjPath: string } -interface IRemoveWatchAppOptions extends IRemoveExtensionsOptions{} +interface IRemoveWatchAppOptions extends IRemoveExtensionsOptions { } interface IRubyFunction { functionName: string; diff --git a/lib/definitions/run.d.ts b/lib/definitions/run.d.ts index 3dfe93169a..f0b9091065 100644 --- a/lib/definitions/run.d.ts +++ b/lib/definitions/run.d.ts @@ -8,7 +8,6 @@ declare global { } interface IDeployData { - buildData: IBuildData; deviceDescriptors: ILiveSyncDeviceDescriptor[]; } diff --git a/lib/helpers/bundle-validator-helper.ts b/lib/helpers/bundle-validator-helper.ts deleted file mode 100644 index 485424f30a..0000000000 --- a/lib/helpers/bundle-validator-helper.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as util from "util"; -import { BundleValidatorMessages } from "../constants"; -import { VersionValidatorHelper } from "./version-validator-helper"; -import { WEBPACK_PLUGIN_NAME } from "../constants"; - -export class BundleValidatorHelper extends VersionValidatorHelper implements IBundleValidatorHelper { - private bundlersMap: IStringDictionary = { - webpack: WEBPACK_PLUGIN_NAME - }; - - constructor(protected $errors: IErrors, - protected $options: IOptions) { - super(); - } - - public validate(projectData: IProjectData, minSupportedVersion?: string): void { - const currentVersion = this.getBundlerDependencyVersion(projectData); - if (!currentVersion) { - this.$errors.failWithoutHelp(BundleValidatorMessages.MissingBundlePlugin); - } - - const shouldThrowError = minSupportedVersion && this.isValidVersion(currentVersion) && this.isVersionLowerThan(currentVersion, minSupportedVersion); - if (shouldThrowError) { - this.$errors.failWithoutHelp(util.format(BundleValidatorMessages.NotSupportedVersion, minSupportedVersion)); - } - } - - public getBundlerDependencyVersion(projectData: IProjectData, bundlerName?: string): string { - let dependencyVersion = null; - const bundlePluginName = bundlerName || this.bundlersMap[this.$options.bundle]; - const bundlerVersionInDependencies = projectData.dependencies && projectData.dependencies[bundlePluginName]; - const bundlerVersionInDevDependencies = projectData.devDependencies && projectData.devDependencies[bundlePluginName]; - dependencyVersion = bundlerVersionInDependencies || bundlerVersionInDevDependencies; - - return dependencyVersion; - - } -} - -$injector.register("bundleValidatorHelper", BundleValidatorHelper); diff --git a/lib/helpers/deploy-command-helper.ts b/lib/helpers/deploy-command-helper.ts index ffc6ac0a89..2ebdeaed58 100644 --- a/lib/helpers/deploy-command-helper.ts +++ b/lib/helpers/deploy-command-helper.ts @@ -32,11 +32,18 @@ export class DeployCommandHelper { projectDir: this.$projectData.projectDir }); - const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, { ...this.$options, outputPath, buildForDevice: !d.isEmulator }); + const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, + { + ...this.$options.argv, + outputPath, + buildForDevice: !d.isEmulator, + skipWatcher: !this.$options.watch, + nativePrepare: { skipNativePrepare: additionalOptions && additionalOptions.skipNativePrepare } + }); const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildData, this.$projectData) : - this.$buildController.prepareAndBuild.bind(this.$buildController, d.deviceInfo.platform, buildData, this.$projectData); + this.$buildController.build.bind(this.$buildController, buildData); const info: ILiveSyncDeviceDescriptor = { identifier: d.deviceInfo.identifier, @@ -50,10 +57,7 @@ export class DeployCommandHelper { return info; }); - await this.$deployController.deploy({ - buildData: this.$buildDataService.getBuildData(this.$projectData.projectDir, platform, { ...this.$options.argv, skipWatcher: !this.$options.watch }), - deviceDescriptors - }); + await this.$deployController.deploy({ deviceDescriptors }); } } $injector.register("deployCommandHelper", DeployCommandHelper); diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index ac58ac0976..f895783f2f 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -2,8 +2,6 @@ import { RunOnDeviceEvents } from "../constants"; import { DeployController } from "../controllers/deploy-controller"; export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { - public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0"; - constructor( private $buildDataService: IBuildDataService, private $projectData: IProjectData, @@ -15,7 +13,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $injector: IInjector, private $buildController: IBuildController, private $analyticsService: IAnalyticsService, - private $bundleValidatorHelper: IBundleValidatorHelper, private $errors: IErrors, private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, private $cleanupService: ICleanupService, @@ -68,7 +65,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { projectDir: this.$projectData.projectDir }); - const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, { ...this.$options.argv, outputPath, buildForDevice: !d.isEmulator }); + const buildData = this.$buildDataService.getBuildData(this.$projectData.projectDir, d.deviceInfo.platform, { ...this.$options.argv, outputPath, buildForDevice: !d.isEmulator, watch: !this.$options.release && this.$options.watch }); const buildAction = additionalOptions && additionalOptions.buildPlatform ? additionalOptions.buildPlatform.bind(additionalOptions.buildPlatform, d.deviceInfo.platform, buildData, this.$projectData) : @@ -86,7 +83,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return info; }); - return deviceDescriptors; + return deviceDescriptors; } public getPlatformsForOperation(platform: string): string[] { @@ -103,7 +100,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const { liveSyncInfo, deviceDescriptors } = await this.executeLiveSyncOperationCore(devices, platform, additionalOptions); if (this.$options.release) { - await this.runInRelease(platform, deviceDescriptors, liveSyncInfo); + await this.runInRelease(platform, deviceDescriptors); return; } @@ -133,13 +130,10 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { result[availablePlatform.toLowerCase()] = validateOutput; } - const minSupportedWebpackVersion = this.$options.hmr ? LiveSyncCommandHelper.MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR : null; - this.$bundleValidatorHelper.validate(this.$projectData, minSupportedWebpackVersion); - return result; } - private async executeLiveSyncOperationCore(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise<{liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[]}> { + private async executeLiveSyncOperationCore(devices: Mobile.IDevice[], platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise<{ liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceDescriptor[] }> { if (!devices || !devices.length) { if (platform) { this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation. Ensure connected devices are trusted and try again."); @@ -166,7 +160,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return { liveSyncInfo, deviceDescriptors }; } - private async runInRelease(platform: string, deviceDescriptors: ILiveSyncDeviceDescriptor[], liveSyncInfo: ILiveSyncInfo): Promise { + private async runInRelease(platform: string, deviceDescriptors: ILiveSyncDeviceDescriptor[]): Promise { await this.$devicesService.initialize({ platform, deviceId: this.$options.device, @@ -175,12 +169,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { sdk: this.$options.sdk }); - const buildData = this.$buildDataService.getBuildData(liveSyncInfo.projectDir, platform, { ...this.$options.argv, clean: true, watch: false }); - - await this.$deployController.deploy({ - buildData, - deviceDescriptors - }); + await this.$deployController.deploy({ deviceDescriptors }); for (const deviceDescriptor of deviceDescriptors) { const device = this.$devicesService.getDeviceByIdentifier(deviceDescriptor.identifier); diff --git a/lib/package-manager.ts b/lib/package-manager.ts index 9359699f41..391825edaf 100644 --- a/lib/package-manager.ts +++ b/lib/package-manager.ts @@ -72,6 +72,9 @@ export class PackageManager implements IPackageManager { public async getTagVersion(packageName: string, tag: string): Promise { let version: string = null; + if (!tag) { + return null; + } try { const result = await this.view(packageName, { "dist-tags": true }); diff --git a/lib/project-data.ts b/lib/project-data.ts index 538651f0db..935d1a497e 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -61,7 +61,6 @@ export class ProjectData implements IProjectData { public buildXcconfigPath: string; public podfilePath: string; public isShared: boolean; - public useLegacyWorkflow: boolean; public previewAppSchema: string; constructor(private $fs: IFileSystem, @@ -137,7 +136,6 @@ export class ProjectData implements IProjectData { this.buildXcconfigPath = path.join(this.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, constants.BUILD_XCCONFIG_FILE_NAME); this.podfilePath = path.join(this.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, constants.PODFILE_NAME); this.isShared = !!(this.nsConfig && this.nsConfig.shared); - this.useLegacyWorkflow = this.nsConfig && this.nsConfig.useLegacyWorkflow; this.previewAppSchema = this.nsConfig && this.nsConfig.previewAppSchema; return; } diff --git a/lib/services/android-resources-migration-service.ts b/lib/services/android-resources-migration-service.ts index a565bacc22..6167154e53 100644 --- a/lib/services/android-resources-migration-service.ts +++ b/lib/services/android-resources-migration-service.ts @@ -20,10 +20,10 @@ export class AndroidResourcesMigrationService implements IAndroidResourcesMigrat return this.$fs.exists(path.join(appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR, constants.SRC_DIR, constants.MAIN_DIR)); } - public async migrate(appResourcesDir: string): Promise { + public async migrate(appResourcesDir: string, backupLocation?: 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); + const appResourcesBackup = path.join(backupLocation || appResourcesDir, AndroidResourcesMigrationService.ANDROID_DIR_OLD); try { await this.tryMigrate(originalAppResources, appResourcesDestination, appResourcesBackup); diff --git a/lib/services/ios-log-filter.ts b/lib/services/ios-log-filter.ts index 4201ea6165..dfc0a7578c 100644 --- a/lib/services/ios-log-filter.ts +++ b/lib/services/ios-log-filter.ts @@ -13,13 +13,11 @@ export class IOSLogFilter implements Mobile.IPlatformLogFilter { private partialLine: string = null; - constructor(private $logger: ILogger, - private $loggingLevels: Mobile.ILoggingLevels) { + constructor(private $loggingLevels: Mobile.ILoggingLevels) { } public filterData(data: string, loggingOptions: Mobile.IDeviceLogOptions = {}): string { const specifiedLogLevel = (loggingOptions.logLevel || '').toUpperCase(); - this.$logger.trace("Logging options", loggingOptions); if (specifiedLogLevel !== this.$loggingLevels.info || !data) { return data; diff --git a/lib/services/livesync-process-data-service.ts b/lib/services/livesync-process-data-service.ts index 6edd76ef88..9975bdb6d2 100644 --- a/lib/services/livesync-process-data-service.ts +++ b/lib/services/livesync-process-data-service.ts @@ -30,5 +30,11 @@ export class LiveSyncProcessDataService implements ILiveSyncProcessDataService { public getAllPersistedData() { return this.processes; } + + public getPlatforms(projectDir: string): string[] { + const liveSyncProcessesInfo = this.processes[projectDir] || {}; + const currentPlatforms = liveSyncProcessesInfo.platforms; + return currentPlatforms || []; + } } $injector.register("liveSyncProcessDataService", LiveSyncProcessDataService); diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index fddd52e561..92162beeb2 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -32,7 +32,9 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I const tempZip = temp.path({ prefix: "sync", suffix: ".zip" }); this.$logger.trace("Creating zip file: " + tempZip); - await this.$fs.zipFiles(tempZip, this.$fs.enumerateFilesInDirectorySync(projectFilesPath), (res) => { + const filesToTransfer = this.$fs.enumerateFilesInDirectorySync(projectFilesPath); + + await this.$fs.zipFiles(tempZip, filesToTransfer, (res) => { return path.join(APP_FOLDER_NAME, path.relative(projectFilesPath, res)); }); @@ -43,6 +45,8 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I deviceProjectRootPath: await deviceAppData.getDeviceProjectRootPath() }]); + await deviceAppData.device.applicationManager.setTransferredAppFiles(filesToTransfer); + return { deviceAppData, isFullSync: true, diff --git a/lib/services/platform/add-platform-service.ts b/lib/services/platform/add-platform-service.ts index 0d6ad7438e..9185127290 100644 --- a/lib/services/platform/add-platform-service.ts +++ b/lib/services/platform/add-platform-service.ts @@ -1,13 +1,12 @@ import * as path from "path"; import * as temp from "temp"; -import { PROJECT_FRAMEWORK_FOLDER_NAME, NativePlatformStatus } from "../../constants"; +import { PROJECT_FRAMEWORK_FOLDER_NAME } from "../../constants"; import { performanceLog } from "../../common/decorators"; export class AddPlatformService implements IAddPlatformService { constructor( private $fs: IFileSystem, private $pacoteService: IPacoteService, - private $projectChangesService: IProjectChangesService, private $projectDataService: IProjectDataService, private $terminalSpinnerService: ITerminalSpinnerService ) { } @@ -61,7 +60,6 @@ export class AddPlatformService implements IAddPlatformService { platformData.platformProjectService.ensureConfigurationFileInAppResources(projectData); await platformData.platformProjectService.interpolateData(projectData); platformData.platformProjectService.afterCreateProject(platformData.projectRoot, projectData); - await this.$projectChangesService.setNativePlatformStatus(platformData, projectData, { nativePlatformStatus: NativePlatformStatus.requiresPrepare }); } } $injector.register("addPlatformService", AddPlatformService); diff --git a/lib/services/platform/platform-validation-service.ts b/lib/services/platform/platform-validation-service.ts index 6648803b51..2ac7981c9d 100644 --- a/lib/services/platform/platform-validation-service.ts +++ b/lib/services/platform/platform-validation-service.ts @@ -11,14 +11,25 @@ export class PlatformValidationService implements IPlatformValidationService { private $platformsDataService: IPlatformsDataService ) { } - public validatePlatform(platform: string, projectData: IProjectData): void { + public isValidPlatform(platform: string, projectData: IProjectData): boolean { if (!platform) { - this.$errors.fail("No platform specified."); + return false; } platform = platform.split("@")[0].toLowerCase(); - if (!this.$platformsDataService.getPlatformData(platform, projectData)) { + return false; + } + + return true; + } + + public validatePlatform(platform: string, projectData: IProjectData): void { + if (!platform) { + this.$errors.fail("No platform specified."); + } + + if (!this.isValidPlatform(platform, projectData)) { const platformNames = helpers.formatListOfNames(this.$mobileHelper.platformNames); this.$errors.fail(`Invalid platform ${platform}. Valid platforms are ${platformNames}.`); } diff --git a/lib/services/platform/prepare-native-platform-service.ts b/lib/services/platform/prepare-native-platform-service.ts index 6b3549cb3f..1a9276beee 100644 --- a/lib/services/platform/prepare-native-platform-service.ts +++ b/lib/services/platform/prepare-native-platform-service.ts @@ -15,12 +15,11 @@ export class PrepareNativePlatformService implements IPrepareNativePlatformServi @hook('prepareNativeApp') public async prepareNativePlatform(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { const { nativePrepare, release } = prepareData; + const changesInfo = await this.$projectChangesService.checkForChanges(platformData, projectData, prepareData); if (nativePrepare && nativePrepare.skipNativePrepare) { - return false; + return changesInfo.hasChanges; } - const changesInfo = await this.$projectChangesService.checkForChanges(platformData, projectData, prepareData); - const hasNativeModulesChange = !changesInfo || changesInfo.nativeChanged; const hasConfigChange = !changesInfo || changesInfo.configChanged; const hasChangesRequirePrepare = !changesInfo || changesInfo.changesRequirePrepare; diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index c539525cc7..55ce723b97 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -1,7 +1,6 @@ import * as path from "path"; import { NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME, PLATFORMS_DIR_NAME } from "../constants"; import { getHash, hook } from "../common/helpers"; -import { PrepareData } from "../data/prepare-data"; const prepareInfoFileName = ".nsprepareinfo"; @@ -51,7 +50,7 @@ export class ProjectChangesService implements IProjectChangesService { } @hook("checkForChanges") - public async checkForChanges(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { + public async checkForChanges(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { this._changesInfo = new ProjectChangesInfo(); const isNewPrepareInfo = await this.ensurePrepareInfo(platformData, projectData, prepareData); if (!isNewPrepareInfo) { @@ -92,7 +91,7 @@ export class ProjectChangesService implements IProjectChangesService { await platformData.platformProjectService.checkForChanges(this._changesInfo, prepareData, projectData); } - if (prepareData.release !== this._prepareInfo.release) { + if (!!prepareData.release !== !!this._prepareInfo.release) { this.$logger.trace(`Setting all setting to true. Current options are: `, prepareData, " old prepare info is: ", this._prepareInfo); this._changesInfo.appResourcesChanged = true; this._changesInfo.configChanged = true; @@ -158,7 +157,7 @@ export class ProjectChangesService implements IProjectChangesService { await this.savePrepareInfo(platformData, projectData, null); } - private async ensurePrepareInfo(platformData: IPlatformData, projectData: IProjectData, prepareData: PrepareData): Promise { + private async ensurePrepareInfo(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { this._prepareInfo = this.getPrepareInfo(platformData); if (this._prepareInfo) { const prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName); diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index 71788a5ffb..b9edeb98f0 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -1,5 +1,7 @@ import * as path from "path"; import { ProjectData } from "../project-data"; +import * as constants from "../constants"; +import { parseJson } from "../common/helpers"; import { exported } from "../common/decorators"; import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, @@ -125,6 +127,12 @@ export class ProjectDataService implements IProjectDataService { }; } + public removeNSConfigProperty(projectDir: string, propertyName: string): void { + this.$logger.trace(`Removing "${propertyName}" property from nsconfig.`); + this.updateNsConfigValue(projectDir, null, [propertyName]); + this.$logger.trace(`"${propertyName}" property successfully removed.`); + } + @exported("projectDataService") public async getAndroidAssetsStructure(opts: IProjectDir): Promise { // TODO: Use image-size package to get the width and height of an image. @@ -180,6 +188,43 @@ export class ProjectDataService implements IProjectDataService { return files; } + private refreshProjectData(projectDir: string) { + if (this.projectDataCache[projectDir]) { + this.projectDataCache[projectDir].initializeProjectData(projectDir); + } + } + + private updateNsConfigValue(projectDir: string, updateObject?: INsConfig, propertiesToRemove?: string[]): void { + const nsConfigPath = path.join(projectDir, constants.CONFIG_NS_FILE_NAME); + const currentNsConfig = this.getNsConfig(nsConfigPath); + let newNsConfig = currentNsConfig; + if (updateObject) { + newNsConfig = _.assign(newNsConfig || this.getNsConfigDefaultObject(), updateObject); + } + + if (newNsConfig && propertiesToRemove && propertiesToRemove.length) { + newNsConfig = _.omit(newNsConfig, propertiesToRemove); + } + + if (newNsConfig) { + this.$fs.writeJson(nsConfigPath, newNsConfig); + this.refreshProjectData(projectDir); + } + } + + private getNsConfig(nsConfigPath: string): INsConfig { + let result: INsConfig = null; + if (this.$fs.exists(nsConfigPath)) { + const nsConfigContent = this.$fs.readText(nsConfigPath); + try { + result = parseJson(nsConfigContent); + } catch (e) { + this.$logger.trace("The `nsconfig` content is not a valid JSON. Parse error: ", e); + } + } + + return result; + } private getImageDefinitions(): IImageDefinitionsStructure { const pathToImageDefinitions = path.join(__dirname, "..", "..", CLI_RESOURCES_DIR_NAME, AssetConstants.assets, AssetConstants.imageDefinitionsFileName); @@ -334,7 +379,7 @@ export class ProjectDataService implements IProjectDataService { } private getNsConfigDefaultObject(data?: Object): INsConfig { - const config: INsConfig = { useLegacyWorkflow: false }; + const config: INsConfig = {}; Object.assign(config, data); return config; diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index f795aaa72d..58110df109 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -2,7 +2,6 @@ import * as path from "path"; import * as child_process from "child_process"; import { EventEmitter } from "events"; import { performanceLog } from "../../common/decorators"; -import { hook } from "../../common/helpers"; import { WEBPACK_COMPILATION_COMPLETE } from "../../constants"; export class WebpackCompilerService extends EventEmitter implements IWebpackCompilerService { @@ -13,7 +12,6 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp public $hooksService: IHooksService, public $hostInfo: IHostInfo, private $logger: ILogger, - private $pluginsService: IPluginsService, private $mobileHelper: Mobile.IMobileHelper, private $cleanupService: ICleanupService ) { super(); } @@ -129,13 +127,10 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp } @performanceLog() - @hook('prepareJSApp') private async startWebpackProcess(platformData: IPlatformData, projectData: IProjectData, prepareData: IPrepareData): Promise { const envData = this.buildEnvData(platformData.platformNameLowerCase, projectData, prepareData); const envParams = this.buildEnvCommandLineParams(envData, platformData, prepareData); - await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); - const args = [ path.join(projectData.projectDir, "node_modules", "webpack", "bin", "webpack.js"), "--preserve-symlinks", diff --git a/test/helpers/bundle-validator-helper.ts b/test/helpers/bundle-validator-helper.ts deleted file mode 100644 index 3feee9c4c2..0000000000 --- a/test/helpers/bundle-validator-helper.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Yok } from "../../lib/common/yok"; -import { BundleValidatorHelper } from "../../lib/helpers/bundle-validator-helper"; -import { assert } from "chai"; -import { format } from "util"; -import { BundleValidatorMessages } from "../../lib/constants"; - -interface ITestCase { - name: string; - isDependency?: boolean; - currentWebpackVersion?: string; - minSupportedWebpackVersion?: string; - expectedError?: string; -} - -let error: string = null; - -function createTestInjector(options: { dependencies?: IStringDictionary, devDependencies?: IStringDictionary }) { - const testInjector = new Yok(); - testInjector.register("projectData", { - initializeProjectData: () => ({}), - dependencies: options.dependencies, - devDependencies: options.devDependencies - }); - testInjector.register("errors", { - fail: (err: string) => error = err, - failWithoutHelp: (err: string) => error = err - }); - testInjector.register("options", ({ bundle: "webpack" })); - testInjector.register("bundleValidatorHelper", BundleValidatorHelper); - - return testInjector; -} - -describe("BundleValidatorHelper", () => { - beforeEach(() => error = null); - - let testCases: ITestCase[] = [ - { - name: "should throw an error when no webpack plugin is installed", - expectedError: BundleValidatorMessages.MissingBundlePlugin - } - ]; - - ["dependencies", "devDependencies"] - .forEach(key => { - const isDependency = key === "dependencies"; - - testCases = testCases.concat([ - { - name: `should not throw an error when webpack is added as ${key}`, - isDependency, - currentWebpackVersion: "0.12.0", - expectedError: null - }, - { - name: `should not throw an error when webpack's version is greater than minSupportedVersion when webpack is installed as ${key}`, - isDependency, - currentWebpackVersion: "0.13.1", - minSupportedWebpackVersion: "0.13.0", - expectedError: null - }, - { - name: `should not throw an error when webpack's version is equal to minSupportedVersion when webpack is installed as ${key}`, - isDependency, - currentWebpackVersion: "0.10.0", - minSupportedWebpackVersion: "0.10.0", - expectedError: null - }, - { - name: `should throw an error when webpack's version is lower than minSupportedVersion when webpack is installed as ${key}`, - isDependency, - currentWebpackVersion: "0.17.0", - minSupportedWebpackVersion: "0.18.0", - expectedError: format(BundleValidatorMessages.NotSupportedVersion, "0.18.0") - }, - { - name: `should not throw an error when prerelease version of webpack is installed as ${key}`, - isDependency, - currentWebpackVersion: "0.17.0-2018-09-28-173604-01", - minSupportedWebpackVersion: "0.17.0", - expectedError: null - }, - { - name: `should not throw an error when next version of webpack is installed as ${key}`, - isDependency, - currentWebpackVersion: "next", - minSupportedWebpackVersion: "0.17.0", - expectedError: null - } - ]); - }); - - _.each(testCases, (testCase: any) => { - const deps = { - "nativescript-dev-webpack": testCase.currentWebpackVersion - }; - - it(`${testCase.name}`, async () => { - const injector = createTestInjector({ dependencies: testCase.isDependency ? deps : null, devDependencies: !testCase.isDependency ? deps : null }); - const bundleValidatorHelper = injector.resolve("bundleValidatorHelper"); - const projectData = injector.resolve("projectData"); - bundleValidatorHelper.validate(projectData, testCase.minSupportedWebpackVersion); - - assert.deepEqual(error, testCase.expectedError); - }); - }); -}); diff --git a/test/options.ts b/test/options.ts index 0e21876dd5..e45bdbcb9d 100644 --- a/test/options.ts +++ b/test/options.ts @@ -263,73 +263,6 @@ describe("options", () => { }); describe("setupOptions", () => { - const testCases = [ - { - name: "no options are provided", - args: [], - data: [ - { useLegacyWorkflow: undefined, expectedHmr: true, expectedBundle: true }, - { useLegacyWorkflow: false, expectedHmr: true, expectedBundle: true }, - { useLegacyWorkflow: true, expectedHmr: true, expectedBundle: true } - ] - }, - { - name: " --hmr is provided", - args: ["--hmr"], - data: [ - { useLegacyWorkflow: undefined, expectedHmr: true, expectedBundle: true }, - { useLegacyWorkflow: false, expectedHmr: true, expectedBundle: true }, - { useLegacyWorkflow: true, expectedHmr: true, expectedBundle: true } - ] - }, - { - name: " --no-hmr is provided", - args: ["--no-hmr"], - data: [ - { useLegacyWorkflow: undefined, expectedHmr: false, expectedBundle: true }, - { useLegacyWorkflow: false, expectedHmr: false, expectedBundle: true }, - { useLegacyWorkflow: true, expectedHmr: false, expectedBundle: true } - ] - }, - { - name: " --bundle is provided", - args: ["--bundle"], - data: [ - { useLegacyWorkflow: undefined, expectedHmr: true, expectedBundle: true }, - { useLegacyWorkflow: false, expectedHmr: true, expectedBundle: true }, - { useLegacyWorkflow: true, expectedHmr: true, expectedBundle: true } - ] - }, - { - name: " --release is provided", - args: ["--release"], - data: [ - { useLegacyWorkflow: undefined, expectedHmr: false, expectedBundle: true }, - { useLegacyWorkflow: false, expectedHmr: false, expectedBundle: true }, - { useLegacyWorkflow: true, expectedHmr: false, expectedBundle: true } - ] - } - ]; - - _.each([undefined, false, true], useLegacyWorkflow => { - _.each(testCases, testCase => { - it(`should pass correctly when ${testCase.name} and useLegacyWorkflow is ${useLegacyWorkflow}`, () => { - (testCase.args || []).forEach(arg => process.argv.push(arg)); - - const options: any = createOptions(testInjector); - const projectData = { useLegacyWorkflow }; - options.setupOptions(projectData); - - (testCase.args || []).forEach(arg => process.argv.pop()); - - const data = testCase.data.find(item => item.useLegacyWorkflow === useLegacyWorkflow); - - assert.equal(!!options.argv.hmr, !!data.expectedHmr); - assert.equal(!!options.argv.bundle, !!data.expectedBundle); - }); - }); - }); - const testCasesExpectingToThrow = [ { name: "--release --hmr", diff --git a/test/services/project-data-service.ts b/test/services/project-data-service.ts index 98c304e431..2499c4b280 100644 --- a/test/services/project-data-service.ts +++ b/test/services/project-data-service.ts @@ -2,10 +2,11 @@ import { Yok } from "../../lib/common/yok"; import { assert } from "chai"; import { ProjectDataService } from "../../lib/services/project-data-service"; import { LoggerStub, ProjectDataStub } from "../stubs"; -import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, PACKAGE_JSON_FILE_NAME, AssetConstants, ProjectTypes } from '../../lib/constants'; +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"; import { FileSystem } from "../../lib/common/file-system"; +import { regExpEscape } from "../../lib/common/helpers"; const CLIENT_NAME_KEY_IN_PROJECT_FILE = "nativescript"; @@ -41,7 +42,7 @@ const testData: any = [ } ]; -const createTestInjector = (readTextData?: string): IInjector => { +const createTestInjector = (packageJsonContent?: string, nsConfigContent?: string): IInjector => { const testInjector = new Yok(); testInjector.register("projectData", ProjectDataStub); testInjector.register("staticConfig", { @@ -55,10 +56,14 @@ const createTestInjector = (readTextData?: string): IInjector => { }, readText: (filename: string, encoding?: IReadFileOptions | string): string => { - return readTextData; + if (filename.indexOf("package.json") > -1) { + return packageJsonContent; + } else if (filename.indexOf("nsconfig.json") > -1) { + return nsConfigContent; + } }, - exists: (filePath: string): boolean => basename(filePath) === PACKAGE_JSON_FILE_NAME, + exists: (filePath: string): boolean => (basename(filePath) === PACKAGE_JSON_FILE_NAME || basename(filePath) === CONFIG_NS_FILE_NAME), readJson: (filePath: string): any => null, @@ -245,6 +250,66 @@ describe("projectDataService", () => { }); }); + describe("removeNSConfigProperty", () => { + + const generateExpectedDataFromTestData = (currentTestData: any) => { + const props = currentTestData.propertyName.split(NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); + props.splice(props.length - 1, 1); + + const data: any = {}; + let currentData: any = data; + + _.each(props, (prop) => { + currentData = currentData[prop] = {}; + }); + + return data; + }; + + _.each(testData, currentTestData => { + + it(currentTestData.description, () => { + const testInjector = createTestInjector(null, generateFileContentFromTestData(currentTestData, true)); + const fs: IFileSystem = testInjector.resolve("fs"); + + let dataPassedToWriteJson: any = null; + fs.writeJson = (filename: string, data: any, space?: string, encoding?: string): void => { + dataPassedToWriteJson = data; + }; + + const projectDataService: IProjectDataService = testInjector.resolve("projectDataService"); + const propDelimiterRegExp = new RegExp(regExpEscape(NATIVESCRIPT_PROPS_INTERNAL_DELIMITER), "g"); + const propertySelector = currentTestData.propertyName.replace(propDelimiterRegExp, "."); + projectDataService.removeNSConfigProperty("projectDir", propertySelector); + + assert.deepEqual(dataPassedToWriteJson, generateExpectedDataFromTestData(currentTestData)); + }); + + }); + + it("removes only the selected property", () => { + const initialData: any = {}; + initialData[CLIENT_NAME_KEY_IN_PROJECT_FILE] = { + "root": { + "id": "1", + "constantItem": "myValue" + } + }; + + const testInjector = createTestInjector(JSON.stringify(initialData)); + const fs: IFileSystem = testInjector.resolve("fs"); + + let dataPassedToWriteJson: any = null; + fs.writeJson = (filename: string, data: any, space?: string, encoding?: string): void => { + dataPassedToWriteJson = data; + }; + + const projectDataService: IProjectDataService = testInjector.resolve("projectDataService"); + projectDataService.removeNSProperty("projectDir", getPropertyName(["root", "id"])); + assert.deepEqual(dataPassedToWriteJson, { nativescript: { root: { constantItem: "myValue" } } }); + }); + }); + describe("removeDependency", () => { it("removes specified dependency from project file", () => { const currentTestData = { diff --git a/test/stubs.ts b/test/stubs.ts index e79f58b4a8..e3ee506cd7 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -327,7 +327,6 @@ export class ProjectDataStub implements IProjectData { public buildXcconfigPath: string; public podfilePath: string; public isShared: boolean; - public useLegacyWorkflow: boolean; public previewAppSchema: string; public initializeProjectData(projectDir?: string): void { @@ -507,6 +506,8 @@ export class ProjectDataService implements IProjectDataService { removeNSProperty(propertyName: string): void { } + removeNSConfigProperty(projectDir: string, propertyName: string): void { } + removeDependency(dependencyName: string): void { } getProjectData(projectDir: string): IProjectData { @@ -532,7 +533,7 @@ export class ProjectDataService implements IProjectDataService { return []; } - getNSValueFromContent(): any {} + getNSValueFromContent(): any { } } export class ProjectHelperStub implements IProjectHelper { diff --git a/test/update.ts b/test/update.ts index e1795b2435..8ab22c7802 100644 --- a/test/update.ts +++ b/test/update.ts @@ -5,6 +5,7 @@ import { assert } from "chai"; import { Options } from "../lib/options"; import { StaticConfig } from "../lib/config"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; +import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; const projectFolder = "test"; function createTestInjector( @@ -26,6 +27,7 @@ function createTestInjector( dependencies: {} }); testInjector.register("settingsService", SettingsService); + testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register("migrateController", { shouldMigrate: () => { return false; }, });