UNPKG

react-native-bootsplash-cli-fork

Version:

Fork of CLI for generating assets for react-native-bootsplash.

553 lines (482 loc) 17.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generate = void 0; var _chalk = _interopRequireDefault(require("chalk")); var _fsExtra = _interopRequireDefault(require("fs-extra")); var _path = _interopRequireDefault(require("path")); var _picocolors = _interopRequireDefault(require("picocolors")); var _sharp = _interopRequireDefault(require("sharp")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const lightLogoFileName = "bootsplash_logo"; const darkLogoFileName = "bootsplash_logo_dark"; const logoAssetName = "BootSplashLogo"; const colorAssetName = "SplashColor"; const androidColorName = "bootsplash_background"; const androidColorRegex = /<color name="bootsplash_background">#\w+<\/color>/g; const toFullHexadecimal = hex => { const prefixed = hex[0] === "#" ? hex : `#${hex}`; const up = prefixed.toUpperCase(); return up.length === 4 ? "#" + up[1] + up[1] + up[2] + up[2] + up[3] + up[3] : up; }; const colorToRGB = hex => { const fullHexColor = toFullHexadecimal(hex); return { r: (parseInt(fullHexColor[1] + fullHexColor[2], 16) / 255).toPrecision(15), g: (parseInt(fullHexColor[3] + fullHexColor[4], 16) / 255).toPrecision(15), b: (parseInt(fullHexColor[5] + fullHexColor[6], 16) / 255).toPrecision(15) }; }; const getLogoContentsJson = includeDarkLogo => `{ "images": [ { "idiom": "universal", "filename": "${lightLogoFileName}.png", "scale": "1x" }, { "idiom": "universal", "filename": "${lightLogoFileName}@2x.png", "scale": "2x" }, { "idiom": "universal", "filename": "${lightLogoFileName}@3x.png", "scale": "3x" }${includeDarkLogo ? DarkImagesContentsJson : ""} ], "info": { "version": 1, "author": "xcode" } } `; const DarkImagesContentsJson = `, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom": "universal", "filename": "${darkLogoFileName}.png", "scale": "1x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom": "universal", "filename": "${darkLogoFileName}@2x.png", "scale": "2x" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom": "universal", "filename": "${darkLogoFileName}@3x.png", "scale": "3x" } `; const getDarkColorsContentsJson = darkColor => { const rgb = colorToRGB(darkColor); return `, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "color" : { "color-space" : "srgb", "components" : { "alpha" : "1.000", "blue" : "${rgb.b}", "green" : "${rgb.g}", "red" : "${rgb.r}" } }, "idiom" : "universal" } `; }; const getColorsContentsJson = (lightColor, darkColor) => { const rgb = colorToRGB(lightColor); return `{ "colors" : [ { "color" : { "color-space" : "srgb", "components" : { "alpha" : "1.000", "blue" : "${rgb.b}", "green" : "${rgb.g}", "red" : "${rgb.r}" } }, "idiom" : "universal" }${darkColor ? getDarkColorsContentsJson(darkColor) : ""} ], "info" : { "author" : "xcode", "version" : 1 } }`; }; const getStoryboard = _ref => { let { height, width } = _ref; return `<?xml version="1.0" encoding="UTF-8"?> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17147" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> <device id="retina4_7" orientation="portrait" appearance="light"/> <dependencies> <deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17120"/> <capability name="Named colors" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> <!--View Controller--> <scene sceneID="EHf-IW-A2E"> <objects> <viewController id="01J-lp-oVM" sceneMemberID="viewController"> <view key="view" autoresizesSubviews="NO" userInteractionEnabled="NO" contentMode="scaleToFill" id="Ze5-6b-2t3"> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask"/> <subviews> <imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="BootSplashLogo" translatesAutoresizingMaskIntoConstraints="NO" id="3lX-Ut-9ad"> <rect key="frame" x="${(375 - width) / 2}" y="${(667 - height) / 2}" width="${width}" height="${height}"/> <accessibility key="accessibilityConfiguration"> <accessibilityTraits key="traits" image="YES" notEnabled="YES"/> </accessibility> </imageView> </subviews> <viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/> <color key="backgroundColor" name="SplashColor"/> <accessibility key="accessibilityConfiguration"> <accessibilityTraits key="traits" notEnabled="YES"/> </accessibility> <constraints> <constraint firstItem="3lX-Ut-9ad" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="Fh9-Fy-1nT"/> <constraint firstItem="3lX-Ut-9ad" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="nvB-Ic-PnI"/> </constraints> </view> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> </objects> <point key="canvasLocation" x="0.0" y="0.0"/> </scene> </scenes> <resources> <image name="${logoAssetName}" width="${width}" height="${height}"/> <namedColor name="SplashColor" /> </resources> </document> `; }; const log = { error: text => console.log(_picocolors.default.red(text)), text: text => console.log(text), warn: text => console.log(_picocolors.default.yellow(text)) }; const logWriteGlobal = (emoji, filePath, workingPath, dimensions) => log.text(`${emoji} ${_path.default.relative(workingPath, filePath)}` + (dimensions != null ? ` (${dimensions.width}x${dimensions.height})` : "")); const isValidHexadecimal = value => /^#?([0-9A-F]{3}){1,2}$/i.test(value); const generate = async _ref2 => { let { android, ios, workingPath, logoPath, darkLogoPath, backgroundColor, darkBackgroundColor, logoWidth, flavor, assetsPath } = _ref2; await generateSingle({ android, ios, workingPath, logoPath, backgroundColor, logoWidth, flavor, assetsPath, theme: "light" }); if (darkLogoPath && darkBackgroundColor) { await generateSingle({ android, ios, workingPath, logoPath: darkLogoPath, backgroundColor: darkBackgroundColor, logoWidth, flavor, assetsPath, theme: "dark" }); } if (ios) { createIosAssets({ projectPath: ios.projectPath, workingPath, includeDarkLogo: !!darkLogoPath, lightBackgroundColor: backgroundColor, darkBackgroundColor: darkBackgroundColor }); } log.text(` ${_chalk.default.blue("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")} ${_chalk.default.blue("┃")} 💖 ${_chalk.default.bold("Love this library? Consider sponsoring!")} ${_chalk.default.blue("┃")} ${_chalk.default.blue("┃")} One-time amounts are available. ${_chalk.default.blue("┃")} ${_chalk.default.blue("┃")} ${_chalk.default.underline("https://github.com/sponsors/zoontek")} ${_chalk.default.blue("┃")} ${_chalk.default.blue("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")} `); log.text(`✅ Done! Thanks for using ${_chalk.default.underline("react-native-bootsplash")}.`); }; exports.generate = generate; const generateSingle = async _ref3 => { let { android, ios, workingPath, logoPath, backgroundColor, logoWidth, flavor, assetsPath, theme } = _ref3; if (!isValidHexadecimal(backgroundColor)) { log.error("--background-color value is not a valid hexadecimal color."); process.exit(1); } const logoFileName = theme === "light" ? lightLogoFileName : darkLogoFileName; const image = (0, _sharp.default)(logoPath); const backgroundColorHex = toFullHexadecimal(backgroundColor); const { format } = await image.metadata(); if (format !== "png" && format !== "svg") { log.error("Input file is an unsupported image format"); process.exit(1); } const logoHeight = await image.clone().resize(logoWidth).toBuffer().then(buffer => (0, _sharp.default)(buffer).metadata()).then(_ref4 => { let { height = 0 } = _ref4; return height; }); const shouldSkipAndroid = logoWidth > 288 || logoHeight > 288; const logAbove288 = dimension => { const message = `⚠️ Logo ${dimension} exceed 288dp. As it will be cropped by Android, we skip generation for this platform.`; log.warn(message); }; const logAbove192 = dimension => { const message = `⚠️ Logo ${dimension} exceed 192dp. It might be cropped by Android.`; log.warn(message); }; if (logoWidth > 288) { logAbove288("width"); } else if (logoHeight > 288) { logAbove288("height"); } else if (logoWidth > 192) { logAbove192("width"); } else if (logoHeight > 192) { logAbove192("height"); } const logWrite = (emoji, filePath, dimensions) => logWriteGlobal(emoji, filePath, workingPath, dimensions); if (assetsPath && _fsExtra.default.existsSync(assetsPath)) { log.text(`\n ${_chalk.default.underline("Assets")}`); await Promise.all([{ ratio: 1, suffix: "" }, { ratio: 1.5, suffix: "@1,5x" }, { ratio: 2, suffix: "@2x" }, { ratio: 3, suffix: "@3x" }, { ratio: 4, suffix: "@4x" }].map(_ref5 => { let { ratio, suffix } = _ref5; const fileName = `${logoFileName}${suffix}.png`; const filePath = _path.default.resolve(assetsPath, fileName); return image.clone().resize(logoWidth * ratio).png({ quality: 100 }).toFile(filePath).then(_ref6 => { let { width, height } = _ref6; logWrite("✨", filePath, { width, height }); }); })); } if (android && !shouldSkipAndroid) { log.text(`\n ${_picocolors.default.underline("Android")}`); const appPath = android.appName ? _path.default.resolve(android.sourceDir, android.appName) : _path.default.resolve(android.sourceDir); // @react-native-community/cli 2.x & 3.x support const resPath = _path.default.resolve(appPath, "src", flavor, "res"); const valuesPath = _path.default.resolve(resPath, theme === "light" ? "values" : "values-night"); _fsExtra.default.ensureDirSync(valuesPath); const colorsXmlPath = _path.default.resolve(valuesPath, "colors.xml"); const colorsXmlEntry = `<color name="${androidColorName}">${backgroundColorHex}</color>`; if (_fsExtra.default.existsSync(colorsXmlPath)) { const colorsXml = _fsExtra.default.readFileSync(colorsXmlPath, "utf-8"); if (colorsXml.match(androidColorRegex)) { _fsExtra.default.writeFileSync(colorsXmlPath, colorsXml.replace(androidColorRegex, colorsXmlEntry), "utf-8"); } else { _fsExtra.default.writeFileSync(colorsXmlPath, colorsXml.replace(/<\/resources>/g, ` ${colorsXmlEntry}\n</resources>`), "utf-8"); } logWrite("✏️ ", colorsXmlPath); } else { _fsExtra.default.writeFileSync(colorsXmlPath, `<resources>\n ${colorsXmlEntry}\n</resources>\n`, "utf-8"); logWrite("✨", colorsXmlPath); } await Promise.all([{ ratio: 1, directory: "mipmap-mdpi" }, { ratio: 1.5, directory: "mipmap-hdpi" }, { ratio: 2, directory: "mipmap-xhdpi" }, { ratio: 3, directory: "mipmap-xxhdpi" }, { ratio: 4, directory: "mipmap-xxxhdpi" }].map(_ref7 => { let { ratio, directory } = _ref7; const fileName = `${logoFileName}.png`; const filePath = _path.default.resolve(resPath, directory, fileName); // https://github.com/androidx/androidx/blob/androidx-main/core/core-splashscreen/src/main/res/values/dimens.xml#L22 const canvasSize = 288 * ratio; // https://sharp.pixelplumbing.com/api-constructor const canvas = (0, _sharp.default)({ create: { width: canvasSize, height: canvasSize, channels: 4, background: { r: 255, g: 255, b: 255, alpha: 0 } } }); return image.clone().resize(logoWidth * ratio).toBuffer().then(input => canvas.composite([{ input }]).png({ quality: 100 }).toFile(filePath)).then(() => { logWrite("✨", filePath, { width: canvasSize, height: canvasSize }); }); })); } if (ios) { log.text(`\n ${_chalk.default.underline("iOS")}`); const projectPath = ios.projectPath; const imagesPath = _path.default.resolve(projectPath, "Images.xcassets"); if (_fsExtra.default.existsSync(projectPath)) { const storyboardPath = _path.default.resolve(projectPath, "BootSplash.storyboard"); _fsExtra.default.writeFileSync(storyboardPath, getStoryboard({ height: logoHeight, width: logoWidth }), "utf-8"); logWrite("✨", storyboardPath); } else { log.text(`No "${projectPath}" directory found. Skipping iOS storyboard generation…`); } if (_fsExtra.default.existsSync(imagesPath)) { const imageSetPath = _path.default.resolve(imagesPath, logoAssetName + ".imageset"); _fsExtra.default.ensureDirSync(imageSetPath); await Promise.all([{ ratio: 1, suffix: "" }, { ratio: 2, suffix: "@2x" }, { ratio: 3, suffix: "@3x" }].map(_ref8 => { let { ratio, suffix } = _ref8; const fileName = `${logoFileName}${suffix}.png`; const filePath = _path.default.resolve(imageSetPath, fileName); return image.clone().resize(logoWidth * ratio).png({ quality: 100 }).toFile(filePath).then(_ref9 => { let { width, height } = _ref9; logWrite("✨", filePath, { width, height }); }); })); } else { log.text(`No "${imagesPath}" directory found. Skipping iOS images generation…`); } } }; const createIosAssets = _ref10 => { let { projectPath, workingPath, includeDarkLogo, lightBackgroundColor, darkBackgroundColor } = _ref10; const logWrite = (emoji, filePath, dimensions) => logWriteGlobal(emoji, filePath, workingPath, dimensions); const imagesPath = _path.default.resolve(projectPath, "Images.xcassets"); if (_fsExtra.default.existsSync(imagesPath)) { const imageSetPath = _path.default.resolve(imagesPath, logoAssetName + ".imageset"); _fsExtra.default.ensureDirSync(imageSetPath); _fsExtra.default.writeFileSync(_path.default.resolve(imageSetPath, "Contents.json"), getLogoContentsJson(includeDarkLogo), "utf-8"); logWrite("✨", _path.default.resolve(imageSetPath, "Contents.json")); const colorSetPath = _path.default.resolve(imagesPath, colorAssetName + ".colorset"); _fsExtra.default.ensureDirSync(colorSetPath); _fsExtra.default.writeFileSync(_path.default.resolve(colorSetPath, "Contents.json"), getColorsContentsJson(lightBackgroundColor, darkBackgroundColor), "utf-8"); logWrite("✨", _path.default.resolve(colorSetPath, "Contents.json")); } }; //# sourceMappingURL=generate.js.map