From 1a37720376a641cc65660172d7aa563114ecc3af Mon Sep 17 00:00:00 2001 From: Matthias Giger Date: Fri, 1 Jul 2022 19:53:52 +0200 Subject: [PATCH] feat(icon): adapt to new interface and add SVG support release-npm --- README.md | 8 +++--- index.ts | 50 +++++++++++++++++++----------------- package.json | 2 +- test/logo.svg | 21 ++++++++++++++++ test/logo.test.ts | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 28 deletions(-) create mode 100644 test/logo.svg diff --git a/README.md b/README.md index dab2ae8..73e828b 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ npm i --save-dev icon-numic-plugin Numic automatically picks up the plugin once installed and adds the various icons to the native folders in `/android` and `/ios` without any changes to commit. The only thing **required is an icon** of the recommended size 1024x1024. The plugin will look for icons in the following locations and pick the first match: -- icon.png -- app-icon.png -- asset/icon.png -- logo.png (also used as Avatar in SourceTree) +- icon.png / icon.svg +- app-icon.png / app-icon.svg +- asset/icon.png / asset/icon.svg +- logo.png / logo.svg (also used as Avatar in SourceTree) ## Configuration diff --git a/index.ts b/index.ts index dc8e581..970ddda 100644 --- a/index.ts +++ b/index.ts @@ -1,36 +1,39 @@ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'fs' import { join, dirname } from 'path' +// Alternative in Rust: https://github.com/silvia-odwyer/photon import sharp from 'sharp' import { contentsWithLinks } from './ios' -// Sharp Docs: https://sharp.pixelplumbing.com/api-constructor -// Alternative: https://github.com/silvia-odwyer/photon // https://github.com/aeirola/react-native-svg-app-icon -// https://www.npmjs.com/package/app-icon (requires imagemagik) type Input = { - cwd?: string + projectPath?: string + nativePath?: string log?: (message: string, type?: string) => void options?: object } -const iconSourcePaths = (cwd: string) => [ - join(cwd, 'icon.png'), - join(cwd, 'app-icon.png'), - join(cwd, 'asset/icon.png'), - join(cwd, 'logo.png'), +const iconSourcePaths = (projectPath: string) => [ + join(projectPath, 'icon.png'), + join(projectPath, 'app-icon.png'), + join(projectPath, 'asset/icon.png'), + join(projectPath, 'logo.png'), + join(projectPath, 'icon.svg'), + join(projectPath, 'app-icon.svg'), + join(projectPath, 'asset/icon.svg'), + join(projectPath, 'logo.svg'), ] -const getInput = (cwd: string, options: { icon?: string }) => { +const getInput = (projectPath: string, options: { icon?: string }) => { if ( typeof options === 'object' && typeof options.icon === 'string' && - existsSync(join(cwd, options.icon)) + existsSync(join(projectPath, options.icon)) ) { - return join(cwd, options.icon) + return join(projectPath, options.icon) } - const paths = iconSourcePaths(cwd) + const paths = iconSourcePaths(projectPath) let match: string | undefined paths.forEach((path) => { @@ -74,10 +77,10 @@ const getIOSFolders = (iosImageDirectory: string) => { ] } -const getSizes = ({ cwd, log }: Input) => { - const iosDirectories = readdirSync(join(cwd, 'ios'), { withFileTypes: true }) +const getSizes = ({ nativePath, log }: Input) => { + const iosDirectories = readdirSync(join(nativePath, 'ios'), { withFileTypes: true }) .filter((dirent) => dirent.isDirectory()) - .filter((dirent) => existsSync(join(cwd, 'ios', dirent.name, 'Images.xcassets'))) + .filter((dirent) => existsSync(join(nativePath, 'ios', dirent.name, 'Images.xcassets'))) .map((dirent) => dirent.name) const iosImageDirectory = iosDirectories.length > 0 ? join('ios', iosDirectories[0], 'Images.xcassets') : null @@ -94,16 +97,17 @@ const getSizes = ({ cwd, log }: Input) => { } export default async ({ - cwd = process.cwd(), + projectPath = process.cwd(), + nativePath = process.cwd(), // eslint-disable-next-line no-console log = console.log, - options, + options = {}, }: Input) => { - const inputFile = getInput(cwd, options) - const sizes = getSizes({ cwd, log, options }) + const inputFile = getInput(projectPath, options) + const sizes = getSizes({ nativePath, projectPath, log, options }) const androidPromises = sizes.android.map((icon) => { - const destinationFile = join(cwd, icon.path) + const destinationFile = join(nativePath, icon.path) const directory = dirname(destinationFile) if (!existsSync(directory)) { mkdirSync(directory, { recursive: true }) @@ -114,7 +118,7 @@ export default async ({ await Promise.all(androidPromises) const iosPromises = sizes.ios.map((icon) => { - const destinationFile = join(cwd, icon.path) + const destinationFile = join(nativePath, icon.path) const directory = dirname(destinationFile) if (!existsSync(directory)) { mkdirSync(directory, { recursive: true }) @@ -126,7 +130,7 @@ export default async ({ // Link ios icons in Contents.json. writeFileSync( - join(cwd, sizes.iosDirectory, 'AppIcon.appiconset/Contents.json'), + join(nativePath, sizes.iosDirectory, 'AppIcon.appiconset/Contents.json'), JSON.stringify(contentsWithLinks, null, 2) ) diff --git a/package.json b/package.json index d2bc250..f6fa944 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "main": "dist/index.js", "exports": { "default": "./dist/index.js", - "package.json": "./package.json" + "./package.json": "./package.json" }, "types": "dist/index.d.ts", "files": [ diff --git a/test/logo.svg b/test/logo.svg new file mode 100644 index 0000000..478e28a --- /dev/null +++ b/test/logo.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/test/logo.test.ts b/test/logo.test.ts index a61b2c1..b2168f4 100644 --- a/test/logo.test.ts +++ b/test/logo.test.ts @@ -62,3 +62,67 @@ test('Icon path can be configured.', async () => { expect(files.length).toBe(20) }) + +test('Native output folder can be configured.', async () => { + prepare([packageJson('logo-native')]) + + const logoPath = join(process.cwd(), 'logo.png') + + cpSync(join(initialCwd, 'test/logo.png'), logoPath) + mkdirSync(join(process.cwd(), '.numic/ios/numic/Images.xcassets'), { recursive: true }) + + expect(existsSync(logoPath)).toBe(true) + + await plugin({ nativePath: join(process.cwd(), '.numic') }) + + const files = listFilesMatching('**/*.png') + + expect(files.length).toBe(20) + expect(files.includes('.numic/android/app/src/main/res/mipmap-mdpi/ic_launcher.png')).toBe(true) + expect(files.includes('.numic/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png')).toBe( + true + ) + expect(files.includes('.numic/ios/numic/Images.xcassets/AppIcon.appiconset/Icon-80.png')).toBe( + true + ) +}) + +test('Also works with svg input file.', async () => { + prepare([packageJson('logo-svg')]) + + const logoPath = join(process.cwd(), 'logo.svg') + + cpSync(join(initialCwd, 'test/logo.svg'), logoPath) + mkdirSync(join(process.cwd(), 'ios/numic/Images.xcassets'), { recursive: true }) + + expect(existsSync(logoPath)).toBe(true) + + await plugin({ options: { icon: 'logo.svg' } }) + + const files = listFilesMatching('**/*.png') + + expect(files.length).toBe(19) + expect(files.includes('android/app/src/main/res/mipmap-mdpi/ic_launcher.png')).toBe(true) + expect(files.includes('android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png')).toBe(true) + expect(files.includes('ios/numic/Images.xcassets/AppIcon.appiconset/Icon-80.png')).toBe(true) +}) + +test('Automatically finds svg in default paths.', async () => { + prepare([packageJson('logo-svg-default')]) + + const logoPath = join(process.cwd(), 'app-icon.svg') + + cpSync(join(initialCwd, 'test/logo.svg'), logoPath) + mkdirSync(join(process.cwd(), 'ios/numic/Images.xcassets'), { recursive: true }) + + expect(existsSync(logoPath)).toBe(true) + + await plugin({}) + + const files = listFilesMatching('**/*.png') + + expect(files.length).toBe(19) + expect(files.includes('android/app/src/main/res/mipmap-mdpi/ic_launcher.png')).toBe(true) + expect(files.includes('android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png')).toBe(true) + expect(files.includes('ios/numic/Images.xcassets/AppIcon.appiconset/Icon-80.png')).toBe(true) +})