From 8b4b9541514eaac46bf943a2bdb44b11ee4a5fff Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Mon, 9 Sep 2024 10:48:10 -0400 Subject: [PATCH 1/6] First group of asset generation additions to Trapeze --- package-lock.json | 20 +++ package.json | 8 +- .../project/src/assets/asset-generator.ts | 30 ++++ packages/project/src/assets/asset-types.ts | 167 ++++++++++++++++++ packages/project/src/assets/input-asset.ts | 60 +++++++ packages/project/src/assets/output-asset.ts | 26 +++ packages/project/src/project.ts | 20 +-- 7 files changed, 314 insertions(+), 17 deletions(-) create mode 100644 packages/project/src/assets/asset-generator.ts create mode 100644 packages/project/src/assets/asset-types.ts create mode 100644 packages/project/src/assets/input-asset.ts create mode 100644 packages/project/src/assets/output-asset.ts diff --git a/package-lock.json b/package-lock.json index 7c21e19..3b783a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "packages/project", "packages/website" ], + "dependencies": { + "@types/sharp": "^0.31.1" + }, "devDependencies": { "@changesets/changelog-github": "^0.4.4", "@changesets/cli": "^2.20.0", @@ -5972,6 +5975,15 @@ "@types/node": "*" } }, + "node_modules/@types/sharp": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", + "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/slice-ansi": { "version": "5.0.0", "license": "MIT" @@ -27765,6 +27777,14 @@ "@types/node": "*" } }, + "@types/sharp": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", + "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==", + "requires": { + "@types/node": "*" + } + }, "@types/slice-ansi": { "version": "5.0.0" }, diff --git a/package.json b/package.json index 0e18518..851443c 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,7 @@ "test": "turbo run test", "shipit": "npm run build-jar -w packages/gradle-parse && npm run build -- --force && npm run test && npx changeset publish && git push --tags" }, - "volta": { - "node": "16.18.1", - "npm": "9.1.2" - }, - "packageManager": "npm@9.1.l" + "dependencies": { + "@types/sharp": "^0.31.1" + } } diff --git a/packages/project/src/assets/asset-generator.ts b/packages/project/src/assets/asset-generator.ts new file mode 100644 index 0000000..4b2df77 --- /dev/null +++ b/packages/project/src/assets/asset-generator.ts @@ -0,0 +1,30 @@ +import type { InputAsset } from './input-asset'; +import type { OutputAsset } from './output-asset'; +import type { MobileProject } from '../project'; + +export abstract class AssetGenerator { + constructor(public options: AssetGeneratorOptions) {} + + abstract generate(asset: InputAsset, project: MobileProject): Promise; +} + +export interface AssetGeneratorOptions { + // Background color for icon generation + iconBackgroundColor?: string; + // Background color for icon generation for use in dark mode scenarios + iconBackgroundColorDark?: string; + // Background color for light mode splash generation + splashBackgroundColor?: string; + // Background color for dark mode splash generation + splashBackgroundColorDark?: string; + // Path to the web app manifest + pwaManifestPath?: string; + // Whether to fetch latest device sizes from official apple site + pwaNoAppleFetch?: boolean; + // Scale amount for logo when generating splashes. Default: 0.2 (20%) + logoSplashScale?: number; + // Specific width for logo when generating splashes. (not used by default) + logoSplashTargetWidth?: number; + // Android product flavor name where generated assets will be created. Default: main + androidFlavor?: string; +} diff --git a/packages/project/src/assets/asset-types.ts b/packages/project/src/assets/asset-types.ts new file mode 100644 index 0000000..42160dd --- /dev/null +++ b/packages/project/src/assets/asset-types.ts @@ -0,0 +1,167 @@ +import type { InputAsset } from './input-asset'; + +export interface Assets { + logo: InputAsset | null; + logoDark: InputAsset | null; + icon: InputAsset | null; + iconForeground: InputAsset | null; + iconBackground: InputAsset | null; + splash: InputAsset | null; + splashDark: InputAsset | null; + + iosIcon?: InputAsset | null; + iosSplash?: InputAsset | null; + iosSplashDark?: InputAsset | null; + + androidIcon?: InputAsset | null; + androidIconForeground?: InputAsset | null; + androidIconBackground?: InputAsset | null; + + androidSplash?: InputAsset | null; + androidSplashDark?: InputAsset | null; + androidNotificationIcon?: InputAsset | null; + + pwaIcon?: InputAsset | null; + pwaSplash?: InputAsset | null; + pwaSplashDark?: InputAsset | null; +} + +export const enum AssetKind { + Logo = 'logo', + LogoDark = 'logo-dark', + AdaptiveIcon = 'adaptive-icon', + Icon = 'icon', + IconForeground = 'icon-foreground', + IconBackground = 'icon-background', + NotificationIcon = 'notification-icon', + Splash = 'splash', + SplashDark = 'splash-dark', +} + +export const enum Platform { + Any = 'any', + Ios = 'ios', + Android = 'android', + Pwa = 'pwa', + // Windows = 'windows' +} + +export const enum Format { + Png = 'png', + Jpeg = 'jpeg', + Svg = 'svg', + WebP = 'webp', + Unknown = 'unknown', +} + +export const enum Orientation { + Default = '', + Portrait = 'portrait', + Landscape = 'landscape', +} + +export const enum Theme { + Any = 'any', + Light = 'light', + Dark = 'dark', +} + +export const enum AndroidDensity { + Default = '', + Ldpi = 'ldpi', + Mdpi = 'mdpi', + Hdpi = 'hdpi', + Xhdpi = 'xhdpi', + Xxhdpi = 'xxhdpi', + Xxxhdpi = 'xxxhdpi', + LandLdpi = 'land-ldpi', + LandMdpi = 'land-mdpi', + LandHdpi = 'land-hdpi', + LandXhdpi = 'land-xhdpi', + LandXxhdpi = 'land-xxhdpi', + LandXxxhdpi = 'land-xxxhdpi', + PortLdpi = 'port-ldpi', + PortMdpi = 'port-mdpi', + PortHdpi = 'port-hdpi', + PortXhdpi = 'port-xhdpi', + PortXxhdpi = 'port-xxhdpi', + PortXxxhdpi = 'port-xxxhdpi', + DefaultNight = 'night', + LdpiNight = 'night-ldpi', + MdpiNight = 'night-mdpi', + HdpiNight = 'night-hdpi', + XhdpiNight = 'night-xhdpi', + XxhdpiNight = 'night-xxhdpi', + XxxhdpiNight = 'night-xxxhdpi', + LandLdpiNight = 'land-night-ldpi', + LandMdpiNight = 'land-night-mdpi', + LandHdpiNight = 'land-night-hdpi', + LandXhdpiNight = 'land-night-xhdpi', + LandXxhdpiNight = 'land-night-xxhdpi', + LandXxxhdpiNight = 'land-night-xxxhdpi', + PortLdpiNight = 'port-night-ldpi', + PortMdpiNight = 'port-night-mdpi', + PortHdpiNight = 'port-night-hdpi', + PortXhdpiNight = 'port-night-xhdpi', + PortXxhdpiNight = 'port-night-xxhdpi', + PortXxxhdpiNight = 'port-night-xxxhdpi', +} + +export interface OutputAssetTemplate { + platform: Platform; + kind: AssetKind; + format: Format; + width: number; + height: number; + scale?: number; +} + +export interface IosOutputAssetTemplate extends OutputAssetTemplate { + name: string; + idiom: IosIdiom; +} + +// https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/ImageSetType.html#//apple_ref/doc/uid/TP40015170-CH25-SW2 +export const enum IosIdiom { + Universal = 'universal', + iPhone = 'iphone', + iPad = 'ipad', + Watch = 'watch', + TV = 'tv', +} + +export type IosOutputAssetTemplateIcon = IosOutputAssetTemplate; +export interface IosOutputAssetTemplateSplash extends IosOutputAssetTemplate { + orientation: Orientation; + theme: Theme; +} +export interface PwaOutputAssetTemplate extends OutputAssetTemplate { + name: string; + orientation?: Orientation; + density?: string; +} + +export interface AndroidOutputAssetTemplate extends OutputAssetTemplate { + density: AndroidDensity; +} +export interface AndroidOutputAssetTemplateSplash extends OutputAssetTemplate { + density: AndroidDensity; + orientation: Orientation; +} +export interface AndroidOutputAssetTemplateAdaptiveIcon extends OutputAssetTemplate { + density: AndroidDensity; +} + +// Shape of the Contents.json file inside of ios app appiconset and imageset folders +export interface IosContents { + images: { + filename: string; + size: string; + scale: string; + idiom: string; + }[]; + info?: { + version: number; + author: string; + }; +} diff --git a/packages/project/src/assets/input-asset.ts b/packages/project/src/assets/input-asset.ts new file mode 100644 index 0000000..c9360c3 --- /dev/null +++ b/packages/project/src/assets/input-asset.ts @@ -0,0 +1,60 @@ +import { basename, extname } from 'path'; +import sharp from 'sharp'; + +import type { AssetGenerator } from './asset-generator'; +import type { AssetKind, Platform } from './asset-types'; +import { Format } from './asset-types'; +import type { OutputAsset } from './output-asset'; +import type { MobileProject } from '../project'; + +/** + * An instance of an asset that we will use to generate + * a number of output assets. + */ +export class InputAsset { + private filename: string; + public width?: number; + public height?: number; + + private _sharp: sharp.Sharp | null = null; + + constructor( + public path: string, + public kind: AssetKind, + public platform: Platform, + ) { + this.filename = basename(path); + } + + pipeline(): sharp.Sharp | undefined { + return this._sharp?.clone(); + } + + format(): Format.Jpeg | Format.Png | Format.Svg | Format.Unknown { + const ext = extname(this.filename); + + switch (ext) { + case '.png': + return Format.Png; + case '.jpg': + case '.jpeg': + return Format.Jpeg; + case '.svg': + return Format.Svg; + } + + return Format.Unknown; + } + + async load(): Promise { + this._sharp = await sharp(this.path); + + const metadata = await this._sharp.metadata(); + this.width = metadata.width; + this.height = metadata.height; + } + + async generate(strategy: AssetGenerator, project: MobileProject): Promise { + return strategy.generate(this, project); + } +} diff --git a/packages/project/src/assets/output-asset.ts b/packages/project/src/assets/output-asset.ts new file mode 100644 index 0000000..d4ef1f4 --- /dev/null +++ b/packages/project/src/assets/output-asset.ts @@ -0,0 +1,26 @@ +import type { OutputInfo } from 'sharp'; + +import type { OutputAssetTemplate } from './asset-types'; +import type { InputAsset } from './input-asset'; +import type { MobileProject } from '../project'; + +/** + * An instance of a generated asset + */ +export class OutputAsset { + constructor( + public template: OutputAssetTemplateType, + public asset: InputAsset, + public project: MobileProject, + public destFilenames: { [name: string]: string }, + public outputInfoMap: { [name: string]: OutputInfo }, + ) {} + + getDestFilename(assetName: string): string { + return this.destFilenames[assetName]; + } + + getOutputInfo(assetName: string): OutputInfo { + return this.outputInfoMap[assetName]; + } +} diff --git a/packages/project/src/project.ts b/packages/project/src/project.ts index 8e16dd0..d5f6d22 100644 --- a/packages/project/src/project.ts +++ b/packages/project/src/project.ts @@ -15,6 +15,7 @@ import { NativeIosFramework } from './frameworks/native-ios'; import { NativeAndroidFramework } from './frameworks/native-android'; import { NativeScriptFramework } from './frameworks/nativescript'; import { Logger } from './logger'; +import { Assets } from './assets/asset-types' export class MobileProject { public framework: Framework | null = null; @@ -22,9 +23,13 @@ export class MobileProject { public android: AndroidProject | null = null; vfs: VFS; + assets: Assets | null = null; + directory: string | null = null; + assetDir: string | null = null; + constructor( public projectRoot: string, - public config: MobileProjectConfig = {} + public config: MobileProjectConfig = {}, ) { this.vfs = new VFS(); this.config.projectRoot = projectRoot; @@ -36,16 +41,6 @@ export class MobileProject { if (typeof config.enableIos === 'undefined') { config.enableIos = true; } - - if (this.config.ios) { - this.config.ios.path = join(this.projectRoot, this.config.ios.path ?? ''); - } - if (this.config.android) { - this.config.android.path = join( - this.projectRoot, - this.config.android.path ?? '', - ); - } } async detectFramework(): Promise { @@ -78,7 +73,8 @@ export class MobileProject { if ( this.config?.enableIos && this.config?.ios?.path && - (await pathExists(this.config.ios?.path))) { + (await pathExists(this.config.ios?.path)) + ) { this.ios = new IosProject(this); await this.ios?.load(); } From 40737a845c71fd1205ccb940616f1fc60b6c5f35 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Mon, 9 Sep 2024 11:11:21 -0400 Subject: [PATCH 2/6] add asset generators --- packages/project/src/assets/android/assets.ts | 382 +++++++++++++ .../project/src/assets/android/generator.ts | 527 ++++++++++++++++++ packages/project/src/assets/ios/assets.ts | 95 ++++ packages/project/src/assets/ios/generator.ts | 370 ++++++++++++ 4 files changed, 1374 insertions(+) create mode 100644 packages/project/src/assets/android/assets.ts create mode 100644 packages/project/src/assets/android/generator.ts create mode 100644 packages/project/src/assets/ios/assets.ts create mode 100644 packages/project/src/assets/ios/generator.ts diff --git a/packages/project/src/assets/android/assets.ts b/packages/project/src/assets/android/assets.ts new file mode 100644 index 0000000..cbac2b7 --- /dev/null +++ b/packages/project/src/assets/android/assets.ts @@ -0,0 +1,382 @@ +import type { + AndroidOutputAssetTemplate, + AndroidOutputAssetTemplateAdaptiveIcon, + AndroidOutputAssetTemplateSplash, +} from '../asset-types'; +import { AssetKind, AndroidDensity, Format, Orientation, Platform } from '../asset-types'; + +export const ANDROID_LDPI_ICON: AndroidOutputAssetTemplate = { + platform: Platform.Android, + kind: AssetKind.Icon, + format: Format.Png, + width: 36, + height: 36, + density: AndroidDensity.Ldpi, +}; + +export const ANDROID_MDPI_ICON: AndroidOutputAssetTemplate = { + platform: Platform.Android, + kind: AssetKind.Icon, + format: Format.Png, + width: 48, + height: 48, + density: AndroidDensity.Mdpi, +}; + +export const ANDROID_HDPI_ICON: AndroidOutputAssetTemplate = { + platform: Platform.Android, + kind: AssetKind.Icon, + format: Format.Png, + width: 72, + height: 72, + density: AndroidDensity.Hdpi, +}; + +export const ANDROID_XHDPI_ICON: AndroidOutputAssetTemplate = { + platform: Platform.Android, + kind: AssetKind.Icon, + format: Format.Png, + width: 96, + height: 96, + density: AndroidDensity.Xhdpi, +}; + +export const ANDROID_XXHDPI_ICON: AndroidOutputAssetTemplate = { + platform: Platform.Android, + kind: AssetKind.Icon, + format: Format.Png, + width: 144, + height: 144, + density: AndroidDensity.Xxhdpi, +}; + +export const ANDROID_XXXHDPI_ICON: AndroidOutputAssetTemplate = { + platform: Platform.Android, + kind: AssetKind.Icon, + format: Format.Png, + width: 192, + height: 192, + density: AndroidDensity.Xxxhdpi, +}; + +/** + * Adaptive icons + */ +export const ANDROID_LDPI_ADAPTIVE_ICON: AndroidOutputAssetTemplateAdaptiveIcon = { + platform: Platform.Android, + kind: AssetKind.AdaptiveIcon, + format: Format.Png, + width: 81, + height: 81, + density: AndroidDensity.Ldpi, +}; + +export const ANDROID_MDPI_ADAPTIVE_ICON: AndroidOutputAssetTemplateAdaptiveIcon = { + platform: Platform.Android, + kind: AssetKind.AdaptiveIcon, + format: Format.Png, + width: 108, + height: 108, + density: AndroidDensity.Mdpi, +}; + +export const ANDROID_HDPI_ADAPTIVE_ICON: AndroidOutputAssetTemplateAdaptiveIcon = { + platform: Platform.Android, + kind: AssetKind.AdaptiveIcon, + format: Format.Png, + width: 162, + height: 162, + density: AndroidDensity.Hdpi, +}; + +export const ANDROID_XHDPI_ADAPTIVE_ICON: AndroidOutputAssetTemplateAdaptiveIcon = { + platform: Platform.Android, + kind: AssetKind.AdaptiveIcon, + format: Format.Png, + width: 216, + height: 216, + density: AndroidDensity.Xhdpi, +}; + +export const ANDROID_XXHDPI_ADAPTIVE_ICON: AndroidOutputAssetTemplateAdaptiveIcon = { + platform: Platform.Android, + kind: AssetKind.AdaptiveIcon, + format: Format.Png, + width: 324, + height: 324, + density: AndroidDensity.Xxhdpi, +}; + +export const ANDROID_XXXHDPI_ADAPTIVE_ICON: AndroidOutputAssetTemplateAdaptiveIcon = { + platform: Platform.Android, + kind: AssetKind.AdaptiveIcon, + format: Format.Png, + width: 432, + height: 432, + density: AndroidDensity.Xxxhdpi, +}; + +// +// Splash screens +// +export const ANDROID_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 320, + height: 480, + density: AndroidDensity.Default, + orientation: Orientation.Default, +}; + +export const ANDROID_LAND_LDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 320, + height: 240, + density: AndroidDensity.LandLdpi, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_MDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 480, + height: 320, + density: AndroidDensity.LandMdpi, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_HDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 800, + height: 480, + density: AndroidDensity.LandHdpi, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_XHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 1280, + height: 720, + density: AndroidDensity.LandXhdpi, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_XXHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 1600, + height: 960, + density: AndroidDensity.LandXxhdpi, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_XXXHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 1920, + height: 1280, + density: AndroidDensity.LandXxxhdpi, + orientation: Orientation.Landscape, +}; + +export const ANDROID_PORT_LDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 240, + height: 320, + density: AndroidDensity.PortLdpi, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_MDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 320, + height: 480, + density: AndroidDensity.PortMdpi, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_HDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 480, + height: 800, + density: AndroidDensity.PortHdpi, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_XHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 720, + height: 1280, + density: AndroidDensity.PortXhdpi, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_XXHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 960, + height: 1600, + density: AndroidDensity.PortXxhdpi, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_XXXHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.Splash, + format: Format.Png, + width: 1280, + height: 1920, + density: AndroidDensity.PortXxxhdpi, + orientation: Orientation.Portrait, +}; + +// Dark/night mode splashes + +export const ANDROID_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 320, + height: 240, + density: AndroidDensity.DefaultNight, + orientation: Orientation.Default, +}; + +export const ANDROID_LAND_LDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 320, + height: 240, + density: AndroidDensity.LandLdpiNight, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_MDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 480, + height: 320, + density: AndroidDensity.LandMdpiNight, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_HDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 800, + height: 480, + density: AndroidDensity.LandHdpiNight, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_XHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 1280, + height: 720, + density: AndroidDensity.LandXhdpiNight, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_XXHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 1600, + height: 960, + density: AndroidDensity.LandXxhdpiNight, + orientation: Orientation.Landscape, +}; + +export const ANDROID_LAND_XXXHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 1920, + height: 1280, + density: AndroidDensity.LandXxxhdpiNight, + orientation: Orientation.Landscape, +}; + +export const ANDROID_PORT_LDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 240, + height: 320, + density: AndroidDensity.PortLdpiNight, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_MDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 320, + height: 480, + density: AndroidDensity.PortMdpiNight, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_HDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 480, + height: 800, + density: AndroidDensity.PortHdpiNight, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_XHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 720, + height: 1280, + density: AndroidDensity.PortXhdpiNight, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_XXHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 960, + height: 1600, + density: AndroidDensity.PortXxhdpiNight, + orientation: Orientation.Portrait, +}; + +export const ANDROID_PORT_XXXHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { + platform: Platform.Android, + kind: AssetKind.SplashDark, + format: Format.Png, + width: 1280, + height: 1920, + density: AndroidDensity.PortXxxhdpiNight, + orientation: Orientation.Portrait, +}; diff --git a/packages/project/src/assets/android/generator.ts b/packages/project/src/assets/android/generator.ts new file mode 100644 index 0000000..92d6489 --- /dev/null +++ b/packages/project/src/assets/android/generator.ts @@ -0,0 +1,527 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { mkdirp, pathExists, writeFile } from '@ionic/utils-fs'; +import { dirname, join, relative } from 'path'; +import type { OutputInfo, Sharp } from 'sharp'; +import sharp from 'sharp'; + +import type { AssetGeneratorOptions } from '../asset-generator'; +import { AssetGenerator } from '../asset-generator'; +import type { + AndroidOutputAssetTemplate, + AndroidOutputAssetTemplateAdaptiveIcon, + AndroidOutputAssetTemplateSplash, +} from '../asset-types'; +import { AssetKind, Platform } from '../asset-types'; +import type { InputAsset } from '../input-asset'; +import { OutputAsset } from '../output-asset'; +import type { MobileProject } from '../../project'; +//import { warn } from '../../util/log'; + +import * as AndroidAssetTemplates from './assets'; + +export class AndroidAssetGenerator extends AssetGenerator { + constructor(options: AssetGeneratorOptions = {}) { + super(options); + } + + async generate(asset: InputAsset, project: MobileProject): Promise { + const androidDir = project.config.android?.path; + + if (!androidDir) { + throw new Error("No android project found") + } + + if (asset.platform !== Platform.Any && asset.platform !== Platform.Android) { + return []; + } + + switch (asset.kind) { + case AssetKind.Logo: + case AssetKind.LogoDark: + return this.generateFromLogo(asset, project); + case AssetKind.Icon: + return this.generateLegacyIcon(asset, project); + case AssetKind.IconForeground: + return this.generateAdaptiveIconForeground(asset, project); + case AssetKind.IconBackground: + return this.generateAdaptiveIconBackground(asset, project); + case AssetKind.Splash: + case AssetKind.SplashDark: + return this.generateSplashes(asset, project); + } + + return []; + } + + /** + * Generate from logo combines all of the other operations into a single operation + * from a single asset source file. In this mode, a logo along with a background color + * is used to generate all icons and splash screens (with dark mode where possible). + */ + private async generateFromLogo(asset: InputAsset, project: MobileProject): Promise { + const pipe = asset.pipeline(); + const generated: OutputAsset[] = []; + + if (!pipe) { + throw new Error('Sharp instance not created'); + } + + // Generate adaptive icons + const generatedAdaptiveIcons = await this._generateAdaptiveIconsFromLogo(project, asset, pipe); + generated.push(...generatedAdaptiveIcons); + + if (asset.kind === AssetKind.Logo) { + // Generate legacy icons + const generatedLegacyIcons = await this.generateLegacyIcon(asset, project); + generated.push(...generatedLegacyIcons); + + const splashes = Object.values(AndroidAssetTemplates).filter((a) => a.kind === AssetKind.Splash); + + const generatedSplashes = await Promise.all( + splashes.map(async (splash) => { + return this._generateSplashesFromLogo( + project, + asset, + splash, + pipe, + this.options.splashBackgroundColor ?? '#ffffff', + ); + }), + ); + + generated.push(...generatedSplashes); + } + + // Generate dark splashes + const darkSplashes = Object.values(AndroidAssetTemplates).filter((a) => a.kind === AssetKind.SplashDark); + const generatedSplashes = await Promise.all( + darkSplashes.map(async (splash) => { + return this._generateSplashesFromLogo( + project, + asset, + splash, + pipe, + this.options.splashBackgroundColorDark ?? '#111111', + ); + }), + ); + + generated.push(...generatedSplashes); + + return [...generated]; + } + + // Generate adaptive icons from the source logo + private async _generateAdaptiveIconsFromLogo( + project: MobileProject, + asset: InputAsset, + pipe: Sharp, + ): Promise { + // Current versions of Android don't appear to support night mode icons (13+ might?) + // so, for now, we only generate light mode ones + if (asset.kind === AssetKind.LogoDark) { + return []; + } + + // Create the background pipeline for the generated icons + const backgroundPipe = sharp({ + create: { + width: asset.width!, + height: asset.height!, + channels: 4, + background: + asset.kind === AssetKind.Logo + ? this.options.iconBackgroundColor ?? '#ffffff' + : this.options.iconBackgroundColorDark ?? '#111111', + }, + }); + + const icons = Object.values(AndroidAssetTemplates).filter( + (a) => a.kind === AssetKind.AdaptiveIcon, + ) as AndroidOutputAssetTemplateAdaptiveIcon[]; + + const backgroundImages = await Promise.all( + icons.map(async (icon) => { + return await this._generateAdaptiveIconBackground(project, asset, icon, backgroundPipe); + }), + ); + + const foregroundImages = await Promise.all( + icons.map(async (icon) => { + return await this._generateAdaptiveIconForeground(project, asset, icon, pipe); + }), + ); + + return [...foregroundImages, ...backgroundImages]; + } + + private async _generateSplashesFromLogo( + project: MobileProject, + asset: InputAsset, + splash: AndroidOutputAssetTemplate, + pipe: Sharp, + backgroundColor: string, + ): Promise { + // Generate light splash + const resPath = this.getResPath(project); + + let drawableDir = `drawable`; + if (splash.density) { + drawableDir = `drawable-${splash.density}`; + } + + const parentDir = join(resPath, drawableDir); + if (!(await pathExists(parentDir))) { + await mkdirp(parentDir); + } + const dest = join(resPath, drawableDir, 'splash.png'); + + const targetLogoWidthPercent = this.options.logoSplashScale ?? 0.2; + let targetWidth = this.options.logoSplashTargetWidth ?? Math.floor((splash.width ?? 0) * targetLogoWidthPercent); + + if (targetWidth > splash.width || targetWidth > splash.height) { + targetWidth = Math.floor((splash.width ?? 0) * targetLogoWidthPercent); + } + + if (targetWidth > splash.width || targetWidth > splash.height) { + warn(`Logo dimensions exceed dimensions of splash ${splash.width}x${splash.height}, using default logo size`); + targetWidth = Math.floor((splash.width ?? 0) * 0.2); + } + + const canvas = sharp({ + create: { + width: splash.width ?? 0, + height: splash.height ?? 0, + channels: 4, + background: backgroundColor, + }, + }); + + const resized = await sharp(asset.path).resize(targetWidth).toBuffer(); + + const outputInfo = await canvas + .composite([{ input: resized, gravity: sharp.gravity.center }]) + .png() + .toFile(dest); + + const splashOutput = new OutputAsset( + splash, + asset, + project, + { + [dest]: dest, + }, + { + [dest]: outputInfo, + }, + ); + + return splashOutput; + } + + private async generateLegacyIcon(asset: InputAsset, project: MobileProject): Promise { + const icons = Object.values(AndroidAssetTemplates).filter( + (a) => a.kind === AssetKind.Icon, + ) as AndroidOutputAssetTemplate[]; + + const pipe = asset.pipeline(); + + if (!pipe) { + throw new Error('Sharp instance not created'); + } + + const collected = await Promise.all( + icons.map(async (icon) => { + const [dest, outputInfo] = await this.generateLegacyLauncherIcon(project, asset, icon); + + return new OutputAsset( + icon, + asset, + project, + { [`mipmap-${icon.density}/ic_launcher.png`]: dest }, + { [`mipmap-${icon.density}/ic_launcher.png`]: outputInfo }, + ); + }), + ); + + collected.push( + ...(await Promise.all( + icons.map(async (icon) => { + const [dest, outputInfo] = await this.generateRoundLauncherIcon(project, asset, icon); + + return new OutputAsset( + icon, + asset, + project, + { [`mipmap-${icon.density}/ic_launcher_round.png`]: dest }, + { [`mipmap-${icon.density}/ic_launcher_round.png`]: outputInfo }, + ); + }), + )), + ); + + await this.updateManifest(project); + + return collected; + } + + private async generateLegacyLauncherIcon( + project: MobileProject, + asset: InputAsset, + template: AndroidOutputAssetTemplate, + ): Promise<[string, OutputInfo]> { + const resPath = this.getResPath(project); + const parentDir = join(resPath, `mipmap-${template.density}`); + if (!(await pathExists(parentDir))) { + await mkdirp(parentDir); + } + const destRound = join(resPath, `mipmap-${template.density}`, 'ic_launcher.png'); + + // This pipeline is trick, but we need two separate pipelines + // per https://github.com/lovell/sharp/issues/2378#issuecomment-864132578 + const padding = 8; + const resized = await sharp(asset.path) + .resize(template.width, template.height) + // .composite([{ input: Buffer.from(svg), blend: 'dest-in' }]) + .toBuffer(); + const composited = await sharp(resized) + .resize(Math.max(0, template.width - padding * 2), Math.max(0, template.height - padding * 2)) + .extend({ + top: padding, + bottom: padding, + left: padding, + right: padding, + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }) + .toBuffer(); + const outputInfo = await sharp(composited).png().toFile(destRound); + + return [destRound, outputInfo]; + } + + private async generateRoundLauncherIcon( + project: MobileProject, + asset: InputAsset, + template: AndroidOutputAssetTemplate, + ): Promise<[string, OutputInfo]> { + const svg = ``; + + const resPath = this.getResPath(project); + const destRound = join(resPath, `mipmap-${template.density}`, 'ic_launcher_round.png'); + + // This pipeline is tricky, but we need two separate pipelines + // per https://github.com/lovell/sharp/issues/2378#issuecomment-864132578 + const resized = await sharp(asset.path).resize(template.width, template.height).toBuffer(); + const composited = await sharp(resized) + .composite([{ input: Buffer.from(svg), blend: 'dest-in' }]) + .toBuffer(); + const outputInfo = await sharp(composited).png().toFile(destRound); + + return [destRound, outputInfo]; + } + + private async generateAdaptiveIconForeground(asset: InputAsset, project: MobileProject): Promise { + const icons = Object.values(AndroidAssetTemplates).filter( + (a) => a.kind === AssetKind.Icon, + ) as AndroidOutputAssetTemplateAdaptiveIcon[]; + + const pipe = asset.pipeline(); + + if (!pipe) { + throw new BadPipelineError('Sharp instance not created'); + } + + return Promise.all( + icons.map(async (icon) => { + return await this._generateAdaptiveIconForeground(project, asset, icon, pipe); + }), + ); + } + + private async _generateAdaptiveIconForeground( + project: MobileProject, + asset: InputAsset, + icon: AndroidOutputAssetTemplateAdaptiveIcon, + pipe: Sharp, + ) { + const resPath = this.getResPath(project); + + // Create the foreground and background images + const destForeground = join(resPath, `mipmap-${icon.density}`, 'ic_launcher_foreground.png'); + const parentDir = dirname(destForeground); + if (!(await pathExists(parentDir))) { + await mkdirp(parentDir); + } + const outputInfoForeground = await pipe.resize(icon.width, icon.height).png().toFile(destForeground); + + // Create the adaptive icon XML + const icLauncherXml = ` + + + + + + + + + + `.trim(); + + const mipmapAnyPath = join(resPath, `mipmap-anydpi-v26`); + if (!(await pathExists(mipmapAnyPath))) { + await mkdirp(mipmapAnyPath); + } + const destIcLauncher = join(mipmapAnyPath, `ic_launcher.xml`); + const destIcLauncherRound = join(mipmapAnyPath, `ic_launcher_round.xml`); + await writeFile(destIcLauncher, icLauncherXml); + await writeFile(destIcLauncherRound, icLauncherXml); + + // Return the created files for this OutputAsset + return new OutputAsset( + icon, + asset, + project, + { + [`mipmap-${icon.density}/ic_launcher_foreground.png`]: destForeground, + 'mipmap-anydpi-v26/ic_launcher.xml': destIcLauncher, + 'mipmap-anydpi-v26/ic_launcher_round.xml': destIcLauncherRound, + }, + { + [`mipmap-${icon.density}/ic_launcher_foreground.png`]: outputInfoForeground, + }, + ); + } + + private async generateAdaptiveIconBackground(asset: InputAsset, project: MobileProject): Promise { + const icons = Object.values(AndroidAssetTemplates).filter( + (a) => a.kind === AssetKind.Icon, + ) as AndroidOutputAssetTemplateAdaptiveIcon[]; + + const pipe = asset.pipeline(); + + if (!pipe) { + throw new BadPipelineError('Sharp instance not created'); + } + + return Promise.all( + icons.map(async (icon) => { + return await this._generateAdaptiveIconBackground(project, asset, icon, pipe); + }), + ); + } + private async _generateAdaptiveIconBackground( + project: MobileProject, + asset: InputAsset, + icon: AndroidOutputAssetTemplateAdaptiveIcon, + pipe: Sharp, + ) { + const resPath = this.getResPath(project); + + const destBackground = join(resPath, `mipmap-${icon.density}`, 'ic_launcher_background.png'); + const parentDir = dirname(destBackground); + if (!(await pathExists(parentDir))) { + await mkdirp(parentDir); + } + + const outputInfoBackground = await pipe.resize(icon.width, icon.height).png().toFile(destBackground); + + // Create the adaptive icon XML + const icLauncherXml = ` + + + + + + + + + + `.trim(); + + const mipmapAnyPath = join(resPath, `mipmap-anydpi-v26`); + if (!(await pathExists(mipmapAnyPath))) { + await mkdirp(mipmapAnyPath); + } + const destIcLauncher = join(mipmapAnyPath, `ic_launcher.xml`); + const destIcLauncherRound = join(mipmapAnyPath, `ic_launcher_round.xml`); + await writeFile(destIcLauncher, icLauncherXml); + await writeFile(destIcLauncherRound, icLauncherXml); + + // Return the created files for this OutputAsset + return new OutputAsset( + icon, + asset, + project, + { + [`mipmap-${icon.density}/ic_launcher_background.png`]: destBackground, + 'mipmap-anydpi-v26/ic_launcher.xml': destIcLauncher, + 'mipmap-anydpi-v26/ic_launcher_round.xml': destIcLauncherRound, + }, + { + [`mipmap-${icon.density}/ic_launcher_background.png`]: outputInfoBackground, + }, + ); + } + + private async updateManifest(project: MobileProject) { + project.android?.getAndroidManifest()?.setAttrs('manifest/application', { + 'android:icon': '@mipmap/ic_launcher', + 'android:roundIcon': '@mipmap/ic_launcher_round', + }); + + await project.commit(); + } + + private async generateSplashes(asset: InputAsset, project: MobileProject): Promise { + const pipe = asset.pipeline(); + + if (!pipe) { + throw new BadPipelineError('Sharp instance not created'); + } + + const splashes = ( + asset.kind === AssetKind.Splash + ? Object.values(AndroidAssetTemplates).filter((a) => a.kind === AssetKind.Splash) + : Object.values(AndroidAssetTemplates).filter((a) => a.kind === AssetKind.SplashDark) + ) as AndroidOutputAssetTemplateSplash[]; + + const resPath = this.getResPath(project); + + const collected = await Promise.all( + splashes.map(async (splash) => { + const [dest, outputInfo] = await this.generateSplash(project, asset, splash, pipe); + + const relPath = relative(resPath, dest); + return new OutputAsset(splash, asset, project, { [relPath]: dest }, { [relPath]: outputInfo }); + }), + ); + + return collected; + } + + private async generateSplash( + project: MobileProject, + asset: InputAsset, + template: AndroidOutputAssetTemplateSplash, + pipe: Sharp, + ): Promise<[string, OutputInfo]> { + const drawableDir = template.density ? `drawable-${template.density}` : 'drawable'; + + const resPath = this.getResPath(project); + const parentDir = join(resPath, drawableDir); + if (!(await pathExists(parentDir))) { + await mkdirp(parentDir); + } + const dest = join(resPath, drawableDir, 'splash.png'); + + const outputInfo = await pipe.resize(template.width, template.height).png().toFile(dest); + + return [dest, outputInfo]; + } + + private getResPath(project: MobileProject): string { + return join(project.config.android!.path!, 'app', 'src', this.options.androidFlavor ?? 'main', 'res'); + } +} diff --git a/packages/project/src/assets/ios/assets.ts b/packages/project/src/assets/ios/assets.ts new file mode 100644 index 0000000..b774e97 --- /dev/null +++ b/packages/project/src/assets/ios/assets.ts @@ -0,0 +1,95 @@ +import type { IosOutputAssetTemplate, IosOutputAssetTemplateSplash } from '../asset-types'; +import { AssetKind, Format, IosIdiom, Orientation, Platform, Theme } from '../asset-types'; + +/** + * 1024px Icon + * + * - iOS 1024 icon + */ +export const IOS_1024_ICON: IosOutputAssetTemplate = { + platform: Platform.Ios, + idiom: IosIdiom.Universal, + kind: AssetKind.Icon, + name: 'AppIcon-512@2x.png', + format: Format.Png, + width: 1024, + height: 1024, +}; + +export const IOS_1X_UNIVERSAL_ANYANY_SPLASH: IosOutputAssetTemplateSplash = { + platform: Platform.Ios, + idiom: IosIdiom.Universal, + kind: AssetKind.Splash, + name: 'Default@1x~universal~anyany.png', + format: Format.Png, + width: 2732, + height: 2732, + orientation: Orientation.Portrait, + scale: 1, + theme: Theme.Any, +}; + +export const IOS_2X_UNIVERSAL_ANYANY_SPLASH: IosOutputAssetTemplateSplash = { + platform: Platform.Ios, + idiom: IosIdiom.Universal, + kind: AssetKind.Splash, + name: 'Default@2x~universal~anyany.png', + format: Format.Png, + width: 2732, + height: 2732, + orientation: Orientation.Portrait, + scale: 2, + theme: Theme.Any, +}; + +export const IOS_3X_UNIVERSAL_ANYANY_SPLASH: IosOutputAssetTemplateSplash = { + platform: Platform.Ios, + idiom: IosIdiom.Universal, + kind: AssetKind.Splash, + name: 'Default@3x~universal~anyany.png', + format: Format.Png, + width: 2732, + height: 2732, + orientation: Orientation.Portrait, + scale: 3, + theme: Theme.Any, +}; + +export const IOS_1X_UNIVERSAL_ANYANY_SPLASH_DARK: IosOutputAssetTemplateSplash = { + platform: Platform.Ios, + idiom: IosIdiom.Universal, + kind: AssetKind.SplashDark, + name: 'Default@1x~universal~anyany-dark.png', + format: Format.Png, + width: 2732, + height: 2732, + orientation: Orientation.Portrait, + scale: 1, + theme: Theme.Dark, +}; + +export const IOS_2X_UNIVERSAL_ANYANY_SPLASH_DARK: IosOutputAssetTemplateSplash = { + platform: Platform.Ios, + idiom: IosIdiom.Universal, + kind: AssetKind.SplashDark, + name: 'Default@2x~universal~anyany-dark.png', + format: Format.Png, + width: 2732, + height: 2732, + orientation: Orientation.Portrait, + scale: 2, + theme: Theme.Dark, +}; + +export const IOS_3X_UNIVERSAL_ANYANY_SPLASH_DARK: IosOutputAssetTemplateSplash = { + platform: Platform.Ios, + idiom: IosIdiom.Universal, + kind: AssetKind.SplashDark, + name: 'Default@3x~universal~anyany-dark.png', + format: Format.Png, + width: 2732, + height: 2732, + orientation: Orientation.Portrait, + scale: 3, + theme: Theme.Dark, +}; diff --git a/packages/project/src/assets/ios/generator.ts b/packages/project/src/assets/ios/generator.ts new file mode 100644 index 0000000..7e985ba --- /dev/null +++ b/packages/project/src/assets/ios/generator.ts @@ -0,0 +1,370 @@ +import { readFile, rmSync, writeFile } from '@ionic/utils-fs'; +import { join } from 'path'; +import sharp from 'sharp'; + +import type { AssetGeneratorOptions } from '../asset-generator'; +import { AssetGenerator } from '../asset-generator'; +import type { IosOutputAssetTemplate } from '../asset-types'; +import { AssetKind, Platform } from '../asset-types'; +import type { InputAsset } from '../input-asset'; +import { OutputAsset } from '../output-asset'; +import type { MobileProject } from '../../project'; + +import { + IOS_1X_UNIVERSAL_ANYANY_SPLASH, + IOS_2X_UNIVERSAL_ANYANY_SPLASH, + IOS_3X_UNIVERSAL_ANYANY_SPLASH, + IOS_1X_UNIVERSAL_ANYANY_SPLASH_DARK, + IOS_2X_UNIVERSAL_ANYANY_SPLASH_DARK, + IOS_3X_UNIVERSAL_ANYANY_SPLASH_DARK, +} from './assets'; +import * as IosAssetTemplates from './assets'; + +export const IOS_APP_ICON_SET_NAME = 'AppIcon'; +export const IOS_APP_ICON_SET_PATH = `App/Assets.xcassets/${IOS_APP_ICON_SET_NAME}.appiconset`; +export const IOS_SPLASH_IMAGE_SET_NAME = 'Splash'; +export const IOS_SPLASH_IMAGE_SET_PATH = `App/Assets.xcassets/${IOS_SPLASH_IMAGE_SET_NAME}.imageset`; + +export class IosAssetGenerator extends AssetGenerator { + constructor(options: AssetGeneratorOptions = {}) { + super(options); + } + + async generate(asset: InputAsset, project: MobileProject): Promise { + const iosDir = project.config.ios?.path; + + if (!iosDir) { + throw new Error('No ios project found'); + } + + if (asset.platform !== Platform.Any && asset.platform !== Platform.Ios) { + return []; + } + + switch (asset.kind) { + case AssetKind.Logo: + case AssetKind.LogoDark: + return this.generateFromLogo(asset, project); + case AssetKind.Icon: + return this.generateIcons(asset, project); + case AssetKind.Splash: + case AssetKind.SplashDark: + return this.generateSplashes(asset, project); + } + + return []; + } + + private async generateFromLogo(asset: InputAsset, project: MobileProject): Promise { + const pipe = asset.pipeline(); + + if (!pipe) { + throw new Error('Sharp instance not created'); + } + + const iosDir = project.config.ios!.path!; + + // Generate logos + let logos: OutputAsset[] = []; + if (asset.kind === AssetKind.Logo) { + logos = await this.generateIconsForLogo(asset, project); + } + + const generated: OutputAsset[] = []; + + const targetLogoWidthPercent = this.options.logoSplashScale ?? 0.2; + const targetWidth = this.options.logoSplashTargetWidth ?? Math.floor((asset.width ?? 0) * targetLogoWidthPercent); + + if (asset.kind === AssetKind.Logo) { + // Generate light splash + const lightDefaultBackground = '#ffffff'; + const lightSplashes = [ + IOS_1X_UNIVERSAL_ANYANY_SPLASH, + IOS_2X_UNIVERSAL_ANYANY_SPLASH, + IOS_3X_UNIVERSAL_ANYANY_SPLASH, + ]; + const lightSplashesGenerated: OutputAsset[] = []; + + for (const lightSplash of lightSplashes) { + const lightDest = join(iosDir, IOS_SPLASH_IMAGE_SET_PATH, lightSplash.name); + + const canvas = sharp({ + create: { + width: lightSplash.width ?? 0, + height: lightSplash.height ?? 0, + channels: 4, + background: this.options.splashBackgroundColor ?? lightDefaultBackground, + }, + }); + const resized = await sharp(asset.path).resize(targetWidth).toBuffer(); + const lightOutputInfo = await canvas + .composite([{ input: resized, gravity: sharp.gravity.center }]) + .png() + .toFile(lightDest); + + const lightSplashOutput = new OutputAsset( + lightSplash, + asset, + project, + { + [lightDest]: lightDest, + }, + { + [lightDest]: lightOutputInfo, + }, + ); + + generated.push(lightSplashOutput); + lightSplashesGenerated.push(lightSplashOutput); + } + + await this.updateSplashContentsJson(lightSplashesGenerated, project); + } + + // Generate dark splash + const darkDefaultBackground = '#111111'; + const darkSplashes = [ + IOS_1X_UNIVERSAL_ANYANY_SPLASH_DARK, + IOS_2X_UNIVERSAL_ANYANY_SPLASH_DARK, + IOS_3X_UNIVERSAL_ANYANY_SPLASH_DARK, + ]; + const darkSplashesGenerated: OutputAsset[] = []; + + for (const darkSplash of darkSplashes) { + const darkDest = join(iosDir, IOS_SPLASH_IMAGE_SET_PATH, darkSplash.name); + const canvas = sharp({ + create: { + width: darkSplash.width ?? 0, + height: darkSplash.height ?? 0, + channels: 4, + background: this.options.splashBackgroundColorDark ?? darkDefaultBackground, + }, + }); + const resized = await sharp(asset.path).resize(targetWidth).toBuffer(); + const darkOutputInfo = await canvas + .composite([{ input: resized, gravity: sharp.gravity.center }]) + .png() + .toFile(darkDest); + + const darkSplashOutput = new OutputAsset( + darkSplash, + asset, + project, + { + [darkDest]: darkDest, + }, + { + [darkDest]: darkOutputInfo, + }, + ); + + generated.push(darkSplashOutput); + darkSplashesGenerated.push(darkSplashOutput); + } + + await this.updateSplashContentsJsonDark(darkSplashesGenerated, project); + + return [...logos, ...generated]; + } + + private async _generateIcons( + asset: InputAsset, + project: MobileProject, + icons: IosOutputAssetTemplate[], + ): Promise { + const pipe = asset.pipeline(); + + if (!pipe) { + throw new Error('Sharp instance not created'); + } + + const iosDir = project.config.ios!.path!; + const lightDefaultBackground = '#ffffff'; + const generated = await Promise.all( + icons.map(async (icon) => { + const dest = join(iosDir, IOS_APP_ICON_SET_PATH, icon.name); + + const outputInfo = await pipe + .resize(icon.width, icon.height) + .png() + .flatten({ background: this.options.iconBackgroundColor ?? lightDefaultBackground }) + .toFile(dest); + + return new OutputAsset( + icon, + asset, + project, + { + [icon.name]: dest, + }, + { + [icon.name]: outputInfo, + }, + ); + }), + ); + + await this.updateIconsContentsJson(generated, project); + + return generated; + } + + // Generate ALL the icons when only given a logo + private async generateIconsForLogo(asset: InputAsset, project: MobileProject): Promise { + const icons = Object.values(IosAssetTemplates).filter((a) => [AssetKind.Icon].find((i) => i === a.kind)); + + return this._generateIcons(asset, project, icons as IosOutputAssetTemplate[]); + } + + private async generateIcons(asset: InputAsset, project: MobileProject): Promise { + const icons = Object.values(IosAssetTemplates).filter((a) => [AssetKind.Icon].find((i) => i === a.kind)); + + return this._generateIcons(asset, project, icons as IosOutputAssetTemplate[]); + } + + private async generateSplashes(asset: InputAsset, project: MobileProject): Promise { + const pipe = asset.pipeline(); + + if (!pipe) { + throw new Error('Sharp instance not created'); + } + + const assetMetas = + asset.kind === AssetKind.Splash + ? [IOS_1X_UNIVERSAL_ANYANY_SPLASH, IOS_2X_UNIVERSAL_ANYANY_SPLASH, IOS_3X_UNIVERSAL_ANYANY_SPLASH] + : [ + IOS_1X_UNIVERSAL_ANYANY_SPLASH_DARK, + IOS_2X_UNIVERSAL_ANYANY_SPLASH_DARK, + IOS_3X_UNIVERSAL_ANYANY_SPLASH_DARK, + ]; + + const generated: OutputAsset[] = []; + + for (const assetMeta of assetMetas) { + const iosDir = project.config.ios!.path!; + const dest = join(iosDir, IOS_SPLASH_IMAGE_SET_PATH, assetMeta.name); + + const outputInfo = await pipe.resize(assetMeta.width, assetMeta.height).png().toFile(dest); + + const g = new OutputAsset( + assetMeta, + asset, + project, + { + [assetMeta.name]: dest, + }, + { + [assetMeta.name]: outputInfo, + }, + ); + + generated.push(g); + } + + if (asset.kind === AssetKind.Splash) { + await this.updateSplashContentsJson(generated, project); + } else if (asset.kind === AssetKind.SplashDark) { + // Need to register this as a dark-mode splash + await this.updateSplashContentsJsonDark(generated, project); + } + + return generated; + } + + private async updateIconsContentsJson(generated: OutputAsset[], project: MobileProject) { + const assetsPath = join(project.config.ios!.path!, IOS_APP_ICON_SET_PATH); + const contentsJsonPath = join(assetsPath, 'Contents.json'); + const json = await readFile(contentsJsonPath, { encoding: 'utf-8' }); + + const parsed = JSON.parse(json); + + const withoutMissing = []; + for (const g of generated) { + const width = g.template.width; + const height = g.template.height; + + parsed.images.map((i: any) => { + if (i.filename !== (g.template as IosOutputAssetTemplate).name) { + rmSync(join(assetsPath, i.filename)); + } + }); + + withoutMissing.push({ + idiom: (g.template as IosOutputAssetTemplate).idiom, + size: `${width}x${height}`, + filename: (g.template as IosOutputAssetTemplate).name, + platform: Platform.Ios, + }); + } + + parsed.images = withoutMissing; + + await writeFile(contentsJsonPath, JSON.stringify(parsed, null, 2)); + } + + private async updateSplashContentsJson(generated: OutputAsset[], project: MobileProject) { + const contentsJsonPath = join(project.config.ios!.path!, IOS_SPLASH_IMAGE_SET_PATH, 'Contents.json'); + const json = await readFile(contentsJsonPath, { encoding: 'utf-8' }); + + const parsed = JSON.parse(json); + + const withoutMissing = parsed.images.filter((i: any) => !!i.filename); + + for (const g of generated) { + const existing = withoutMissing.find( + (f: any) => + f.scale === `${g.template.scale}x` && f.idiom === 'universal' && typeof f.appearances === 'undefined', + ); + + if (existing) { + existing.filename = (g.template as IosOutputAssetTemplate).name; + } else { + withoutMissing.push({ + idiom: 'universal', + scale: `${g.template.scale ?? 1}x`, + filename: (g.template as IosOutputAssetTemplate).name, + }); + } + } + + parsed.images = withoutMissing; + + await writeFile(contentsJsonPath, JSON.stringify(parsed, null, 2)); + } + + private async updateSplashContentsJsonDark(generated: OutputAsset[], project: MobileProject) { + const contentsJsonPath = join(project.config.ios!.path!, IOS_SPLASH_IMAGE_SET_PATH, 'Contents.json'); + const json = await readFile(contentsJsonPath, { encoding: 'utf-8' }); + + const parsed = JSON.parse(json); + + const withoutMissing = parsed.images.filter((i: any) => !!i.filename); + + for (const g of generated) { + const existing = withoutMissing.find( + (f: any) => + f.scale === `${g.template.scale}x` && f.idiom === 'universal' && typeof f.appearances !== 'undefined', + ); + + if (existing) { + existing.filename = (g.template as IosOutputAssetTemplate).name; + } else { + withoutMissing.push({ + appearances: [ + { + appearance: 'luminosity', + value: 'dark', + }, + ], + idiom: 'universal', + scale: `${g.template.scale ?? 1}x`, + filename: (g.template as IosOutputAssetTemplate).name, + }); + } + } + + parsed.images = withoutMissing; + + await writeFile(contentsJsonPath, JSON.stringify(parsed, null, 2)); + } +} From 4589a8fe636298b7086e7d7cb836cc18b96cbb40 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Mon, 9 Sep 2024 11:47:35 -0400 Subject: [PATCH 3/6] Project fixes --- packages/project/src/assets/android/generator.ts | 10 +++++----- packages/project/src/project.ts | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/project/src/assets/android/generator.ts b/packages/project/src/assets/android/generator.ts index 92d6489..ec7c321 100644 --- a/packages/project/src/assets/android/generator.ts +++ b/packages/project/src/assets/android/generator.ts @@ -15,7 +15,7 @@ import { AssetKind, Platform } from '../asset-types'; import type { InputAsset } from '../input-asset'; import { OutputAsset } from '../output-asset'; import type { MobileProject } from '../../project'; -//import { warn } from '../../util/log'; +import { Logger } from '../../logger'; import * as AndroidAssetTemplates from './assets'; @@ -184,7 +184,7 @@ export class AndroidAssetGenerator extends AssetGenerator { } if (targetWidth > splash.width || targetWidth > splash.height) { - warn(`Logo dimensions exceed dimensions of splash ${splash.width}x${splash.height}, using default logo size`); + Logger.warn(`Logo dimensions exceed dimensions of splash ${splash.width}x${splash.height}, using default logo size`); targetWidth = Math.floor((splash.width ?? 0) * 0.2); } @@ -330,7 +330,7 @@ export class AndroidAssetGenerator extends AssetGenerator { const pipe = asset.pipeline(); if (!pipe) { - throw new BadPipelineError('Sharp instance not created'); + throw new Error('Sharp instance not created'); } return Promise.all( @@ -402,7 +402,7 @@ export class AndroidAssetGenerator extends AssetGenerator { const pipe = asset.pipeline(); if (!pipe) { - throw new BadPipelineError('Sharp instance not created'); + throw new Error('Sharp instance not created'); } return Promise.all( @@ -478,7 +478,7 @@ export class AndroidAssetGenerator extends AssetGenerator { const pipe = asset.pipeline(); if (!pipe) { - throw new BadPipelineError('Sharp instance not created'); + throw new Error('Sharp instance not created'); } const splashes = ( diff --git a/packages/project/src/project.ts b/packages/project/src/project.ts index d5f6d22..4c20845 100644 --- a/packages/project/src/project.ts +++ b/packages/project/src/project.ts @@ -99,4 +99,20 @@ export class MobileProject { const srcPath = join(this.projectRoot, src); return copy(srcPath, destPath); } + + async androidExists(): Promise { + return this.config.android?.path !== undefined && (await pathExists(this.config.android?.path)); + } + + async iosExists(): Promise { + return this.config.ios?.path !== undefined && (await pathExists(this.config.ios?.path)); + } + + async assetDirExists(): Promise { + if (this.assetDir !== null) { + return pathExists(this.assetDir); + } else { + return false + } + } } From 3d3f6df3bd397ac69b553c7df219435964d3b528 Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Mon, 16 Sep 2024 14:45:57 -0400 Subject: [PATCH 4/6] Trapeze Icon Support --- packages/project/src/assets/android/assets.ts | 270 +----------------- .../project/src/assets/android/generator.ts | 82 ------ packages/project/src/assets/asset-types.ts | 8 +- packages/project/src/assets/ios/assets.ts | 95 ------ packages/project/src/assets/ios/generator.ts | 260 ++--------------- 5 files changed, 21 insertions(+), 694 deletions(-) delete mode 100644 packages/project/src/assets/ios/assets.ts diff --git a/packages/project/src/assets/android/assets.ts b/packages/project/src/assets/android/assets.ts index cbac2b7..4e62c31 100644 --- a/packages/project/src/assets/android/assets.ts +++ b/packages/project/src/assets/android/assets.ts @@ -1,9 +1,8 @@ import type { AndroidOutputAssetTemplate, - AndroidOutputAssetTemplateAdaptiveIcon, - AndroidOutputAssetTemplateSplash, + AndroidOutputAssetTemplateAdaptiveIcon } from '../asset-types'; -import { AssetKind, AndroidDensity, Format, Orientation, Platform } from '../asset-types'; +import { AssetKind, AndroidDensity, Format, Platform } from '../asset-types'; export const ANDROID_LDPI_ICON: AndroidOutputAssetTemplate = { platform: Platform.Android, @@ -115,268 +114,3 @@ export const ANDROID_XXXHDPI_ADAPTIVE_ICON: AndroidOutputAssetTemplateAdaptiveIc height: 432, density: AndroidDensity.Xxxhdpi, }; - -// -// Splash screens -// -export const ANDROID_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 320, - height: 480, - density: AndroidDensity.Default, - orientation: Orientation.Default, -}; - -export const ANDROID_LAND_LDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 320, - height: 240, - density: AndroidDensity.LandLdpi, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_MDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 480, - height: 320, - density: AndroidDensity.LandMdpi, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_HDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 800, - height: 480, - density: AndroidDensity.LandHdpi, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_XHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 1280, - height: 720, - density: AndroidDensity.LandXhdpi, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_XXHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 1600, - height: 960, - density: AndroidDensity.LandXxhdpi, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_XXXHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 1920, - height: 1280, - density: AndroidDensity.LandXxxhdpi, - orientation: Orientation.Landscape, -}; - -export const ANDROID_PORT_LDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 240, - height: 320, - density: AndroidDensity.PortLdpi, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_MDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 320, - height: 480, - density: AndroidDensity.PortMdpi, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_HDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 480, - height: 800, - density: AndroidDensity.PortHdpi, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_XHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 720, - height: 1280, - density: AndroidDensity.PortXhdpi, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_XXHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 960, - height: 1600, - density: AndroidDensity.PortXxhdpi, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_XXXHDPI_SCREEN: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.Splash, - format: Format.Png, - width: 1280, - height: 1920, - density: AndroidDensity.PortXxxhdpi, - orientation: Orientation.Portrait, -}; - -// Dark/night mode splashes - -export const ANDROID_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 320, - height: 240, - density: AndroidDensity.DefaultNight, - orientation: Orientation.Default, -}; - -export const ANDROID_LAND_LDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 320, - height: 240, - density: AndroidDensity.LandLdpiNight, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_MDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 480, - height: 320, - density: AndroidDensity.LandMdpiNight, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_HDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 800, - height: 480, - density: AndroidDensity.LandHdpiNight, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_XHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 1280, - height: 720, - density: AndroidDensity.LandXhdpiNight, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_XXHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 1600, - height: 960, - density: AndroidDensity.LandXxhdpiNight, - orientation: Orientation.Landscape, -}; - -export const ANDROID_LAND_XXXHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 1920, - height: 1280, - density: AndroidDensity.LandXxxhdpiNight, - orientation: Orientation.Landscape, -}; - -export const ANDROID_PORT_LDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 240, - height: 320, - density: AndroidDensity.PortLdpiNight, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_MDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 320, - height: 480, - density: AndroidDensity.PortMdpiNight, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_HDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 480, - height: 800, - density: AndroidDensity.PortHdpiNight, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_XHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 720, - height: 1280, - density: AndroidDensity.PortXhdpiNight, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_XXHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 960, - height: 1600, - density: AndroidDensity.PortXxhdpiNight, - orientation: Orientation.Portrait, -}; - -export const ANDROID_PORT_XXXHDPI_SCREEN_DARK: AndroidOutputAssetTemplateSplash = { - platform: Platform.Android, - kind: AssetKind.SplashDark, - format: Format.Png, - width: 1280, - height: 1920, - density: AndroidDensity.PortXxxhdpiNight, - orientation: Orientation.Portrait, -}; diff --git a/packages/project/src/assets/android/generator.ts b/packages/project/src/assets/android/generator.ts index ec7c321..fed3ebd 100644 --- a/packages/project/src/assets/android/generator.ts +++ b/packages/project/src/assets/android/generator.ts @@ -45,9 +45,6 @@ export class AndroidAssetGenerator extends AssetGenerator { return this.generateAdaptiveIconForeground(asset, project); case AssetKind.IconBackground: return this.generateAdaptiveIconBackground(asset, project); - case AssetKind.Splash: - case AssetKind.SplashDark: - return this.generateSplashes(asset, project); } return []; @@ -74,40 +71,8 @@ export class AndroidAssetGenerator extends AssetGenerator { // Generate legacy icons const generatedLegacyIcons = await this.generateLegacyIcon(asset, project); generated.push(...generatedLegacyIcons); - - const splashes = Object.values(AndroidAssetTemplates).filter((a) => a.kind === AssetKind.Splash); - - const generatedSplashes = await Promise.all( - splashes.map(async (splash) => { - return this._generateSplashesFromLogo( - project, - asset, - splash, - pipe, - this.options.splashBackgroundColor ?? '#ffffff', - ); - }), - ); - - generated.push(...generatedSplashes); } - // Generate dark splashes - const darkSplashes = Object.values(AndroidAssetTemplates).filter((a) => a.kind === AssetKind.SplashDark); - const generatedSplashes = await Promise.all( - darkSplashes.map(async (splash) => { - return this._generateSplashesFromLogo( - project, - asset, - splash, - pipe, - this.options.splashBackgroundColorDark ?? '#111111', - ); - }), - ); - - generated.push(...generatedSplashes); - return [...generated]; } @@ -474,53 +439,6 @@ export class AndroidAssetGenerator extends AssetGenerator { await project.commit(); } - private async generateSplashes(asset: InputAsset, project: MobileProject): Promise { - const pipe = asset.pipeline(); - - if (!pipe) { - throw new Error('Sharp instance not created'); - } - - const splashes = ( - asset.kind === AssetKind.Splash - ? Object.values(AndroidAssetTemplates).filter((a) => a.kind === AssetKind.Splash) - : Object.values(AndroidAssetTemplates).filter((a) => a.kind === AssetKind.SplashDark) - ) as AndroidOutputAssetTemplateSplash[]; - - const resPath = this.getResPath(project); - - const collected = await Promise.all( - splashes.map(async (splash) => { - const [dest, outputInfo] = await this.generateSplash(project, asset, splash, pipe); - - const relPath = relative(resPath, dest); - return new OutputAsset(splash, asset, project, { [relPath]: dest }, { [relPath]: outputInfo }); - }), - ); - - return collected; - } - - private async generateSplash( - project: MobileProject, - asset: InputAsset, - template: AndroidOutputAssetTemplateSplash, - pipe: Sharp, - ): Promise<[string, OutputInfo]> { - const drawableDir = template.density ? `drawable-${template.density}` : 'drawable'; - - const resPath = this.getResPath(project); - const parentDir = join(resPath, drawableDir); - if (!(await pathExists(parentDir))) { - await mkdirp(parentDir); - } - const dest = join(resPath, drawableDir, 'splash.png'); - - const outputInfo = await pipe.resize(template.width, template.height).png().toFile(dest); - - return [dest, outputInfo]; - } - private getResPath(project: MobileProject): string { return join(project.config.android!.path!, 'app', 'src', this.options.androidFlavor ?? 'main', 'res'); } diff --git a/packages/project/src/assets/asset-types.ts b/packages/project/src/assets/asset-types.ts index 42160dd..c9ebd13 100644 --- a/packages/project/src/assets/asset-types.ts +++ b/packages/project/src/assets/asset-types.ts @@ -33,17 +33,13 @@ export const enum AssetKind { Icon = 'icon', IconForeground = 'icon-foreground', IconBackground = 'icon-background', - NotificationIcon = 'notification-icon', - Splash = 'splash', - SplashDark = 'splash-dark', + NotificationIcon = 'notification-icon' } export const enum Platform { Any = 'any', Ios = 'ios', - Android = 'android', - Pwa = 'pwa', - // Windows = 'windows' + Android = 'android' } export const enum Format { diff --git a/packages/project/src/assets/ios/assets.ts b/packages/project/src/assets/ios/assets.ts deleted file mode 100644 index b774e97..0000000 --- a/packages/project/src/assets/ios/assets.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { IosOutputAssetTemplate, IosOutputAssetTemplateSplash } from '../asset-types'; -import { AssetKind, Format, IosIdiom, Orientation, Platform, Theme } from '../asset-types'; - -/** - * 1024px Icon - * - * - iOS 1024 icon - */ -export const IOS_1024_ICON: IosOutputAssetTemplate = { - platform: Platform.Ios, - idiom: IosIdiom.Universal, - kind: AssetKind.Icon, - name: 'AppIcon-512@2x.png', - format: Format.Png, - width: 1024, - height: 1024, -}; - -export const IOS_1X_UNIVERSAL_ANYANY_SPLASH: IosOutputAssetTemplateSplash = { - platform: Platform.Ios, - idiom: IosIdiom.Universal, - kind: AssetKind.Splash, - name: 'Default@1x~universal~anyany.png', - format: Format.Png, - width: 2732, - height: 2732, - orientation: Orientation.Portrait, - scale: 1, - theme: Theme.Any, -}; - -export const IOS_2X_UNIVERSAL_ANYANY_SPLASH: IosOutputAssetTemplateSplash = { - platform: Platform.Ios, - idiom: IosIdiom.Universal, - kind: AssetKind.Splash, - name: 'Default@2x~universal~anyany.png', - format: Format.Png, - width: 2732, - height: 2732, - orientation: Orientation.Portrait, - scale: 2, - theme: Theme.Any, -}; - -export const IOS_3X_UNIVERSAL_ANYANY_SPLASH: IosOutputAssetTemplateSplash = { - platform: Platform.Ios, - idiom: IosIdiom.Universal, - kind: AssetKind.Splash, - name: 'Default@3x~universal~anyany.png', - format: Format.Png, - width: 2732, - height: 2732, - orientation: Orientation.Portrait, - scale: 3, - theme: Theme.Any, -}; - -export const IOS_1X_UNIVERSAL_ANYANY_SPLASH_DARK: IosOutputAssetTemplateSplash = { - platform: Platform.Ios, - idiom: IosIdiom.Universal, - kind: AssetKind.SplashDark, - name: 'Default@1x~universal~anyany-dark.png', - format: Format.Png, - width: 2732, - height: 2732, - orientation: Orientation.Portrait, - scale: 1, - theme: Theme.Dark, -}; - -export const IOS_2X_UNIVERSAL_ANYANY_SPLASH_DARK: IosOutputAssetTemplateSplash = { - platform: Platform.Ios, - idiom: IosIdiom.Universal, - kind: AssetKind.SplashDark, - name: 'Default@2x~universal~anyany-dark.png', - format: Format.Png, - width: 2732, - height: 2732, - orientation: Orientation.Portrait, - scale: 2, - theme: Theme.Dark, -}; - -export const IOS_3X_UNIVERSAL_ANYANY_SPLASH_DARK: IosOutputAssetTemplateSplash = { - platform: Platform.Ios, - idiom: IosIdiom.Universal, - kind: AssetKind.SplashDark, - name: 'Default@3x~universal~anyany-dark.png', - format: Format.Png, - width: 2732, - height: 2732, - orientation: Orientation.Portrait, - scale: 3, - theme: Theme.Dark, -}; diff --git a/packages/project/src/assets/ios/generator.ts b/packages/project/src/assets/ios/generator.ts index 7e985ba..875e07f 100644 --- a/packages/project/src/assets/ios/generator.ts +++ b/packages/project/src/assets/ios/generator.ts @@ -5,26 +5,25 @@ import sharp from 'sharp'; import type { AssetGeneratorOptions } from '../asset-generator'; import { AssetGenerator } from '../asset-generator'; import type { IosOutputAssetTemplate } from '../asset-types'; -import { AssetKind, Platform } from '../asset-types'; +import { AssetKind, Format, IosIdiom, Platform } from '../asset-types'; import type { InputAsset } from '../input-asset'; import { OutputAsset } from '../output-asset'; import type { MobileProject } from '../../project'; -import { - IOS_1X_UNIVERSAL_ANYANY_SPLASH, - IOS_2X_UNIVERSAL_ANYANY_SPLASH, - IOS_3X_UNIVERSAL_ANYANY_SPLASH, - IOS_1X_UNIVERSAL_ANYANY_SPLASH_DARK, - IOS_2X_UNIVERSAL_ANYANY_SPLASH_DARK, - IOS_3X_UNIVERSAL_ANYANY_SPLASH_DARK, -} from './assets'; -import * as IosAssetTemplates from './assets'; - export const IOS_APP_ICON_SET_NAME = 'AppIcon'; export const IOS_APP_ICON_SET_PATH = `App/Assets.xcassets/${IOS_APP_ICON_SET_NAME}.appiconset`; -export const IOS_SPLASH_IMAGE_SET_NAME = 'Splash'; -export const IOS_SPLASH_IMAGE_SET_PATH = `App/Assets.xcassets/${IOS_SPLASH_IMAGE_SET_NAME}.imageset`; - +// export const IOS_SPLASH_IMAGE_SET_NAME = 'Splash'; +// export const IOS_SPLASH_IMAGE_SET_PATH = `App/Assets.xcassets/${IOS_SPLASH_IMAGE_SET_NAME}.imageset`; + +export const IOS_1024_ICON: IosOutputAssetTemplate = { + platform: Platform.Ios, + idiom: IosIdiom.Universal, + kind: AssetKind.Icon, + name: 'AppIcon-512@2x.png', + format: Format.Png, + width: 1024, + height: 1024, +}; export class IosAssetGenerator extends AssetGenerator { constructor(options: AssetGeneratorOptions = {}) { super(options); @@ -46,10 +45,7 @@ export class IosAssetGenerator extends AssetGenerator { case AssetKind.LogoDark: return this.generateFromLogo(asset, project); case AssetKind.Icon: - return this.generateIcons(asset, project); - case AssetKind.Splash: - case AssetKind.SplashDark: - return this.generateSplashes(asset, project); + return this.generateIcons(asset, project, [IOS_1024_ICON]); } return []; @@ -67,107 +63,13 @@ export class IosAssetGenerator extends AssetGenerator { // Generate logos let logos: OutputAsset[] = []; if (asset.kind === AssetKind.Logo) { - logos = await this.generateIconsForLogo(asset, project); - } - - const generated: OutputAsset[] = []; - - const targetLogoWidthPercent = this.options.logoSplashScale ?? 0.2; - const targetWidth = this.options.logoSplashTargetWidth ?? Math.floor((asset.width ?? 0) * targetLogoWidthPercent); - - if (asset.kind === AssetKind.Logo) { - // Generate light splash - const lightDefaultBackground = '#ffffff'; - const lightSplashes = [ - IOS_1X_UNIVERSAL_ANYANY_SPLASH, - IOS_2X_UNIVERSAL_ANYANY_SPLASH, - IOS_3X_UNIVERSAL_ANYANY_SPLASH, - ]; - const lightSplashesGenerated: OutputAsset[] = []; - - for (const lightSplash of lightSplashes) { - const lightDest = join(iosDir, IOS_SPLASH_IMAGE_SET_PATH, lightSplash.name); - - const canvas = sharp({ - create: { - width: lightSplash.width ?? 0, - height: lightSplash.height ?? 0, - channels: 4, - background: this.options.splashBackgroundColor ?? lightDefaultBackground, - }, - }); - const resized = await sharp(asset.path).resize(targetWidth).toBuffer(); - const lightOutputInfo = await canvas - .composite([{ input: resized, gravity: sharp.gravity.center }]) - .png() - .toFile(lightDest); - - const lightSplashOutput = new OutputAsset( - lightSplash, - asset, - project, - { - [lightDest]: lightDest, - }, - { - [lightDest]: lightOutputInfo, - }, - ); - - generated.push(lightSplashOutput); - lightSplashesGenerated.push(lightSplashOutput); - } - - await this.updateSplashContentsJson(lightSplashesGenerated, project); - } - - // Generate dark splash - const darkDefaultBackground = '#111111'; - const darkSplashes = [ - IOS_1X_UNIVERSAL_ANYANY_SPLASH_DARK, - IOS_2X_UNIVERSAL_ANYANY_SPLASH_DARK, - IOS_3X_UNIVERSAL_ANYANY_SPLASH_DARK, - ]; - const darkSplashesGenerated: OutputAsset[] = []; - - for (const darkSplash of darkSplashes) { - const darkDest = join(iosDir, IOS_SPLASH_IMAGE_SET_PATH, darkSplash.name); - const canvas = sharp({ - create: { - width: darkSplash.width ?? 0, - height: darkSplash.height ?? 0, - channels: 4, - background: this.options.splashBackgroundColorDark ?? darkDefaultBackground, - }, - }); - const resized = await sharp(asset.path).resize(targetWidth).toBuffer(); - const darkOutputInfo = await canvas - .composite([{ input: resized, gravity: sharp.gravity.center }]) - .png() - .toFile(darkDest); - - const darkSplashOutput = new OutputAsset( - darkSplash, - asset, - project, - { - [darkDest]: darkDest, - }, - { - [darkDest]: darkOutputInfo, - }, - ); - - generated.push(darkSplashOutput); - darkSplashesGenerated.push(darkSplashOutput); + logos = await this.generateIcons(asset, project, [IOS_1024_ICON]); } - await this.updateSplashContentsJsonDark(darkSplashesGenerated, project); - - return [...logos, ...generated]; + return [...logos]; } - private async _generateIcons( + private async generateIcons( asset: InputAsset, project: MobileProject, icons: IosOutputAssetTemplate[], @@ -209,68 +111,6 @@ export class IosAssetGenerator extends AssetGenerator { return generated; } - // Generate ALL the icons when only given a logo - private async generateIconsForLogo(asset: InputAsset, project: MobileProject): Promise { - const icons = Object.values(IosAssetTemplates).filter((a) => [AssetKind.Icon].find((i) => i === a.kind)); - - return this._generateIcons(asset, project, icons as IosOutputAssetTemplate[]); - } - - private async generateIcons(asset: InputAsset, project: MobileProject): Promise { - const icons = Object.values(IosAssetTemplates).filter((a) => [AssetKind.Icon].find((i) => i === a.kind)); - - return this._generateIcons(asset, project, icons as IosOutputAssetTemplate[]); - } - - private async generateSplashes(asset: InputAsset, project: MobileProject): Promise { - const pipe = asset.pipeline(); - - if (!pipe) { - throw new Error('Sharp instance not created'); - } - - const assetMetas = - asset.kind === AssetKind.Splash - ? [IOS_1X_UNIVERSAL_ANYANY_SPLASH, IOS_2X_UNIVERSAL_ANYANY_SPLASH, IOS_3X_UNIVERSAL_ANYANY_SPLASH] - : [ - IOS_1X_UNIVERSAL_ANYANY_SPLASH_DARK, - IOS_2X_UNIVERSAL_ANYANY_SPLASH_DARK, - IOS_3X_UNIVERSAL_ANYANY_SPLASH_DARK, - ]; - - const generated: OutputAsset[] = []; - - for (const assetMeta of assetMetas) { - const iosDir = project.config.ios!.path!; - const dest = join(iosDir, IOS_SPLASH_IMAGE_SET_PATH, assetMeta.name); - - const outputInfo = await pipe.resize(assetMeta.width, assetMeta.height).png().toFile(dest); - - const g = new OutputAsset( - assetMeta, - asset, - project, - { - [assetMeta.name]: dest, - }, - { - [assetMeta.name]: outputInfo, - }, - ); - - generated.push(g); - } - - if (asset.kind === AssetKind.Splash) { - await this.updateSplashContentsJson(generated, project); - } else if (asset.kind === AssetKind.SplashDark) { - // Need to register this as a dark-mode splash - await this.updateSplashContentsJsonDark(generated, project); - } - - return generated; - } - private async updateIconsContentsJson(generated: OutputAsset[], project: MobileProject) { const assetsPath = join(project.config.ios!.path!, IOS_APP_ICON_SET_PATH); const contentsJsonPath = join(assetsPath, 'Contents.json'); @@ -301,70 +141,4 @@ export class IosAssetGenerator extends AssetGenerator { await writeFile(contentsJsonPath, JSON.stringify(parsed, null, 2)); } - - private async updateSplashContentsJson(generated: OutputAsset[], project: MobileProject) { - const contentsJsonPath = join(project.config.ios!.path!, IOS_SPLASH_IMAGE_SET_PATH, 'Contents.json'); - const json = await readFile(contentsJsonPath, { encoding: 'utf-8' }); - - const parsed = JSON.parse(json); - - const withoutMissing = parsed.images.filter((i: any) => !!i.filename); - - for (const g of generated) { - const existing = withoutMissing.find( - (f: any) => - f.scale === `${g.template.scale}x` && f.idiom === 'universal' && typeof f.appearances === 'undefined', - ); - - if (existing) { - existing.filename = (g.template as IosOutputAssetTemplate).name; - } else { - withoutMissing.push({ - idiom: 'universal', - scale: `${g.template.scale ?? 1}x`, - filename: (g.template as IosOutputAssetTemplate).name, - }); - } - } - - parsed.images = withoutMissing; - - await writeFile(contentsJsonPath, JSON.stringify(parsed, null, 2)); - } - - private async updateSplashContentsJsonDark(generated: OutputAsset[], project: MobileProject) { - const contentsJsonPath = join(project.config.ios!.path!, IOS_SPLASH_IMAGE_SET_PATH, 'Contents.json'); - const json = await readFile(contentsJsonPath, { encoding: 'utf-8' }); - - const parsed = JSON.parse(json); - - const withoutMissing = parsed.images.filter((i: any) => !!i.filename); - - for (const g of generated) { - const existing = withoutMissing.find( - (f: any) => - f.scale === `${g.template.scale}x` && f.idiom === 'universal' && typeof f.appearances !== 'undefined', - ); - - if (existing) { - existing.filename = (g.template as IosOutputAssetTemplate).name; - } else { - withoutMissing.push({ - appearances: [ - { - appearance: 'luminosity', - value: 'dark', - }, - ], - idiom: 'universal', - scale: `${g.template.scale ?? 1}x`, - filename: (g.template as IosOutputAssetTemplate).name, - }); - } - } - - parsed.images = withoutMissing; - - await writeFile(contentsJsonPath, JSON.stringify(parsed, null, 2)); - } } From f3f35c04498f441c598ec81d1f7463823b0fe19d Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Tue, 17 Sep 2024 16:11:37 -0400 Subject: [PATCH 5/6] Add sharp, fix asset loading --- package-lock.json | 621 +++++++++++++++++- packages/project/package.json | 1 + .../project/src/assets/android/generator.ts | 4 +- packages/project/src/assets/ios/generator.ts | 1 + 4 files changed, 619 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3b783a6..9ecbafa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4196,6 +4196,16 @@ "node": ">= 10.0.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", + "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@endiliey/react-ideal-image": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/@endiliey/react-ideal-image/-/react-ideal-image-0.0.11.tgz", @@ -4230,6 +4240,367 @@ "node": ">=6.9.0" } }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@ionic-internal/design-system": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/@ionic-internal/design-system/-/design-system-0.0.0.tgz", @@ -9228,9 +9599,10 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -23394,6 +23766,7 @@ "prettier": "^2.7.1", "prompts": "^2.4.2", "replace": "^1.1.0", + "sharp": "^0.33.5", "tempy": "^1.0.1", "tmp": "^0.2.1", "ts-node": "^10.2.1", @@ -23452,6 +23825,57 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "packages/project/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "packages/project/node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, "packages/website": { "name": "configure-website", "version": "0.0.1", @@ -26389,6 +26813,15 @@ } } }, + "@emnapi/runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", + "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, "@endiliey/react-ideal-image": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/@endiliey/react-ideal-image/-/react-ideal-image-0.0.11.tgz", @@ -26411,6 +26844,147 @@ "@hutson/parse-repository-url": { "version": "3.0.2" }, + "@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "optional": true, + "requires": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "optional": true, + "requires": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "optional": true + }, + "@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "optional": true + }, + "@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "optional": true + }, + "@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "optional": true + }, + "@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "optional": true + }, + "@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "optional": true + }, + "@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "optional": true + }, + "@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "optional": true + }, + "@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "optional": true, + "requires": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "optional": true, + "requires": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "optional": true, + "requires": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "optional": true, + "requires": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "optional": true, + "requires": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "optional": true, + "requires": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "optional": true, + "requires": { + "@emnapi/runtime": "^1.2.0" + } + }, + "@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "optional": true + }, + "@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "optional": true + }, "@ionic-internal/design-system": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/@ionic-internal/design-system/-/design-system-0.0.0.tgz", @@ -27379,6 +27953,7 @@ "prompts": "^2.4.2", "replace": "^1.1.0", "rimraf": "^3.0.2", + "sharp": "^0.33.5", "tempy": "^1.0.1", "tmp": "^0.2.1", "ts-jest": "^27.0.7", @@ -27405,6 +27980,40 @@ }, "prettier": { "version": "2.7.1" + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" + }, + "sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "requires": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5", + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + } } } }, @@ -30038,9 +30647,9 @@ "dev": true }, "detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==" }, "detect-newline": { "version": "3.1.0", diff --git a/packages/project/package.json b/packages/project/package.json index cdca765..325bd04 100644 --- a/packages/project/package.json +++ b/packages/project/package.json @@ -38,6 +38,7 @@ "prettier": "^2.7.1", "prompts": "^2.4.2", "replace": "^1.1.0", + "sharp": "^0.33.5", "tempy": "^1.0.1", "tmp": "^0.2.1", "ts-node": "^10.2.1", diff --git a/packages/project/src/assets/android/generator.ts b/packages/project/src/assets/android/generator.ts index fed3ebd..16dbdf5 100644 --- a/packages/project/src/assets/android/generator.ts +++ b/packages/project/src/assets/android/generator.ts @@ -8,8 +8,7 @@ import type { AssetGeneratorOptions } from '../asset-generator'; import { AssetGenerator } from '../asset-generator'; import type { AndroidOutputAssetTemplate, - AndroidOutputAssetTemplateAdaptiveIcon, - AndroidOutputAssetTemplateSplash, + AndroidOutputAssetTemplateAdaptiveIcon } from '../asset-types'; import { AssetKind, Platform } from '../asset-types'; import type { InputAsset } from '../input-asset'; @@ -26,6 +25,7 @@ export class AndroidAssetGenerator extends AssetGenerator { async generate(asset: InputAsset, project: MobileProject): Promise { const androidDir = project.config.android?.path; + await asset.load() if (!androidDir) { throw new Error("No android project found") diff --git a/packages/project/src/assets/ios/generator.ts b/packages/project/src/assets/ios/generator.ts index 875e07f..ebbce7c 100644 --- a/packages/project/src/assets/ios/generator.ts +++ b/packages/project/src/assets/ios/generator.ts @@ -31,6 +31,7 @@ export class IosAssetGenerator extends AssetGenerator { async generate(asset: InputAsset, project: MobileProject): Promise { const iosDir = project.config.ios?.path; + await asset.load() if (!iosDir) { throw new Error('No ios project found'); From 4cb96094d1b746dae838515b04d8b7af3bcd646d Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Tue, 24 Sep 2024 14:29:20 -0400 Subject: [PATCH 6/6] chore: remove unused tests --- .../test/frameworks/dotnet-maui.test.ts | 15 ------- .../project/test/frameworks/flutter.test.ts | 38 ---------------- .../test/frameworks/native-android.test.ts | 16 ------- .../test/frameworks/native-ios.test.ts | 37 ---------------- .../test/frameworks/nativescript.test.ts | 16 ------- .../test/frameworks/react-native.test.ts | 44 ------------------- 6 files changed, 166 deletions(-) delete mode 100644 packages/project/test/frameworks/dotnet-maui.test.ts delete mode 100644 packages/project/test/frameworks/flutter.test.ts delete mode 100644 packages/project/test/frameworks/native-android.test.ts delete mode 100644 packages/project/test/frameworks/native-ios.test.ts delete mode 100644 packages/project/test/frameworks/nativescript.test.ts delete mode 100644 packages/project/test/frameworks/react-native.test.ts diff --git a/packages/project/test/frameworks/dotnet-maui.test.ts b/packages/project/test/frameworks/dotnet-maui.test.ts deleted file mode 100644 index 633fd3b..0000000 --- a/packages/project/test/frameworks/dotnet-maui.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { DotNetMauiFramework } from '../../src/frameworks/dotnet-maui'; -import { MobileProject } from '../../src/project'; - -describe('frameworks: .NET MAUI', () => { - let project: MobileProject; - - beforeEach(async () => { - }); - - it('should detect Dot Net Maui project', async () => { - project = new MobileProject('../common/test/fixtures/frameworks/DotNetMauiApp'); - const fwk = await DotNetMauiFramework.getFramework(project); - expect(fwk).not.toBe(null); - }); -}); diff --git a/packages/project/test/frameworks/flutter.test.ts b/packages/project/test/frameworks/flutter.test.ts deleted file mode 100644 index 31a48b8..0000000 --- a/packages/project/test/frameworks/flutter.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { FlutterFramework } from '../../src/frameworks/flutter'; -import { MobileProject } from '../../src/project'; - -describe('frameworks: Flutter', () => { - let project: MobileProject; - - beforeEach(async () => { - project = new MobileProject('../common/test/fixtures/frameworks/flutter_configure_test'); - }); - - it('should detect flutter project', async () => { - expect(await FlutterFramework.getFramework(project)).not.toBe(null); - }); - - describe('ios', () => { - let project: MobileProject; - - beforeEach(async () => { - project = new MobileProject('../common/test/fixtures/frameworks/flutter_configure_test', { - ios: { - path: 'ios' - }, - android: { - path: 'android' - } - }); - await project.load(); - }); - - it('should find pbxproj', async () => { - expect(await project.ios?.pbxprojName()).toBe('project.pbxproj'); - }); - - it('should find xcodeproj', async () => { - expect(await project.ios?.xcodeprojName()).toBe('Runner.xcodeproj'); - }); - }); -}); diff --git a/packages/project/test/frameworks/native-android.test.ts b/packages/project/test/frameworks/native-android.test.ts deleted file mode 100644 index b770091..0000000 --- a/packages/project/test/frameworks/native-android.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NativeAndroidFramework } from '../../src/frameworks/native-android'; -import { MobileProject } from '../../src/project'; - -describe('frameworks: Native Android', () => { - let project: MobileProject; - - beforeEach(async () => { }); - - it('should detect project', async () => { - project = new MobileProject( - '../common/test/fixtures/frameworks/NativeAndroidApp', - ); - const fwk = await NativeAndroidFramework.getFramework(project); - expect(fwk).not.toBe(null); - }); -}); diff --git a/packages/project/test/frameworks/native-ios.test.ts b/packages/project/test/frameworks/native-ios.test.ts deleted file mode 100644 index a0ca8dc..0000000 --- a/packages/project/test/frameworks/native-ios.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NativeIosFramework } from '../../src/frameworks/native-ios'; -import { MobileProject } from '../../src/project'; - -describe('frameworks: Native iOS', () => { - let project: MobileProject; - - beforeEach(async () => { }); - - it('should detect project', async () => { - project = new MobileProject( - '../common/test/fixtures/frameworks/NativeIosApp', - ); - const fwk = await NativeIosFramework.getFramework(project); - expect(fwk).not.toBe(null); - }); - - describe('ios', () => { - let project: MobileProject; - - beforeEach(async () => { - project = new MobileProject('../common/test/fixtures/frameworks/NativeIosApp', { - ios: { - path: '.' - } - }); - await project.load(); - }); - - it('should find pbxproj', async () => { - expect(await project.ios?.pbxprojName()).toBe('project.pbxproj'); - }); - - it('should find xcodeproj', async () => { - expect(await project.ios?.xcodeprojName()).toBe('NativeIosApp.xcodeproj'); - }); - }); -}); diff --git a/packages/project/test/frameworks/nativescript.test.ts b/packages/project/test/frameworks/nativescript.test.ts deleted file mode 100644 index 87eff74..0000000 --- a/packages/project/test/frameworks/nativescript.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NativeScriptFramework } from '../../src/frameworks/nativescript'; -import { MobileProject } from '../../src/project'; - -describe('frameworks: NativeScript', () => { - let project: MobileProject; - - beforeEach(async () => { }); - - it('should detect project', async () => { - project = new MobileProject( - '../common/test/fixtures/frameworks/NativeScriptApp', - ); - const fwk = await NativeScriptFramework.getFramework(project); - expect(fwk).not.toBe(null); - }); -}); diff --git a/packages/project/test/frameworks/react-native.test.ts b/packages/project/test/frameworks/react-native.test.ts deleted file mode 100644 index 6fb54d8..0000000 --- a/packages/project/test/frameworks/react-native.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ReactNativeFramework } from '../../src/frameworks/react-native'; -import { MobileProject } from '../../src/project'; - -describe('frameworks: React Native', () => { - let project: MobileProject; - - it('should detect standard React Native project', async () => { - project = new MobileProject('../common/test/fixtures/frameworks/ReactNativeProject'); - const fwk = await ReactNativeFramework.getFramework(project); - expect(fwk).not.toBe(null); - expect(fwk!.isExpo).toBe(false); - }); - - it('should detect expo React Native project', async () => { - project = new MobileProject('../common/test/fixtures/frameworks/ReactNativeExpo'); - const fwk = await ReactNativeFramework.getFramework(project); - expect(fwk).not.toBe(null); - expect(fwk!.isExpo).toBe(true); - }); - - describe('ios', () => { - let project: MobileProject; - - beforeEach(async () => { - project = new MobileProject('../common/test/fixtures/frameworks/ReactNativeProject', { - ios: { - path: 'ios' - }, - android: { - path: 'android' - } - }); - await project.load(); - }); - - it('should find pbxproj', async () => { - expect(await project.ios?.pbxprojName()).toBe('project.pbxproj'); - }); - - it('should find xcodeproj', async () => { - expect(await project.ios?.xcodeprojName()).toBe('ReactNativeProject.xcodeproj'); - }); - }); -});