react-native-bootsplash-cli-fork
Version:
Fork of CLI for generating assets for react-native-bootsplash.
553 lines (482 loc) • 17.2 kB
JavaScript
"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