diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 4afff5e3bf..6cb8217524 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -1,4 +1,4 @@ -import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE, AndroidAppBundleMessages, ANDROID_APP_BUNDLE_SIGNING_ERROR_MESSAGE } from "../constants"; +import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE, AndroidAppBundleMessages } from "../constants"; import { ValidatePlatformCommandBase } from "./command-base"; import { hasValidAndroidSigning } from "../common/helpers"; @@ -124,12 +124,8 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { this.$androidBundleValidatorHelper.validateRuntimeVersion(this.$projectData); let canExecute = await super.canExecuteCommandBase(platform, { notConfiguredEnvOptions: { hideSyncToPreviewAppOption: true } }); if (canExecute) { - if ((this.$options.release || this.$options.aab) && !hasValidAndroidSigning(this.$options)) { - if (this.$options.release) { - this.$errors.failWithHelp(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); - } else { - this.$errors.failWithHelp(ANDROID_APP_BUNDLE_SIGNING_ERROR_MESSAGE); - } + if (this.$options.release && !hasValidAndroidSigning(this.$options)) { + this.$errors.failWithHelp(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } canExecute = await super.validateArgs(args, platform); diff --git a/lib/constants.ts b/lib/constants.ts index 014c1eae9f..1f23c5c110 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -10,6 +10,8 @@ export const NATIVESCRIPT_KEY_NAME = "nativescript"; export const NODE_MODULES_FOLDER_NAME = "node_modules"; export const TNS_MODULES_FOLDER_NAME = "tns_modules"; export const TNS_CORE_MODULES_NAME = "tns-core-modules"; +export const TNS_CORE_THEME_NAME = "nativescript-theme-core"; +export const SCOPED_TNS_CORE_THEME_NAME = "@nativescript/theme"; export const WEBPACK_PLUGIN_NAME = "nativescript-dev-webpack"; export const TNS_CORE_MODULES_WIDGETS_NAME = "tns-core-modules-widgets"; export const TNS_ANDROID_RUNTIME_NAME = "tns-android"; diff --git a/lib/services/assets-generation/assets-generation-service.ts b/lib/services/assets-generation/assets-generation-service.ts index e73a902d47..1a23e5ad65 100644 --- a/lib/services/assets-generation/assets-generation-service.ts +++ b/lib/services/assets-generation/assets-generation-service.ts @@ -71,6 +71,12 @@ export class AssetsGenerationService implements IAssetsGenerationService { const outputPath = assetItem.path; const width = assetItem.width * scale; const height = assetItem.height * scale; + + if (!width || !height) { + this.$logger.warn(`Image ${assetItem.filename} is skipped as its width and height are invalid.`); + continue; + } + let image: Jimp; switch (operation) { case Operations.OverlayWith: diff --git a/lib/services/livesync/playground/preview-app-plugins-service.ts b/lib/services/livesync/playground/preview-app-plugins-service.ts index 7a7bdd13f4..89eec630ed 100644 --- a/lib/services/livesync/playground/preview-app-plugins-service.ts +++ b/lib/services/livesync/playground/preview-app-plugins-service.ts @@ -4,7 +4,7 @@ import * as util from "util"; import { Device } from "nativescript-preview-sdk"; import { PluginComparisonMessages } from "./preview-app-constants"; import { NODE_MODULES_DIR_NAME } from "../../../common/constants"; -import { PLATFORMS_DIR_NAME, PACKAGE_JSON_FILE_NAME } from "../../../constants"; +import { PLATFORMS_DIR_NAME, PACKAGE_JSON_FILE_NAME, TNS_CORE_THEME_NAME, SCOPED_TNS_CORE_THEME_NAME } from "../../../constants"; export class PreviewAppPluginsService implements IPreviewAppPluginsService { constructor(private $errors: IErrors, @@ -41,10 +41,11 @@ export class PreviewAppPluginsService implements IPreviewAppPluginsService { public getExternalPlugins(device: Device): string[] { const devicePlugins = this.getDevicePlugins(device); + const themeNamesArray = [TNS_CORE_THEME_NAME, SCOPED_TNS_CORE_THEME_NAME]; const result = _.keys(devicePlugins) // The core theme links are custom and // should be handled by webpack during build. - .filter(plugin => plugin !== "nativescript-theme-core"); + .filter(plugin => themeNamesArray.indexOf(plugin) === -1); return result; } diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index b9edeb98f0..db02f78245 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -236,10 +236,12 @@ export class ProjectDataService implements IProjectDataService { private async getIOSAssetSubGroup(dirPath: string): Promise { const pathToContentJson = path.join(dirPath, AssetConstants.iOSResourcesFileName); const content = this.$fs.exists(pathToContentJson) && this.$fs.readJson(pathToContentJson) || { images: [] }; + const finalContent: IAssetSubGroup = { images: [] }; const imageDefinitions = this.getImageDefinitions().ios; _.each(content && content.images, image => { + let foundMatchingDefinition = false; // In some cases the image may not be available, it will just be described. // When this happens, the filename will be empty. // So we'll keep the path empty as well. @@ -247,22 +249,23 @@ export class ProjectDataService implements IProjectDataService { image.path = path.join(dirPath, image.filename); } + if (image.size) { + // size is basically x + const [width, height] = image.size.toString().split(AssetConstants.sizeDelimiter); + if (width && height) { + image.width = +width; + image.height = +height; + } + } + // Find the image size based on the hardcoded values in the image-definitions.json _.each(imageDefinitions, (assetSubGroup: IAssetItem[]) => { const assetItem = _.find(assetSubGroup, assetElement => assetElement.filename === image.filename && path.basename(assetElement.directory) === path.basename(dirPath) ); - if (image.size) { - // size is basically x - const [width, height] = image.size.toString().split(AssetConstants.sizeDelimiter); - if (width && height) { - image.width = +width; - image.height = +height; - } - } - if (assetItem) { + foundMatchingDefinition = true; if (!image.width || !image.height) { image.width = assetItem.width; image.height = assetItem.height; @@ -273,13 +276,18 @@ export class ProjectDataService implements IProjectDataService { image.overlayImageScale = image.overlayImageScale || assetItem.overlayImageScale; image.scale = image.scale || assetItem.scale; image.rgba = assetItem.rgba; + finalContent.images.push(image); // break each return false; } }); + + if (!foundMatchingDefinition && image.filename) { + this.$logger.warn(`Didn't find a matching image definition for file ${path.join(path.basename(dirPath), image.filename)}. This file will be skipped from reources generation.`); + } }); - return content; + return finalContent; } private getAndroidAssetSubGroup(assetItems: IAssetItem[], realPaths: string[]): IAssetSubGroup { diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 2603f5a762..4f0dd015da 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -7,7 +7,7 @@ import { WEBPACK_COMPILATION_COMPLETE, WEBPACK_PLUGIN_NAME } from "../../constan export class WebpackCompilerService extends EventEmitter implements IWebpackCompilerService { private webpackProcesses: IDictionary = {}; - private expectedHash: string = null; + private expectedHashes: IStringDictionary = {}; constructor( private $errors: IErrors, @@ -42,14 +42,14 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp if (message.emittedFiles) { if (isFirstWebpackWatchCompilation) { isFirstWebpackWatchCompilation = false; - this.expectedHash = message.hash; + this.expectedHashes[platformData.platformNameLowerCase] = message.hash; return; } let result; if (prepareData.hmr) { - result = this.getUpdatedEmittedFiles(message.emittedFiles, message.chunkFiles, message.hash); + result = this.getUpdatedEmittedFiles(message.emittedFiles, message.chunkFiles, message.hash, platformData.platformNameLowerCase); } else { result = { emittedFiles: message.emittedFiles, fallbackFiles: [], hash: "" }; } @@ -248,7 +248,7 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp return args; } - public getUpdatedEmittedFiles(allEmittedFiles: string[], chunkFiles: string[], nextHash: string) { + public getUpdatedEmittedFiles(allEmittedFiles: string[], chunkFiles: string[], nextHash: string, platform: string) { const currentHash = this.getCurrentHotUpdateHash(allEmittedFiles); // This logic is needed as there are already cases when webpack doesn't emit any files physically. @@ -260,8 +260,8 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp // if files will be emitted or not. This way, the first successful compilation after fixing the compilation error generates // a hash that is not the same as the one expected in the latest emitted hot-update.json file. // As a result, the hmr chain is broken and the changes are not applied. - const isHashValid = nextHash ? this.expectedHash === currentHash : true; - this.expectedHash = nextHash; + const isHashValid = nextHash ? this.expectedHashes[platform] === currentHash : true; + this.expectedHashes[platform] = nextHash; const emittedHotUpdatesAndAssets = isHashValid ? _.difference(allEmittedFiles, chunkFiles) : allEmittedFiles; diff --git a/test/services/webpack/webpack-compiler-service.ts b/test/services/webpack/webpack-compiler-service.ts index e23a701fa4..22fb85686d 100644 --- a/test/services/webpack/webpack-compiler-service.ts +++ b/test/services/webpack/webpack-compiler-service.ts @@ -2,6 +2,8 @@ import { Yok } from "../../../lib/common/yok"; import { WebpackCompilerService } from "../../../lib/services/webpack/webpack-compiler-service"; import { assert } from "chai"; +const iOSPlatformName = "ios"; +const androidPlatformName = "android"; const chunkFiles = ["bundle.js", "runtime.js", "vendor.js"]; function getAllEmittedFiles(hash: string) { @@ -35,39 +37,54 @@ describe("WebpackCompilerService", () => { describe("getUpdatedEmittedFiles", () => { // backwards compatibility with old versions of nativescript-dev-webpack it("should return only hot updates when nextHash is not provided", async () => { - const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, null); + const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, null, iOSPlatformName); const expectedEmittedFiles = ['bundle.hash1.hot-update.js', 'hash1.hot-update.json']; assert.deepEqual(result.emittedFiles, expectedEmittedFiles); }); // 2 successful webpack compilations it("should return only hot updates when nextHash is provided", async () => { - webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2"); - const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash2"), chunkFiles, "hash3"); + webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2", iOSPlatformName); + const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash2"), chunkFiles, "hash3", iOSPlatformName); assert.deepEqual(result.emittedFiles, ['bundle.hash2.hot-update.js', 'hash2.hot-update.json']); }); // 1 successful webpack compilation, n compilations with no emitted files it("should return all files when there is a webpack compilation with no emitted files", () => { - webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2"); - const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash4"), chunkFiles, "hash5"); + webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2", iOSPlatformName); + const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash4"), chunkFiles, "hash5", iOSPlatformName); assert.deepEqual(result.emittedFiles, ['bundle.js', 'runtime.js', 'bundle.hash4.hot-update.js', 'hash4.hot-update.json']); }); // 1 successful webpack compilation, n compilations with no emitted files, 1 successful webpack compilation it("should return only hot updates after fixing the compilation error", () => { - webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2"); - webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash5"), chunkFiles, "hash6"); - const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash6"), chunkFiles, "hash7"); + webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2", iOSPlatformName); + webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash5"), chunkFiles, "hash6", iOSPlatformName); + const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash6"), chunkFiles, "hash7", iOSPlatformName); assert.deepEqual(result.emittedFiles, ['bundle.hash6.hot-update.js', 'hash6.hot-update.json']); }); // 1 webpack compilation with no emitted files it("should return all files when first compilation on livesync change is not successful", () => { - (webpackCompilerService).expectedHash = "hash1"; - const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2"); + (webpackCompilerService).expectedHashes = { + "ios": "hash1" + }; + const result = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2", iOSPlatformName); assert.deepEqual(result.emittedFiles, ["bundle.hash1.hot-update.js", "hash1.hot-update.json"]); }); + it("should return correct hashes when there are more than one platform", () => { + webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash1"), chunkFiles, "hash2", iOSPlatformName); + webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash3"), chunkFiles, "hash4", androidPlatformName); + + webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash2"), chunkFiles, "hash5", iOSPlatformName); + webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash4"), chunkFiles, "hash6", androidPlatformName); + + const iOSResult = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash5"), chunkFiles, "hash7", iOSPlatformName); + assert.deepEqual(iOSResult.emittedFiles, ["bundle.hash5.hot-update.js", "hash5.hot-update.json"]); + + const androidResult = webpackCompilerService.getUpdatedEmittedFiles(getAllEmittedFiles("hash6"), chunkFiles, "hash8", androidPlatformName); + assert.deepEqual(androidResult.emittedFiles, ["bundle.hash6.hot-update.js", "hash6.hot-update.json"]); + }); }); });