[
  {
    "path": ".gitignore",
    "content": "node_modules\ntests/**/output\ncoverage\nAppTestName\ndist\n"
  },
  {
    "path": ".npmignore",
    "content": ".eslintrc.js\nexample\ntests\nassets\n.circleci\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n# [1.1.6] (2023-08-12)\n\n### Fixed\n\n- Overwriting ASSETCATALOG* build settings for iOS ([#25])\n\n# [1.0.0] (2022-05-29)\n\n### Added\n\n- Support for JSON config file (`.iconsetrc.json`)\n- Generating the round icons for Android by default ([#7])\n\n### Fixed\n\n- Merge config parameters with CLI options ([#17])\n\n### Changed\n\n- The name for config file now is `.iconsetrc.js` instead of `iconset.config.js`\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021-2023 martiliones\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[1]: https://www.npmjs.com/package/icon-set-creator\n\n<header>\n<p align=\"center\">\n  <img src=\"assets/iphone.png\" alt=\"logo\" width=\"400\">\n</p>\n<h1 align=\"center\">\nIcon Set Creator\n</h1>\n<p align=\"center\">\nAndroid & iOS icon generator for React Native\n</p>\n<p align=\"center\">\n<a href=\"https://www.npmjs.com/package/icon-set-creator\" target=\"__blank\"><img src=\"https://img.shields.io/npm/v/icon-set-creator?color=7DE1D1&label=\" alt=\"NPM version\"></a>\n<a href=\"https://www.npmjs.com/package/icon-set-creator\" target=\"__blank\"><img alt=\"NPM Downloads\" src=\"https://img.shields.io/npm/dm/icon-set-creator?color=6AC704&label=\"></a>\n<a href=\"https://github.com/martiliones/icon-set-creator\" target=\"__blank\"><img src=\"https://img.shields.io/github/license/martiliones/icon-set-creator.svg?label=&message=themes&color=FFB706\" alt=\"License\"></a>\n<img src=\"https://img.shields.io/codecov/c/github/martiliones/icon-set-creator?color=FF4F4D&logoColor=FF4F4D\" alt=\"Code Coverage\">\n<br>\n<a href=\"https://github.com/martiliones/icon-set-creator\" target=\"__blank\"><img alt=\"GitHub stars\" src=\"https://img.shields.io/github/stars/martiliones/icon-set-creator?style=social\"></a>\n</p>\n</header>\n\n- 🌈 <b>Easy to install</b> — does not require additional software\n- ⚡️ <b>Fast</b> — image manipulation powered by [sharp](https://github.com/lovell/sharp)\n- 🛠 <b>Configurable</b> — using cli options or config file\n- 📱 <b>iOS and Android support</b> — create icons for both platforms with one command\n- 🌟 <b>Adaptive Icons</b> — support for color and image backgrounds\n- 🟢 <b>Round Icons</b> — automatically generated for Android\n\n<h2>⚡️ Quick Start</h2>\n\nYou can run the icon generator with the npx command (available in Node.js 8.2.0 and later).\n\n```bash\n$ npx icon-set-creator create ./path/to/icon.png\n```\n\nFor earlier Node versions, see [🚀 Installation](#-installation) section below.\n\n<h2>🚀 Installation</h2>\n\n> **Node Version Requirement**\n>\n> Icon set creator requires Node.js version 14.0 or above (v16+ recommended). You can manage multiple versions of Node on the same machine with [n](https://github.com/tj/n), [nvm](https://github.com/creationix/nvm) or [nvm-windows](https://github.com/coreybutler/nvm-windows) .\n\n<h3>Global</h3>\n\nTo install the new package **globally**, use one of the following commands. You need administrator privileges to execute these unless npm was installed on your system through a Node.js version manager (e.g. n or nvm).\n\n```bash\n$ npm install -g icon-set-creator\n# OR\n$ yarn global add icon-set-creator\n```\n\nAfter installation, you will have access to the iconset binary in your command line. You can verify that it is properly installed by simply running `iconset`, which should present you with a help message listing all available commands.\n\nYou can check you have the right version with this command:\n\n```bash\n$ iconset --version\n```\n\n<h3>Local for a project</h3>\n\nIf you want to install the [`icon-set-creator`][1] **locally**, use one of the following commands:\n\n```bash\n$ npm install icon-set-creator -D\n# OR\n$ yarn add icon-set-creator -D\n```\n\n<h2>🧪 Usage</h2>\n\nTo create app icon you need:\n- PNG icon for IOS and Android (Highly recommend using an icon with a size of at least 1024x1024 pixels). You can check the [`example`](https://github.com/martiliones/icon-set-creator/tree/master/example) folder for example icons.\n- You can also create Adaptive Icon for Android, which can display a variety of shapes across different device models ([Learn More](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)). To create it you need a foreground image and a background color or image. [There is also a good article](https://medium.com/google-design/designing-adaptive-icons-515af294c783) on how to design such icons.\n\nThe easiest way to use [`icon-set-creator`][1] is to specify the path to icon using `iconset create` command in root of your project:\n```bash\n$ iconset create ./icon.png\n```\n\nIf you have the package installed locally, you can do same with the `package.json` script and then run it with `npm run create-appicon`:\n```json5\n{\n  \"scripts\": {\n    \"create-appicon\": \"iconset create ./icon.png\"\n  }\n}\n```\n\nIt will generate icons of different sizes for Android and iOS.\n\n<h2>⚙️ Configuration</h2>\n\nThere are two primary ways to configure [`icon-set-creator`][1]:\n\n- **CLI parameters** - use the command options.\n- **Configuration files** - use a JavaScript, JSON, or `package.json` file to specify configuration information to generate an application icon depending on your code style.\n\n<h3> CLI parameters </h3>\n\nTo display all of the options for the given command, run `iconset <command> --help`. For example:\n\n```bash\n$ iconset create --help\n\nUsage: index create [options] [image-path]\n\nGenerate a new icon set for React Native project\n\nOptions:\n  -d, --disable-launcher-icon                  Disable changing the launcher icon for iOS and Android\n  -A, --android [icon-name]                    Generate icon set for android\n  -IPA, --image-path-android                   Image path for android\n  --flavor [flavor]                            Flavor name\n  -b, --adaptive-icon-background <background>  The color (E.g. \"#ffffff\") or image asset (E.g. \"assets/images/christmas-background.png\") which will be\n                                               used to fill out the background of the adaptive icon.\n  -f, --adaptive-icon-foreground <foreground>  The image asset which will be used for the icon foreground of the adaptive icon\n  -I, --ios                                    Generate icon set for ios\n  --group <group>                              Group for ios\n  -IPI, --image-path-ios                       Image path for ios\n  -h, --help                                   display help for command\n```\n\n<h3> Configuration files </h3>\n\n[`icon-set-creator`][1] supports configuration files in several formats:\n\n- JavaScript - use `.iconsetrc.js` and export an object containing your configuration.\n- JSON - use `.iconsetrc.json` to define the configuration structure.\n- `package.json` - create an `iconsetConfig` property in your package.json file and define your configuration there.\n\nIf there are multiple configuration files in the same directory, `icon-set-creator` will only use one. The priority order is as follows:\n\n- `.iconsetrc.js`\n- `.iconsetrc.json`\n- `package.json`\n\n[`icon-set-creator`][1] will automatically look for them in the directory path to be used to run the CLI.\n\nHere's an example JavaScript configuration file that uses the `adaptiveIconBackground`/`adaptiveIconForeground` options to support adaptive icons:\n\n```js\n// .iconsetrc.js\nmodule.exports = {\n  imagePath: './assets/icon.png',\n\n  adaptiveIconBackground: './assets/icon-background.png',\n  adaptiveIconForeground: './assets/icon-foreground.png',\n};\n```\n\n<h4> iconset create </h4>\n\n- `imagePath` — The location of the icon image file which you want to use as the app launcher icon. e.g. `./assets/icon.png`\n- `disableLauncherIcon` - Generate only icons without changing manifest files\n- `android`/`ios` (optional): `true` — Override the default existing React-Native launcher icon for the platform specified, `false` — ignore making launcher icons for this platform, `icon_name` — this will generate a new launcher icons for the platform with the name you specify, without removing the old default existing React-Native launcher icon.\n- `imagePathAndroid` — The location of the icon image file specific for Android platform (optional — if not defined then the `imagePath` is used)\n- `imagePathIos` — The location of the icon image file specific for iOS platform (optional — if not defined then the `imagePath` is used)\n\nThe next two attributes are only used when generating Android launcher icon:\n\n- `adaptiveIconBackground` — The color (E.g. `\"#ffffff\"`) or image asset (E.g. `\"assets/images/dark-background.png\"`) which will be used to fill out the background of the adaptive icon\n- `adaptiveIconForeground` — The image asset which will be used for the icon foreground of the adaptive icon\n\n<h1></h1>\n\n<p align=\"center\">✨ You are amazing!</p>\n"
  },
  {
    "path": "index.ts",
    "content": "#!/usr/bin/env node\nimport leven from 'leven';\nimport minimist from 'minimist';\nimport { Command, program } from 'commander';\n\nimport IconCreator from './lib/creator/index';\n\nimport { chalk, semver } from './utils/index';\nimport { engines } from './package.json';\n\nconst requiredVersion = engines.node;\n\nconst checkNodeVersion = (wanted: string, id: string) => {\n  if (!semver.satisfies(process.version, wanted, { includePrerelease: true })) {\n    console.log(chalk.red(\n      'You are using Node ' + process.version + ', but this version of ' + id +\n      ' requires Node ' + wanted + '.\\nPlease upgrade your Node version.'\n    ));\n\n    process.exit(1);\n  }\n};\n\n// Check node version before requiring/doing anything else\n// The user may be on a very old node version\ncheckNodeVersion(requiredVersion, 'icon-set-creator');\n\nconst suggestCommands = (unknownCommand: string) => {\n  const availableCommands = program.commands.map((cmd: Command) => cmd.name());\n\n  let suggestion = '';\n\n  availableCommands.forEach((cmd: string) => {\n    const isBestMatch = leven(cmd, unknownCommand) < leven(suggestion || '', unknownCommand);\n\n    if (leven(cmd, unknownCommand) < 3 && isBestMatch) {\n      suggestion = cmd;\n    }\n  });\n\n  if (suggestion) {\n    console.log('  ' + chalk.red(`Did you mean ${chalk.yellow(suggestion)}?`));\n  }\n};\n\nprogram\n  .version(`icon-set-creator ${require('./package').version}`)\n  .usage('<command> [options]');\n\nprogram\n  .command('create [image-path]')\n  .description('Generate a new icon set for React Native project')\n\n  .option('-d, --disable-launcher-icon', 'Disable changing the launcher icon for iOS and Android')\n\n  .option('-A, --android [icon-name]', 'Generate icon set for android')\n  .option('-IPA, --image-path-android', 'Image path for android')\n  .option('--flavor [flavor]', 'Flavor name')\n  .option('-b, --adaptive-icon-background <background>', 'The color (E.g. \"#ffffff\") or image asset (E.g. \"assets/images/christmas-background.png\") which will be used to fill out the background of the adaptive icon.')\n  .option('-f, --adaptive-icon-foreground <foreground>', 'The image asset which will be used for the icon foreground of the adaptive icon')\n\n  .option('-I, --ios', 'Generate icon set for ios')\n  .option('--group <group>', 'Group for ios')\n  .option('-IPI, --image-path-ios', 'Image path for ios')\n  .action((imagePath: string, options) => {\n    if (minimist(process.argv.slice(3))._.length > 1) {\n      console.log(chalk.yellow('\\n Info: You provided more than one argument. The first one will be used as the source file, the rest are ignored.'));\n    }\n\n    const iconCreator = new IconCreator({ ...options, imagePath });\n\n    iconCreator.run();\n  });\n\nprogram\n  .command('remove')\n  .description('remove a icon set from React Native project')\n  .option('-A, --android', 'remove icon set for android')\n  .option('-I, --ios', 'remove icon set for ios')\n  .action((options) => {\n    console.log(options);\n    // require('../lib/remove')(options);\n  });\n\n// output help information on unknown commands\nprogram.on('command:*', ([cmd]) => {\n  program.outputHelp();\n\n  console.log('  ' + chalk.red(`Unknown command ${chalk.yellow(cmd)}.`));\n  console.log();\n\n  suggestCommands(cmd);\n\n  process.exitCode = 1;\n});\n\n// add some useful info on help\nprogram.on('--help', () => {\n  console.log();\n  console.log(`  Run ${chalk.cyan('iconset <command> --help')} for detailed usage of given command.`);\n  console.log();\n});\n\nprogram.commands.forEach(c => c.on('--help', () => console.log()));\n\n// enhance common error messages\nimport enhanceErrorMessages from './utils/enhanceErrorMessages';\n\nenhanceErrorMessages('missingArgument', (argName: string) => {\n  return `Missing required argument ${chalk.yellow(`<${argName}>`)}.`;\n});\n\nenhanceErrorMessages('unknownOption', (optionName: string) => {\n  return `Unknown option ${chalk.yellow(optionName)}.`;\n});\n\nenhanceErrorMessages('optionMissingArgument', (option: { flags: any; }, flag: string) => {\n  return `Missing required argument for option ${chalk.yellow(option.flags)}` + (\n    flag ? `, got ${chalk.yellow(flag)}` : ''\n  );\n});\n\nprogram.parse(process.argv);\n"
  },
  {
    "path": "lib/creator/android.ts",
    "content": "import sharp from 'sharp';\nimport fs from 'fs';\nimport path from 'path';\n\nimport { log, warn, createDirectory } from '../../utils/index';\n\nimport {\n  getAndroidResDirectory,\n  getAndroidAdaptiveXmlFolder,\n  getAndroidColorsFile,\n  isAndroidIconNameCorrectFormat,\n  getIcLauncherDrawableBackgroundXml,\n  getIcLauncherXml,\n  getColorsXmlTemplate,\n  getRoundedCornersLayer,\n  androidIcons,\n  adaptiveAndroidIcons,\n  androidManifestFile,\n  androidAdaptiveForegroundFileName,\n  androidAdaptiveBackgroundFileName,\n  AndroidIcon,\n} from '../utils/android';\n\ninterface AndroidCreatorOptions {\n  flavor?: string;\n  android?: any;\n  disableLauncherIcon?: boolean;\n}\n\nclass AndroidIconCreator {\n  context: string;\n  options: AndroidCreatorOptions;\n\n  constructor(context: string, opts: AndroidCreatorOptions) {\n    this.context = context;\n    this.options = opts;\n\n    if (typeof opts.android === 'string') {\n      if (!isAndroidIconNameCorrectFormat(opts.android)) {\n        throw new Error('The icon name must contain only lowercase a-z, 0-9, or underscore: \\nE.g. \"ic_my_new_icon\"');\n      }\n    }\n  }\n\n  createAndroidIcons(imagePath: string): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const androidResDirectory = path.resolve(this.context, getAndroidResDirectory(this.options.flavor));\n\n      let iconName = this.options.android;\n\n      if (typeof iconName === 'string') {\n        log('🚀 Adding a new Android launcher icon');\n      } else {\n        iconName = 'ic_launcher';\n\n        log('Overwriting the default Android launcher icon with a new icon');\n      }\n\n      fs.readFile(imagePath, async (err, image) => {\n        if (err) {\n          return reject(err);\n        }\n\n        if (!this.options.disableLauncherIcon) {\n          await this.overwriteAndroidManifestIcon(iconName!);\n        }\n\n        for (const androidIcon of androidIcons) {\n          const iconDirectory = path.resolve(androidResDirectory, androidIcon.directoryName);\n\n          await this.saveIcon(image, iconDirectory, iconName, androidIcon);\n          await this.saveRoundedIcon(image, iconDirectory, iconName, androidIcon);\n        }\n\n        sharp(image)\n          .resize(512, 512)\n          .toFile(path.resolve(androidResDirectory, 'playstore-icon.png'), (err) => {\n            if (err) {\n              return reject(err);\n            }\n\n            resolve();\n          });\n      });\n    });\n  };\n\n  createAdaptiveIcons(adaptiveIconBackground: string, adaptiveIconForeground: string): Promise<void> {\n    const { flavor, android } = this.options;\n\n    return new Promise((resolve, reject) => {\n      fs.readFile(path.resolve(this.context, adaptiveIconForeground), async (err, foreground) => {\n        if (err) {\n          return reject(err);\n        }\n\n        const androidResDirectory = path.resolve(this.context, getAndroidResDirectory(flavor));\n\n        const foregroundIconName = typeof android === 'string'\n          ? `${android}_foreground` : androidAdaptiveForegroundFileName;\n\n        for (const adaptiveIcon of adaptiveAndroidIcons) {\n          const iconDirectory = path.resolve(androidResDirectory, adaptiveIcon.directoryName);\n\n          await this.saveIcon(foreground, iconDirectory, foregroundIconName, adaptiveIcon);\n        }\n\n        if (path.extname(adaptiveIconBackground) === '.png') {\n          await this.createAdaptiveBackgrounds(adaptiveIconBackground, androidResDirectory);\n        } else {\n          await this.createAdaptiveIconMipmapXmlFile();\n          await this.updateColorsXmlFile(adaptiveIconBackground);\n        }\n\n        resolve();\n      });\n    });\n  }\n\n  saveRoundedIcon(image: Buffer, iconDirectory: string, iconName: string, androidIcon: AndroidIcon) {\n    const roundIconName = `${iconName}_round`;\n    const { size } = androidIcon;\n\n    return new Promise((resolve, reject) => {\n      sharp(image)\n        .resize(size, size)\n        .composite([{\n          input: getRoundedCornersLayer(size),\n          blend: 'dest-in'\n        }])\n        .toFile(path.resolve(iconDirectory, `${roundIconName}.png`), (err, info) => {\n          if (err) {\n            return reject(err);\n          }\n\n          resolve(info);\n        });\n    });\n  }\n\n  saveIcon(image: Buffer, iconDirectory: string, iconName: string, androidIcon: AndroidIcon) {\n    return new Promise((resolve, reject) => {\n      createDirectory(iconDirectory);\n\n      sharp(image)\n        .resize(androidIcon.size, androidIcon.size)\n        .toFile(path.resolve(iconDirectory, `${iconName}.png`), (err, info) => {\n          if (err) {\n            return reject(err);\n          }\n\n          resolve(info);\n        });\n    });\n  }\n\n  updateColorsXmlFile(adaptiveIconBackground: string) {\n    const { flavor } = this.options;\n\n    return new Promise((resolve) => {\n      const colorsXml = path.resolve(this.context, getAndroidColorsFile(flavor));\n\n      if (fs.existsSync(colorsXml)) {\n        log('📄 Updating colors.xml with color for adaptive icon background');\n\n        resolve(this.updateColorsFile(colorsXml, adaptiveIconBackground));\n      } else {\n        log('⚠️ No colors.xml file found in your Android project');\n        log('Creating colors.xml file and adding it to your Android project');\n\n        resolve(this.createNewColorsFile(adaptiveIconBackground));\n      }\n    });\n  }\n\n  updateColorsFile(colorsXml: string, adaptiveIconBackground: string): Promise<void> {\n    return new Promise((resolve, reject) => {\n      fs.readFile(colorsXml, 'utf-8', (err, colors) => {\n        if (err) {\n          return reject(err);\n        }\n\n        const lines = colors.split('\\n');\n\n        let foundExisting = false;\n\n        for (let i = 0; i < lines.length; i++) {\n          const line = lines[i];\n\n          if (line.includes('name=\"ic_launcher_background\"')) {\n            foundExisting = true;\n            // replace anything between tags which does not contain another tag\n            lines[i] = line.replace(/>([^><]*)</g, `>${adaptiveIconBackground}<`);\n\n            break;\n          }\n        }\n\n        if (!foundExisting) {\n          lines.splice(lines.length - 1, 0,\n            `\\t<color name=\"ic_launcher_background\">${adaptiveIconBackground}</color>`);\n        }\n\n        fs.writeFileSync(colorsXml, lines.join('\\n'));\n\n        resolve();\n      });\n    });\n  }\n\n  createNewColorsFile(adaptiveIconBackground: string) {\n    return new Promise((resolve) => {\n      const colorsXml = path.resolve(this.context, getAndroidColorsFile(this.options.flavor));\n\n      createDirectory(path.dirname(colorsXml));\n\n      const { android } = this.options;\n\n      const iconName = typeof android === 'string'\n        ? android : 'ic_launcher';\n\n      fs.writeFileSync(colorsXml, getColorsXmlTemplate(iconName));\n\n      resolve(this.updateColorsFile(colorsXml, adaptiveIconBackground));\n    });\n  }\n\n  createAdaptiveIconMipmapXmlFile(): Promise<void> {\n    const { android, flavor } = this.options;\n\n    return new Promise((resolve, reject) => {\n      const iconName = typeof android === 'string'\n        ? android : 'ic_launcher';\n      const iconFileName = `${iconName}.xml`;\n\n      const directory = path.resolve(this.context, getAndroidAdaptiveXmlFolder(flavor));\n\n      createDirectory(directory);\n\n      fs.writeFile(path.resolve(directory, iconFileName), getIcLauncherXml(iconName), (err) => {\n        if (err) {\n          return reject(err);\n        }\n\n        resolve();\n      });\n    });\n  }\n\n  createAdaptiveBackgrounds(adaptiveIconBackground: string, androidResDirectory: string): Promise<void> {\n    const { android, flavor } = this.options;\n\n    const adaptiveIconBackgroundPath = path.resolve(this.context, adaptiveIconBackground);\n\n    return new Promise((resolve, reject) => {\n      fs.readFile(adaptiveIconBackgroundPath, async (err, background) => {\n        if (err) {\n          return reject(err);\n        }\n\n        const backgroundIconName = typeof android === 'string'\n          ? `${android}_background` : androidAdaptiveBackgroundFileName;\n\n        for (const adaptiveIcon of adaptiveAndroidIcons) {\n          const iconDirectory = path.resolve(androidResDirectory, adaptiveIcon.directoryName);\n\n          await this.saveIcon(background, iconDirectory, backgroundIconName, adaptiveIcon);\n        }\n\n        const iconName = typeof android === 'string'\n          ? android : 'ic_launcher';\n\n        const directory = path.resolve(this.context, getAndroidAdaptiveXmlFolder(flavor));\n\n        createDirectory(directory);\n\n        fs.writeFile(path.resolve(directory, `${iconName}.xml`), getIcLauncherDrawableBackgroundXml(iconName), (err) => {\n          if (err) {\n            return reject(err);\n          }\n\n          resolve();\n        });\n      });\n    });\n  }\n\n  overwriteAndroidManifestIcon(iconName: string): Promise<void> {\n    return new Promise((resolve, reject) => {\n      fs.readFile(androidManifestFile, 'utf-8', (err, manifest) => {\n        if (err) {\n          if (err.code === 'ENOENT') {\n            warn('No AndroidManifest.xml was found, icon can\\'t be replaced. Skipped');\n\n            return resolve();\n          }\n\n          return reject(err);\n        }\n\n        log('Overwriting icon in AndroidManifest.xml');\n\n        const newManifest = this.transformAndroidManifestIcon(manifest, iconName);\n\n        fs.writeFile(androidManifestFile, newManifest, (err) => {\n          if (err) {\n            return reject(err);\n          }\n\n          resolve();\n        });\n      });\n    });\n  }\n\n  transformAndroidManifestIcon(oldManifest: string, iconName: string) {\n    return oldManifest.split('\\n').map((line) => {\n      if (line.includes('android:icon')) {\n        // Using RegExp replace the value of android:icon to point to the new icon\n        // anything but a quote of any length: [^\"]*\n        // an escaped quote: \\\\\" (escape slash, because it exists regex)\n        // quote, no quote / quote with things behind : \\\"[^\"]*\n        // repeat as often as wanted with no quote at start: [^\"]*(\\\"[^\"]*)*\n        // escaping the slash to place in string: [^\"]*(\\\\\"[^\"]*)*\"\n        // result: any string which does only include escaped quotes\n        return line.replace(/android:icon=\"[^\"]*(\\\\\"[^\"]*)*\"/g,\n          `android:icon=\"@mipmap/${iconName}\"`);\n      } else if (line.includes('android:roundIcon')) {\n        return line.replace(/android:icon=\"[^\"]*(\\\\\"[^\"]*)*\"/g,\n          `android:roundIcon=\"@mipmap/${iconName}_round\"`);\n      } else {\n        return line;\n      }\n    }).join('\\n');\n  }\n}\n\nexport default AndroidIconCreator;\n"
  },
  {
    "path": "lib/creator/index.ts",
    "content": "import path from 'path';\nimport { resolveConfig, error, info, log } from '../../utils/index';\n\nimport IOSIconCreator from './ios';\nimport AndroidIconCreator from './android';\n\ninterface creatorOptions {\n  imagePath?: string;\n  android?: boolean | string;\n  ios?: boolean | string;\n  imagePathAndroid?: string;\n  imagePathIos?: string;\n  flavor?: string;\n  adaptiveIconBackground?: string;\n  adaptiveIconForeground?: string;\n  group?: string;\n  disableLauncherIcon?: boolean;\n};\n\nexport default class Creator {\n  options: creatorOptions;\n  context: string;\n\n  constructor(opts: creatorOptions) {\n    const context = process.cwd();\n\n    const config = resolveConfig(context);\n\n    if (!opts.imagePath) {\n      opts.imagePath = config.imagePath;\n    }\n\n    const options = {\n      ...config,\n      ...opts\n    };\n\n    // both android and ios are included by default\n    if (!options.android && !options.ios) {\n      options.android = true;\n      options.ios = true;\n    }\n\n    this.context = context;\n    this.options = options;\n\n    this.resovleOptionPaths();\n  }\n\n  resovleOptionPaths() {\n    const context = process.cwd();\n\n    const options = this.options;\n\n    const paths = [\n      'imagePath',\n      'imagePathIos',\n      'imagePathAndroid',\n      'adaptiveIconBackground',\n      'adaptiveIconForeground',\n    ] as const;\n\n    for (const prop of paths) {\n      if (typeof options[prop] !== 'undefined') {\n        if (!options[prop]!.match(/^#[0-9A-Za-z]{6}$/)) {\n          options[prop] = path.resolve(context, options[prop]!);\n        }\n      }\n    }\n  }\n\n  async run() {\n    const options = this.options;\n\n    const context = this.context;\n\n    if (options.android) {\n      info('Creating icons for Android...');\n\n      const imagePathAndroid = options.imagePathAndroid || options.imagePath;\n\n      if (!imagePathAndroid) {\n        return error('No image path was specified for android');\n      }\n\n      const androidIconCreator = new AndroidIconCreator(context, {\n        flavor: options.flavor,\n        android: options.android,\n        disableLauncherIcon: options.disableLauncherIcon,\n      });\n\n      await androidIconCreator.createAndroidIcons(imagePathAndroid);\n\n      const { adaptiveIconBackground, adaptiveIconForeground } = options;\n\n      if (adaptiveIconBackground && adaptiveIconForeground) {\n        await androidIconCreator.createAdaptiveIcons(adaptiveIconBackground, adaptiveIconForeground);\n      }\n    }\n\n    if (options.ios) {\n      info('Creating icons for IOS...');\n\n      const iOSIconCreator = new IOSIconCreator(context, {\n        ios: options.ios,\n        flavor: options.flavor,\n        group: options.group,\n        disableLauncherIcon: options.disableLauncherIcon,\n      });\n\n      const imagePathIos = options.imagePathIos || options.imagePath;\n\n      if (!imagePathIos) {\n        return error('No image path was specified for iOS');\n      }\n\n      await iOSIconCreator.createIosIcons(imagePathIos!);\n    }\n\n    log();\n    log('🎉  Successfully generated icons.');\n  }\n}\n"
  },
  {
    "path": "lib/creator/ios.ts",
    "content": "import sharp from 'sharp';\nimport fs from 'fs';\nimport path from 'path';\n\nimport { warn, createDirectory } from '../../utils/index';\n\nimport {\n  iosDefaultIconName,\n  iosDefaultCatalogName,\n  getIosDefaultIconFolder,\n  getIosConfigFile,\n  getIosAssetFolder,\n  generateContentsFile,\n  iosIcons,\n  IosIcon,\n} from '../utils/ios';\n\ninterface IOSCreatorOptions {\n  ios?: boolean | string;\n  flavor?: string;\n  group?: string;\n  disableLauncherIcon?: boolean;\n}\n\nclass IOSIconCreator {\n  context: string;\n  options: IOSCreatorOptions;\n\n  constructor(context: string, opts: IOSCreatorOptions) {\n    this.context = context;\n    this.options = opts;\n  }\n\n  createIosIcons(imagePath: string): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const projectName = this.getIosProjectName();\n\n      if (!projectName) {\n        return reject('No Project Directory for IOS was found');\n      }\n\n      fs.readFile(imagePath, async (err, image) => {\n        if (err) {\n          return reject(err);\n        }\n\n        let iconName = iosDefaultIconName;\n        let catalogName = iosDefaultCatalogName;\n\n        const { flavor, ios, disableLauncherIcon } = this.options;\n\n        if (flavor) {\n          catalogName = `AppIcon-${flavor}`;\n\n          iconName = iosDefaultIconName;\n        } else if (typeof ios === 'string') {\n          iconName = ios;\n          catalogName = iconName;\n        }\n\n        for (const iosIcon of iosIcons) {\n          const flavorPath = path.resolve(this.context, getIosDefaultIconFolder(projectName, flavor));\n\n          await this.saveIosIcon(\n            image,\n            flavorPath,\n            iconName,\n            iosIcon\n          );\n        }\n\n        if (!disableLauncherIcon) {\n          await this.changeIosLauncherIcon(catalogName, projectName);\n        }\n\n        this.modifyContentsFile(catalogName, iconName, projectName);\n\n        resolve();\n      });\n    });\n  }\n\n  getIosProjectName() {\n    const { group } = this.options;\n\n    if (group) {\n      return group;\n    }\n\n    const appFilePath = path.resolve(this.context, 'app.json');\n\n    if (fs.existsSync(appFilePath)) {\n      const app = require(appFilePath);\n\n      if (typeof app === 'object' && app.name) {\n        return app.name;\n      }\n    }\n\n    const iosDirectory = path.resolve(this.context, 'ios');\n\n    const directories = fs.readdirSync(iosDirectory, { withFileTypes: true });\n\n    for (const dir of directories) {\n      if (!dir.isDirectory()) {\n        continue;\n      }\n\n      if (fs.existsSync(path.resolve(iosDirectory, dir.name, 'Images.xcassets'))) {\n        return dir.name;\n      }\n    }\n\n    return 'AppName';\n  }\n\n  saveIosIcon(image: Buffer, iconDirectory: string, iconName: string, iosIcon: IosIcon) {\n    return new Promise((resolve, reject) => {\n      createDirectory(iconDirectory);\n\n      sharp(image)\n        .resize(iosIcon.size, iosIcon.size)\n        .removeAlpha() // Icons with alpha channel are not allowed in the Apple App Store\n        .toFile(path.resolve(iconDirectory, `${iconName + iosIcon.name}.png`), (err, data) => {\n          if (err) {\n            return reject(err);\n          }\n\n          resolve(data);\n        });\n    });\n  }\n\n  changeIosLauncherIcon(catalogName: string, projectName: string): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const iOSconfigFile = path.resolve(this.context, getIosConfigFile(projectName));\n\n      fs.readFile(iOSconfigFile, 'utf-8', (err, config) => {\n        if (err) {\n          if (err.code === 'ENOENT') {\n            warn('No project.pbxproj was found, icon can\\'t be replaced in config file. Skipped');\n\n            return resolve();\n          }\n\n          return reject(err);\n        }\n\n        const lines = config.split('\\n');\n\n        let currentConfig, onConfigurationSection = false;\n\n        for (let i = 0; i <= lines.length - 1; i++) {\n          const line = lines[i];\n\n          if (line.includes('/* Begin XCBuildConfiguration section */')) {\n            onConfigurationSection = true;\n          }\n\n          if (line.includes('/* End XCBuildConfiguration section */')) {\n            onConfigurationSection = false;\n          }\n\n          if (onConfigurationSection) {\n            const regex = /.*\\/* (.*)\\.xcconfig \\*\\/;/;\n            const match = regex.exec(line);\n\n            if (match) {\n              currentConfig = match[1];\n            }\n\n            if (currentConfig && line.includes('ASSETCATALOG_COMPILER_APPICON_NAME')) {\n              lines[i] = line.replace(/=(.*);/g, `= ${catalogName};`);\n            }\n          }\n        }\n\n        const entireFile = lines.join('\\n');\n        resolve(fs.writeFileSync(iOSconfigFile, entireFile));\n      });\n    });\n  }\n\n  modifyContentsFile(newCatalogName: string, newIconName: string, projectName: string) {\n    const newIconDirectory = path.resolve(\n      this.context,\n      getIosAssetFolder(projectName),\n      `${newCatalogName}.appiconset/Contents.json`\n    );\n\n    createDirectory(path.dirname(newIconDirectory));\n\n    const contentsFileContent = generateContentsFile(newIconName);\n\n    fs.writeFileSync(newIconDirectory, JSON.stringify(contentsFileContent, null, 2));\n  }\n}\n\nexport default IOSIconCreator;\n"
  },
  {
    "path": "lib/utils/android.ts",
    "content": "export interface AndroidIcon {\n  directoryName: string;\n  size: number;\n}\n\nexport const androidManifestFile = 'android/app/src/main/AndroidManifest.xml';\n\nexport const getAndroidResDirectory = (flavor?: string) => `android/app/src/${flavor ?? 'main'}/res/`;\nexport const getAndroidAdaptiveXmlFolder = (flavor?: string) => `${getAndroidResDirectory(flavor)}mipmap-anydpi-v26/`;\nexport const getAndroidColorsFile = (flavor?: string) => `${getAndroidResDirectory(flavor)}/values/colors.xml`;\n\nexport const isAndroidIconNameCorrectFormat = (iconName: string) => {\n  return /^[a-z0-9_]+$/.exec(iconName);\n};\n\nexport const androidAdaptiveForegroundFileName = 'ic_launcher_foreground';\nexport const androidAdaptiveBackgroundFileName = 'ic_launcher_background';\n\nexport const adaptiveAndroidIcons: AndroidIcon[] = [\n  { directoryName: 'drawable-mdpi', size: 108 },\n  { directoryName: 'drawable-hdpi', size: 162 },\n  { directoryName: 'drawable-xhdpi', size: 216 },\n  { directoryName: 'drawable-xxhdpi', size: 324 },\n  { directoryName: 'drawable-xxxhdpi', size: 432 },\n];\n\nexport const androidIcons: AndroidIcon[] = [\n  { directoryName: 'mipmap-mdpi', size: 48 },\n  { directoryName: 'mipmap-hdpi', size: 72 },\n  { directoryName: 'mipmap-xhdpi', size: 96 },\n  { directoryName: 'mipmap-xxhdpi', size: 144 },\n  { directoryName: 'mipmap-xxxhdpi', size: 192 },\n];\n\nexport const getRoundedCornersLayer = (size: number) => Buffer.from(\n  `<svg><rect x=\"0\" y=\"0\" width=\"${size}\" height=\"${size}\" rx=\"${Math.floor(size / 2)}\" ry=\"${Math.floor(size / 2)}\"/></svg>`\n);\n\nexport const getIcLauncherXml = (iconName?: string) => `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/${iconName || 'ic_launcher'}_background\"/>\n    <foreground android:drawable=\"@drawable/${iconName || 'ic_launcher'}_foreground\"/>\n</adaptive-icon>\n`;\n\nexport const getIcLauncherDrawableBackgroundXml = (iconName?: string) => `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/${iconName || 'ic_launcher'}_background\"/>\n    <foreground android:drawable=\"@drawable/${iconName || 'ic_launcher'}_foreground\"/>\n</adaptive-icon>\n`;\n\nexport const getColorsXmlTemplate = (iconName?: string) => `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"${iconName || 'ic_launcher'}_background\">#FF000000</color>\n</resources>\n`;\n"
  },
  {
    "path": "lib/utils/ios.ts",
    "content": "export interface IosIcon {\n  name: string;\n  size: number;\n}\n\nexport const iosDefaultIconName = 'Icon-App';\nexport const iosDefaultCatalogName = 'AppIcon';\n\nexport const getIosDefaultIconFolder = (projectName: string, flavor?: string) => (\n  `ios/${projectName}/Images.xcassets/AppIcon${flavor?`-${flavor}`:''}.appiconset/`\n);\nexport const getIosConfigFile = (projectName: string) => `ios/${projectName}.xcodeproj/project.pbxproj`;\nexport const getIosAssetFolder = (projectName: string) => `ios/${projectName}/Images.xcassets/`;\n\nexport const iosIcons: IosIcon[] = [\n  { name: '-20x20@1x', size: 20 },\n  { name: '-20x20@2x', size: 40 },\n  { name: '-20x20@3x', size: 60 },\n  { name: '-29x29@1x', size: 29 },\n  { name: '-29x29@2x', size: 58 },\n  { name: '-29x29@3x', size: 87 },\n  { name: '-40x40@1x', size: 40 },\n  { name: '-40x40@2x', size: 80 },\n  { name: '-40x40@3x', size: 120 },\n  { name: '-60x60@2x', size: 120 },\n  { name: '-60x60@3x', size: 180 },\n  { name: '-76x76@1x', size: 76 },\n  { name: '-76x76@2x', size: 152 },\n  { name: '-83.5x83.5@2x', size: 167 },\n  { name: '-1024x1024@1x', size: 1024 },\n];\n\nexport const generateContentsFile = (newIconName: string) => ({\n  images: createImageList(newIconName),\n  info: { version: 1, author: 'xcode' },\n});\n\nexport const createImageList = (newIconName: string) => {\n  return [\n    {\n      size: '20x20',\n      idiom: 'iphone',\n      filename: `${newIconName}-20x20@2x.png`,\n      scale: '2x'\n    },\n    {\n      size: '20x20',\n      idiom: 'iphone',\n      filename: `${newIconName}-20x20@3x.png`,\n      scale: '3x'\n    },\n    {\n      size: '29x29',\n      idiom: 'iphone',\n      filename: `${newIconName}-29x29@1x.png`,\n      scale: '1x'\n    },\n    {\n      size: '29x29',\n      idiom: 'iphone',\n      filename: `${newIconName}-29x29@2x.png`,\n      scale: '2x'\n    },\n    {\n      size: '29x29',\n      idiom: 'iphone',\n      filename: `${newIconName}-29x29@3x.png`,\n      scale: '3x'\n    },\n    {\n      size: '40x40',\n      idiom: 'iphone',\n      filename: `${newIconName}-40x40@2x.png`,\n      scale: '2x'\n    },\n    {\n      size: '40x40',\n      idiom: 'iphone',\n      filename: `${newIconName}-40x40@3x.png`,\n      scale: '3x'\n    },\n    {\n      size: '60x60',\n      idiom: 'iphone',\n      filename: `${newIconName}-60x60@2x.png`,\n      scale: '2x'\n    },\n    {\n      size: '60x60',\n      idiom: 'iphone',\n      filename: `${newIconName}-60x60@3x.png`,\n      scale: '3x'\n    },\n    {\n      size: '20x20',\n      idiom: 'ipad',\n      filename: `${newIconName}-20x20@1x.png`,\n      scale: '1x'\n    },\n    {\n      size: '20x20',\n      idiom: 'ipad',\n      filename: `${newIconName}-20x20@2x.png`,\n      scale: '2x'\n    },\n    {\n      size: '29x29',\n      idiom: 'ipad',\n      filename: `${newIconName}-29x29@1x.png`,\n      scale: '1x'\n    },\n    {\n      size: '29x29',\n      idiom: 'ipad',\n      filename: `${newIconName}-29x29@2x.png`,\n      scale: '2x'\n    },\n    {\n      size: '40x40',\n      idiom: 'ipad',\n      filename: `${newIconName}-40x40@1x.png`,\n      scale: '1x'\n    },\n    {\n      size: '40x40',\n      idiom: 'ipad',\n      filename: `${newIconName}-40x40@2x.png`,\n      scale: '2x'\n    },\n    {\n      size: '76x76',\n      idiom: 'ipad',\n      filename: `${newIconName}-76x76@1x.png`,\n      scale: '1x'\n    },\n    {\n      size: '76x76',\n      idiom: 'ipad',\n      filename: `${newIconName}-76x76@2x.png`,\n      scale: '2x'\n    },\n    {\n      size: '83.5x83.5',\n      idiom: 'ipad',\n      filename: `${newIconName}-83.5x83.5@2x.png`,\n      scale: '2x'\n    },\n    {\n      size: '1024x1024',\n      idiom: 'ios-marketing',\n      filename: `${newIconName}-1024x1024@1x.png`,\n      scale: '1x'\n    }\n  ];\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"icon-set-creator\",\n  \"version\": \"1.2.6\",\n  \"description\": \"Android & iOS icon generator for React Native\",\n  \"main\": \"index.js\",\n  \"bin\": {\n    \"iconset\": \"index.js\"\n  },\n  \"author\": \"martiliones\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/martiliones/icon-set-creator/issues\"\n  },\n  \"homepage\": \"https://github.com/martiliones/icon-set-creator#readme\",\n  \"engines\": {\n    \"node\": \">=14\"\n  },\n  \"scripts\": {\n    \"build\": \"tsc --project tsconfig.json\",\n    \"lint\": \"eslint ./**/*.ts\",\n    \"lint:fix\": \"eslint ./**/*.ts --fix\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/martiliones/icon-set-creator.git\"\n  },\n  \"keywords\": [\n    \"cli\",\n    \"mobile\",\n    \"react-native\",\n    \"icons\",\n    \"android-studio\",\n    \"Xcode\",\n    \"icon-set-creator\"\n  ],\n  \"devDependencies\": {\n    \"@tsconfig/recommended\": \"^1.0.1\",\n    \"@types/minimist\": \"^1.2.2\",\n    \"@types/node\": \"^17.0.45\",\n    \"@types/semver\": \"^7.3.9\",\n    \"@types/sharp\": \"^0.30.2\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.26.0\",\n    \"@typescript-eslint/parser\": \"^5.26.0\",\n    \"eslint\": \"^8.16.0\",\n    \"eslint-config-airbnb-base\": \"^15.0.0\",\n    \"eslint-config-airbnb-typescript\": \"^17.0.0\",\n    \"eslint-plugin-import\": \"^2.26.0\",\n    \"typescript\": \"^4.7.2\"\n  },\n  \"dependencies\": {\n    \"chalk\": \"^4.1.2\",\n    \"commander\": \"^9.2.0\",\n    \"leven\": \"^3.1.0\",\n    \"minimist\": \"^1.2.6\",\n    \"read-pkg\": \"^5.1.1\",\n    \"semver\": \"^7.3.7\",\n    \"sharp\": \"^0.30.5\",\n    \"strip-ansi\": \"^6.0.0\"\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"@tsconfig/recommended/tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"esModuleInterop\": true,\n    \"noImplicitAny\": true,\n    \"preserveConstEnums\": true,\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"outDir\": \"dist\"\n  },\n  \"include\": [\"./**/*.ts\"],\n  \"exclude\": [\"node_modules\", \"**/*.spec.ts\"],\n}\n"
  },
  {
    "path": "utils/dir.ts",
    "content": "import fs from 'fs';\n\nexport default (dir: string) => {\n  if (!fs.existsSync(dir)) {\n    fs.mkdirSync(dir, { recursive: true });\n  }\n};\n"
  },
  {
    "path": "utils/enhanceErrorMessages.ts",
    "content": "import { program } from 'commander';\nimport { chalk } from '.';\n\nexport default (methodName: string, log: (...args: any[]) => string) => {\n  (program as any)[methodName] = function enhanceErrorMessages(...args: any[]) {\n    /* eslint-disable no-underscore-dangle */\n    if (methodName === 'unknownOption' && this._allowUnknownOption) {\n      return;\n    }\n\n    this.outputHelp();\n    console.log(`  ${chalk.red(log(...args))}`);\n    console.log();\n\n    process.exit(1);\n  };\n};\n"
  },
  {
    "path": "utils/index.ts",
    "content": "import chalk from 'chalk';\nimport createDirectory from './dir';\nimport resolveConfig from './pkg';\n\nexport { chalk, createDirectory, resolveConfig };\n\nexport * as semver from 'semver';\nexport * from './logger';\n"
  },
  {
    "path": "utils/logger.ts",
    "content": "/* eslint-disable no-debugger, no-console */\nimport chalk from 'chalk';\nimport stripAnsi from 'strip-ansi';\n\nconst format = (label: string, msg: string) => msg.split('\\n').map((line, i) => (\n  i === 0\n    ? `${label} ${line}`\n    : line.padStart(stripAnsi(label).length + line.length + 1)\n)).join('\\n');\n\nconst chalkTag = (msg: string) => chalk.bgBlackBright.white.dim(` ${msg} `);\n\nexport const log = (msg = '', tag = null) => (\n  tag ? console.log(format(chalkTag(tag), msg)) : console.log(msg)\n);\n\nexport const info = (msg: string, tag = null) => {\n  console.log(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ''), msg));\n};\n\nexport const warn = (msg: string, tag = null) => {\n  console.warn(format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ''), chalk.yellow(msg)));\n};\n\nexport const error = (msg: string, tag = null) => {\n  console.error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ''), chalk.red(msg)));\n};\n"
  },
  {
    "path": "utils/pkg.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport readPkg from 'read-pkg';\n\nexport default (context: string) => {\n  if (fs.existsSync(path.join(context, '.iconsetrc.js'))) {\n    return require(path.join(context, '.iconsetrc.js'));\n  }\n\n  if (fs.existsSync(path.join(context, '.iconsetrc.json'))) {\n    return require(path.join(context, '.iconsetrc.json'));\n  }\n\n  if (fs.existsSync(path.join(context, 'package.json'))) {\n    return readPkg.sync({ cwd: context })?.iconsetConfig || {};\n  }\n\n  return {};\n};\n"
  }
]