diff --git a/.vscode/launch.json b/.vscode/launch.json index 88a6442f47..53e4510378 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,7 +22,9 @@ // "args": [ "run", "android", "--path", "cliapp"] // "args": [ "debug", "android", "--path", "cliapp"] // "args": [ "livesync", "android", "--path", "cliapp"] - // "args": [ "livesync", "android", "--watch", "--path", "cliapp"] + // "args": [ "livesync", "android", "--watch", "--path", "cliapp"], + // "args": [ "resources", "generate", "icons", "./test/image-generation-test.png", "--path", "cliapp" ], + // "args": [ "resources", "generate", "splashes", "./test/image-generation-test.png", "--path", "cliapp", "--background", "#8000ff" ], }, { // in case you want to debug a single test, modify it's code to be `it.only(...` instead of `it(...` diff --git a/PublicAPI.md b/PublicAPI.md index c7fa6e6124..4009ed25e3 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -14,6 +14,10 @@ const tns = require("nativescript"); * [projectDataService](#projectdataservice) * [getProjectData](#getprojectdata) * [getProjectDataFromContent](#getprojectdatafromcontent) + * [getNsConfigDefaultContent](#getnsconfigdefaultcontent) + * [getAssetsStructure](#getassetsstructure) + * [getIOSAssetsStructure](#getiosassetsstructure) + * [getAndroidAssetsStructure](#getandroidassetsstructure) * [extensibilityService](#extensibilityservice) * [installExtension](#installextension) * [uninstallExtension](#uninstallextension) @@ -41,7 +45,9 @@ const tns = require("nativescript"); * [analyticsSettingsService](#analyticsSettingsService) * [getClientId](#getClientId) * [constants](#constants) - +* [assetsGenerationService](#assetsgenerationservice) + * [generateIcons](#generateicons) + * [generateSplashScreens](#generatesplashscreens) ## Module projectService @@ -199,6 +205,66 @@ Returns the default content of "nsconfig.json" merged with the properties provid getNsConfigDefaultContent(data?: Object): string ``` +### getAssetsStructure +Gives information about the whole assets structure for both iOS and Android. For each of the platforms, the returned object will contain icons, splashBackgrounds, splashCenterImages and splashImages (only for iOS). +* Definition: +```TypeScript +/** + * Gives information about the whole assets structure for both iOS and Android. + * For each of the platforms, the returned object will contain icons, splashBackgrounds, splashCenterImages and splashImages (only for iOS). + * @param {IProjectDir} opts Object with a single property - projectDir. This is the root directory where NativeScript project is located. + * @returns {Promise} An object describing the current asset structure. + */ +getAssetsStructure(opts: IProjectDir): Promise; +``` + +* Usage: +```JavaScript +tns.projectDataService.getAssetsStructure({ projectDir: "/Users/username/myNativeScriptProject" }) + .then(assetsStructure => console.log(`The current assets structure is ${JSON.stringify(assetsStructure, null, 2)}.`)) + .catch(err => console.log("Failed to get assets structure.")); +``` + +### getIOSAssetsStructure +Gives information about the assets structure for iOS .The returned object will contain icons, splashBackgrounds, splashCenterImages and splashImages. +* Definition: +```TypeScript +/** + * Gives information about the whole assets structure for iOS. + * The returned object will contain icons, splashBackgrounds, splashCenterImages and splashImages. + * @param {IProjectDir} opts Object with a single property - projectDir. This is the root directory where NativeScript project is located. + * @returns {Promise} An object describing the current asset structure for iOS. + */ +getIOSAssetsStructure(opts: IProjectDir): Promise; +``` + +* Usage: +```JavaScript +tns.projectDataService.getIOSAssetsStructure({ projectDir: "/Users/username/myNativeScriptProject" }) + .then(assetsStructure => console.log(`The current assets structure for iOS is ${JSON.stringify(assetsStructure, null, 2)}.`)) + .catch(err => console.log("Failed to get assets structure.")); +``` + +### getAndroidAssetsStructure +Gives information about the assets structure for Android .The returned object will contain icons, splashBackgrounds and splashCenterImages. +* Definition: +```TypeScript +/** + * Gives information about the whole assets structure for Android. + * The returned object will contain icons, splashBackgrounds and splashCenterImages. + * @param {IProjectDir} opts Object with a single property - projectDir. This is the root directory where NativeScript project is located. + * @returns {Promise} An object describing the current asset structure for Android. + */ +getAndroidAssetsStructure(opts: IProjectDir): Promise; +``` + +* Usage: +```JavaScript +tns.projectDataService.getAndroidAssetsStructure({ projectDir: "/Users/username/myNativeScriptProject" }) + .then(assetsStructure => console.log(`The current assets structure for Android is ${JSON.stringify(assetsStructure, null, 2)}.`)) + .catch(err => console.log("Failed to get assets structure.")); +``` + ## extensibilityService `extensibilityService` module gives access to methods for working with CLI's extensions - list, install, uninstall, load them. The extensions add new functionality to CLI, so once an extension is loaded, all methods added to it's public API are accessible directly through CLI when it is used as a library. Extensions may also add new commands, so they are accessible through command line when using NativeScript CLI. @@ -1013,6 +1079,54 @@ tns.analyticsSettingsService.getPlaygroundInfo("/my/project/path") ## constants Contains various constants related to NativeScript. +## assetsGenerationService +`assetsGenerationService` module allows generation of assets - icons and splashes. + +### generateIcons +The `generateIcons` method generates icons for specified platform (or both iOS and Android in case platform is not specified) and places them on correct location in the specified project. + +* Definition: +```TypeScript +/** + * Generate icons for iOS and Android + * @param {IResourceGenerationData} iconsGenerationData Provides the data needed for icons generation + * @returns {Promise} + */ +generateIcons({ imagePath: string, projectDir: string, platform?: string }): Promise; +``` + +* Usage: +```JavaScript +tns.assetsGenerationService.generateIcons({ projectDir: "/Users/username/myNativeScriptProject", imagePath: "/Users/username/image.png" }) + .then(() => { + console.log("Successfully generated icons"); + }); +``` + + +### generateSplashScreens +The `generateSplashScreens` method generates icons for specified platform (or both iOS and Android in case platform is not specified) and places them on correct location in the specified project. + +* Definition: +```TypeScript +/** + * Generate splash screens for iOS and Android + * @param {ISplashesGenerationData} splashesGenerationData Provides the data needed for splash screens generation + * @returns {Promise} + */ +generateSplashScreens({ imagePath: string, projectDir: string, platform?: string, background?: string }): Promise; +``` + +* Usage: +```JavaScript +tns.assetsGenerationService.generateSplashScreens({ projectDir: "/Users/username/myNativeScriptProject", imagePath: "/Users/username/image.png", background: "blue" }) + .then(() => { + console.log("Successfully generated splash screens"); + }); +``` + + + ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. diff --git a/docs/man_pages/project/configuration/resources/resources-generate-icons.md b/docs/man_pages/project/configuration/resources/resources-generate-icons.md new file mode 100644 index 0000000000..e9ff2033ba --- /dev/null +++ b/docs/man_pages/project/configuration/resources/resources-generate-icons.md @@ -0,0 +1,28 @@ +<% if (isJekyll) { %>--- +title: tns resources generate icons +position: 11 +---<% } %> +# tns resources generate icons + +Usage | Synopsis +------|------- +`$ tns resources generate icons ` | Generate all icons for Android and iOS based on the specified image. + +Generates all icons for Android and iOS platforms and places the generated images in the correct directories under `App_Resources/` directory. + +### Attributes +* `` is a valid path to an image that will be used to generate all icons. + +<% if(isHtml) { %> +### 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. +[resources update](resources-update.md) | Updates the `App_Resources/Android` internal folder structure to conform to that of an Android Studio project. +[resources generate splashes](resources-generate-splashes.md) | Generates splashscreens for Android and iOS. +<% } %> \ No newline at end of file diff --git a/docs/man_pages/project/configuration/resources/resources-generate-splashes.md b/docs/man_pages/project/configuration/resources/resources-generate-splashes.md new file mode 100644 index 0000000000..5db76757dc --- /dev/null +++ b/docs/man_pages/project/configuration/resources/resources-generate-splashes.md @@ -0,0 +1,29 @@ +<% if (isJekyll) { %>--- +title: tns resources generate splashes +position: 12 +---<% } %> +# tns resources generate splashes + +Usage | Synopsis +------|------- +`$ tns resources generate splashes []` | Generate all splashscreens for Android and iOS based on the specified image. + +Generates all icons for Android and iOS platforms and places the generated images in the correct directories under `App_Resources/` directory. + +### Attributes +* `` is a valid path to an image that will be used to generate all splashscreens. +* `` is a valid path to an image that will be used as a background of the splashscreen. Defaults to white in case it is not specified. + +<% if(isHtml) { %> +### 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. +[resources update](resources-update.md) | Updates the `App_Resources/Android` internal folder structure to conform to that of an Android Studio project. +[resources generate icons](resources-generate-icons.md) | Generates icons for Android and iOS. +<% } %> \ No newline at end of file diff --git a/docs/man_pages/project/configuration/resources/resources-update.md b/docs/man_pages/project/configuration/resources/resources-update.md index 128a885a32..c5cbba0628 100644 --- a/docs/man_pages/project/configuration/resources/resources-update.md +++ b/docs/man_pages/project/configuration/resources/resources-update.md @@ -1,8 +1,8 @@ <% if (isJekyll) { %>--- title: tns resources update -position: 9 +position: 10 ---<% } %> -#tns resources update +# tns resources update ========== Usage | Synopsis @@ -11,7 +11,7 @@ Usage | Synopsis `$ tns resources update android` | Updates the App_Resources/Android's folder structure. Updates the App_Resources/'s internal folder structure to conform to that of an Android Studio project. Android resource files and directories will be located at the following paths: -- `drawable-*`, `values`, `raw`, etc. can be found at `App_Resources/Android/src/main/res` +- `drawable-*`, `values`, `raw`, etc. can be found at `App_Resources/Android/src/main/res` - `AndroidManifest.xml` can be found at `App_Resources/Android/src/main/AndroidManifest.xml` - Java source files can be dropped in at `App_Resources/Android/src/main/java` after creating the proper package subdirectory structure - Additional arbitrary assets can be dropped in at `App_Resources/Android/src/main/assets` diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 848a2573a6..d1e532459f 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -161,3 +161,7 @@ $injector.require("terminalSpinnerService", "./services/terminal-spinner-service $injector.require('playgroundService', './services/playground-service'); $injector.require("platformEnvironmentRequirements", "./services/platform-environment-requirements"); $injector.require("nativescriptCloudExtensionService", "./services/nativescript-cloud-extension-service"); + +$injector.requireCommand("resources|generate|icons", "./commands/generate-assets"); +$injector.requireCommand("resources|generate|splashes", "./commands/generate-assets"); +$injector.requirePublic("assetsGenerationService", "./services/assets-generation/assets-generation-service"); diff --git a/lib/commands/generate-assets.ts b/lib/commands/generate-assets.ts new file mode 100644 index 0000000000..6abc48245c --- /dev/null +++ b/lib/commands/generate-assets.ts @@ -0,0 +1,50 @@ +export abstract class GenerateCommandBase implements ICommand { + public allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("You have to provide path to image to generate other images based on it.")]; + + constructor(protected $options: IOptions, + protected $injector: IInjector, + protected $projectData: IProjectData, + protected $stringParameterBuilder: IStringParameterBuilder, + protected $assetsGenerationService: IAssetsGenerationService) { + this.$projectData.initializeProjectData(); + } + + public async execute(args: string[]): Promise { + const [ imagePath ] = args; + await this.generate(imagePath, this.$options.background); + } + + protected abstract generate(imagePath: string, background?: string): Promise; +} + +export class GenerateIconsCommand extends GenerateCommandBase implements ICommand { + constructor(protected $options: IOptions, + $injector: IInjector, + protected $projectData: IProjectData, + protected $stringParameterBuilder: IStringParameterBuilder, + $assetsGenerationService: IAssetsGenerationService) { + super($options, $injector, $projectData, $stringParameterBuilder, $assetsGenerationService); + } + + protected async generate(imagePath: string, background?: string): Promise { + await this.$assetsGenerationService.generateIcons({ imagePath, projectDir: this.$projectData.projectDir }); + } +} + +$injector.registerCommand("resources|generate|icons", GenerateIconsCommand); + +export class GenerateSplashScreensCommand extends GenerateCommandBase implements ICommand { + constructor(protected $options: IOptions, + $injector: IInjector, + protected $projectData: IProjectData, + protected $stringParameterBuilder: IStringParameterBuilder, + $assetsGenerationService: IAssetsGenerationService) { + super($options, $injector, $projectData, $stringParameterBuilder, $assetsGenerationService); + } + + protected async generate(imagePath: string, background?: string): Promise { + await this.$assetsGenerationService.generateSplashScreens({ imagePath, background, projectDir: this.$projectData.projectDir }); + } +} + +$injector.registerCommand("resources|generate|splashes", GenerateSplashScreensCommand); diff --git a/lib/constants.ts b/lib/constants.ts index 1ff3ad5f49..e196f57971 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -148,3 +148,18 @@ export const NATIVESCRIPT_CLOUD_EXTENSION_NAME = "nativescript-cloud"; * Used in ProjectDataService to concatenate the names of the properties inside nativescript key of package.json. */ export const NATIVESCRIPT_PROPS_INTERNAL_DELIMITER = "**|__**"; +export const CLI_RESOURCES_DIR_NAME = "resources"; + +export class AssetConstants { + public static iOSResourcesFileName = "Contents.json"; + public static iOSAssetsDirName = "Assets.xcassets"; + public static iOSIconsDirName = "AppIcon.appiconset"; + public static iOSSplashBackgroundsDirName = "LaunchScreen.AspectFill.imageset"; + public static iOSSplashCenterImagesDirName = "LaunchScreen.Center.imageset"; + public static iOSSplashImagesDirName = "LaunchImage.launchimage"; + + public static imageDefinitionsFileName = "image-definitions.json"; + public static assets = "assets"; + + public static sizeDelimiter = "x"; +} diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index f9d78a8a2f..bb644633c1 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -463,6 +463,7 @@ interface IOptions extends ICommonOptions, IBundleString, IPlatformTemplate, IEm liveEdit: boolean; chrome: boolean; inspector: boolean; // the counterpart to --chrome + background: string; } interface IEnvOptions { @@ -570,7 +571,7 @@ interface IAndroidToolsInfo { */ validateAndroidHomeEnvVariable(options?: IAndroidToolsInfoOptions): boolean; - /** + /** * Validates target sdk * @param {IAndroidToolsInfoOptions} options @optional Defines if the warning messages should treated as error. * @returns {boolean} True if there are detected issues, false otherwise @@ -790,7 +791,51 @@ interface IBundleValidatorHelper { interface INativescriptCloudExtensionService { /** * Installs nativescript-cloud extension - * @return {Promise} returns the extension data + * @return {Promise} returns the extension data */ install(): Promise; } + +/** + * Describes the basic data needed for resource generation + */ +interface IResourceGenerationData extends IProjectDir { + /** + * @param {string} imagePath Path to the image that will be used for generation + */ + imagePath: string, + + /** + * @param {string} platform Specify for which platform to generate assets. If not defined will generate for all platforms + */ + platform?: string +} + +/** + * Describes the data needed for splash screens generation + */ +interface ISplashesGenerationData extends IResourceGenerationData { + /** + * @param {string} background Background color that will be used for background. Defaults to #FFFFFF + */ + background?: string +} + +/** + * Describes service used for assets generation + */ +interface IAssetsGenerationService { + /** + * Generate icons for iOS and Android + * @param {IResourceGenerationData} iconsGenerationData Provides the data needed for icons generation + * @returns {Promise} + */ + generateIcons(iconsGenerationData: IResourceGenerationData): Promise; + + /** + * Generate splash screens for iOS and Android + * @param {ISplashesGenerationData} splashesGenerationData Provides the data needed for splash screens generation + * @returns {Promise} + */ + generateSplashScreens(splashesGenerationData: ISplashesGenerationData): Promise; +} \ No newline at end of file diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 224f4122bf..ce00fa481a 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -55,7 +55,7 @@ interface IProjectService { interface INsConfig { appPath?: string; - appResourcesPath?:string; + appResourcesPath?: string; } interface IProjectData extends IProjectDir { @@ -123,6 +123,74 @@ interface IProjectDataService { removeDependency(projectDir: string, dependencyName: string): void; getProjectData(projectDir?: string): IProjectData; + + /** + * Gives information about the whole assets structure for both iOS and Android. + * For each of the platforms, the returned object will contain icons, splashBackgrounds, splashCenterImages and splashImages (only for iOS). + * @param {IProjectDir} opts Object with a single property - projectDir. This is the root directory where NativeScript project is located. + * @returns {Promise} An object describing the current asset structure. + */ + getAssetsStructure(opts: IProjectDir): Promise; + + + /** + * Gives information about the whole assets structure for iOS. + * The returned object will contain icons, splashBackgrounds, splashCenterImages and splashImages. + * @param {IProjectDir} opts Object with a single property - projectDir. This is the root directory where NativeScript project is located. + * @returns {Promise} An object describing the current asset structure for iOS. + */ + getIOSAssetsStructure(opts: IProjectDir): Promise; + + /** + * Gives information about the whole assets structure for Android. + * The returned object will contain icons, splashBackgrounds and splashCenterImages. + * @param {IProjectDir} opts Object with a single property - projectDir. This is the root directory where NativeScript project is located. + * @returns {Promise} An object describing the current asset structure for Android. + */ + getAndroidAssetsStructure(opts: IProjectDir): Promise; +} + +interface IAssetItem { + path: string; + size: string; + width: number; + height: number; + filename: string; + directory: string; + scale: number; + idiom: string; + resizeOperation?: string; +} + +interface IAssetSubGroup { + images: IAssetItem[]; + info?: { version: string, author: string }; + [imageType: string]: any; +} + +interface IAssetGroup { + icons: IAssetSubGroup; + splashBackgrounds: IAssetSubGroup; + splashCenterImages: IAssetSubGroup; + splashImages?: IAssetSubGroup; + [imageType: string]: IAssetSubGroup; +} + +interface IAssetsStructure { + ios: IAssetGroup; + android: IAssetGroup; +} + +interface IImageDefinitionGroup { + icons: IAssetItem[]; + splashBackgrounds: IAssetItem[]; + splashCenterImages: IAssetItem[]; + splashImages?: IAssetItem[]; +} + +interface IImageDefinitionsStructure { + ios: IImageDefinitionGroup; + android: IImageDefinitionGroup; } /** diff --git a/lib/options.ts b/lib/options.ts index e9002e59c8..c952b07f83 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -38,7 +38,8 @@ export class Options extends commonOptionsLibPath.OptionsBase { chrome: { type: OptionType.Boolean }, inspector: { type: OptionType.Boolean }, clean: { type: OptionType.Boolean }, - watch: { type: OptionType.Boolean, default: true } + watch: { type: OptionType.Boolean, default: true }, + background: { type: OptionType.String } }, $errors, $staticConfig, $settingsService); diff --git a/lib/services/assets-generation/assets-generation-service.ts b/lib/services/assets-generation/assets-generation-service.ts new file mode 100644 index 0000000000..4b2d2e3fd7 --- /dev/null +++ b/lib/services/assets-generation/assets-generation-service.ts @@ -0,0 +1,109 @@ +import * as Jimp from "jimp"; +import * as Color from "color"; +import { exported } from "../../common/decorators"; + +export const enum Operations { + OverlayWith = "overlayWith", + Blank = "blank", + Resize = "resize" +} + +export class AssetsGenerationService implements IAssetsGenerationService { + private get propertiesToEnumerate(): IDictionary { + return { + icon: ["icons"], + splash: ["splashBackgrounds", "splashCenterImages", "splashImages"] + }; + } + + constructor(private $logger: ILogger, + private $projectDataService: IProjectDataService) { + } + + @exported("assetsGenerationService") + public async generateIcons(resourceGenerationData: IResourceGenerationData): Promise { + this.$logger.info("Generating icons ..."); + await this.generateImagesForDefinitions(resourceGenerationData, this.propertiesToEnumerate.icon); + this.$logger.info("Icons generation completed."); + } + + @exported("assetsGenerationService") + public async generateSplashScreens(splashesGenerationData: ISplashesGenerationData): Promise { + this.$logger.info("Generating splash screens ..."); + await this.generateImagesForDefinitions(splashesGenerationData, this.propertiesToEnumerate.splash); + this.$logger.info("Splash screens generation completed."); + } + + private async generateImagesForDefinitions(generationData: ISplashesGenerationData, propertiesToEnumerate: string[]): Promise { + generationData.background = generationData.background || "white"; + const assetsStructure = await this.$projectDataService.getAssetsStructure(generationData); + + const assetItems = _(assetsStructure) + .filter((assetGroup: IAssetGroup, platform: string) => { + return !generationData.platform || platform.toLowerCase() === generationData.platform.toLowerCase(); + }) + .map((assetGroup: IAssetGroup) => + _.filter(assetGroup, (assetSubGroup: IAssetSubGroup, imageTypeKey: string) => + propertiesToEnumerate.indexOf(imageTypeKey) !== -1 && !assetSubGroup[imageTypeKey] + ) + ) + .flatten() + .map(assetSubGroup => assetSubGroup.images) + .flatten() + .filter(assetItem => !!assetItem.filename) + .value(); + + for (const assetItem of assetItems) { + const operation = assetItem.resizeOperation || Operations.Resize; + const scale = assetItem.scale || 0.8; + const outputPath = assetItem.path; + + switch (operation) { + case Operations.OverlayWith: + const imageResize = Math.round(Math.min(assetItem.width, assetItem.height) * scale); + const image = await this.resize(generationData.imagePath, imageResize, imageResize); + await this.generateImage(generationData.background, assetItem.width, assetItem.height, outputPath, image); + break; + case Operations.Blank: + await this.generateImage(generationData.background, assetItem.width, assetItem.height, outputPath); + break; + case Operations.Resize: + const resizedImage = await this.resize(generationData.imagePath, assetItem.width, assetItem.height); + resizedImage.write(outputPath); + break; + default: + throw new Error(`Invalid image generation operation: ${operation}`); + } + } + } + + private async resize(imagePath: string, width: number, height: number): Promise { + const image = await Jimp.read(imagePath); + return image.scaleToFit(width, height); + } + + private generateImage(background: string, width: number, height: number, outputPath: string, overlayImage?: Jimp): void { + // Typescript declarations for Jimp are not updated to define the constructor with backgroundColor so we workaround it by casting it to for this case only. + const J = Jimp; + const backgroundColor = this.getRgbaNumber(background); + let image = new J(width, height, backgroundColor); + + if (overlayImage) { + const centeredWidth = (width - overlayImage.bitmap.width) / 2; + const centeredHeight = (height - overlayImage.bitmap.height) / 2; + image = image.composite(overlayImage, centeredWidth, centeredHeight); + } + + image.write(outputPath); + } + + private getRgbaNumber(colorString: string): number { + const color = new Color(colorString); + const colorRgb = color.rgb(); + const alpha = Math.round(colorRgb.alpha() * 255); + + return Jimp.rgbaToInt(colorRgb.red(), colorRgb.green(), colorRgb.blue(), alpha); + } +} + +$injector.register("assetsGenerationService", AssetsGenerationService); diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index a93368f29a..4a78882c0f 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -1,7 +1,7 @@ import * as path from "path"; import { ProjectData } from "../project-data"; import { exported } from "../common/decorators"; -import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER } from "../constants"; +import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, AssetConstants, SRC_DIR, RESOURCES_DIR, MAIN_DIR, CLI_RESOURCES_DIR_NAME } from "../constants"; interface IProjectFileData { projectData: any; @@ -14,6 +14,8 @@ export class ProjectDataService implements IProjectDataService { constructor(private $fs: IFileSystem, private $staticConfig: IStaticConfig, private $logger: ILogger, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $androidResourcesMigrationService: IAndroidResourcesMigrationService, private $injector: IInjector) { } @@ -51,6 +53,135 @@ export class ProjectDataService implements IProjectDataService { return projectDataInstance; } + @exported("projectDataService") + public async getAssetsStructure(opts: IProjectDir): Promise { + const iOSAssetStructure = await this.getIOSAssetsStructure(opts); + const androidAssetStructure = await this.getAndroidAssetsStructure(opts); + + this.$logger.trace("iOS Assets structure:", JSON.stringify(iOSAssetStructure, null, 2)); + this.$logger.trace("Android Assets structure:", JSON.stringify(androidAssetStructure, null, 2)); + + return { + ios: iOSAssetStructure, + android: androidAssetStructure + }; + } + + @exported("projectDataService") + public async getIOSAssetsStructure(opts: IProjectDir): Promise { + const projectDir = opts.projectDir; + const projectData = this.getProjectData(projectDir); + + const basePath = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, AssetConstants.iOSAssetsDirName); + const pathToIcons = path.join(basePath, AssetConstants.iOSIconsDirName); + const icons = await this.getIOSAssetSubGroup(pathToIcons); + + const pathToSplashBackgrounds = path.join(basePath, AssetConstants.iOSSplashBackgroundsDirName); + const splashBackgrounds = await this.getIOSAssetSubGroup(pathToSplashBackgrounds); + + const pathToSplashCenterImages = path.join(basePath, AssetConstants.iOSSplashCenterImagesDirName); + const splashCenterImages = await this.getIOSAssetSubGroup(pathToSplashCenterImages); + + const pathToSplashImages = path.join(basePath, AssetConstants.iOSSplashImagesDirName); + const splashImages = await this.getIOSAssetSubGroup(pathToSplashImages); + + return { + icons, + splashBackgrounds, + splashCenterImages, + splashImages + }; + } + + @exported("projectDataService") + public async getAndroidAssetsStructure(opts: IProjectDir): Promise { + // TODO: Use image-size package to get the width and height of an image. + // TODO: Parse the splash_screen.xml in nodpi directory and get from it the names of the background and center image. + // TODO: Parse the AndroidManifest.xml to get the name of the icon. + // This way we'll not use the image-definitions.json and the method will return the real android structure. + const projectDir = opts.projectDir; + const projectData = this.getProjectData(projectDir); + const pathToAndroidDir = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.Android); + const hasMigrated = this.$androidResourcesMigrationService.hasMigrated(projectData.appResourcesDirectoryPath); + const basePath = hasMigrated ? path.join(pathToAndroidDir, SRC_DIR, MAIN_DIR, RESOURCES_DIR) : pathToAndroidDir; + + const currentStructure = this.$fs.enumerateFilesInDirectorySync(basePath); + const content = this.getImageDefinitions().android; + + return { + icons: this.getAndroidAssetSubGroup(content.icons, currentStructure), + splashBackgrounds: this.getAndroidAssetSubGroup(content.splashBackgrounds, currentStructure), + splashCenterImages: this.getAndroidAssetSubGroup(content.splashCenterImages, currentStructure), + splashImages: null + }; + } + + private getImageDefinitions(): IImageDefinitionsStructure { + const pathToImageDefinitions = path.join(__dirname, "..", "..", CLI_RESOURCES_DIR_NAME, AssetConstants.assets, AssetConstants.imageDefinitionsFileName); + const imageDefinitions = this.$fs.readJson(pathToImageDefinitions); + + return imageDefinitions; + } + + private async getIOSAssetSubGroup(dirPath: string): Promise { + const pathToContentJson = path.join(dirPath, AssetConstants.iOSResourcesFileName); + const content = this.$fs.readJson(pathToContentJson); + + const imageDefinitions = this.getImageDefinitions().ios; + + _.each(content && content.images, image => { + // 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. + if (image.filename) { + 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; + } + } else { + // Find the image size based on the hardcoded values in the image-definitions.json + _.each(imageDefinitions, (assetSubGroup: IAssetItem[]) => { + _.each(assetSubGroup, assetItem => { + if (assetItem.filename === image.filename && path.basename(assetItem.directory) === path.basename(dirPath)) { + image.width = assetItem.width; + image.height = assetItem.height; + image.size = `${assetItem.width}${AssetConstants.sizeDelimiter}${assetItem.height}`; + } + }); + }); + } + }); + + return content; + } + + private getAndroidAssetSubGroup(assetItems: IAssetItem[], realPaths: string[]): IAssetSubGroup { + const assetSubGroup: IAssetSubGroup = { + images: [] + }; + + const normalizedPaths = _.map(realPaths, p => path.normalize(p)); + _.each(assetItems, assetItem => { + _.each(normalizedPaths, currentNormalizedPath => { + const imagePath = path.join(assetItem.directory, assetItem.filename); + if (currentNormalizedPath.indexOf(path.normalize(imagePath)) !== -1) { + assetItem.path = currentNormalizedPath; + assetItem.size = `${assetItem.width}${AssetConstants.sizeDelimiter}${assetItem.height}`; + assetSubGroup.images.push(assetItem); + return false; + } + }); + }); + + return assetSubGroup; + } + private getValue(projectDir: string, propertyName: string): any { const projectData = this.getProjectFileData(projectDir).projectData; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5407d2768f..fdee47de07 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -28,6 +28,30 @@ "@types/node": "6.0.61" } }, + "@types/color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.0.tgz", + "integrity": "sha512-5qqtNia+m2I0/85+pd2YzAXaTyKO8j+svirO5aN+XaQJ5+eZ8nx0jPtEWZLxCi50xwYsX10xUHetFzfb1WEs4Q==", + "dev": true, + "requires": { + "@types/color-convert": "1.9.0" + } + }, + "@types/color-convert": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg==", + "dev": true, + "requires": { + "@types/color-name": "1.1.0" + } + }, + "@types/color-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.0.tgz", + "integrity": "sha512-gZ/Rb+MFXF0pXSEQxdRoPMm5jeO3TycjOdvbpbcpHX/B+n9AqaHFe5q6Ga9CsZ7ir/UgIWPfrBzUzn3F19VH/w==", + "dev": true + }, "@types/form-data": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", @@ -84,7 +108,7 @@ "@types/sinon": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.0.0.tgz", - "integrity": "sha512-cuK4xM8Lg2wd8cxshcQa8RG4IK/xfyB6TNE6tNVvkrShR4xdrYgsV04q6Dp6v1Lp6biEFdzD8k8zg/ujQeiw+A==", + "integrity": "sha1-mpP/pO4TKehRZieKXtmfgdxMg2I=", "dev": true }, "@types/source-map": { @@ -323,6 +347,11 @@ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.23.tgz", "integrity": "sha1-6F1QgiDHTj9DpM5y7tUfPaTblNE=" }, + "bignumber.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=" + }, "binary": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", @@ -337,6 +366,11 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz", "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=" }, + "bmp-js": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.0.3.tgz", + "integrity": "sha1-ZBE+nHzxICs3btYHvzBibr5XsYo=" + }, "body-parser": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", @@ -430,6 +464,11 @@ "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", "dev": true }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" + }, "bufferpack": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/bufferpack/-/bufferpack-0.0.6.tgz", @@ -662,6 +701,15 @@ "integrity": "sha1-EpOLz5vhlI+gBvkuDEyegXBRCMA=", "dev": true }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "1.9.1", + "color-string": "1.5.2" + } + }, "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", @@ -675,6 +723,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, + "color-string": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.2.tgz", + "integrity": "sha1-JuRYFLw8mny9Z1FkikFDRRSnc6k=", + "requires": { + "color-name": "1.1.3", + "simple-swizzle": "0.2.2" + } + }, "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", @@ -898,6 +955,11 @@ "isarray": "1.0.0" } }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -1292,6 +1354,11 @@ "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", "dev": true }, + "exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=" + }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -1378,6 +1445,11 @@ "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=", "dev": true }, + "file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -1445,6 +1517,14 @@ "write": "0.2.1" } }, + "for-each": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", + "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", + "requires": { + "is-function": "1.0.1" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -2394,6 +2474,15 @@ "is-glob": "2.0.1" } }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "2.19.0", + "process": "0.5.2" + } + }, "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", @@ -3062,6 +3151,11 @@ } } }, + "ip-regex": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-1.0.3.tgz", + "integrity": "sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0=" + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3128,6 +3222,11 @@ "number-is-nan": "1.0.1" } }, + "is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", @@ -3311,6 +3410,41 @@ } } }, + "jimp": { + "version": "0.2.28", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.2.28.tgz", + "integrity": "sha1-3VKak3GQ9ClXp5N9Gsw6d2KZbqI=", + "requires": { + "bignumber.js": "2.4.0", + "bmp-js": "0.0.3", + "es6-promise": "3.3.1", + "exif-parser": "0.1.12", + "file-type": "3.9.0", + "jpeg-js": "0.2.0", + "load-bmfont": "1.3.0", + "mime": "1.6.0", + "mkdirp": "0.5.1", + "pixelmatch": "4.0.2", + "pngjs": "3.3.2", + "read-chunk": "1.0.1", + "request": "2.81.0", + "stream-to-buffer": "0.1.0", + "tinycolor2": "1.4.1", + "url-regex": "3.2.0" + }, + "dependencies": { + "es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=" + } + } + }, + "jpeg-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz", + "integrity": "sha1-U+RI7J0mPmgyZkZ+lELSxaLvVII=" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -3403,7 +3537,7 @@ "just-extend": { "version": "1.1.27", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", - "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "integrity": "sha1-7G55QQ/5FORyZSq/oOYDwD1g6QU=", "dev": true }, "kind-of": { @@ -3445,6 +3579,27 @@ "integrity": "sha1-bIclfmSKtHW8JOoldFftzB+NC8I=", "dev": true }, + "load-bmfont": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.3.0.tgz", + "integrity": "sha1-u358cQ3mvK/LE8s7jIHgwBMey8k=", + "requires": { + "buffer-equal": "0.0.1", + "mime": "1.6.0", + "parse-bmfont-ascii": "1.0.6", + "parse-bmfont-binary": "1.0.6", + "parse-bmfont-xml": "1.1.3", + "xhr": "2.4.1", + "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -3610,7 +3765,7 @@ "lolex": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.1.tgz", - "integrity": "sha512-mQuW55GhduF3ppo+ZRUTz1PRjEh1hS5BbqU7d8D0ez2OKxHDod7StPPeAVKisZR5aLkHZjdGWSL42LSONUJsZw==", + "integrity": "sha1-PSMZiURx6glQ72RpLq0qUxjP82I=", "dev": true }, "longest": { @@ -3638,7 +3793,7 @@ "marked": { "version": "0.3.12", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz", - "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==" + "integrity": "sha1-fPJf8iUmMvP+JAa94ljpTu6SdRk=" }, "marked-terminal": { "version": "2.0.0", @@ -3750,6 +3905,11 @@ "regex-cache": "0.4.3" } }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, "mime-db": { "version": "1.27.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", @@ -3768,6 +3928,14 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "0.1.1" + } + }, "minimatch": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.2.tgz", @@ -3867,7 +4035,7 @@ "natives": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz", - "integrity": "sha512-8eRaxn8u/4wN8tGkhlc2cgwwvOLMLUMUn4IYTexMgWd+LyUDfeXVkk2ygQR0hvIHbJQXgHujia3ieUUDwNGkEA==" + "integrity": "sha1-ARrM4ffL2H97prMJPWzZOSvhxXQ=" }, "nativescript-doctor": { "version": "0.11.0", @@ -3903,7 +4071,7 @@ "nise": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.0.tgz", - "integrity": "sha512-q9jXh3UNsMV28KeqI43ILz5+c3l+RiNW8mhurEwCKckuHQbL+hTJIKKTiUlCPKlgQ/OukFvSnKB/Jk3+sFbkGA==", + "integrity": "sha1-B51srbvLErow448cmZ82rU1rqlM=", "dev": true, "requires": { "formatio": "1.2.0", @@ -4177,6 +4345,25 @@ "resolved": "https://registry.npmjs.org/over/-/over-0.0.5.tgz", "integrity": "sha1-8phS5w/X4l82DgE6jsRMgq7bVwg=" }, + "parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU=" + }, + "parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY=" + }, + "parse-bmfont-xml": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.3.tgz", + "integrity": "sha1-1rZqNxr9OcUAfZ8O6yYqTyzOe3w=", + "requires": { + "xml-parse-from-string": "1.0.1", + "xml2js": "0.4.19" + } + }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -4188,6 +4375,15 @@ "is-glob": "2.0.1" } }, + "parse-headers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.1.tgz", + "integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=", + "requires": { + "for-each": "0.3.2", + "trim": "0.0.1" + } + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -4298,6 +4494,14 @@ "pinkie": "2.0.4" } }, + "pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=", + "requires": { + "pngjs": "3.3.2" + } + }, "pkg-conf": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz", @@ -4372,6 +4576,11 @@ "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", "dev": true }, + "pngjs": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.2.tgz", + "integrity": "sha512-bVNd3LMXRzdo6s4ehr4XW2wFMu9cb40nPgHEjSSppm8/++Xc+g0b2QQb+SeDesgfANXbjydOr1or9YQ+pcCZPQ==" + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -4383,6 +4592,11 @@ "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + }, "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", @@ -4412,7 +4626,7 @@ "proxy-lib": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/proxy-lib/-/proxy-lib-0.4.0.tgz", - "integrity": "sha512-oUDDpf0NTtKPyXjBNUcKzwZhA9GjEdu8Z47GsxGv5rZvKyCqsSrHurJtlL1yp7uVzA2NOmxd4aX7qmB1ZOdCwQ==", + "integrity": "sha1-Dt7+MSUKacPMU8xJ7RR9zk8zbXs=", "requires": { "osenv": "0.1.4" }, @@ -4539,6 +4753,11 @@ } } }, + "read-chunk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-1.0.1.tgz", + "integrity": "sha1-X2jKswfmY/GZk1J9m1icrORmEZQ=" + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -4787,7 +5006,7 @@ "samsam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "integrity": "sha1-jR2TUOJWItow3j5EumkrUiGrfFA=", "dev": true }, "sax": { @@ -4900,6 +5119,21 @@ } } }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.1.tgz", + "integrity": "sha1-wt/DhquqDD4zxI2z/ocFnmkGXv0=" + } + } + }, "single-line-log": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.3.1.tgz", @@ -4908,7 +5142,7 @@ "sinon": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.1.2.tgz", - "integrity": "sha512-5uLBZPdCWl59Lpbf45ygKj7Z0LVol+ftBe7RDIXOQV/sF58pcFmbK8raA7bt6eljNuGnvBP+/ZxlicVn0emDjA==", + "integrity": "sha1-ZWEFIdkm+1N0LdhM1ZnwuJqC9EA=", "dev": true, "requires": { "diff": "3.4.0", @@ -4923,7 +5157,7 @@ "diff": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", - "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", + "integrity": "sha1-sdhVB9rzlkgo3lSzfQ1zumfdpWw=", "dev": true }, "has-flag": { @@ -4996,7 +5230,7 @@ "source-map-support": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.3.tgz", - "integrity": "sha512-eKkTgWYeBOQqFGXRfKabMFdnWepo51vWqEdoeikaEPFiJC7MCU5j2h4+6Q8npkZTeLGbSyecZvRxiSoWl3rh+w==", + "integrity": "sha1-Kz1f/ymM+k0a/X1DUtVp6aAVjnY=", "dev": true, "requires": { "source-map": "0.6.1" @@ -5005,7 +5239,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -5072,6 +5306,19 @@ "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", "integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=" }, + "stream-to": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-to/-/stream-to-0.2.2.tgz", + "integrity": "sha1-hDBgmNhf25kLn6MAsbPM9V6O8B0=" + }, + "stream-to-buffer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stream-to-buffer/-/stream-to-buffer-0.1.0.tgz", + "integrity": "sha1-JnmdkDqyAlyb1VCsRxcbAPjdgKk=", + "requires": { + "stream-to": "0.2.2" + } + }, "streamroller": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.2.2.tgz", @@ -5340,6 +5587,11 @@ } } }, + "tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" + }, "tough-cookie": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", @@ -5353,6 +5605,11 @@ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", @@ -5609,6 +5866,14 @@ } } }, + "url-regex": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-3.2.0.tgz", + "integrity": "sha1-260eDJ4p4QXdCx8J9oYvf9tIJyQ=", + "requires": { + "ip-regex": "1.0.3" + } + }, "user-home": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", @@ -5737,6 +6002,34 @@ "pegjs": "0.6.2" } }, + "xhr": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.1.tgz", + "integrity": "sha512-pAIU5vBr9Hiy5cpFIbPnwf0C18ZF86DBsZKrlsf87N5De/JbA6RJ83UP/cv+aljl4S40iRVMqP4pr4sF9Dnj0A==", + "requires": { + "global": "4.3.2", + "is-function": "1.0.1", + "parse-headers": "2.0.1", + "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } + } + }, + "xml": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/xml/-/xml-0.0.12.tgz", + "integrity": "sha1-8Is0cQmRK+AChXhfRvFa2OUKX2c=" + }, + "xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha1-qQKekp09vN7RafPG4oI42VpdWig=" + }, "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", diff --git a/package.json b/package.json index d1fbad58cb..fd199c061e 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "chalk": "1.1.0", "chokidar": "1.7.0", "cli-table": "https://github.com/telerik/cli-table/tarball/v0.3.1.2", + "color": "3.0.0", "colors": "1.1.2", "email-validator": "1.0.4", "esprima": "2.7.0", @@ -48,6 +49,7 @@ "ios-device-lib": "0.4.10", "ios-mobileprovision-finder": "1.0.10", "ios-sim-portable": "3.3.0", + "jimp": "0.2.28", "lockfile": "1.0.3", "lodash": "4.13.1", "log4js": "1.0.1", @@ -90,6 +92,7 @@ "@types/chai": "4.0.1", "@types/chai-as-promised": "0.0.31", "@types/chokidar": "1.6.0", + "@types/color": "3.0.0", "@types/lockfile": "1.0.0", "@types/node": "6.0.61", "@types/qr-image": "3.2.0", diff --git a/resources/assets/image-definitions.json b/resources/assets/image-definitions.json new file mode 100644 index 0000000000..08916a5f43 --- /dev/null +++ b/resources/assets/image-definitions.json @@ -0,0 +1,380 @@ +{ + "ios": { + "icons": [ + { + "width": 180, + "height": 180, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-60@3x.png" + }, + { + "width": 120, + "height": 120, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-60@2x.png" + }, + { + "width": 40, + "height": 40, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-40.png" + }, + { + "width": 80, + "height": 80, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-40@2x.png" + }, + { + "width": 120, + "height": 120, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-40@3x.png" + }, + { + "width": 50, + "height": 50, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-50.png" + }, + { + "width": 100, + "height": 100, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-50@2x.png" + }, + { + "width": 60, + "height": 60, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-60.png" + }, + { + "width": 72, + "height": 72, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-72.png" + }, + { + "width": 144, + "height": 144, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-72@2x.png" + }, + { + "width": 76, + "height": 76, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-76.png" + }, + { + "width": 152, + "height": 152, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-76@2x.png" + }, + { + "width": 167, + "height": 167, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-83.5@2x.png" + }, + { + "width": 120, + "height": 120, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-120.png" + }, + { + "width": 29, + "height": 29, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-29.png" + }, + { + "width": 58, + "height": 58, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-29@2x.png" + }, + { + "width": 87, + "height": 87, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-29@3x.png" + }, + { + "width": 57, + "height": 57, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-57.png" + }, + { + "width": 114, + "height": 114, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-57@2x.png" + }, + { + "width": 1024, + "height": 1024, + "directory": "Assets.xcassets/AppIcon.appiconset", + "filename": "icon-1024.png" + } + ], + "splashBackgrounds": [ + { + "width": 768, + "height": 1024, + "directory": "Assets.xcassets/LaunchScreen.AspectFill.imageset", + "filename": "LaunchScreen-AspectFill.png", + "resizeOperation": "blank" + }, + { + "width": 1536, + "height": 2048, + "directory": "Assets.xcassets/LaunchScreen.AspectFill.imageset", + "filename": "LaunchScreen-AspectFill@2x.png", + "resizeOperation": "blank" + }, + { + "width": 2304, + "height": 3072, + "directory": "Assets.xcassets/LaunchScreen.AspectFill.imageset", + "filename": "LaunchScreen-AspectFill@3x.png", + "resizeOperation": "blank" + } + ], + "splashCenterImages": [ + { + "width": 384, + "height": 512, + "directory": "Assets.xcassets/LaunchScreen.Center.imageset", + "filename": "LaunchScreen-Center.png", + "resizeOperation": "overlayWith" + }, + { + "width": 768, + "height": 1024, + "directory": "Assets.xcassets/LaunchScreen.Center.imageset", + "filename": "LaunchScreen-Center@2x.png", + "resizeOperation": "overlayWith" + } + ], + "splashImages": [ + { + "width": 640, + "height": 1136, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-568h@2x.png", + "resizeOperation": "overlayWith" + }, + { + "width": 1024, + "height": 768, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-Landscape.png", + "resizeOperation": "overlayWith" + }, + { + "width": 2048, + "height": 1536, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-Landscape@2x.png", + "resizeOperation": "overlayWith" + }, + { + "width": 2208, + "height": 1242, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-Landscape@3x.png", + "resizeOperation": "overlayWith" + }, + { + "width": 1536, + "height": 2048, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-Portrait@2x.png", + "resizeOperation": "overlayWith" + }, + { + "width": 768, + "height": 1024, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-Portrait.png", + "resizeOperation": "overlayWith" + }, + { + "width": 640, + "height": 960, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default@2x.png", + "resizeOperation": "overlayWith" + }, + { + "width": 320, + "height": 480, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default.png", + "resizeOperation": "overlayWith" + }, + { + "width": 750, + "height": 1344, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-667h@2x.png", + "resizeOperation": "overlayWith" + }, + { + "width": 1242, + "height": 2208, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-736h@3x.png", + "resizeOperation": "overlayWith" + }, + { + "width": 2208, + "height": 1242, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-Landscape-736h.png", + "resizeOperation": "overlayWith" + }, + { + "width": 1125, + "height": 2436, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-1125h.png", + "resizeOperation": "overlayWith" + }, + { + "width": 2436, + "height": 1125, + "directory": "Assets.xcassets/LaunchImage.launchimage", + "filename": "Default-Landscape-X.png", + "resizeOperation": "overlayWith" + } + ] + }, + "android": { + "icons": [ + { + "width": 72, + "height": 72, + "directory": "drawable-hdpi", + "filename": "icon.png" + }, + { + "width": 36, + "height": 36, + "directory": "drawable-ldpi", + "filename": "icon.png" + }, + { + "width": 48, + "height": 48, + "directory": "drawable-mdpi", + "filename": "icon.png" + }, + { + "width": 96, + "height": 96, + "directory": "drawable-xhdpi", + "filename": "icon.png" + }, + { + "width": 144, + "height": 144, + "directory": "drawable-xxhdpi", + "filename": "icon.png" + }, + { + "width": 345, + "height": 345, + "directory": "drawable-xxxhdpi", + "filename": "icon.png" + } + ], + "splashBackgrounds": [ + { + "width": 576, + "height": 768, + "directory": "drawable-hdpi", + "filename": "background.png", + "resizeOperation": "blank" + }, + { + "width": 288, + "height": 384, + "directory": "drawable-ldpi", + "filename": "background.png", + "resizeOperation": "blank" + }, + { + "width": 384, + "height": 512, + "directory": "drawable-mdpi", + "filename": "background.png", + "resizeOperation": "blank" + }, + { + "width": 768, + "height": 1024, + "directory": "drawable-xhdpi", + "filename": "background.png", + "resizeOperation": "blank" + }, + { + "width": 1154, + "height": 1536, + "directory": "drawable-xxhdpi", + "filename": "background.png", + "resizeOperation": "blank" + }, + { + "width": 1536, + "height": 2048, + "directory": "drawable-xxxhdpi", + "filename": "background.png", + "resizeOperation": "blank" + } + ], + "splashCenterImages": [ + { + "width": 476, + "height": 476, + "directory": "drawable-hdpi", + "filename": "logo.png" + }, + { + "width": 188, + "height": 188, + "directory": "drawable-ldpi", + "filename": "logo.png" + }, + { + "width": 284, + "height": 284, + "directory": "drawable-mdpi", + "filename": "logo.png" + }, + { + "width": 668, + "height": 668, + "directory": "drawable-xhdpi", + "filename": "logo.png" + }, + { + "width": 1024, + "height": 1024, + "directory": "drawable-xxhdpi", + "filename": "logo.png" + }, + { + "width": 1024, + "height": 1024, + "directory": "drawable-xxxhdpi", + "filename": "logo.png" + } + ] + } +} \ No newline at end of file diff --git a/test/image-generation-test.png b/test/image-generation-test.png new file mode 100644 index 0000000000..c5666536fb Binary files /dev/null and b/test/image-generation-test.png differ diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 578c7393ee..cc40ba743c 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -16,7 +16,14 @@ describe("nativescript-cli-lib", () => { settingsService: ["setSettings"], deviceEmitter: null, projectService: ["createProject", "isValidNativeScriptProject"], - projectDataService: ["getProjectData", "getProjectDataFromContent", "getNsConfigDefaultContent"], + projectDataService: [ + "getProjectData", + "getProjectDataFromContent", + "getNsConfigDefaultContent", + "getAssetsStructure", + "getIOSAssetsStructure", + "getAndroidAssetsStructure" + ], constants: ["CONFIG_NS_APP_RESOURCES_ENTRY", "CONFIG_NS_APP_ENTRY", "CONFIG_NS_FILE_NAME"], localBuildService: ["build"], deviceLogProvider: null, @@ -37,6 +44,10 @@ describe("nativescript-cli-lib", () => { "isCompanionAppInstalledOnDevices", "mapAbstractToTcpPort", "setLogLevel" + ], + assetsGenerationService: [ + "generateIcons", + "generateSplashScreens" ] }; diff --git a/test/npm-installation-manager.ts b/test/npm-installation-manager.ts index 63c516b846..6c502fe09f 100644 --- a/test/npm-installation-manager.ts +++ b/test/npm-installation-manager.ts @@ -25,6 +25,8 @@ function createTestInjector(): IInjector { testInjector.register("childProcess", ChildProcessLib.ChildProcess); testInjector.register("settingsService", SettingsService); testInjector.register("projectDataService", ProjectDataService); + testInjector.register("devicePlatformsConstants", {}); + testInjector.register("androidResourcesMigrationService", {}); testInjector.register("npmInstallationManager", NpmInstallationManagerLib.NpmInstallationManager); diff --git a/test/project-service.ts b/test/project-service.ts index d75c9e59b9..027598df2d 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -157,6 +157,8 @@ class ProjectIntegrationTest { }); this.testInjector.register("npmInstallationManager", NpmInstallationManager); this.testInjector.register("settingsService", SettingsService); + this.testInjector.register("devicePlatformsConstants", {}); + this.testInjector.register("androidResourcesMigrationService", {}); } } diff --git a/test/services/project-data-service.ts b/test/services/project-data-service.ts index 447dad0413..32e08d6dc9 100644 --- a/test/services/project-data-service.ts +++ b/test/services/project-data-service.ts @@ -3,6 +3,7 @@ import { assert } from "chai"; import { ProjectDataService } from "../../lib/services/project-data-service"; import { LoggerStub } from "../stubs"; import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER } from '../../lib/constants'; +import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants"; const CLIENT_NAME_KEY_IN_PROJECT_FILE = "nativescript"; @@ -59,6 +60,10 @@ const createTestInjector = (readTextData?: string): IInjector => { testInjector.register("projectDataService", ProjectDataService); + testInjector.register("androidResourcesMigrationService", {}); + + testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); + testInjector.register("injector", testInjector); return testInjector; diff --git a/test/stubs.ts b/test/stubs.ts index 8fb02569e6..8a43f1eebd 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -446,6 +446,18 @@ export class ProjectDataService implements IProjectDataService { removeDependency(dependencyName: string): void { } getProjectData(projectDir: string): IProjectData { return null; } + + async getAssetsStructure(opts: IProjectDir): Promise { + return null; + } + + async getIOSAssetsStructure(opts: IProjectDir): Promise { + return null; + } + + async getAndroidAssetsStructure(opts: IProjectDir): Promise { + return null; + } } export class ProjectHelperStub implements IProjectHelper { @@ -661,7 +673,7 @@ export class ProjectChangesService implements IProjectChangesService { } export class CommandsService implements ICommandsService { - public currentCommandData = { commandName: "test", commandArguments: [""]}; + public currentCommandData = { commandName: "test", commandArguments: [""] }; public allCommands(opts: { includeDevCommands: boolean }): string[] { return [];