This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,78 @@
import { SplashScreenImageResizeModeType, SplashScreenStatusBarStyleType } from './constants';
export declare type Color = [number, number, number, number];
/**
* iOS SplashScreen config.
*/
export declare type IosSplashScreenConfig = {
backgroundColor: Color;
image?: string;
imageResizeMode?: SplashScreenImageResizeModeType;
statusBar?: {
hidden?: boolean;
style?: SplashScreenStatusBarStyleType;
};
darkMode?: {
backgroundColor?: Color;
image?: string;
};
};
/**
* Android SplashScreen config.
*/
export declare type AndroidSplashScreenConfig = {
backgroundColor: Color;
image?: string;
imageResizeMode?: SplashScreenImageResizeModeType;
statusBar?: {
hidden?: boolean;
style?: SplashScreenStatusBarStyleType;
translucent?: boolean;
backgroundColor?: Color;
};
darkMode?: {
backgroundColor?: Color;
image?: string;
statusBar?: {
style?: SplashScreenStatusBarStyleType;
backgroundColor?: Color;
};
};
};
/**
* The very same as `IosSplashScreenConfig`, but JSON-friendly (values for each property are JavaScript built-in types).
*/
export declare type IosSplashScreenConfigJSON = {
backgroundColor: string;
image?: string;
imageResizeMode?: string;
statusBar?: {
hidden?: boolean;
style?: string;
};
darkMode?: {
backgroundColor?: string;
image?: string;
};
};
/**
* The very same as `IosSplashScreenConfig`, but JSON-friendly (values for each property are JavaScript built-in types).
*/
export declare type AndroidSplashScreenConfigJSON = {
backgroundColor: string;
image?: string;
imageResizeMode?: string;
statusBar?: {
hidden?: boolean;
style?: string;
translucent?: boolean;
backgroundColor?: string;
};
darkMode?: {
backgroundColor?: string;
image?: string;
statusBar?: {
style?: string;
backgroundColor?: string;
};
};
};

View File

@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=SplashScreenConfig.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"SplashScreenConfig.js","sourceRoot":"","sources":["../src/SplashScreenConfig.ts"],"names":[],"mappings":"","sourcesContent":["import { SplashScreenImageResizeModeType, SplashScreenStatusBarStyleType } from './constants';\n\nexport type Color = [number, number, number, number];\n\n/**\n * iOS SplashScreen config.\n */\nexport type IosSplashScreenConfig = {\n backgroundColor: Color;\n\n image?: string;\n imageResizeMode?: SplashScreenImageResizeModeType;\n\n statusBar?: {\n hidden?: boolean;\n style?: SplashScreenStatusBarStyleType;\n };\n\n darkMode?: {\n backgroundColor?: Color;\n image?: string;\n };\n};\n\n/**\n * Android SplashScreen config.\n */\nexport type AndroidSplashScreenConfig = {\n backgroundColor: Color;\n\n image?: string;\n imageResizeMode?: SplashScreenImageResizeModeType;\n\n statusBar?: {\n hidden?: boolean;\n style?: SplashScreenStatusBarStyleType;\n translucent?: boolean;\n backgroundColor?: Color;\n };\n\n darkMode?: {\n backgroundColor?: Color;\n image?: string;\n statusBar?: {\n style?: SplashScreenStatusBarStyleType;\n backgroundColor?: Color;\n };\n };\n};\n\n/**\n * The very same as `IosSplashScreenConfig`, but JSON-friendly (values for each property are JavaScript built-in types).\n */\nexport type IosSplashScreenConfigJSON = {\n backgroundColor: string;\n\n image?: string;\n imageResizeMode?: string;\n\n statusBar?: {\n hidden?: boolean;\n style?: string;\n };\n\n darkMode?: {\n backgroundColor?: string;\n image?: string;\n };\n};\n\n/**\n * The very same as `IosSplashScreenConfig`, but JSON-friendly (values for each property are JavaScript built-in types).\n */\nexport type AndroidSplashScreenConfigJSON = {\n backgroundColor: string;\n\n image?: string;\n imageResizeMode?: string;\n\n statusBar?: {\n hidden?: boolean;\n style?: string;\n translucent?: boolean;\n backgroundColor?: string;\n };\n\n darkMode?: {\n backgroundColor?: string;\n image?: string;\n statusBar?: {\n style?: string;\n backgroundColor?: string;\n };\n };\n};\n"]}

View File

@ -0,0 +1,4 @@
/**
* @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
export default function configureAndroidManifestXml(androidMainPath: string): Promise<void>;

View File

@ -0,0 +1,48 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const xml_manipulation_1 = require("../xml-manipulation");
const ANDROID_MANIFEST_XML_FILE_PATH = './AndroidManifest.xml';
function configureAndroidManifest(xml) {
const result = xml_manipulation_1.mergeXmlElements(xml, {
elements: [
{
name: 'manifest',
elements: [
{
name: 'application',
attributes: {
'android:name': '.MainApplication',
},
elements: [
{
name: 'activity',
attributes: {
'android:name': '.MainActivity',
'android:theme': {
newValue: '@style/Theme.App.SplashScreen',
},
},
},
],
},
],
},
],
});
return result;
}
/**
* @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
async function configureAndroidManifestXml(androidMainPath) {
const filePath = path_1.default.resolve(androidMainPath, ANDROID_MANIFEST_XML_FILE_PATH);
const xmlContent = await xml_manipulation_1.readXmlFile(filePath);
const configuredXmlContent = configureAndroidManifest(xmlContent);
await xml_manipulation_1.writeXmlFile(filePath, configuredXmlContent);
}
exports.default = configureAndroidManifestXml;
//# sourceMappingURL=AndroidManifest.xml.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AndroidManifest.xml.js","sourceRoot":"","sources":["../../src/android/AndroidManifest.xml.ts"],"names":[],"mappings":";;;;;AAAA,gDAAwB;AAGxB,0DAAkF;AAElF,MAAM,8BAA8B,GAAG,uBAAuB,CAAC;AAE/D,SAAS,wBAAwB,CAAC,GAAY;IAC5C,MAAM,MAAM,GAAG,mCAAgB,CAAC,GAAG,EAAE;QACnC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,UAAU;gBAChB,QAAQ,EAAE;oBACR;wBACE,IAAI,EAAE,aAAa;wBACnB,UAAU,EAAE;4BACV,cAAc,EAAE,kBAAkB;yBACnC;wBACD,QAAQ,EAAE;4BACR;gCACE,IAAI,EAAE,UAAU;gCAChB,UAAU,EAAE;oCACV,cAAc,EAAE,eAAe;oCAC/B,eAAe,EAAE;wCACf,QAAQ,EAAE,+BAA+B;qCAC1C;iCACF;6BACF;yBACF;qBACF;iBACF;aACF;SACF;KACF,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACY,KAAK,UAAU,2BAA2B,CAAC,eAAuB;IAC/E,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,eAAe,EAAE,8BAA8B,CAAC,CAAC;IAC/E,MAAM,UAAU,GAAG,MAAM,8BAAW,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,oBAAoB,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;IAClE,MAAM,+BAAY,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;AACrD,CAAC;AALD,8CAKC","sourcesContent":["import path from 'path';\nimport { Element } from 'xml-js';\n\nimport { readXmlFile, writeXmlFile, mergeXmlElements } from '../xml-manipulation';\n\nconst ANDROID_MANIFEST_XML_FILE_PATH = './AndroidManifest.xml';\n\nfunction configureAndroidManifest(xml: Element): Element {\n const result = mergeXmlElements(xml, {\n elements: [\n {\n name: 'manifest',\n elements: [\n {\n name: 'application',\n attributes: {\n 'android:name': '.MainApplication',\n },\n elements: [\n {\n name: 'activity',\n attributes: {\n 'android:name': '.MainActivity',\n 'android:theme': {\n newValue: '@style/Theme.App.SplashScreen',\n },\n },\n },\n ],\n },\n ],\n },\n ],\n });\n return result;\n}\n\n/**\n * @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.\n */\nexport default async function configureAndroidManifestXml(androidMainPath: string) {\n const filePath = path.resolve(androidMainPath, ANDROID_MANIFEST_XML_FILE_PATH);\n const xmlContent = await readXmlFile(filePath);\n const configuredXmlContent = configureAndroidManifest(xmlContent);\n await writeXmlFile(filePath, configuredXmlContent);\n}\n"]}

View File

@ -0,0 +1,16 @@
import { Color } from '../SplashScreenConfig';
/**
* @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
export default function configureColorsXml(androidMainPath: string, config: {
backgroundColor: Color;
statusBar?: {
backgroundColor?: Color;
};
darkMode?: {
backgroundColor?: Color;
statusBar?: {
backgroundColor?: Color;
};
};
}): Promise<void>;

View File

@ -0,0 +1,96 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const color_string_1 = __importDefault(require("color-string"));
const path_1 = __importDefault(require("path"));
const xml_manipulation_1 = require("../xml-manipulation");
const COLORS_XML_FILE_PATH = './res/values/colors.xml';
const COLORS_NIGHT_XML_FILE_PATH = './res/values-night/colors.xml';
function ensureDesiredXmlContent(xml, { backgroundColor, statusBarBackgroundColor, }) {
let idx = 0;
const result = xml_manipulation_1.mergeXmlElements(xml, {
elements: [
{
name: 'resources',
elements: [
{
idx: idx++,
comment: ` Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually `,
},
{
deletionFlag: !backgroundColor,
idx: !backgroundColor ? undefined : idx++,
name: 'color',
attributes: {
name: 'splashscreen_background',
},
elements: [
{
text: backgroundColor ? getAndroidStyleHex(backgroundColor) : '',
},
],
},
{
deletionFlag: !statusBarBackgroundColor,
idx: !statusBarBackgroundColor ? undefined : idx++,
name: 'color',
attributes: {
name: 'splashscreen_statusbar_color',
},
elements: [
{
text: statusBarBackgroundColor ? getAndroidStyleHex(statusBarBackgroundColor) : '',
},
],
},
],
},
],
});
return result;
}
/**
* css-recognized hex is of format `#RRGGBB(AA)` or `#RGB(A)`, while Android accepts `#(AA)RRGGBB` or `#(A)RGB` (https://developer.android.com/guide/topics/resources/color-list-resource)
* This function converts following formats:
* - `#RRGGBBAA` ➡️ `#AARRGGBB`,
* - `#RGBA` ➡️ `#ARGB`.
*/
function getAndroidStyleHex(color) {
return color_string_1.default.to
.hex(color)
.replace(/^(#)([0-F]{2})([0-F]{4})([0-F]{2}$)/i, '$1$4$2$3')
.replace(/^(#)([0-F])([0-F]{2})([0-F])$/i, '$1$4$2$3');
}
/**
* @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
async function configureColorsXml(androidMainPath, config) {
var _a, _b, _c, _d;
const backgroundColor = config.backgroundColor;
const darkModeBackgroundColor = (_a = config.darkMode) === null || _a === void 0 ? void 0 : _a.backgroundColor;
const statusBarBackgroundColor = (_b = config.statusBar) === null || _b === void 0 ? void 0 : _b.backgroundColor;
const darkModeStatusBarBackgroundColor = (_d = (_c = config.darkMode) === null || _c === void 0 ? void 0 : _c.statusBar) === null || _d === void 0 ? void 0 : _d.backgroundColor;
if (darkModeStatusBarBackgroundColor && !statusBarBackgroundColor) {
throw new Error(`'darkModeStatusBarBackgroundColor' is available only if 'statusBarBackgroundColor' is provided as well.`);
}
const filePath = path_1.default.resolve(androidMainPath, COLORS_XML_FILE_PATH);
const darkFilePath = path_1.default.resolve(androidMainPath, COLORS_NIGHT_XML_FILE_PATH);
const xmlContent = await xml_manipulation_1.readXmlFile(filePath);
const darkFileContent = await xml_manipulation_1.readXmlFile(darkFilePath);
const configuredXmlContent = ensureDesiredXmlContent(xmlContent, {
backgroundColor,
statusBarBackgroundColor,
});
const configuredDarkXmlContent = ensureDesiredXmlContent(darkFileContent, {
backgroundColor: darkModeBackgroundColor,
statusBarBackgroundColor: darkModeStatusBarBackgroundColor,
});
await xml_manipulation_1.writeXmlFileOrRemoveFileUponNoResources(darkFilePath, configuredDarkXmlContent, {
disregardComments: true,
});
await xml_manipulation_1.writeXmlFile(filePath, configuredXmlContent);
}
exports.default = configureColorsXml;
//# sourceMappingURL=Colors.xml.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
import { SplashScreenImageResizeModeType } from '../constants';
/**
* @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
export default function configureDrawableXml(androidMainPath: string, config?: {
imageResizeMode?: SplashScreenImageResizeModeType;
}): Promise<void>;

View File

@ -0,0 +1,63 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const constants_1 = require("../constants");
const xml_manipulation_1 = require("../xml-manipulation");
const DRAWABLE_XML_FILE_PATH = './res/drawable/splashscreen.xml';
function configureDrawable(xml, resizeMode) {
const expected = {
elements: [
{
idx: 0,
comment: `\n This file was created by '@expo/configure-splash-screen' and some of it's content shouldn't be modified by hand\n`,
},
{
name: 'layer-list',
attributes: {
'xmlns:android': 'http://schemas.android.com/apk/res/android',
},
elements: {
newValue: [
{
name: 'item',
attributes: {
'android:drawable': '@color/splashscreen_background',
},
},
].concat(resizeMode !== constants_1.SplashScreenImageResizeMode.NATIVE
? []
: [
{
name: 'item',
elements: [
{
name: 'bitmap',
attributes: {
'android:gravity': 'center',
'android:src': '@drawable/splashscreen_image',
},
},
],
},
]),
},
},
],
};
const result = xml_manipulation_1.mergeXmlElements(xml, expected);
return result;
}
/**
* @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
async function configureDrawableXml(androidMainPath, config = {}) {
const filePath = path_1.default.resolve(androidMainPath, DRAWABLE_XML_FILE_PATH);
const xmlContent = await xml_manipulation_1.readXmlFile(filePath);
const configuredXmlContent = configureDrawable(xmlContent, config.imageResizeMode);
await xml_manipulation_1.writeXmlFile(filePath, configuredXmlContent);
}
exports.default = configureDrawableXml;
//# sourceMappingURL=Drawable.xml.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Drawable.xml.js","sourceRoot":"","sources":["../../src/android/Drawable.xml.ts"],"names":[],"mappings":";;;;;AAAA,gDAAwB;AAGxB,4CAA4F;AAC5F,0DAM6B;AAE7B,MAAM,sBAAsB,GAAG,iCAAiC,CAAC;AAEjE,SAAS,iBAAiB,CAAC,GAAY,EAAE,UAA4C;IACnF,MAAM,QAAQ,GAAyB;QACrC,QAAQ,EAAE;YACR;gBACE,GAAG,EAAE,CAAC;gBACN,OAAO,EAAE,uHAAuH;aACjI;YACD;gBACE,IAAI,EAAE,YAAY;gBAClB,UAAU,EAAE;oBACV,eAAe,EAAE,4CAA4C;iBAC9D;gBACD,QAAQ,EAAE;oBACR,QAAQ,EAAG;wBACT;4BACE,IAAI,EAAE,MAAM;4BACZ,UAAU,EAAE;gCACV,kBAAkB,EAAE,gCAAgC;6BACrD;yBACF;qBACwB,CAAC,MAAM,CAChC,UAAU,KAAK,uCAA2B,CAAC,MAAM;wBAC/C,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC;4BACE;gCACE,IAAI,EAAE,MAAM;gCACZ,QAAQ,EAAE;oCACR;wCACE,IAAI,EAAE,QAAQ;wCACd,UAAU,EAAE;4CACV,iBAAiB,EAAE,QAAQ;4CAC3B,aAAa,EAAE,8BAA8B;yCAC9C;qCACF;iCACF;6BACF;yBACF,CACN;iBACF;aACF;SACF;KACF,CAAC;IACF,MAAM,MAAM,GAAG,mCAAgB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC/C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACY,KAAK,UAAU,oBAAoB,CAChD,eAAuB,EACvB,SAEI,EAAE;IAEN,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;IACvE,MAAM,UAAU,GAAG,MAAM,8BAAW,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,oBAAoB,GAAG,iBAAiB,CAAC,UAAU,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC;IACnF,MAAM,+BAAY,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;AACrD,CAAC;AAVD,uCAUC","sourcesContent":["import path from 'path';\nimport { Element } from 'xml-js';\n\nimport { SplashScreenImageResizeMode, SplashScreenImageResizeModeType } from '../constants';\nimport {\n mergeXmlElements,\n readXmlFile,\n writeXmlFile,\n ExpectedElementsType,\n ExpectedElementType,\n} from '../xml-manipulation';\n\nconst DRAWABLE_XML_FILE_PATH = './res/drawable/splashscreen.xml';\n\nfunction configureDrawable(xml: Element, resizeMode?: SplashScreenImageResizeModeType): Element {\n const expected: ExpectedElementsType = {\n elements: [\n {\n idx: 0,\n comment: `\\n This file was created by '@expo/configure-splash-screen' and some of it's content shouldn't be modified by hand\\n`,\n },\n {\n name: 'layer-list',\n attributes: {\n 'xmlns:android': 'http://schemas.android.com/apk/res/android',\n },\n elements: {\n newValue: ([\n {\n name: 'item',\n attributes: {\n 'android:drawable': '@color/splashscreen_background',\n },\n },\n ] as ExpectedElementType[]).concat(\n resizeMode !== SplashScreenImageResizeMode.NATIVE\n ? []\n : [\n {\n name: 'item',\n elements: [\n {\n name: 'bitmap',\n attributes: {\n 'android:gravity': 'center',\n 'android:src': '@drawable/splashscreen_image',\n },\n },\n ],\n },\n ]\n ),\n },\n },\n ],\n };\n const result = mergeXmlElements(xml, expected);\n return result;\n}\n\n/**\n * @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.\n */\nexport default async function configureDrawableXml(\n androidMainPath: string,\n config: {\n imageResizeMode?: SplashScreenImageResizeModeType;\n } = {}\n) {\n const filePath = path.resolve(androidMainPath, DRAWABLE_XML_FILE_PATH);\n const xmlContent = await readXmlFile(filePath);\n const configuredXmlContent = configureDrawable(xmlContent, config.imageResizeMode);\n await writeXmlFile(filePath, configuredXmlContent);\n}\n"]}

View File

@ -0,0 +1,13 @@
/**
* Deletes all previous splash_screen_images and copies new one to desired drawable directory.
* If path isn't provided then no new image is placed in drawable directories.
* @see https://developer.android.com/training/multiscreen/screendensities
*
* @param androidMainPath Absolute path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
export default function configureDrawables(androidMainPath: string, config?: {
image?: string;
darkMode?: {
image?: string;
};
}): Promise<void>;

View File

@ -0,0 +1,112 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const SPLASH_SCREEN_FILENAME = 'splashscreen_image.png';
const DRAWABLES_CONFIGS = {
default: {
modes: {
light: {
path: `./res/drawable/${SPLASH_SCREEN_FILENAME}`,
},
dark: {
path: `./res/drawable-night/${SPLASH_SCREEN_FILENAME}`,
},
},
dimensionsMultiplier: 1,
},
mdpi: {
modes: {
light: {
path: `./res/drawable-mdpi/${SPLASH_SCREEN_FILENAME}`,
},
dark: {
path: `./res/drawable-night-mdpi/${SPLASH_SCREEN_FILENAME}`,
},
},
dimensionsMultiplier: 1,
},
hdpi: {
modes: {
light: {
path: `./res/drawable-hdpi/${SPLASH_SCREEN_FILENAME}`,
},
dark: {
path: `./res/drawable-night-hdpi/${SPLASH_SCREEN_FILENAME}`,
},
},
dimensionsMultiplier: 1.5,
},
xhdpi: {
modes: {
light: {
path: `./res/drawable-xhdpi/${SPLASH_SCREEN_FILENAME}`,
},
dark: {
path: `./res/drawable-night-xhdpi/${SPLASH_SCREEN_FILENAME}`,
},
},
dimensionsMultiplier: 2,
},
xxhdpi: {
modes: {
light: {
path: `./res/drawable-xxhdpi/${SPLASH_SCREEN_FILENAME}`,
},
dark: {
path: `./res/drawable-night-xxhdpi/${SPLASH_SCREEN_FILENAME}`,
},
},
dimensionsMultiplier: 3,
},
xxxhdpi: {
modes: {
light: {
path: `./res/drawable-xxxhdpi/${SPLASH_SCREEN_FILENAME}`,
},
dark: {
path: `./res/drawable-night-xxxhdpi/${SPLASH_SCREEN_FILENAME}`,
},
},
dimensionsMultiplier: 4,
},
};
/**
* @param srcPath Absolute path
* @param dstPath Absolute path
*/
async function copyDrawableFile(srcPath, dstPath) {
if (!srcPath) {
return;
}
if (!(await fs_extra_1.default.pathExists(path_1.default.dirname(dstPath)))) {
await fs_extra_1.default.mkdir(path_1.default.dirname(dstPath));
}
await fs_extra_1.default.copyFile(srcPath, path_1.default.resolve(dstPath));
}
/**
* Deletes all previous splash_screen_images and copies new one to desired drawable directory.
* If path isn't provided then no new image is placed in drawable directories.
* @see https://developer.android.com/training/multiscreen/screendensities
*
* @param androidMainPath Absolute path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
async function configureDrawables(androidMainPath, config = {}) {
var _a;
await Promise.all(Object.values(DRAWABLES_CONFIGS).map(async ({ modes }) => {
await Promise.all(Object.values(modes).map(async ({ path: filePath }) => {
if (await fs_extra_1.default.pathExists(path_1.default.resolve(androidMainPath, filePath))) {
await fs_extra_1.default.remove(path_1.default.resolve(androidMainPath, filePath));
}
}));
}));
await Promise.all([
copyDrawableFile(config.image, path_1.default.resolve(androidMainPath, DRAWABLES_CONFIGS.default.modes.light.path)),
copyDrawableFile((_a = config.darkMode) === null || _a === void 0 ? void 0 : _a.image, path_1.default.resolve(androidMainPath, DRAWABLES_CONFIGS.default.modes.dark.path)),
]);
}
exports.default = configureDrawables;
//# sourceMappingURL=Drawables.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
import { SplashScreenImageResizeModeType } from '../constants';
/**
* Injects specific code to MainActivity that would trigger SplashScreen mounting process.
*/
export default function configureMainActivity(projectRootPath: string, config?: {
imageResizeMode?: SplashScreenImageResizeModeType;
statusBar?: {
translucent?: boolean;
};
}): Promise<void>;

View File

@ -0,0 +1,137 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = __importDefault(require("fs-extra"));
const constants_1 = require("../constants");
const StateManager_1 = __importDefault(require("../utils/StateManager"));
const string_utils_1 = require("../utils/string-utils");
const Paths_1 = require("./Paths");
/**
* Injects specific code to MainActivity that would trigger SplashScreen mounting process.
*/
async function configureMainActivity(projectRootPath, config = {}) {
var _a, _b, _c;
const resizeMode = (_a = config.imageResizeMode) !== null && _a !== void 0 ? _a : constants_1.SplashScreenImageResizeMode.CONTAIN;
const statusBarTranslucent = (_c = (_b = config.statusBar) === null || _b === void 0 ? void 0 : _b.translucent) !== null && _c !== void 0 ? _c : false;
// eslint-disable-next-line
const mainActivity = await Paths_1.getMainActivityAsync(projectRootPath);
if (!mainActivity) {
throw new Error(`Failed to configure 'MainActivity'.`);
}
const isJava = mainActivity.language === 'java';
const isKotlin = mainActivity.language === 'kt';
const LE = isJava ? ';' : '';
const { state: newFileContent } = new StateManager_1.default(mainActivity.contents)
// importing ReactRootView
.applyAction(content => {
const [succeeded, newContent] = string_utils_1.replace(content, {
replacePattern: /^import com\.facebook\.react\.ReactRootView.*?$/m,
replaceContent: `import com.facebook.react.ReactRootView${LE}`,
});
return [newContent, 'replacedReactRootViewImports', succeeded];
})
.applyAction((content, { replacedReactRootViewImports }) => {
if (replacedReactRootViewImports) {
return [content, 'insertedReactRootViewImports', false];
}
const [succeeded, newContent] = string_utils_1.insert(content, {
insertPattern: isJava ? /(?=public class .* extends .* {.*$)/m : /(?=class .* : .* {.*$)/m,
insertContent: `import com.facebook.react.ReactRootView${LE}
`,
});
return [newContent, 'insertedReactRootViewImports', succeeded];
})
// importing SplashScreen
.applyAction(content => {
const [succeeded, newContent] = string_utils_1.replace(content, {
replacePattern: /^import expo\.modules\.splashscreen\..*?SplashScreen.*?\nimport expo\.modules\.splashscreen\.SplashScreenImageResizeMode.*?$/m,
replaceContent: `import expo.modules.splashscreen.singletons.SplashScreen${LE}
import expo.modules.splashscreen.SplashScreenImageResizeMode${LE}`,
});
return [newContent, 'replacedSplashImports', succeeded];
})
.applyAction((content, { replacedSplashImports }) => {
if (replacedSplashImports) {
return [content, 'insertedSplashImports', false];
}
const [succeeded, newContent] = string_utils_1.insert(content, {
insertPattern: isJava ? /(?=public class .* extends .* {.*$)/m : /(?=class .* : .* {.*$)/m,
insertContent: `import expo.modules.splashscreen.singletons.SplashScreen${LE}
import expo.modules.splashscreen.SplashScreenImageResizeMode${LE}
`,
});
return [newContent, 'insertedSplashImports', succeeded];
})
// registering SplashScreen in onCreate()
.applyAction(content => {
const [succeeded, newContent] = string_utils_1.replace(content, {
replacePattern: /(?<=super\.onCreate(.|\n)*?)SplashScreen\.show\(this, SplashScreenImageResizeMode\..*\).*$/m,
replaceContent: `SplashScreen.show(this, SplashScreenImageResizeMode.${resizeMode.toUpperCase()}, ReactRootView${isKotlin ? '::class.java' : '.class'}, ${statusBarTranslucent})${LE}`,
});
return [newContent, 'replacedInOnCreate', succeeded];
})
.applyAction((content, { replacedInOnCreate }) => {
if (replacedInOnCreate) {
return [content, 'insertedInOnCreate', false];
}
const [succeeded, newContent] = string_utils_1.insert(content, {
insertPattern: /(?<=^.*super\.onCreate.*$)/m,
insertContent: `
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
SplashScreen.show(this, SplashScreenImageResizeMode.${resizeMode.toUpperCase()}, ReactRootView${isKotlin ? '::class.java' : '.class'}, ${statusBarTranslucent})${LE}`,
});
return [newContent, 'insertedInOnCreate', succeeded];
})
// inserting basic onCreate()
.applyAction((content, { replacedInOnCreate, insertedInOnCreate }) => {
if (replacedInOnCreate || insertedInOnCreate) {
return [content, 'insertedOnCreate', false];
}
const [succeeded, newContent] = string_utils_1.insert(content, {
insertPattern: isJava
? /(?<=public class .* extends .* {.*$)/m
: /(?<=class .* : .* {.*$)/m,
insertContent: `
${isJava
? `@Override
protected void onCreate(Bundle savedInstanceState`
: 'override fun onCreate(savedInstanceState: Bundle?'}) {
super.onCreate(savedInstanceState)${LE}
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
SplashScreen.show(this, SplashScreenImageResizeMode.${resizeMode.toUpperCase()}, ReactRootView${isKotlin ? '::class.java' : '.class'}, ${statusBarTranslucent})${LE}
}
`,
});
return [newContent, 'insertedOnCreate', succeeded];
})
// importing Bundle
.applyAction((content, { replacedInOnCreate, insertedInOnCreate }) => {
if (replacedInOnCreate || insertedInOnCreate) {
return [content, 'replacedBundleImport', false];
}
const [succeeded, newContent] = string_utils_1.replace(content, {
replacePattern: /import android\.os\.Bundle/m,
replaceContent: 'import android.os.Bundle',
});
return [newContent, 'replacedBundleImport', succeeded];
})
.applyAction((content, { replacedInOnCreate, insertedInOnCreate }) => {
if (replacedInOnCreate || insertedInOnCreate) {
return [content, 'insertedBundleImport', false];
}
const [succeeded, newContent] = string_utils_1.insert(content, {
insertPattern: /(?<=(^.*?package .*?$))/m,
insertContent: `\n\nimport android.os.Bundle${LE}`,
});
return [newContent, 'insertedBundleImport', succeeded];
});
await fs_extra_1.default.writeFile(mainActivity.path, newFileContent);
}
exports.default = configureMainActivity;
//# sourceMappingURL=MainActivity.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
export interface ProjectFile<L extends string = string> {
path: string;
language: L;
contents: string;
}
export declare function assert(value: any, message?: string | Error): asserts value;
export declare type ApplicationProjectFile = ProjectFile<'java' | 'kt'>;
export declare function getMainActivityAsync(projectRoot: string): Promise<ApplicationProjectFile>;

View File

@ -0,0 +1,56 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMainActivityAsync = exports.assert = void 0;
const assert_1 = __importDefault(require("assert"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const glob_1 = require("glob");
const path = __importStar(require("path"));
function assert(value, message) {
// TODO: Upgrade node? TypeScript isn't properly asserting values without this wrapper.
return assert_1.default(value, message);
}
exports.assert = assert;
async function getProjectFileAsync(projectRoot, name) {
const mainActivityJavaPath = glob_1.sync(path.join(projectRoot, `android/app/src/main/java/**/${name}.{java,kt}`))[0];
assert(mainActivityJavaPath, `Project file "${name}" does not exist in android project for root "${projectRoot}"`);
const mainActivityPathJava = path.resolve(mainActivityJavaPath, `../${name}.java`);
const mainActivityPathKotlin = path.resolve(mainActivityJavaPath, `../${name}.kt`);
const isJava = await fs_extra_1.default.pathExists(mainActivityPathJava);
const isKotlin = !isJava && (await fs_extra_1.default.pathExists(mainActivityPathKotlin));
if (!isJava && !isKotlin) {
throw new Error(`Failed to find '${name}' file for project: ${projectRoot}.`);
}
const filePath = isJava ? mainActivityPathJava : mainActivityPathKotlin;
return {
path: path.normalize(filePath),
contents: fs_extra_1.default.readFileSync(filePath, 'utf8'),
language: isJava ? 'java' : 'kt',
};
}
async function getMainActivityAsync(projectRoot) {
return getProjectFileAsync(projectRoot, 'MainActivity');
}
exports.getMainActivityAsync = getMainActivityAsync;
//# sourceMappingURL=Paths.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Paths.js","sourceRoot":"","sources":["../../src/android/Paths.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAAgC;AAChC,wDAA0B;AAC1B,+BAAwC;AACxC,2CAA6B;AAQ7B,SAAgB,MAAM,CAAC,KAAU,EAAE,OAAwB;IACzD,uFAAuF;IACvF,OAAO,gBAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACpC,CAAC;AAHD,wBAGC;AAID,KAAK,UAAU,mBAAmB,CAChC,WAAmB,EACnB,IAAY;IAEZ,MAAM,oBAAoB,GAAG,WAAQ,CACnC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,gCAAgC,IAAI,YAAY,CAAC,CACzE,CAAC,CAAC,CAAC,CAAC;IACL,MAAM,CACJ,oBAAoB,EACpB,iBAAiB,IAAI,iDAAiD,WAAW,GAAG,CACrF,CAAC;IAEF,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,MAAM,IAAI,OAAO,CAAC,CAAC;IACnF,MAAM,sBAAsB,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,MAAM,IAAI,KAAK,CAAC,CAAC;IAEnF,MAAM,MAAM,GAAG,MAAM,kBAAE,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,kBAAE,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC;IAE1E,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE;QACxB,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,uBAAuB,WAAW,GAAG,CAAC,CAAC;KAC/E;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,sBAAsB,CAAC;IACxE,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;QAC9B,QAAQ,EAAE,kBAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC3C,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;KACjC,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,oBAAoB,CAAC,WAAmB;IAC5D,OAAO,mBAAmB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;AAC1D,CAAC;AAFD,oDAEC","sourcesContent":["import nodeAssert from 'assert';\nimport fs from 'fs-extra';\nimport { sync as globSync } from 'glob';\nimport * as path from 'path';\n\nexport interface ProjectFile<L extends string = string> {\n path: string;\n language: L;\n contents: string;\n}\n\nexport function assert(value: any, message?: string | Error): asserts value {\n // TODO: Upgrade node? TypeScript isn't properly asserting values without this wrapper.\n return nodeAssert(value, message);\n}\n\nexport type ApplicationProjectFile = ProjectFile<'java' | 'kt'>;\n\nasync function getProjectFileAsync(\n projectRoot: string,\n name: string\n): Promise<ApplicationProjectFile> {\n const mainActivityJavaPath = globSync(\n path.join(projectRoot, `android/app/src/main/java/**/${name}.{java,kt}`)\n )[0];\n assert(\n mainActivityJavaPath,\n `Project file \"${name}\" does not exist in android project for root \"${projectRoot}\"`\n );\n\n const mainActivityPathJava = path.resolve(mainActivityJavaPath, `../${name}.java`);\n const mainActivityPathKotlin = path.resolve(mainActivityJavaPath, `../${name}.kt`);\n\n const isJava = await fs.pathExists(mainActivityPathJava);\n const isKotlin = !isJava && (await fs.pathExists(mainActivityPathKotlin));\n\n if (!isJava && !isKotlin) {\n throw new Error(`Failed to find '${name}' file for project: ${projectRoot}.`);\n }\n const filePath = isJava ? mainActivityPathJava : mainActivityPathKotlin;\n return {\n path: path.normalize(filePath),\n contents: fs.readFileSync(filePath, 'utf8'),\n language: isJava ? 'java' : 'kt',\n };\n}\n\nexport async function getMainActivityAsync(projectRoot: string): Promise<ApplicationProjectFile> {\n return getProjectFileAsync(projectRoot, 'MainActivity');\n}\n"]}

View File

@ -0,0 +1,17 @@
import { Color } from '../SplashScreenConfig';
import { SplashScreenStatusBarStyleType } from '../constants';
/**
* @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
export default function configureStylesXml(androidMainPath: string, config?: {
statusBar?: {
style?: SplashScreenStatusBarStyleType;
hidden?: boolean;
backgroundColor?: Color;
};
darkMode?: {
statusBar?: {
style?: SplashScreenStatusBarStyleType;
};
};
}): Promise<void>;

View File

@ -0,0 +1,183 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const constants_1 = require("../constants");
const xml_manipulation_1 = require("../xml-manipulation");
const STYLES_XML_FILE_PATH = './res/values/styles.xml';
const STYLES_V23_XML_FILE_PATH = './res/values-v23/styles.xml';
const STYLES_DARK_V23_XML_FILE_PATH = './res/values-night-v23/styles.xml';
const STYLE_NAME = 'Theme.App.SplashScreen';
function configureStyle(xml, { statusBarHidden, statusBarStyle, addStatusBarBackgroundColor, }) {
let idx = 0;
const result = xml_manipulation_1.mergeXmlElements(xml, {
elements: [
{
name: 'resources',
elements: [
{
name: 'style',
attributes: {
name: STYLE_NAME,
parent: 'Theme.AppCompat.Light.NoActionBar',
},
elements: [
{
idx: idx++,
comment: ` Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually `,
},
{
idx: idx++,
name: 'item',
attributes: {
name: 'android:windowBackground',
},
elements: [
{
text: '@drawable/splashscreen',
},
],
},
{
idx: statusBarHidden === undefined ? undefined : idx++,
deletionFlag: statusBarHidden === undefined,
name: 'item',
attributes: {
name: 'android:windowFullscreen',
},
elements: [{ text: String(statusBarHidden) }],
},
{
idx: statusBarStyle === undefined ||
statusBarStyle === constants_1.SplashScreenStatusBarStyle.DEFAULT
? undefined
: idx++,
deletionFlag: statusBarStyle === undefined ||
statusBarStyle === constants_1.SplashScreenStatusBarStyle.DEFAULT,
name: 'item',
attributes: {
name: 'android:windowLightStatusBar',
},
elements: [
{
text: statusBarStyle === constants_1.SplashScreenStatusBarStyle.LIGHT_CONTENT
? 'false'
: statusBarStyle === constants_1.SplashScreenStatusBarStyle.DARK_CONTENT
? 'true'
: '',
},
],
},
{
idx: addStatusBarBackgroundColor ? idx++ : undefined,
deletionFlag: !addStatusBarBackgroundColor,
name: 'item',
attributes: {
name: 'android:statusBarColor',
},
elements: [{ text: '@color/splashscreen_statusbar_color' }],
},
{
comment: ` Customize your splash screen theme here `,
},
],
},
],
},
],
});
return result;
}
/**
* Compares two subparts (`style` elements with STYLE_NAME name attribute) of given elements disregarding comments
*/
function areStyleElementsEqual(a, b) {
var _a, _b, _c, _d;
const styleA = (_b = (_a = a.elements) === null || _a === void 0 ? void 0 : _a[0].elements) === null || _b === void 0 ? void 0 : _b.find(({ name, attributes }) => name === 'style' && (attributes === null || attributes === void 0 ? void 0 : attributes.name) === STYLE_NAME);
const styleB = (_d = (_c = b.elements) === null || _c === void 0 ? void 0 : _c[0].elements) === null || _d === void 0 ? void 0 : _d.find(({ name, attributes }) => name === 'style' && (attributes === null || attributes === void 0 ? void 0 : attributes.name) === STYLE_NAME);
return !!styleA && !!styleB && xml_manipulation_1.xmlElementsEqual(styleA, styleB, { disregardComments: true });
}
/**
* Removes `style` element with STYLE_NAME name attribute from given element.
* Function assumes that the structure of the input `element` is correct (`element.elements[name = resources].elements[name = style, attributes.name = STYLE_NAME]`).
*/
function removeStyleElement(element) {
var _a, _b, _c, _d;
const resources = (_a = element.elements) === null || _a === void 0 ? void 0 : _a.find(el => el.name === 'resources');
const idxToBeRemoved = (_c = (_b = resources === null || resources === void 0 ? void 0 : resources.elements) === null || _b === void 0 ? void 0 : _b.findIndex(el => { var _a; return el.name === 'style' && ((_a = el.attributes) === null || _a === void 0 ? void 0 : _a.name) === STYLE_NAME; })) !== null && _c !== void 0 ? _c : -1;
if (idxToBeRemoved !== -1) {
// eslint-disable-next-line no-unused-expressions
(_d = resources === null || resources === void 0 ? void 0 : resources.elements) === null || _d === void 0 ? void 0 : _d.splice(idxToBeRemoved, 1);
}
return element;
}
/**
* Creates proper element structure with single `style` element disregarding all other styles.
* Use to create more specific configuration file, but preserving previous attributes.
* Function assumes that the structure of the input `element` is correct (`element.elements[name = resources].elements[name = style, attributes.name = STYLE_NAME]`).
*/
function elementWithStyleElement(element) {
var _a, _b;
const result = { ...element };
const resources = (_a = element.elements) === null || _a === void 0 ? void 0 : _a.find(el => el.name === 'resources');
if (!resources) {
return;
}
const styleElement = (_b = resources === null || resources === void 0 ? void 0 : resources.elements) === null || _b === void 0 ? void 0 : _b.find(el => { var _a; return el.name === 'style' && ((_a = el.attributes) === null || _a === void 0 ? void 0 : _a.name) === STYLE_NAME; });
if (!styleElement) {
return;
}
result.elements = [{ ...resources, elements: [styleElement] }];
return result;
}
/**
* @param androidMainPath Path to the main directory containing code and resources in Android project. In general that would be `android/app/src/main`.
*/
async function configureStylesXml(androidMainPath, config = {}) {
var _a, _b, _c, _d, _e, _f;
const statusBarStyle = (_b = (_a = config.statusBar) === null || _a === void 0 ? void 0 : _a.style) !== null && _b !== void 0 ? _b : constants_1.SplashScreenStatusBarStyle.DEFAULT;
const statusBarHidden = (_c = config.statusBar) === null || _c === void 0 ? void 0 : _c.hidden;
const darkModeStatusBarStyle = (_e = (_d = config.darkMode) === null || _d === void 0 ? void 0 : _d.statusBar) === null || _e === void 0 ? void 0 : _e.style;
const addStatusBarBackgroundColor = Boolean((_f = config.statusBar) === null || _f === void 0 ? void 0 : _f.backgroundColor);
if (darkModeStatusBarStyle && !statusBarStyle) {
throw new Error(`'darkModeStatusBarStyle' is available only if 'statusBarStyle' is provided as well.`);
}
const filePath = path_1.default.resolve(androidMainPath, STYLES_XML_FILE_PATH);
const v23FilePath = path_1.default.resolve(androidMainPath, STYLES_V23_XML_FILE_PATH);
const v23DarkFilePath = path_1.default.resolve(androidMainPath, STYLES_DARK_V23_XML_FILE_PATH);
const xmlContent = await xml_manipulation_1.readXmlFile(filePath);
const contentWithSingleStyle = elementWithStyleElement(xmlContent);
const v23XmlContent = await xml_manipulation_1.readXmlFile(v23FilePath, contentWithSingleStyle);
const v23DarkXmlContent = await xml_manipulation_1.readXmlFile(v23DarkFilePath, contentWithSingleStyle);
const configuredXmlContent = configureStyle(xmlContent, {
statusBarHidden,
addStatusBarBackgroundColor,
});
const configuredV23XmlContent = configureStyle(v23XmlContent, {
statusBarHidden,
statusBarStyle,
addStatusBarBackgroundColor,
});
const configuredV23DarkXmlContent = configureStyle(v23DarkXmlContent, {
statusBarHidden,
statusBarStyle: darkModeStatusBarStyle !== null && darkModeStatusBarStyle !== void 0 ? darkModeStatusBarStyle : statusBarStyle,
addStatusBarBackgroundColor,
});
if (areStyleElementsEqual(configuredV23DarkXmlContent, configuredV23XmlContent)) {
await xml_manipulation_1.writeXmlFileOrRemoveFileUponNoResources(v23DarkFilePath, removeStyleElement(configuredV23DarkXmlContent));
}
else {
await xml_manipulation_1.writeXmlFile(v23DarkFilePath, configuredV23DarkXmlContent);
}
if (areStyleElementsEqual(configuredV23XmlContent, configuredXmlContent)) {
await xml_manipulation_1.writeXmlFileOrRemoveFileUponNoResources(v23FilePath, removeStyleElement(configuredV23XmlContent));
}
else {
await xml_manipulation_1.writeXmlFile(v23FilePath, configuredV23XmlContent);
}
await xml_manipulation_1.writeXmlFile(filePath, configuredXmlContent);
}
exports.default = configureStylesXml;
//# sourceMappingURL=Styles.xml.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
import { AndroidSplashScreenConfigJSON } from '../SplashScreenConfig';
export default function configureAndroid(projectRootPath: string, configJSON: AndroidSplashScreenConfigJSON): Promise<void>;

View File

@ -0,0 +1,27 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const validators_1 = require("../validators");
const AndroidManifest_xml_1 = __importDefault(require("./AndroidManifest.xml"));
const Colors_xml_1 = __importDefault(require("./Colors.xml"));
const Drawable_xml_1 = __importDefault(require("./Drawable.xml"));
const Drawables_1 = __importDefault(require("./Drawables"));
const MainActivity_1 = __importDefault(require("./MainActivity"));
const Styles_xml_1 = __importDefault(require("./Styles.xml"));
async function configureAndroid(projectRootPath, configJSON) {
const validatedConfig = await validators_1.validateAndroidConfig(configJSON);
const androidMainPath = path_1.default.resolve(projectRootPath, 'android/app/src/main');
await Promise.all([
Drawables_1.default(androidMainPath, validatedConfig),
Colors_xml_1.default(androidMainPath, validatedConfig),
Drawable_xml_1.default(androidMainPath, validatedConfig),
Styles_xml_1.default(androidMainPath, validatedConfig),
AndroidManifest_xml_1.default(androidMainPath),
MainActivity_1.default(projectRootPath, validatedConfig),
]);
}
exports.default = configureAndroid;
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/android/index.ts"],"names":[],"mappings":";;;;;AAAA,gDAAwB;AAGxB,8CAAsD;AACtD,gFAAgE;AAChE,8DAA8C;AAC9C,kEAAkD;AAClD,4DAA6C;AAC7C,kEAAmD;AACnD,8DAA8C;AAE/B,KAAK,UAAU,gBAAgB,CAC5C,eAAuB,EACvB,UAAyC;IAEzC,MAAM,eAAe,GAAG,MAAM,kCAAqB,CAAC,UAAU,CAAC,CAAC;IAEhE,MAAM,eAAe,GAAG,cAAI,CAAC,OAAO,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;IAE9E,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,mBAAkB,CAAC,eAAe,EAAE,eAAe,CAAC;QACpD,oBAAkB,CAAC,eAAe,EAAE,eAAe,CAAC;QACpD,sBAAoB,CAAC,eAAe,EAAE,eAAe,CAAC;QACtD,oBAAkB,CAAC,eAAe,EAAE,eAAe,CAAC;QACpD,6BAA2B,CAAC,eAAe,CAAC;QAC5C,sBAAqB,CAAC,eAAe,EAAE,eAAe,CAAC;KACxD,CAAC,CAAC;AACL,CAAC;AAhBD,mCAgBC","sourcesContent":["import path from 'path';\n\nimport { AndroidSplashScreenConfigJSON } from '../SplashScreenConfig';\nimport { validateAndroidConfig } from '../validators';\nimport configureAndroidManifestXml from './AndroidManifest.xml';\nimport configureColorsXml from './Colors.xml';\nimport configureDrawableXml from './Drawable.xml';\nimport configureDrawables from './Drawables';\nimport configureMainActivity from './MainActivity';\nimport configureStylesXml from './Styles.xml';\n\nexport default async function configureAndroid(\n projectRootPath: string,\n configJSON: AndroidSplashScreenConfigJSON\n) {\n const validatedConfig = await validateAndroidConfig(configJSON);\n\n const androidMainPath = path.resolve(projectRootPath, 'android/app/src/main');\n\n await Promise.all([\n configureDrawables(androidMainPath, validatedConfig),\n configureColorsXml(androidMainPath, validatedConfig),\n configureDrawableXml(androidMainPath, validatedConfig),\n configureStylesXml(androidMainPath, validatedConfig),\n configureAndroidManifestXml(androidMainPath),\n configureMainActivity(projectRootPath, validatedConfig),\n ]);\n}\n"]}

View File

@ -0,0 +1,2 @@
declare const _default: () => import("commander").Command;
export default _default;

View File

@ -0,0 +1,105 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const android_1 = __importDefault(require("./android"));
const constants_1 = require("./constants");
const ios_1 = __importDefault(require("./ios"));
const validators_1 = require("./validators");
async function action(configuration) {
const { platform, android, ios } = configuration;
const rootDir = process.cwd();
switch (platform) {
case constants_1.Platform.ANDROID:
await android_1.default(rootDir, android);
break;
case constants_1.Platform.IOS:
await ios_1.default(rootDir, ios);
break;
case constants_1.Platform.ALL:
default:
await android_1.default(rootDir, android);
await ios_1.default(rootDir, ios);
break;
}
}
function configurationFromOptions({ platform, imageResizeMode, backgroundColor, imagePath, statusBarHidden, statusBarStyle, statusBarTranslucent, statusBarBackgroundColor, darkModeBackgroundColor, darkModeImagePath, darkModeStatusBarStyle, darkModeStatusBarBackgroundColor, }) {
let resolvedPlatform = constants_1.Platform.ALL;
try {
resolvedPlatform = (platform && validators_1.validateEnumValue(platform, constants_1.Platform)) || constants_1.Platform.ALL;
}
catch (e) {
throw new Error(`'platform': ${e.message}`);
}
if (!backgroundColor) {
throw new Error(`'backgroundColor': Required option is not provided. Provide a valid color string.`);
}
const resolvedStatusBarHidden = statusBarHidden === undefined ? undefined : Boolean(statusBarHidden);
const resolvedStatusBarTranslucent = statusBarTranslucent === undefined ? undefined : Boolean(statusBarTranslucent);
const genericConfig = {
imageResizeMode,
backgroundColor,
image: imagePath,
};
const genericStatusBarConfig = {
hidden: resolvedStatusBarHidden,
style: statusBarStyle,
};
const genericDarkModeConfig = {
backgroundColor: darkModeBackgroundColor,
image: darkModeImagePath,
};
const result = {
platform: resolvedPlatform,
android: {
...genericConfig,
statusBar: {
...genericStatusBarConfig,
translucent: resolvedStatusBarTranslucent,
backgroundColor: statusBarBackgroundColor,
},
darkMode: {
...genericDarkModeConfig,
statusBar: {
style: darkModeStatusBarStyle,
backgroundColor: darkModeStatusBarBackgroundColor,
},
},
},
ios: {
...genericConfig,
statusBar: genericStatusBarConfig,
darkMode: genericDarkModeConfig,
},
};
return result;
}
function getAvailableOptions(o) {
return Object.values(o)
.map(v => `"${v}"`)
.join(' | ');
}
exports.default = () => new commander_1.Command()
.description('Idempotent operation that configures native splash screens using provided backgroundColor and optional .png file. Supports light and dark modes configuration. Dark mode is supported only on iOS 13+ and Android 10+.')
.version(require('../package.json').version)
.allowUnknownOption(false)
.passCommandToAction(false)
.option('-p, --platform <platform>', `Selected platform to configure. Available values: ${getAvailableOptions(constants_1.Platform)} (default: "${constants_1.Platform.ALL}").`)
.option('-b, --background-color <color>', `(required) Valid css-formatted color (hex (#RRGGBB[AA]), rgb[a], hsl[a], named color (https://drafts.csswg.org/css-color/#named-colors)) that would be used as the background color for native splash screen view.`)
.option('-i, --image-path <path>', 'Path to valid .png image that will be displayed on the splash screen.')
.option('-r, --image-resize-mode <resizeMode>', `Resize mode to be used for the splash screen image. Available only if 'image-path' is provided as well. Available values: ${getAvailableOptions(constants_1.SplashScreenImageResizeMode)} ("native" is only available for Android)) (default: "${constants_1.SplashScreenImageResizeMode.CONTAIN}").`)
.option('--dark-mode-background-color <color>', `Color (see 'background-color' supported formats) that would be used as the background color for the splash screen in dark mode. Providing this option enables other dark-mode related options.`)
.option('--dark-mode-image-path <path>', `Path to valid .png image that will be displayed on the splash screen in dark mode only. Available only if 'dark-mode-background-color' is provided as well.`)
.option('--status-bar-style <style>', `Customizes the color of the status bar icons. Available values: ${getAvailableOptions(constants_1.SplashScreenStatusBarStyle)} (default: "${constants_1.SplashScreenStatusBarStyle.DEFAULT}").`)
.option('--status-bar-hidden', `Hides the status bar.`)
.option('--status-bar-background-color <color>', `(only for Android platform) Customizes the background color of the status bar. Accepts a valid color (see 'background-color' supported formats).`)
.option('--status-bar-translucent', `(only for Android platform) Makes the status bar translucent (enables drawing under the status bar area).`)
.option('--dark-mode-status-bar-style <style>', `(only for Android platform) The same as 'status-bar-style', but applied only in dark mode. Available only if 'dark-mode-background-color' and 'status-bar-style' are provided as well.`)
.option('--dark-mode-status-bar-background-color <color>', `(only for Android platform) The same as 'status-bar-background-color', but applied only in the dark mode. Available only if 'dark-mode-background-color' and 'status-bar-style' are provided as well.`)
.action(async (options) => {
const configuration = configurationFromOptions(options);
await action(configuration);
});
//# sourceMappingURL=cli-command.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
export declare const SplashScreenImageResizeMode: {
readonly CONTAIN: "contain";
readonly COVER: "cover";
readonly NATIVE: "native";
};
export declare type SplashScreenImageResizeModeType = TypeFromConstObject<typeof SplashScreenImageResizeMode>;
export declare const Platform: {
readonly ANDROID: "android";
readonly IOS: "ios";
readonly ALL: "all";
};
export declare type PlatformType = TypeFromConstObject<typeof Platform>;
export declare const SplashScreenStatusBarStyle: {
readonly DEFAULT: "default";
readonly LIGHT_CONTENT: "light-content";
readonly DARK_CONTENT: "dark-content";
};
export declare type SplashScreenStatusBarStyleType = TypeFromConstObject<typeof SplashScreenStatusBarStyle>;
declare type TypeFromConstObject<T extends object> = T[keyof T];
export {};

View File

@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SplashScreenStatusBarStyle = exports.Platform = exports.SplashScreenImageResizeMode = void 0;
exports.SplashScreenImageResizeMode = {
CONTAIN: 'contain',
COVER: 'cover',
NATIVE: 'native',
};
exports.Platform = {
ANDROID: 'android',
IOS: 'ios',
ALL: 'all',
};
exports.SplashScreenStatusBarStyle = {
DEFAULT: 'default',
LIGHT_CONTENT: 'light-content',
DARK_CONTENT: 'dark-content',
};
//# sourceMappingURL=constants.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,2BAA2B,GAAG;IACzC,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,QAAQ;CACR,CAAC;AAKE,QAAA,QAAQ,GAAG;IACtB,OAAO,EAAE,SAAS;IAClB,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;CACF,CAAC;AAGE,QAAA,0BAA0B,GAAG;IACxC,OAAO,EAAE,SAAS;IAClB,aAAa,EAAE,eAAe;IAC9B,YAAY,EAAE,cAAc;CACpB,CAAC","sourcesContent":["export const SplashScreenImageResizeMode = {\n CONTAIN: 'contain',\n COVER: 'cover',\n NATIVE: 'native',\n} as const;\nexport type SplashScreenImageResizeModeType = TypeFromConstObject<\n typeof SplashScreenImageResizeMode\n>;\n\nexport const Platform = {\n ANDROID: 'android',\n IOS: 'ios',\n ALL: 'all',\n} as const;\nexport type PlatformType = TypeFromConstObject<typeof Platform>;\n\nexport const SplashScreenStatusBarStyle = {\n DEFAULT: 'default',\n LIGHT_CONTENT: 'light-content',\n DARK_CONTENT: 'dark-content',\n} as const;\nexport type SplashScreenStatusBarStyleType = TypeFromConstObject<typeof SplashScreenStatusBarStyle>;\n\ntype TypeFromConstObject<T extends object> = T[keyof T];\n"]}

View File

@ -0,0 +1,2 @@
#!/usr/bin/env node
export {};

View File

@ -0,0 +1,18 @@
#!/usr/bin/env node
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const cli_command_1 = __importDefault(require("./cli-command"));
async function runAsync() {
try {
await cli_command_1.default().parseAsync(process.argv);
}
catch (e) {
console.error(`\n${e.message}\n`);
process.exit(1);
}
}
runAsync();
//# sourceMappingURL=index-cli.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index-cli.js","sourceRoot":"","sources":["../src/index-cli.ts"],"names":[],"mappings":";;;;;;AAEA,gEAA0C;AAE1C,KAAK,UAAU,QAAQ;IACrB,IAAI;QACF,MAAM,qBAAa,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;KAChD;IAAC,OAAO,CAAC,EAAE;QACV,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACjB;AACH,CAAC;AAED,QAAQ,EAAE,CAAC","sourcesContent":["#!/usr/bin/env node\n\nimport createCommand from './cli-command';\n\nasync function runAsync() {\n try {\n await createCommand().parseAsync(process.argv);\n } catch (e) {\n console.error(`\\n${e.message}\\n`);\n process.exit(1);\n }\n}\n\nrunAsync();\n"]}

View File

@ -0,0 +1,4 @@
export { default as configureIosSplashScreen } from './ios';
export { default as configureAndroidSplashScreen } from './android';
export { SplashScreenImageResizeMode, SplashScreenStatusBarStyle } from './constants';
export { IosSplashScreenConfigJSON as IosSplashScreenConfig, AndroidSplashScreenConfigJSON as AndroidSplashScreenConfig, } from './SplashScreenConfig';

View File

@ -0,0 +1,14 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SplashScreenStatusBarStyle = exports.SplashScreenImageResizeMode = exports.configureAndroidSplashScreen = exports.configureIosSplashScreen = void 0;
var ios_1 = require("./ios");
Object.defineProperty(exports, "configureIosSplashScreen", { enumerable: true, get: function () { return __importDefault(ios_1).default; } });
var android_1 = require("./android");
Object.defineProperty(exports, "configureAndroidSplashScreen", { enumerable: true, get: function () { return __importDefault(android_1).default; } });
var constants_1 = require("./constants");
Object.defineProperty(exports, "SplashScreenImageResizeMode", { enumerable: true, get: function () { return constants_1.SplashScreenImageResizeMode; } });
Object.defineProperty(exports, "SplashScreenStatusBarStyle", { enumerable: true, get: function () { return constants_1.SplashScreenStatusBarStyle; } });
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,6BAA4D;AAAnD,gIAAA,OAAO,OAA4B;AAC5C,qCAAoE;AAA3D,wIAAA,OAAO,OAAgC;AAEhD,yCAAsF;AAA7E,wHAAA,2BAA2B,OAAA;AAAE,uHAAA,0BAA0B,OAAA","sourcesContent":["export { default as configureIosSplashScreen } from './ios';\nexport { default as configureAndroidSplashScreen } from './android';\n\nexport { SplashScreenImageResizeMode, SplashScreenStatusBarStyle } from './constants';\nexport {\n IosSplashScreenConfigJSON as IosSplashScreenConfig,\n AndroidSplashScreenConfigJSON as AndroidSplashScreenConfig,\n} from './SplashScreenConfig';\n"]}

View File

@ -0,0 +1,10 @@
import { Color } from '../SplashScreenConfig';
/**
* Creates imageset containing solid color image that is used as a background for Splash Screen.
*/
export default function configureAssets(iosProjectPath: string, config: {
backgroundColor: Color;
darkMode?: {
backgroundColor?: Color;
};
}): Promise<void>;

View File

@ -0,0 +1,60 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const pngjs_1 = require("pngjs");
const Contents_json_1 = require("./Contents.json");
const PNG_FILENAME = 'background.png';
const DARK_PNG_FILENAME = 'dark_background.png';
const IMAGESET_PATH = 'Images.xcassets/SplashScreenBackground.imageset';
const CONTENTS_PATH = `${IMAGESET_PATH}/Contents.json`;
const PNG_PATH = `${IMAGESET_PATH}/${PNG_FILENAME}`;
const DARK_PNG_PATH = `${IMAGESET_PATH}/${DARK_PNG_FILENAME}`;
async function createContentsJsonFile(iosProjectPath, imageSetPath, darkModeEnabled) {
await fs_extra_1.default.mkdirp(path_1.default.resolve(iosProjectPath, IMAGESET_PATH));
await Contents_json_1.writeContentsJsonFile(path_1.default.resolve(iosProjectPath, CONTENTS_PATH), PNG_FILENAME, darkModeEnabled ? DARK_PNG_FILENAME : undefined);
await fs_extra_1.default.mkdirp(imageSetPath);
}
async function createPngFile(filePath, color) {
const png = new pngjs_1.PNG({
width: 1,
height: 1,
bitDepth: 8,
colorType: 6,
inputColorType: 6,
inputHasAlpha: true,
});
const [r, g, b, a] = color;
const bitmap = new Uint8Array([r, g, b, a * 255]);
const buffer = Buffer.from(bitmap);
png.data = buffer;
return new Promise(resolve => {
png.pack().pipe(fs_extra_1.default.createWriteStream(filePath)).on('finish', resolve);
});
}
async function createFiles(iosProjectPath, color, darkModeColor) {
await createPngFile(path_1.default.resolve(iosProjectPath, PNG_PATH), color);
if (darkModeColor) {
await createPngFile(path_1.default.resolve(iosProjectPath, DARK_PNG_PATH), darkModeColor);
}
}
/**
* Creates imageset containing solid color image that is used as a background for Splash Screen.
*/
async function configureAssets(iosProjectPath, config) {
var _a;
const backgroundColor = config.backgroundColor;
const darkModeBackgroundColor = (_a = config.darkMode) === null || _a === void 0 ? void 0 : _a.backgroundColor;
const imageSetPath = path_1.default.resolve(iosProjectPath, IMAGESET_PATH);
// ensure old SplashScreenBackground imageSet is removed
if (await fs_extra_1.default.pathExists(imageSetPath)) {
await fs_extra_1.default.remove(imageSetPath);
}
await createContentsJsonFile(iosProjectPath, imageSetPath, !!darkModeBackgroundColor);
await createFiles(iosProjectPath, backgroundColor, darkModeBackgroundColor);
}
exports.default = configureAssets;
//# sourceMappingURL=BackgroundAsset.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"BackgroundAsset.js","sourceRoot":"","sources":["../../src/ios/BackgroundAsset.ts"],"names":[],"mappings":";;;;;AAAA,wDAA0B;AAC1B,gDAAwB;AACxB,iCAA4B;AAG5B,mDAAwD;AAExD,MAAM,YAAY,GAAG,gBAAgB,CAAC;AACtC,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEhD,MAAM,aAAa,GAAG,iDAAiD,CAAC;AACxE,MAAM,aAAa,GAAG,GAAG,aAAa,gBAAgB,CAAC;AACvD,MAAM,QAAQ,GAAG,GAAG,aAAa,IAAI,YAAY,EAAE,CAAC;AACpD,MAAM,aAAa,GAAG,GAAG,aAAa,IAAI,iBAAiB,EAAE,CAAC;AAE9D,KAAK,UAAU,sBAAsB,CACnC,cAAsB,EACtB,YAAoB,EACpB,eAAwB;IAExB,MAAM,kBAAE,CAAC,MAAM,CAAC,cAAI,CAAC,OAAO,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC,CAAC;IAE7D,MAAM,qCAAqB,CACzB,cAAI,CAAC,OAAO,CAAC,cAAc,EAAE,aAAa,CAAC,EAC3C,YAAY,EACZ,eAAe,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAChD,CAAC;IAEF,MAAM,kBAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;AAChC,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,KAAY;IACzD,MAAM,GAAG,GAAG,IAAI,WAAG,CAAC;QAClB,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,QAAQ,EAAE,CAAC;QACX,SAAS,EAAE,CAAC;QACZ,cAAc,EAAE,CAAC;QACjB,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC;IACH,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC;IAElB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,kBAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,cAAsB,EAAE,KAAY,EAAE,aAAqB;IACpF,MAAM,aAAa,CAAC,cAAI,CAAC,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IACnE,IAAI,aAAa,EAAE;QACjB,MAAM,aAAa,CAAC,cAAI,CAAC,OAAO,CAAC,cAAc,EAAE,aAAa,CAAC,EAAE,aAAa,CAAC,CAAC;KACjF;AACH,CAAC;AAED;;GAEG;AACY,KAAK,UAAU,eAAe,CAC3C,cAAsB,EACtB,MAKC;;IAED,MAAM,eAAe,GAAG,MAAM,CAAC,eAAe,CAAC;IAC/C,MAAM,uBAAuB,SAAG,MAAM,CAAC,QAAQ,0CAAE,eAAe,CAAC;IAEjE,MAAM,YAAY,GAAG,cAAI,CAAC,OAAO,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;IAEjE,wDAAwD;IACxD,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE;QACrC,MAAM,kBAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;KAC/B;IAED,MAAM,sBAAsB,CAAC,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC,uBAAuB,CAAC,CAAC;IACtF,MAAM,WAAW,CAAC,cAAc,EAAE,eAAe,EAAE,uBAAuB,CAAC,CAAC;AAC9E,CAAC;AArBD,kCAqBC","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\nimport { PNG } from 'pngjs';\n\nimport { Color } from '../SplashScreenConfig';\nimport { writeContentsJsonFile } from './Contents.json';\n\nconst PNG_FILENAME = 'background.png';\nconst DARK_PNG_FILENAME = 'dark_background.png';\n\nconst IMAGESET_PATH = 'Images.xcassets/SplashScreenBackground.imageset';\nconst CONTENTS_PATH = `${IMAGESET_PATH}/Contents.json`;\nconst PNG_PATH = `${IMAGESET_PATH}/${PNG_FILENAME}`;\nconst DARK_PNG_PATH = `${IMAGESET_PATH}/${DARK_PNG_FILENAME}`;\n\nasync function createContentsJsonFile(\n iosProjectPath: string,\n imageSetPath: string,\n darkModeEnabled: boolean\n) {\n await fs.mkdirp(path.resolve(iosProjectPath, IMAGESET_PATH));\n\n await writeContentsJsonFile(\n path.resolve(iosProjectPath, CONTENTS_PATH),\n PNG_FILENAME,\n darkModeEnabled ? DARK_PNG_FILENAME : undefined\n );\n\n await fs.mkdirp(imageSetPath);\n}\n\nasync function createPngFile(filePath: string, color: Color) {\n const png = new PNG({\n width: 1,\n height: 1,\n bitDepth: 8,\n colorType: 6,\n inputColorType: 6,\n inputHasAlpha: true,\n });\n const [r, g, b, a] = color;\n const bitmap = new Uint8Array([r, g, b, a * 255]);\n const buffer = Buffer.from(bitmap);\n png.data = buffer;\n\n return new Promise(resolve => {\n png.pack().pipe(fs.createWriteStream(filePath)).on('finish', resolve);\n });\n}\n\nasync function createFiles(iosProjectPath: string, color: Color, darkModeColor?: Color) {\n await createPngFile(path.resolve(iosProjectPath, PNG_PATH), color);\n if (darkModeColor) {\n await createPngFile(path.resolve(iosProjectPath, DARK_PNG_PATH), darkModeColor);\n }\n}\n\n/**\n * Creates imageset containing solid color image that is used as a background for Splash Screen.\n */\nexport default async function configureAssets(\n iosProjectPath: string,\n config: {\n backgroundColor: Color;\n darkMode?: {\n backgroundColor?: Color;\n };\n }\n) {\n const backgroundColor = config.backgroundColor;\n const darkModeBackgroundColor = config.darkMode?.backgroundColor;\n\n const imageSetPath = path.resolve(iosProjectPath, IMAGESET_PATH);\n\n // ensure old SplashScreenBackground imageSet is removed\n if (await fs.pathExists(imageSetPath)) {\n await fs.remove(imageSetPath);\n }\n\n await createContentsJsonFile(iosProjectPath, imageSetPath, !!darkModeBackgroundColor);\n await createFiles(iosProjectPath, backgroundColor, darkModeBackgroundColor);\n}\n"]}

View File

@ -0,0 +1 @@
export declare function writeContentsJsonFile(contentsJsonFilePath: string, filename: string, darkModeFilename?: string): Promise<void>;

View File

@ -0,0 +1,65 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeContentsJsonFile = void 0;
const fs_extra_1 = __importDefault(require("fs-extra"));
async function writeContentsJsonFile(contentsJsonFilePath, filename, darkModeFilename) {
const images = [
{
idiom: 'universal',
filename,
scale: '1x',
},
{
appearances: [
{
appearance: 'luminosity',
value: 'dark',
},
],
idiom: 'universal',
filename: darkModeFilename,
scale: '1x',
},
{
idiom: 'universal',
scale: '2x',
},
{
appearances: [
{
appearance: 'luminosity',
value: 'dark',
},
],
idiom: 'universal',
scale: '2x',
},
{
idiom: 'universal',
scale: '3x',
},
{
appearances: [
{
appearance: 'luminosity',
value: 'dark',
},
],
idiom: 'universal',
scale: '3x',
},
].filter(el => { var _a, _b; return (((_b = (_a = el.appearances) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) === 'dark' ? Boolean(darkModeFilename) : true); });
const contentsJson = {
images,
info: {
version: 1,
author: 'xcode',
},
};
await fs_extra_1.default.writeFile(contentsJsonFilePath, JSON.stringify(contentsJson, null, 2));
}
exports.writeContentsJsonFile = writeContentsJsonFile;
//# sourceMappingURL=Contents.json.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Contents.json.js","sourceRoot":"","sources":["../../src/ios/Contents.json.ts"],"names":[],"mappings":";;;;;;AAAA,wDAA0B;AAEnB,KAAK,UAAU,qBAAqB,CACzC,oBAA4B,EAC5B,QAAgB,EAChB,gBAAyB;IAEzB,MAAM,MAAM,GAQN;QACJ;YACE,KAAK,EAAE,WAAoB;YAC3B,QAAQ;YACR,KAAK,EAAE,IAAa;SACrB;QACD;YACE,WAAW,EAAE;gBACX;oBACE,UAAU,EAAE,YAAqB;oBACjC,KAAK,EAAE,MAAe;iBACvB;aACF;YACD,KAAK,EAAE,WAAoB;YAC3B,QAAQ,EAAE,gBAAgB;YAC1B,KAAK,EAAE,IAAa;SACrB;QACD;YACE,KAAK,EAAE,WAAoB;YAC3B,KAAK,EAAE,IAAa;SACrB;QACD;YACE,WAAW,EAAE;gBACX;oBACE,UAAU,EAAE,YAAqB;oBACjC,KAAK,EAAE,MAAe;iBACvB;aACF;YACD,KAAK,EAAE,WAAoB;YAC3B,KAAK,EAAE,IAAa;SACrB;QACD;YACE,KAAK,EAAE,WAAoB;YAC3B,KAAK,EAAE,IAAa;SACrB;QACD;YACE,WAAW,EAAE;gBACX;oBACE,UAAU,EAAE,YAAqB;oBACjC,KAAK,EAAE,MAAe;iBACvB;aACF;YACD,KAAK,EAAE,WAAoB;YAC3B,KAAK,EAAE,IAAa;SACrB;KACF,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,eAAC,OAAA,CAAC,aAAA,EAAE,CAAC,WAAW,0CAAG,CAAC,2CAAG,KAAK,MAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA,EAAA,CAAC,CAAC;IAE3F,MAAM,YAAY,GAAG;QACnB,MAAM;QACN,IAAI,EAAE;YACJ,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,OAAO;SAChB;KACF,CAAC;IAEF,MAAM,kBAAE,CAAC,SAAS,CAAC,oBAAoB,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAClF,CAAC;AArED,sDAqEC","sourcesContent":["import fs from 'fs-extra';\n\nexport async function writeContentsJsonFile(\n contentsJsonFilePath: string,\n filename: string,\n darkModeFilename?: string\n) {\n const images: {\n idiom: 'universal';\n filename?: string;\n scale: '1x' | '2x' | '3x';\n appearances?: {\n appearance: 'luminosity';\n value: 'dark';\n }[];\n }[] = [\n {\n idiom: 'universal' as const,\n filename,\n scale: '1x' as const,\n },\n {\n appearances: [\n {\n appearance: 'luminosity' as const,\n value: 'dark' as const,\n },\n ],\n idiom: 'universal' as const,\n filename: darkModeFilename,\n scale: '1x' as const,\n },\n {\n idiom: 'universal' as const,\n scale: '2x' as const,\n },\n {\n appearances: [\n {\n appearance: 'luminosity' as const,\n value: 'dark' as const,\n },\n ],\n idiom: 'universal' as const,\n scale: '2x' as const,\n },\n {\n idiom: 'universal' as const,\n scale: '3x' as const,\n },\n {\n appearances: [\n {\n appearance: 'luminosity' as const,\n value: 'dark' as const,\n },\n ],\n idiom: 'universal' as const,\n scale: '3x' as const,\n },\n ].filter(el => (el.appearances?.[0]?.value === 'dark' ? Boolean(darkModeFilename) : true));\n\n const contentsJson = {\n images,\n info: {\n version: 1,\n author: 'xcode',\n },\n };\n\n await fs.writeFile(contentsJsonFilePath, JSON.stringify(contentsJson, null, 2));\n}\n"]}

View File

@ -0,0 +1,9 @@
/**
* Creates imageset containing image for Splash/Launch Screen.
*/
export default function configureImageAssets(iosProjectPath: string, config?: {
image?: string;
darkMode?: {
image?: string;
};
}): Promise<void>;

View File

@ -0,0 +1,46 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const Contents_json_1 = require("./Contents.json");
const PNG_FILENAME = 'splashscreen.png';
const DARK_PNG_FILENAME = 'dark_splashscreen.png';
const IMAGESET_PATH = 'Images.xcassets/SplashScreen.imageset';
const CONTENTS_PATH = `${IMAGESET_PATH}/Contents.json`;
const PNG_PATH = `${IMAGESET_PATH}/${PNG_FILENAME}`;
const DARK_PNG_PATH = `${IMAGESET_PATH}/${DARK_PNG_FILENAME}`;
async function createContentsJsonFile(iosProjectPath, imageSetPath, imagePath, darkModeImagePath) {
if (!imagePath) {
return;
}
await fs_extra_1.default.mkdirp(imageSetPath);
await Contents_json_1.writeContentsJsonFile(path_1.default.resolve(iosProjectPath, CONTENTS_PATH), PNG_FILENAME, darkModeImagePath && DARK_PNG_FILENAME);
}
async function copyImageFiles(iosProjectPath, imagePath, darkModeImagePath) {
if (imagePath) {
await fs_extra_1.default.copyFile(imagePath, path_1.default.resolve(iosProjectPath, PNG_PATH));
}
if (darkModeImagePath) {
await fs_extra_1.default.copyFile(darkModeImagePath, path_1.default.resolve(iosProjectPath, DARK_PNG_PATH));
}
}
/**
* Creates imageset containing image for Splash/Launch Screen.
*/
async function configureImageAssets(iosProjectPath, config = {}) {
var _a;
const imagePath = config.image;
const darkModeImagePath = (_a = config.darkMode) === null || _a === void 0 ? void 0 : _a.image;
const imageSetPath = path_1.default.resolve(iosProjectPath, IMAGESET_PATH);
// ensure old SplashScreen imageSet is removed
if (await fs_extra_1.default.pathExists(imageSetPath)) {
await fs_extra_1.default.remove(imageSetPath);
}
await createContentsJsonFile(iosProjectPath, imageSetPath, imagePath, darkModeImagePath);
await copyImageFiles(iosProjectPath, imagePath, darkModeImagePath);
}
exports.default = configureImageAssets;
//# sourceMappingURL=ImageAsset.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"ImageAsset.js","sourceRoot":"","sources":["../../src/ios/ImageAsset.ts"],"names":[],"mappings":";;;;;AAAA,wDAA0B;AAC1B,gDAAwB;AAExB,mDAAwD;AAExD,MAAM,YAAY,GAAG,kBAAkB,CAAC;AACxC,MAAM,iBAAiB,GAAG,uBAAuB,CAAC;AAElD,MAAM,aAAa,GAAG,uCAAuC,CAAC;AAC9D,MAAM,aAAa,GAAG,GAAG,aAAa,gBAAgB,CAAC;AACvD,MAAM,QAAQ,GAAG,GAAG,aAAa,IAAI,YAAY,EAAE,CAAC;AACpD,MAAM,aAAa,GAAG,GAAG,aAAa,IAAI,iBAAiB,EAAE,CAAC;AAE9D,KAAK,UAAU,sBAAsB,CACnC,cAAsB,EACtB,YAAoB,EACpB,SAAkB,EAClB,iBAA0B;IAE1B,IAAI,CAAC,SAAS,EAAE;QACd,OAAO;KACR;IAED,MAAM,kBAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC9B,MAAM,qCAAqB,CACzB,cAAI,CAAC,OAAO,CAAC,cAAc,EAAE,aAAa,CAAC,EAC3C,YAAY,EACZ,iBAAiB,IAAI,iBAAiB,CACvC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,cAAsB,EACtB,SAAkB,EAClB,iBAA0B;IAE1B,IAAI,SAAS,EAAE;QACb,MAAM,kBAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,cAAI,CAAC,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;KACtE;IACD,IAAI,iBAAiB,EAAE;QACrB,MAAM,kBAAE,CAAC,QAAQ,CAAC,iBAAiB,EAAE,cAAI,CAAC,OAAO,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC,CAAC;KACnF;AACH,CAAC;AAED;;GAEG;AACY,KAAK,UAAU,oBAAoB,CAChD,cAAsB,EACtB,SAKI,EAAE;;IAEN,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC;IAC/B,MAAM,iBAAiB,SAAG,MAAM,CAAC,QAAQ,0CAAE,KAAK,CAAC;IAEjD,MAAM,YAAY,GAAG,cAAI,CAAC,OAAO,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;IAEjE,8CAA8C;IAC9C,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE;QACrC,MAAM,kBAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;KAC/B;IAED,MAAM,sBAAsB,CAAC,cAAc,EAAE,YAAY,EAAE,SAAS,EAAE,iBAAiB,CAAC,CAAC;IACzF,MAAM,cAAc,CAAC,cAAc,EAAE,SAAS,EAAE,iBAAiB,CAAC,CAAC;AACrE,CAAC;AArBD,uCAqBC","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\n\nimport { writeContentsJsonFile } from './Contents.json';\n\nconst PNG_FILENAME = 'splashscreen.png';\nconst DARK_PNG_FILENAME = 'dark_splashscreen.png';\n\nconst IMAGESET_PATH = 'Images.xcassets/SplashScreen.imageset';\nconst CONTENTS_PATH = `${IMAGESET_PATH}/Contents.json`;\nconst PNG_PATH = `${IMAGESET_PATH}/${PNG_FILENAME}`;\nconst DARK_PNG_PATH = `${IMAGESET_PATH}/${DARK_PNG_FILENAME}`;\n\nasync function createContentsJsonFile(\n iosProjectPath: string,\n imageSetPath: string,\n imagePath?: string,\n darkModeImagePath?: string\n) {\n if (!imagePath) {\n return;\n }\n\n await fs.mkdirp(imageSetPath);\n await writeContentsJsonFile(\n path.resolve(iosProjectPath, CONTENTS_PATH),\n PNG_FILENAME,\n darkModeImagePath && DARK_PNG_FILENAME\n );\n}\n\nasync function copyImageFiles(\n iosProjectPath: string,\n imagePath?: string,\n darkModeImagePath?: string\n) {\n if (imagePath) {\n await fs.copyFile(imagePath, path.resolve(iosProjectPath, PNG_PATH));\n }\n if (darkModeImagePath) {\n await fs.copyFile(darkModeImagePath, path.resolve(iosProjectPath, DARK_PNG_PATH));\n }\n}\n\n/**\n * Creates imageset containing image for Splash/Launch Screen.\n */\nexport default async function configureImageAssets(\n iosProjectPath: string,\n config: {\n image?: string;\n darkMode?: {\n image?: string;\n };\n } = {}\n) {\n const imagePath = config.image;\n const darkModeImagePath = config.darkMode?.image;\n\n const imageSetPath = path.resolve(iosProjectPath, IMAGESET_PATH);\n\n // ensure old SplashScreen imageSet is removed\n if (await fs.pathExists(imageSetPath)) {\n await fs.remove(imageSetPath);\n }\n\n await createContentsJsonFile(iosProjectPath, imageSetPath, imagePath, darkModeImagePath);\n await copyImageFiles(iosProjectPath, imagePath, darkModeImagePath);\n}\n"]}

View File

@ -0,0 +1,10 @@
import { SplashScreenStatusBarStyleType } from '../constants';
/**
* Configures [INFO_PLIST] to show [STORYBOARD] filename as Splash/Launch Screen.
*/
export default function configureInfoPlist(iosProjectPath: string, config?: {
statusBar?: {
hidden?: boolean;
style?: SplashScreenStatusBarStyleType;
};
}): Promise<void>;

View File

@ -0,0 +1,109 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const StateManager_1 = __importDefault(require("../utils/StateManager"));
const string_utils_1 = require("../utils/string-utils");
const INFO_PLIST_FILE_PATH = 'Info.plist';
function getUIStatusBarStyle(statusBarStyle) {
return `UIStatusBarStyle${statusBarStyle
.replace(/(^\w)|(-\w)/g, s => s.toUpperCase())
.replace(/-/g, '')}`;
}
/**
* Configures [INFO_PLIST] to show [STORYBOARD] filename as Splash/Launch Screen.
*/
async function configureInfoPlist(iosProjectPath, config = {}) {
var _a, _b;
const statusBarHidden = (_a = config.statusBar) === null || _a === void 0 ? void 0 : _a.hidden;
const statusBarStyle = (_b = config.statusBar) === null || _b === void 0 ? void 0 : _b.style;
const filePath = path_1.default.resolve(iosProjectPath, INFO_PLIST_FILE_PATH);
const fileContent = await fs_extra_1.default.readFile(filePath, 'utf-8');
const { state: newContent } = new StateManager_1.default(fileContent)
// LaunchScreen
.applyAction(content => {
const [succeeded, newContent] = string_utils_1.replace(content, {
replaceContent: '<string>SplashScreen</string>',
replacePattern: /(?<=<key>UILaunchStoryboardName<\/key>(.|\n)*?)<string>.*?<\/string>/m,
});
return [newContent, 'launchScreenReplaced', succeeded];
})
.applyAction((content, { launchScreenReplaced }) => {
if (launchScreenReplaced) {
return [content, 'launchScreenInserted', false];
}
const [succeeded, newContent] = string_utils_1.insert(content, {
insertContent: ` <key>UILaunchStoryboardName</key>\n <string>SplashScreen</string>\n`,
insertPattern: /<\/dict>/gm,
}, true);
return [newContent, 'inserted', succeeded];
})
// StatusBar hiding
.applyAction(content => {
if (statusBarHidden === undefined) {
const [succeeded, newContent] = string_utils_1.replace(content, {
replaceContent: '',
replacePattern: /^.*<key>UIStatusBarHidden<\/key>(.|\n)*?<.*\/>.*$/m,
});
return [newContent, 'statusBarHidingRemoved', succeeded];
}
return [content, 'statusBarHidingRemoved', false];
})
.applyAction((content, { statusBarHidingRemoved }) => {
if (statusBarHidingRemoved || statusBarHidden === undefined) {
return [content, 'statusBarHidingReplaced', false];
}
const [succeeded, newContent] = string_utils_1.replace(content, {
replaceContent: String(statusBarHidden),
replacePattern: /(?<=<key>UIStatusBarHidden<\/key>(.|\n)*?<).*(?=\/>)/m,
});
return [newContent, 'statusBarHidingReplaced', succeeded];
})
.applyAction((content, { statusBarHidingReplaced }) => {
if (statusBarHidingReplaced || statusBarHidden === undefined) {
return [content, 'statusBarHidingInserted', false];
}
const [succeeded, newContent] = string_utils_1.insert(content, {
insertContent: ` <key>UIStatusBarHidden</key>\n <${statusBarHidden}/>\n`,
insertPattern: /<\/dict>/gm,
}, true);
return [newContent, 'statusBarHidingInserted', succeeded];
})
// StatusBar style
.applyAction(content => {
if (statusBarStyle === undefined) {
const [succeeded, newContent] = string_utils_1.replace(content, {
replacePattern: /^.*<key>UIStatusBarStyle<\/key>(.|\n)*?<string>.*<\/string>.*$/m,
replaceContent: '',
});
return [newContent, 'statusBarStyleRemoved', succeeded];
}
return [content, 'statusBarStyleRemoved', false];
})
.applyAction((content, { statusBarStyleRemoved }) => {
if (statusBarStyleRemoved || statusBarStyle === undefined) {
return [content, 'statusBarStyleReplaced', false];
}
const [succeeded, newContent] = string_utils_1.replace(content, {
replaceContent: getUIStatusBarStyle(statusBarStyle),
replacePattern: /(?<=<key>UIStatusBarStyle<\/key>(.|\n)*?<string>).*(?=<\/string>)/m,
});
return [newContent, 'statusBarStyleReplaced', succeeded];
})
.applyAction((content, { statusBarStyleReplaced }) => {
if (statusBarStyleReplaced || statusBarStyle === undefined) {
return [content, 'statusBarStyleInserted', false];
}
const [succeeded, newContent] = string_utils_1.insert(content, {
insertContent: ` <key>UIStatusBarStyle</key>\n <string>${getUIStatusBarStyle(statusBarStyle)}</string>\n`,
insertPattern: /<\/dict>/gm,
}, true);
return [newContent, 'statusBarStyleInserted', succeeded];
});
await fs_extra_1.default.writeFile(filePath, newContent);
}
exports.default = configureInfoPlist;
//# sourceMappingURL=Info.plist.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
import { XcodeProject } from 'xcode';
export declare function getAllXcodeProjectPaths(projectRoot: string): string[];
export declare function getAllPBXProjectPaths(projectRoot: string): string[];
export declare function getApplicationNativeTarget({ project, projectName, }: {
project: XcodeProject;
projectName: string;
}): {
uuid: string;
target: import("xcode").PBXNativeTarget;
};

View File

@ -0,0 +1,63 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getApplicationNativeTarget = exports.getAllPBXProjectPaths = exports.getAllXcodeProjectPaths = void 0;
// Copied over from `@expo/config-plugins/src/ios/Paths`
const assert_1 = __importDefault(require("assert"));
const fs_extra_1 = require("fs-extra");
const glob_1 = require("glob");
const path = __importStar(require("path"));
const ignoredPaths = ['**/@(Carthage|Pods|node_modules)/**'];
function getAllXcodeProjectPaths(projectRoot) {
const iosFolder = 'ios';
const pbxprojPaths = glob_1.sync('**/*.xcodeproj', { cwd: projectRoot, ignore: ignoredPaths })
.filter(project => !/test|example|sample/i.test(project) || path.dirname(project) === iosFolder)
.sort(project => (path.dirname(project) === iosFolder ? -1 : 1))
// sort alphabetically to ensure this works the same across different devices (Fail in CI (linux) without this)
.sort();
if (!pbxprojPaths.length) {
throw new Error(`Failed to locate the ios/*.xcodeproj files relative to path "${projectRoot}".`);
}
return pbxprojPaths.map(value => path.join(projectRoot, value));
}
exports.getAllXcodeProjectPaths = getAllXcodeProjectPaths;
function getAllPBXProjectPaths(projectRoot) {
const projectPaths = getAllXcodeProjectPaths(projectRoot);
const paths = projectPaths
.map(value => path.join(value, 'project.pbxproj'))
.filter(value => fs_extra_1.pathExistsSync(value));
if (!paths.length) {
throw new Error(`Failed to locate the ios/*.xcodeproj/project.pbxproj files relative to path "${projectRoot}".`);
}
return paths;
}
exports.getAllPBXProjectPaths = getAllPBXProjectPaths;
function getApplicationNativeTarget({ project, projectName, }) {
const applicationNativeTarget = project.getTarget('com.apple.product-type.application');
assert_1.default(applicationNativeTarget, `Couldn't locate application PBXNativeTarget in '.xcodeproj' file.`);
assert_1.default(String(applicationNativeTarget.target.name) === projectName, `Application native target name mismatch. Expected ${projectName}, but found ${applicationNativeTarget.target.name}.`);
return applicationNativeTarget;
}
exports.getApplicationNativeTarget = getApplicationNativeTarget;
//# sourceMappingURL=Paths.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Paths.js","sourceRoot":"","sources":["../../src/ios/Paths.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wDAAwD;AACxD,oDAA4B;AAC5B,uCAA0C;AAC1C,+BAAwC;AACxC,2CAA6B;AAG7B,MAAM,YAAY,GAAG,CAAC,qCAAqC,CAAC,CAAC;AAE7D,SAAgB,uBAAuB,CAAC,WAAmB;IACzD,MAAM,SAAS,GAAG,KAAK,CAAC;IACxB,MAAM,YAAY,GAAG,WAAQ,CAAC,gBAAgB,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;SACxF,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC;SAC/F,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChE,+GAA+G;SAC9G,IAAI,EAAE,CAAC;IAEV,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;QACxB,MAAM,IAAI,KAAK,CACb,gEAAgE,WAAW,IAAI,CAChF,CAAC;KACH;IACD,OAAO,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;AAClE,CAAC;AAdD,0DAcC;AAED,SAAgB,qBAAqB,CAAC,WAAmB;IACvD,MAAM,YAAY,GAAG,uBAAuB,CAAC,WAAW,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,YAAY;SACvB,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;SACjD,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,yBAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAE1C,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;QACjB,MAAM,IAAI,KAAK,CACb,gFAAgF,WAAW,IAAI,CAChG,CAAC;KACH;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAZD,sDAYC;AAED,SAAgB,0BAA0B,CAAC,EACzC,OAAO,EACP,WAAW,GAIZ;IACC,MAAM,uBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC,oCAAoC,CAAC,CAAC;IACxF,gBAAM,CACJ,uBAAuB,EACvB,mEAAmE,CACpE,CAAC;IACF,gBAAM,CACJ,MAAM,CAAC,uBAAuB,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,WAAW,EAC3D,qDAAqD,WAAW,eAAe,uBAAuB,CAAC,MAAM,CAAC,IAAI,GAAG,CACtH,CAAC;IACF,OAAO,uBAAuB,CAAC;AACjC,CAAC;AAjBD,gEAiBC","sourcesContent":["// Copied over from `@expo/config-plugins/src/ios/Paths`\nimport assert from 'assert';\nimport { pathExistsSync } from 'fs-extra';\nimport { sync as globSync } from 'glob';\nimport * as path from 'path';\nimport { XcodeProject } from 'xcode';\n\nconst ignoredPaths = ['**/@(Carthage|Pods|node_modules)/**'];\n\nexport function getAllXcodeProjectPaths(projectRoot: string): string[] {\n const iosFolder = 'ios';\n const pbxprojPaths = globSync('**/*.xcodeproj', { cwd: projectRoot, ignore: ignoredPaths })\n .filter(project => !/test|example|sample/i.test(project) || path.dirname(project) === iosFolder)\n .sort(project => (path.dirname(project) === iosFolder ? -1 : 1))\n // sort alphabetically to ensure this works the same across different devices (Fail in CI (linux) without this)\n .sort();\n\n if (!pbxprojPaths.length) {\n throw new Error(\n `Failed to locate the ios/*.xcodeproj files relative to path \"${projectRoot}\".`\n );\n }\n return pbxprojPaths.map(value => path.join(projectRoot, value));\n}\n\nexport function getAllPBXProjectPaths(projectRoot: string): string[] {\n const projectPaths = getAllXcodeProjectPaths(projectRoot);\n const paths = projectPaths\n .map(value => path.join(value, 'project.pbxproj'))\n .filter(value => pathExistsSync(value));\n\n if (!paths.length) {\n throw new Error(\n `Failed to locate the ios/*.xcodeproj/project.pbxproj files relative to path \"${projectRoot}\".`\n );\n }\n return paths;\n}\n\nexport function getApplicationNativeTarget({\n project,\n projectName,\n}: {\n project: XcodeProject;\n projectName: string;\n}) {\n const applicationNativeTarget = project.getTarget('com.apple.product-type.application');\n assert(\n applicationNativeTarget,\n `Couldn't locate application PBXNativeTarget in '.xcodeproj' file.`\n );\n assert(\n String(applicationNativeTarget.target.name) === projectName,\n `Application native target name mismatch. Expected ${projectName}, but found ${applicationNativeTarget.target.name}.`\n );\n return applicationNativeTarget;\n}\n"]}

View File

@ -0,0 +1,10 @@
import { SplashScreenImageResizeModeType } from '../constants';
import { IosProject } from './pbxproj';
/**
* Creates [STORYBOARD] file containing ui description of Splash/Launch Screen.
* > WARNING: modifies `pbxproj`
*/
export default function configureStoryboard(iosProject: IosProject, config?: {
imageResizeMode?: SplashScreenImageResizeModeType;
image?: string;
}): Promise<void>;

View File

@ -0,0 +1,151 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const constants_1 = require("../constants");
const file_utils_1 = require("../utils/file-utils");
const xcode_1 = require("../xcode");
const STORYBOARD_FILE_PATH = './SplashScreen.storyboard';
/**
* Modifies `.pbxproj` by:
* - adding reference for `.storyboard` file
*/
function updatePbxProject({ projectName, pbxProject, applicationNativeTarget }) {
// Check if `${projectName}/SplashScreen.storyboard` already exists
// Path relative to `ios` directory
const storyboardFilePath = path_1.default.join(projectName, STORYBOARD_FILE_PATH);
if (!pbxProject.hasFile(storyboardFilePath)) {
const group = pbxProject.findPBXGroupKey({ name: projectName });
if (!group) {
throw new Error(`Couldn't locate proper PBXGroup '.xcodeproj' file.`);
}
xcode_1.addStoryboardFileToProject(pbxProject, storyboardFilePath, {
target: applicationNativeTarget.uuid,
group,
});
}
}
/**
* Creates [STORYBOARD] file containing ui description of Splash/Launch Screen.
* > WARNING: modifies `pbxproj`
*/
async function configureStoryboard(iosProject, config = {}) {
var _a;
const resizeMode = (_a = config.imageResizeMode) !== null && _a !== void 0 ? _a : constants_1.SplashScreenImageResizeMode.CONTAIN;
const splashScreenImagePresent = Boolean(config.image);
let contentMode;
switch (resizeMode) {
case constants_1.SplashScreenImageResizeMode.CONTAIN:
contentMode = 'scaleAspectFit';
break;
case constants_1.SplashScreenImageResizeMode.COVER:
contentMode = 'scaleAspectFill';
break;
default:
throw new Error(`resizeMode = ${resizeMode} is not supported for iOS platform.`);
}
const filePath = path_1.default.resolve(iosProject.projectPath, STORYBOARD_FILE_PATH);
await file_utils_1.createDirAndWriteFile(filePath, `<?xml version="1.0" encoding="UTF-8"?>
<document
type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB"
version="3.0"
toolsVersion="16096"
targetRuntime="iOS.CocoaTouch"
propertyAccessControl="none"
useAutolayout="YES"
launchScreen="YES"
useTraitCollections="YES"
useSafeAreas="YES"
colorMatched="YES"
initialViewController="EXPO-VIEWCONTROLLER-1"
>
<device id="retina5_5" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<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="EXPO-SCENE-1">
<objects>
<viewController
storyboardIdentifier="SplashScreenViewController"
id="EXPO-VIEWCONTROLLER-1"
sceneMemberID="viewController"
>
<view
key="view"
userInteractionEnabled="NO"
contentMode="scaleToFill"
insetsLayoutMarginsFromSafeArea="NO"
id="EXPO-ContainerView"
userLabel="ContainerView"
>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView
userInteractionEnabled="NO"
contentMode="scaleAspectFill"
horizontalHuggingPriority="251"
verticalHuggingPriority="251"
insetsLayoutMarginsFromSafeArea="NO"
image="SplashScreenBackground"
translatesAutoresizingMaskIntoConstraints="NO"
id="EXPO-SplashScreenBackground"
userLabel="SplashScreenBackground"
>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
</imageView>${!splashScreenImagePresent
? ''
: `
<imageView
clipsSubviews="YES"
userInteractionEnabled="NO"
contentMode="${contentMode}"
horizontalHuggingPriority="251"
verticalHuggingPriority="251"
translatesAutoresizingMaskIntoConstraints="NO"
image="SplashScreen"
id="EXPO-SplashScreen"
userLabel="SplashScreen"
>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
</imageView>`}
</subviews>
<constraints>
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="1gX-mQ-vu6"/>
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="6tX-OG-Sck"/>
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="ABX-8g-7v4"/>
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="jkI-2V-eW5"/>${!splashScreenImagePresent
? ''
: `
<constraint firstItem="EXPO-SplashScreen" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="2VS-Uz-0LU"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="LhH-Ei-DKo"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="I6l-TP-6fn"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="nbp-HC-eaG"/>`}
</constraints>
<viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="140.625" y="129.4921875"/>
</scene>
</scenes>
<resources>${!splashScreenImagePresent
? ''
: `
<image name="SplashScreen" width="414" height="736"/>`}
<image name="SplashScreenBackground" width="1" height="1"/>
</resources>
</document>
`);
await updatePbxProject(iosProject);
}
exports.default = configureStoryboard;
//# sourceMappingURL=Storyboard.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
import { IosSplashScreenConfigJSON } from '../SplashScreenConfig';
export default function configureIos(projectRootPath: string, config: IosSplashScreenConfigJSON): Promise<void>;

View File

@ -0,0 +1,25 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = __importDefault(require("fs-extra"));
const validators_1 = require("../validators");
const BackgroundAsset_1 = __importDefault(require("./BackgroundAsset"));
const ImageAsset_1 = __importDefault(require("./ImageAsset"));
const Info_plist_1 = __importDefault(require("./Info.plist"));
const Storyboard_1 = __importDefault(require("./Storyboard"));
const pbxproj_1 = __importDefault(require("./pbxproj"));
async function configureIos(projectRootPath, config) {
const validatedConfig = await validators_1.validateIosConfig(config);
const iosProject = await pbxproj_1.default(projectRootPath);
await Promise.all([
Info_plist_1.default(iosProject.projectPath, validatedConfig),
ImageAsset_1.default(iosProject.projectPath, validatedConfig),
BackgroundAsset_1.default(iosProject.projectPath, validatedConfig),
Storyboard_1.default(iosProject, validatedConfig),
]);
await fs_extra_1.default.writeFile(iosProject.pbxProject.filepath, iosProject.pbxProject.writeSync());
}
exports.default = configureIos;
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ios/index.ts"],"names":[],"mappings":";;;;;AAAA,wDAA0B;AAG1B,8CAAkD;AAClD,wEAAyD;AACzD,8DAA+C;AAC/C,8DAA8C;AAC9C,8DAA+C;AAC/C,wDAAuC;AAExB,KAAK,UAAU,YAAY,CACxC,eAAuB,EACvB,MAAiC;IAEjC,MAAM,eAAe,GAAG,MAAM,8BAAiB,CAAC,MAAM,CAAC,CAAC;IAExD,MAAM,UAAU,GAAG,MAAM,iBAAc,CAAC,eAAe,CAAC,CAAC;IAEzD,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,oBAAkB,CAAC,UAAU,CAAC,WAAW,EAAE,eAAe,CAAC;QAC3D,oBAAmB,CAAC,UAAU,CAAC,WAAW,EAAE,eAAe,CAAC;QAC5D,yBAAwB,CAAC,UAAU,CAAC,WAAW,EAAE,eAAe,CAAC;QACjE,oBAAmB,CAAC,UAAU,EAAE,eAAe,CAAC;KACjD,CAAC,CAAC;IAEH,MAAM,kBAAE,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC;AACxF,CAAC;AAhBD,+BAgBC","sourcesContent":["import fs from 'fs-extra';\n\nimport { IosSplashScreenConfigJSON } from '../SplashScreenConfig';\nimport { validateIosConfig } from '../validators';\nimport configureBackgroundAsset from './BackgroundAsset';\nimport configureImageAsset from './ImageAsset';\nimport configureInfoPlist from './Info.plist';\nimport configureStoryboard from './Storyboard';\nimport readPbxProject from './pbxproj';\n\nexport default async function configureIos(\n projectRootPath: string,\n config: IosSplashScreenConfigJSON\n) {\n const validatedConfig = await validateIosConfig(config);\n\n const iosProject = await readPbxProject(projectRootPath);\n\n await Promise.all([\n configureInfoPlist(iosProject.projectPath, validatedConfig),\n configureImageAsset(iosProject.projectPath, validatedConfig),\n configureBackgroundAsset(iosProject.projectPath, validatedConfig),\n configureStoryboard(iosProject, validatedConfig),\n ]);\n\n await fs.writeFile(iosProject.pbxProject.filepath, iosProject.pbxProject.writeSync());\n}\n"]}

View File

@ -0,0 +1,23 @@
import { PBXNativeTarget, UUID, XcodeProject } from 'xcode';
export interface IosProject {
projectName: string;
/**
* Root path to directory containing project source files.
*/
projectPath: string;
/**
* pbxProject reference that allows to modify `.pbxproj` file.
*/
pbxProject: XcodeProject;
/**
* main application PBXNativeTarget from `.pbxproj` file.
*/
applicationNativeTarget: {
uuid: UUID;
target: PBXNativeTarget;
};
}
/**
* Reads iOS project and locates `.pbxproj` file for further parsing and modifications.
*/
export default function readPbxProject(projectRootPath: string): Promise<IosProject>;

View File

@ -0,0 +1,58 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = __importStar(require("path"));
const xcode_1 = __importDefault(require("xcode"));
const Paths_1 = require("./Paths");
function getProjectConfig(projectRoot) {
return {
projectPath: Paths_1.getAllXcodeProjectPaths(projectRoot)[0],
pbxprojPath: Paths_1.getAllPBXProjectPaths(projectRoot)[0],
};
}
/**
* Reads iOS project and locates `.pbxproj` file for further parsing and modifications.
*/
async function readPbxProject(projectRootPath) {
const config = getProjectConfig(projectRootPath);
const { projectPath: xcodeProjPath, pbxprojPath } = config;
const projectPath = xcodeProjPath.substring(0, xcodeProjPath.length - '.xcodeproj'.length);
const projectName = path.basename(projectPath);
const pbxProject = xcode_1.default.project(pbxprojPath);
await new Promise(resolve => pbxProject.parse(err => {
if (err) {
throw new Error(`.pbxproj file parsing issue: ${err.message}.`);
}
resolve();
}));
const applicationNativeTarget = Paths_1.getApplicationNativeTarget({ project: pbxProject, projectName });
return {
projectName,
projectPath,
pbxProject,
applicationNativeTarget,
};
}
exports.default = readPbxProject;
//# sourceMappingURL=pbxproj.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"pbxproj.js","sourceRoot":"","sources":["../../src/ios/pbxproj.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA6B;AAC7B,kDAAmE;AAEnE,mCAIiB;AAkBjB,SAAS,gBAAgB,CAAC,WAAmB;IAC3C,OAAO;QACL,WAAW,EAAE,+BAAuB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAE;QACrD,WAAW,EAAE,6BAAqB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAE;KACpD,CAAC;AACJ,CAAC;AAED;;GAEG;AACY,KAAK,UAAU,cAAc,CAAC,eAAuB;IAClE,MAAM,MAAM,GAAG,gBAAgB,CAAC,eAAe,CAAC,CAAC;IAEjD,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAE3D,MAAM,WAAW,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAC3F,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAE/C,MAAM,UAAU,GAAG,eAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAE9C,MAAM,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,CAChC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;QACrB,IAAI,GAAG,EAAE;YACP,MAAM,IAAI,KAAK,CAAC,gCAAgC,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC;SACjE;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,uBAAuB,GAAG,kCAA0B,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC;IAEjG,OAAO;QACL,WAAW;QACX,WAAW;QACX,UAAU;QACV,uBAAuB;KACxB,CAAC;AACJ,CAAC;AA3BD,iCA2BC","sourcesContent":["import * as path from 'path';\nimport xcode, { PBXNativeTarget, UUID, XcodeProject } from 'xcode';\n\nimport {\n getAllPBXProjectPaths,\n getAllXcodeProjectPaths,\n getApplicationNativeTarget,\n} from './Paths';\n\nexport interface IosProject {\n projectName: string;\n /**\n * Root path to directory containing project source files.\n */\n projectPath: string;\n /**\n * pbxProject reference that allows to modify `.pbxproj` file.\n */\n pbxProject: XcodeProject;\n /**\n * main application PBXNativeTarget from `.pbxproj` file.\n */\n applicationNativeTarget: { uuid: UUID; target: PBXNativeTarget };\n}\n\nfunction getProjectConfig(projectRoot: string): { projectPath: string; pbxprojPath: string } {\n return {\n projectPath: getAllXcodeProjectPaths(projectRoot)[0]!,\n pbxprojPath: getAllPBXProjectPaths(projectRoot)[0]!,\n };\n}\n\n/**\n * Reads iOS project and locates `.pbxproj` file for further parsing and modifications.\n */\nexport default async function readPbxProject(projectRootPath: string): Promise<IosProject> {\n const config = getProjectConfig(projectRootPath);\n\n const { projectPath: xcodeProjPath, pbxprojPath } = config;\n\n const projectPath = xcodeProjPath.substring(0, xcodeProjPath.length - '.xcodeproj'.length);\n const projectName = path.basename(projectPath);\n\n const pbxProject = xcode.project(pbxprojPath);\n\n await new Promise<void>(resolve =>\n pbxProject.parse(err => {\n if (err) {\n throw new Error(`.pbxproj file parsing issue: ${err.message}.`);\n }\n resolve();\n })\n );\n\n const applicationNativeTarget = getApplicationNativeTarget({ project: pbxProject, projectName });\n\n return {\n projectName,\n projectPath,\n pbxProject,\n applicationNativeTarget,\n };\n}\n"]}

View File

@ -0,0 +1,10 @@
export default class StateManager<StateType, AppliedActionResultType, ActionName extends string = never> {
state: StateType;
constructor(state: StateType);
appliedActions: {
[K in ActionName]: AppliedActionResultType;
};
applyAction: <NewActionName extends string>(action: (content: StateType, actions: {
[K in ActionName]: AppliedActionResultType;
}) => [StateType, NewActionName, AppliedActionResultType]) => StateManager<StateType, AppliedActionResultType, ActionName | NewActionName>;
}

View File

@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class StateManager {
constructor(state) {
this.state = state;
// @ts-ignore
this.appliedActions = {};
// @ts-ignore
this.applyAction = action => {
const [state, actionName, appliedAction] = action(this.state, this.appliedActions);
this.state = state;
// @ts-ignore
this.appliedActions[actionName] = appliedAction;
return this;
};
}
}
exports.default = StateManager;
//# sourceMappingURL=StateManager.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"StateManager.js","sourceRoot":"","sources":["../../src/utils/StateManager.ts"],"names":[],"mappings":";;AAAA,MAAqB,YAAY;IAK/B,YAAmB,KAAgB;QAAhB,UAAK,GAAL,KAAK,CAAW;QACnC,aAAa;QACb,mBAAc,GAAmD,EAAE,CAAC;QACpE,aAAa;QACb,gBAAW,GAKyE,MAAM,CAAC,EAAE;YAC3F,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;YACnF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,aAAa;YACb,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;IAfoC,CAAC;CAgBxC;AArBD,+BAqBC","sourcesContent":["export default class StateManager<\n StateType,\n AppliedActionResultType,\n ActionName extends string = never\n> {\n constructor(public state: StateType) {}\n // @ts-ignore\n appliedActions: { [K in ActionName]: AppliedActionResultType } = {};\n // @ts-ignore\n applyAction: <NewActionName extends string>(\n action: (\n content: StateType,\n actions: { [K in ActionName]: AppliedActionResultType }\n ) => [StateType, NewActionName, AppliedActionResultType]\n ) => StateManager<StateType, AppliedActionResultType, ActionName | NewActionName> = action => {\n const [state, actionName, appliedAction] = action(this.state, this.appliedActions);\n this.state = state;\n // @ts-ignore\n this.appliedActions[actionName] = appliedAction;\n return this;\n };\n}\n"]}

View File

@ -0,0 +1,9 @@
/**
* Creates file with given content with possible parent directories creation.
*/
export declare function createDirAndWriteFile(filePath: string, content: string): Promise<void>;
/**
* Reads given file as UTF-8 with fallback to given content when file is not found.
*/
export declare function readFileWithFallback(filePath: string, fallbackContent?: string): Promise<string>;
export declare function removeFileIfExists(filePath: string): Promise<void>;

View File

@ -0,0 +1,38 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeFileIfExists = exports.readFileWithFallback = exports.createDirAndWriteFile = void 0;
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
/**
* Creates file with given content with possible parent directories creation.
*/
async function createDirAndWriteFile(filePath, content) {
if (!(await fs_extra_1.default.pathExists(path_1.default.dirname(filePath)))) {
await fs_extra_1.default.mkdirp(path_1.default.dirname(filePath));
}
await fs_extra_1.default.writeFile(filePath, content);
}
exports.createDirAndWriteFile = createDirAndWriteFile;
/**
* Reads given file as UTF-8 with fallback to given content when file is not found.
*/
async function readFileWithFallback(filePath, fallbackContent) {
if (await fs_extra_1.default.pathExists(filePath)) {
return fs_extra_1.default.readFile(filePath, 'utf-8');
}
if (fallbackContent) {
return fallbackContent;
}
throw Error(`File not found ${filePath}`);
}
exports.readFileWithFallback = readFileWithFallback;
async function removeFileIfExists(filePath) {
if (await fs_extra_1.default.pathExists(filePath)) {
await fs_extra_1.default.unlink(filePath);
}
}
exports.removeFileIfExists = removeFileIfExists;
//# sourceMappingURL=file-utils.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":";;;;;;AAAA,wDAA0B;AAC1B,gDAAwB;AAExB;;GAEG;AACI,KAAK,UAAU,qBAAqB,CAAC,QAAgB,EAAE,OAAe;IAC3E,IAAI,CAAC,CAAC,MAAM,kBAAE,CAAC,UAAU,CAAC,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;QAClD,MAAM,kBAAE,CAAC,MAAM,CAAC,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;KACzC;IACD,MAAM,kBAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC;AALD,sDAKC;AAED;;GAEG;AACI,KAAK,UAAU,oBAAoB,CAAC,QAAgB,EAAE,eAAwB;IACnF,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QACjC,OAAO,kBAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;KACvC;IACD,IAAI,eAAe,EAAE;QACnB,OAAO,eAAe,CAAC;KACxB;IACD,MAAM,KAAK,CAAC,kBAAkB,QAAQ,EAAE,CAAC,CAAC;AAC5C,CAAC;AARD,oDAQC;AAEM,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACvD,IAAI,MAAM,kBAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QACjC,MAAM,kBAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;KAC3B;AACH,CAAC;AAJD,gDAIC","sourcesContent":["import fs from 'fs-extra';\nimport path from 'path';\n\n/**\n * Creates file with given content with possible parent directories creation.\n */\nexport async function createDirAndWriteFile(filePath: string, content: string) {\n if (!(await fs.pathExists(path.dirname(filePath)))) {\n await fs.mkdirp(path.dirname(filePath));\n }\n await fs.writeFile(filePath, content);\n}\n\n/**\n * Reads given file as UTF-8 with fallback to given content when file is not found.\n */\nexport async function readFileWithFallback(filePath: string, fallbackContent?: string) {\n if (await fs.pathExists(filePath)) {\n return fs.readFile(filePath, 'utf-8');\n }\n if (fallbackContent) {\n return fallbackContent;\n }\n throw Error(`File not found ${filePath}`);\n}\n\nexport async function removeFileIfExists(filePath: string) {\n if (await fs.pathExists(filePath)) {\n await fs.unlink(filePath);\n }\n}\n"]}

View File

@ -0,0 +1,16 @@
import 'core-js/es/string/match-all';
/**
* @returns [`true`, modifiedContent: string] if replacement is successful, [`false`, originalContent] otherwise.
*/
export declare function replace(content: string, { replaceContent, replacePattern }: {
replaceContent: string;
replacePattern: string | RegExp;
}): [boolean, string];
/**
* Inserts content just before first occurrence of provided pattern.
* @returns [`true`, modifiedContent: string] if insertion is successful, [`false`, originalContent] otherwise.
*/
export declare function insert(content: string, { insertContent, insertPattern }: {
insertContent: string;
insertPattern: RegExp | string;
}, insertBeforeLastOccurrence?: boolean): [boolean, string];

View File

@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.insert = exports.replace = void 0;
// runtime polyfills
require("core-js/es/string/match-all");
/**
* @returns [`true`, modifiedContent: string] if replacement is successful, [`false`, originalContent] otherwise.
*/
function replace(content, { replaceContent, replacePattern }) {
const replacePatternOccurrence = content.search(replacePattern);
if (replacePatternOccurrence !== -1) {
return [true, content.replace(replacePattern, replaceContent)];
}
return [false, content];
}
exports.replace = replace;
/**
* Inserts content just before first occurrence of provided pattern.
* @returns [`true`, modifiedContent: string] if insertion is successful, [`false`, originalContent] otherwise.
*/
function insert(content, { insertContent, insertPattern }, insertBeforeLastOccurrence = false) {
if (insertBeforeLastOccurrence) {
return insertBeforeLastOccurrenceFun(content, { insertContent, insertPattern });
}
const insertPatternOccurrence = content.search(insertPattern);
if (insertPatternOccurrence !== -1) {
return [
true,
`${content.slice(0, insertPatternOccurrence)}${insertContent}${content.slice(insertPatternOccurrence)}`,
];
}
return [false, content];
}
exports.insert = insert;
/**
* Finds last occurrence of provided pattern and inserts content just before it.
*@returns [`true`, modifiedContent: string] if insertion is successful, [`false`, originalContent] otherwise.
*/
function insertBeforeLastOccurrenceFun(content, { insertContent, insertPattern }) {
const results = [...content.matchAll(new RegExp(insertPattern, 'gm'))];
const patternLastOccurrence = results[results.length - 1];
if (!patternLastOccurrence) {
return [false, content];
}
return [
true,
`${content.slice(0, patternLastOccurrence.index)}${insertContent}${content.slice(patternLastOccurrence.index)}`,
];
}
//# sourceMappingURL=string-utils.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"string-utils.js","sourceRoot":"","sources":["../../src/utils/string-utils.ts"],"names":[],"mappings":";;;AAAA,oBAAoB;AACpB,uCAAqC;AAErC;;GAEG;AACH,SAAgB,OAAO,CACrB,OAAe,EACf,EAAE,cAAc,EAAE,cAAc,EAA+D;IAE/F,MAAM,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAChE,IAAI,wBAAwB,KAAK,CAAC,CAAC,EAAE;QACnC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;KAChE;IACD,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAC1B,CAAC;AATD,0BASC;AAED;;;GAGG;AACH,SAAgB,MAAM,CACpB,OAAe,EACf,EAAE,aAAa,EAAE,aAAa,EAA6D,EAC3F,6BAAsC,KAAK;IAE3C,IAAI,0BAA0B,EAAE;QAC9B,OAAO,6BAA6B,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC,CAAC;KACjF;IACD,MAAM,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAC9D,IAAI,uBAAuB,KAAK,CAAC,CAAC,EAAE;QAClC,OAAO;YACL,IAAI;YACJ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,uBAAuB,CAAC,GAAG,aAAa,GAAG,OAAO,CAAC,KAAK,CAC1E,uBAAuB,CACxB,EAAE;SACJ,CAAC;KACH;IACD,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAC1B,CAAC;AAlBD,wBAkBC;AAED;;;GAGG;AACH,SAAS,6BAA6B,CACpC,OAAe,EACf,EAAE,aAAa,EAAE,aAAa,EAA6D;IAE3F,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACvE,MAAM,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,qBAAqB,EAAE;QAC1B,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;KACzB;IACD,OAAO;QACL,IAAI;QACJ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,aAAa,GAAG,OAAO,CAAC,KAAK,CAC9E,qBAAqB,CAAC,KAAK,CAC5B,EAAE;KACJ,CAAC;AACJ,CAAC","sourcesContent":["// runtime polyfills\nimport 'core-js/es/string/match-all';\n\n/**\n * @returns [`true`, modifiedContent: string] if replacement is successful, [`false`, originalContent] otherwise.\n */\nexport function replace(\n content: string,\n { replaceContent, replacePattern }: { replaceContent: string; replacePattern: string | RegExp }\n): [boolean, string] {\n const replacePatternOccurrence = content.search(replacePattern);\n if (replacePatternOccurrence !== -1) {\n return [true, content.replace(replacePattern, replaceContent)];\n }\n return [false, content];\n}\n\n/**\n * Inserts content just before first occurrence of provided pattern.\n * @returns [`true`, modifiedContent: string] if insertion is successful, [`false`, originalContent] otherwise.\n */\nexport function insert(\n content: string,\n { insertContent, insertPattern }: { insertContent: string; insertPattern: RegExp | string },\n insertBeforeLastOccurrence: boolean = false\n): [boolean, string] {\n if (insertBeforeLastOccurrence) {\n return insertBeforeLastOccurrenceFun(content, { insertContent, insertPattern });\n }\n const insertPatternOccurrence = content.search(insertPattern);\n if (insertPatternOccurrence !== -1) {\n return [\n true,\n `${content.slice(0, insertPatternOccurrence)}${insertContent}${content.slice(\n insertPatternOccurrence\n )}`,\n ];\n }\n return [false, content];\n}\n\n/**\n * Finds last occurrence of provided pattern and inserts content just before it.\n *@returns [`true`, modifiedContent: string] if insertion is successful, [`false`, originalContent] otherwise.\n */\nfunction insertBeforeLastOccurrenceFun(\n content: string,\n { insertContent, insertPattern }: { insertContent: string; insertPattern: RegExp | string }\n): [boolean, string] {\n const results = [...content.matchAll(new RegExp(insertPattern, 'gm'))];\n const patternLastOccurrence = results[results.length - 1];\n if (!patternLastOccurrence) {\n return [false, content];\n }\n return [\n true,\n `${content.slice(0, patternLastOccurrence.index)}${insertContent}${content.slice(\n patternLastOccurrence.index\n )}`,\n ];\n}\n"]}

View File

@ -0,0 +1,20 @@
import type { JsonShape, OptionalPromise, NonPrimitiveAndNonArrayKeys, DeepRequired, IsNever } from './types';
/**
* This class is responsible for validating configuration object in a form of json and produce validated object based on validating `rules` added via `addRule` method.
*/
export default class FromJsonValidator<From extends JsonShape<To>, To extends object> {
/**
* Records:
* - keys are stringified array paths to the properties
* - values are functions accepting
*/
private rules;
/**
* Add rule that determined what property is copied from JSON object into actual validated object.
* @param name an array describing property path (just like in lodash.get function)
* @param validatingFunction optional parameter that is responsible for actual type conversion and semantic checking (e.g. check is given string is actually a path or a valid color). Not providing it results in copying over value without any semantic checking.
*/
addRule<TK1 extends NonPrimitiveAndNonArrayKeys<DeepRequired<To>>, TK2 extends NonPrimitiveAndNonArrayKeys<DeepRequired<To>[TK1]>, TK3 extends NonPrimitiveAndNonArrayKeys<DeepRequired<To>[TK1][TK2]>>(name: [TK1] | [TK1, TK2] | [TK1, TK2, TK3], validatingFunction?: (value: IsNever<TK3, IsNever<TK2, DeepRequired<From>[TK1], DeepRequired<From>[TK1][TK2]>, DeepRequired<From>[TK1][TK2][TK3]>, config: To) => OptionalPromise<IsNever<TK3, IsNever<TK2, DeepRequired<To>[TK1], DeepRequired<To>[TK1][TK2]>, DeepRequired<To>[TK1][TK2][TK3]>>): this;
validate(jsonConfig: From): Promise<To>;
private formatErrors;
}

View File

@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash");
/**
* This class is responsible for validating configuration object in a form of json and produce validated object based on validating `rules` added via `addRule` method.
*/
class FromJsonValidator {
constructor() {
/**
* Records:
* - keys are stringified array paths to the properties
* - values are functions accepting
*/
this.rules = [];
}
/**
* Add rule that determined what property is copied from JSON object into actual validated object.
* @param name an array describing property path (just like in lodash.get function)
* @param validatingFunction optional parameter that is responsible for actual type conversion and semantic checking (e.g. check is given string is actually a path or a valid color). Not providing it results in copying over value without any semantic checking.
*/
addRule(name, validatingFunction = value => value) {
const idx = this.rules.findIndex(([propertyPath]) => propertyPath.join('.') === name.join('.'));
if (idx === -1) {
// @ts-ignore
this.rules.push([name, validatingFunction]);
}
else {
// @ts-ignore
this.rules[idx] = [name, validatingFunction];
}
return this;
}
async validate(jsonConfig) {
// @ts-ignore
const config = {};
const errors = [];
for (const [propertyPath, validatingFunc] of this.rules) {
try {
const rawValue = lodash_1.get(jsonConfig, propertyPath);
if (rawValue === undefined) {
// No value for this propertyPath
continue;
}
const value = await validatingFunc(rawValue, config);
lodash_1.set(config, propertyPath, value);
}
catch (e) {
errors.push([propertyPath, e]);
}
}
if (errors.length > 0) {
throw new Error(`Validating error:\n${this.formatErrors(errors)}`);
}
return config;
}
formatErrors(errors) {
return errors
.map(([propertyPath, error]) => {
return ` '${propertyPath.map(el => String(el)).join('.')}': ${error.message}`;
})
.join('\n');
}
}
exports.default = FromJsonValidator;
//# sourceMappingURL=FromJsonValidator.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"FromJsonValidator.js","sourceRoot":"","sources":["../../src/validators/FromJsonValidator.ts"],"names":[],"mappings":";;AAAA,mCAAkC;AAUlC;;GAEG;AACH,MAAqB,iBAAiB;IAAtC;QACE;;;;WAIG;QACK,UAAK,GAAgF,EAAE,CAAC;IAyElG,CAAC;IAvEC;;;;OAIG;IACH,OAAO,CAKL,IAA0C,EAC1C,qBAgBI,KAAK,CAAC,EAAE,CAAC,KAAK;QAElB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAChG,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE;YACd,aAAa;YACb,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC;SAC7C;aAAM;YACL,aAAa;YACb,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;SAC9C;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,UAAgB;QAC7B,aAAa;QACb,MAAM,MAAM,GAAO,EAAE,CAAC;QACtB,MAAM,MAAM,GAA6B,EAAE,CAAC;QAC5C,KAAK,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE;YACvD,IAAI;gBACF,MAAM,QAAQ,GAAG,YAAG,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;gBAC/C,IAAI,QAAQ,KAAK,SAAS,EAAE;oBAC1B,iCAAiC;oBACjC,SAAS;iBACV;gBACD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBACrD,YAAG,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;aAClC;YAAC,OAAO,CAAC,EAAE;gBACV,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC;aAChC;SACF;QAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE;YACrB,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;SACpE;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,YAAY,CAAC,MAAgC;QACnD,OAAO,MAAM;aACV,GAAG,CAAC,CAAC,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,EAAE;YAC7B,OAAO,MAAM,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACjF,CAAC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;CACF;AA/ED,oCA+EC","sourcesContent":["import { get, set } from 'lodash';\n\nimport type {\n JsonShape,\n OptionalPromise,\n NonPrimitiveAndNonArrayKeys,\n DeepRequired,\n IsNever,\n} from './types';\n\n/**\n * This class is responsible for validating configuration object in a form of json and produce validated object based on validating `rules` added via `addRule` method.\n */\nexport default class FromJsonValidator<From extends JsonShape<To>, To extends object> {\n /**\n * Records:\n * - keys are stringified array paths to the properties\n * - values are functions accepting\n */\n private rules: [PropertyKey[], (value: unknown, config: To) => OptionalPromise<unknown>][] = [];\n\n /**\n * Add rule that determined what property is copied from JSON object into actual validated object.\n * @param name an array describing property path (just like in lodash.get function)\n * @param validatingFunction optional parameter that is responsible for actual type conversion and semantic checking (e.g. check is given string is actually a path or a valid color). Not providing it results in copying over value without any semantic checking.\n */\n addRule<\n TK1 extends NonPrimitiveAndNonArrayKeys<DeepRequired<To>>,\n TK2 extends NonPrimitiveAndNonArrayKeys<DeepRequired<To>[TK1]>,\n TK3 extends NonPrimitiveAndNonArrayKeys<DeepRequired<To>[TK1][TK2]>\n >(\n name: [TK1] | [TK1, TK2] | [TK1, TK2, TK3],\n validatingFunction: (\n value: IsNever<\n TK3,\n // @ts-ignore\n IsNever<TK2, DeepRequired<From>[TK1], DeepRequired<From>[TK1][TK2]>,\n // @ts-ignore\n DeepRequired<From>[TK1][TK2][TK3]\n >,\n config: To\n ) => OptionalPromise<\n IsNever<\n TK3,\n IsNever<TK2, DeepRequired<To>[TK1], DeepRequired<To>[TK1][TK2]>,\n DeepRequired<To>[TK1][TK2][TK3]\n >\n // @ts-ignore\n > = value => value\n ): this {\n const idx = this.rules.findIndex(([propertyPath]) => propertyPath.join('.') === name.join('.'));\n if (idx === -1) {\n // @ts-ignore\n this.rules.push([name, validatingFunction]);\n } else {\n // @ts-ignore\n this.rules[idx] = [name, validatingFunction];\n }\n return this;\n }\n\n async validate(jsonConfig: From): Promise<To> {\n // @ts-ignore\n const config: To = {};\n const errors: [PropertyKey[], Error][] = [];\n for (const [propertyPath, validatingFunc] of this.rules) {\n try {\n const rawValue = get(jsonConfig, propertyPath);\n if (rawValue === undefined) {\n // No value for this propertyPath\n continue;\n }\n const value = await validatingFunc(rawValue, config);\n set(config, propertyPath, value);\n } catch (e) {\n errors.push([propertyPath, e]);\n }\n }\n\n if (errors.length > 0) {\n throw new Error(`Validating error:\\n${this.formatErrors(errors)}`);\n }\n return config;\n }\n\n private formatErrors(errors: [PropertyKey[], Error][]): string {\n return errors\n .map(([propertyPath, error]) => {\n return ` '${propertyPath.map(el => String(el)).join('.')}': ${error.message}`;\n })\n .join('\\n');\n }\n}\n"]}

View File

@ -0,0 +1,51 @@
import { IosSplashScreenConfigJSON, IosSplashScreenConfig, AndroidSplashScreenConfigJSON, AndroidSplashScreenConfig } from '../SplashScreenConfig';
export { validateEnumValue } from './utils';
/**
* Validates given iOS configuration and converts it to it's semantically ready equivalent.
* Ensures following generic config semantic requirements are met:
* - `config.backgroundColor` is a valid css-formatted color,
* - `config.imagePath` is pointing to a valid .png file,
* - `config.imageResizeMode`
* - is provided only if `config.imagePath` is provided as well
* - and it's a recognizable value (one of `SplashScreenResizeMode`)
* - and its value isn't `SplashScreenImageResizeMode.NATIVE`
*
* - `config.statusBar.hidden` might exists
* - `config.statusBar.style` is a recognizable value (one of `SplashScreenStatusBarStyle`),
*
* - `config.darkMode.backgroundColor` is a valid css-formatted color,
* - `config.darkMode.imagePath`
* - is provided only if `config.darkMode.backgroundColor` is provided as well
* - and it's pointing to a valid .png file,
*/
export declare function validateIosConfig(config: IosSplashScreenConfigJSON): Promise<IosSplashScreenConfig>;
/**
* Validates given Android configuration and converts it to it's semantically ready equivalent.
*
* Ensures following generic config semantic requirements are met:
* - `config.backgroundColor` is a valid css-formatted color,
* - `config.imagePath` is pointing to a valid .png file,
* - `config.imageResizeMode`
* - is provided only if `config.imagePath` is provided as well
* - and it's a recognizable value (one of `SplashScreenResizeMode`)
*
* - `config.statusBar.hidden` might exists,
* - `config.statusBar.style` is a recognizable value (one of `SplashScreenStatusBarStyle`),
* - `config.statusBar.translucent` might exist,
* - `config.statusBar.backgroundColor` is a valid css-formatted color,
*
* - `config.darkMode.backgroundColor` is a valid css-formatted color,
* - `config.darkMode.imagePath`
* - is provided only if `config.darkMode.backgroundColor` is provided as well
* - and it's pointing to a valid .png file,
*
* - `config.darkMode.statusBar.style`
* - is provided only if `config.darkMode.backgroundColor` is provided as well
* - and `config.statusBar.style` is provided as well
* - and it's a recognizable value (one of `SplashScreenStatusBarStyle`),
* - `config.darkMode.statusBar.backgroundColor`
* - is provided only if `config.darkMode.backgroundColor` is provided as well
* - and `config.statusBar.backgroundColor` is provided as well
* - and it's a valid css-formatted color,
*/
export declare function validateAndroidConfig(config: AndroidSplashScreenConfigJSON): Promise<AndroidSplashScreenConfig>;

View File

@ -0,0 +1,116 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateAndroidConfig = exports.validateIosConfig = exports.validateEnumValue = void 0;
const constants_1 = require("../constants");
const FromJsonValidator_1 = __importDefault(require("./FromJsonValidator"));
const utils_1 = require("./utils");
var utils_2 = require("./utils");
Object.defineProperty(exports, "validateEnumValue", { enumerable: true, get: function () { return utils_2.validateEnumValue; } });
/**
* Validates given iOS configuration and converts it to it's semantically ready equivalent.
* Ensures following generic config semantic requirements are met:
* - `config.backgroundColor` is a valid css-formatted color,
* - `config.imagePath` is pointing to a valid .png file,
* - `config.imageResizeMode`
* - is provided only if `config.imagePath` is provided as well
* - and it's a recognizable value (one of `SplashScreenResizeMode`)
* - and its value isn't `SplashScreenImageResizeMode.NATIVE`
*
* - `config.statusBar.hidden` might exists
* - `config.statusBar.style` is a recognizable value (one of `SplashScreenStatusBarStyle`),
*
* - `config.darkMode.backgroundColor` is a valid css-formatted color,
* - `config.darkMode.imagePath`
* - is provided only if `config.darkMode.backgroundColor` is provided as well
* - and it's pointing to a valid .png file,
*/
async function validateIosConfig(config) {
const validator = new FromJsonValidator_1.default()
.addRule(['backgroundColor'], utils_1.validateColor)
.addRule(['image'], utils_1.validateFileIsPng)
.addRule(['imageResizeMode'], (value, config) => {
utils_1.ensurePropertyExists(config, ['image']);
const result = utils_1.generateValidateEnumValue(constants_1.SplashScreenImageResizeMode)(value);
if (result === constants_1.SplashScreenImageResizeMode.NATIVE) {
const { NATIVE, ...availableValues } = constants_1.SplashScreenImageResizeMode;
throw new Error(`Invalid value '${value}'. This value is not supported on iOS platform. Available values on iOS platform are ${Object.values(availableValues)
.map(v => `"${v}"`)
.join(' | ')}.`);
}
return result;
})
.addRule(['statusBar', 'hidden'])
.addRule(['statusBar', 'style'], utils_1.generateValidateEnumValue(constants_1.SplashScreenStatusBarStyle))
.addRule(['darkMode', 'backgroundColor'], utils_1.validateColor)
.addRule(['darkMode', 'image'], (value, config) => {
utils_1.ensurePropertyExists(config, ['darkMode', 'backgroundColor']);
return utils_1.validateFileIsPng(value);
});
const validatedConfig = await validator.validate(config);
return validatedConfig;
}
exports.validateIosConfig = validateIosConfig;
/**
* Validates given Android configuration and converts it to it's semantically ready equivalent.
*
* Ensures following generic config semantic requirements are met:
* - `config.backgroundColor` is a valid css-formatted color,
* - `config.imagePath` is pointing to a valid .png file,
* - `config.imageResizeMode`
* - is provided only if `config.imagePath` is provided as well
* - and it's a recognizable value (one of `SplashScreenResizeMode`)
*
* - `config.statusBar.hidden` might exists,
* - `config.statusBar.style` is a recognizable value (one of `SplashScreenStatusBarStyle`),
* - `config.statusBar.translucent` might exist,
* - `config.statusBar.backgroundColor` is a valid css-formatted color,
*
* - `config.darkMode.backgroundColor` is a valid css-formatted color,
* - `config.darkMode.imagePath`
* - is provided only if `config.darkMode.backgroundColor` is provided as well
* - and it's pointing to a valid .png file,
*
* - `config.darkMode.statusBar.style`
* - is provided only if `config.darkMode.backgroundColor` is provided as well
* - and `config.statusBar.style` is provided as well
* - and it's a recognizable value (one of `SplashScreenStatusBarStyle`),
* - `config.darkMode.statusBar.backgroundColor`
* - is provided only if `config.darkMode.backgroundColor` is provided as well
* - and `config.statusBar.backgroundColor` is provided as well
* - and it's a valid css-formatted color,
*/
async function validateAndroidConfig(config) {
const validator = new FromJsonValidator_1.default()
.addRule(['backgroundColor'], utils_1.validateColor)
.addRule(['image'], utils_1.validateFileIsPng)
.addRule(['imageResizeMode'], (value, config) => {
utils_1.ensurePropertyExists(config, ['image']);
return utils_1.generateValidateEnumValue(constants_1.SplashScreenImageResizeMode)(value);
})
.addRule(['statusBar', 'hidden'])
.addRule(['statusBar', 'style'], utils_1.generateValidateEnumValue(constants_1.SplashScreenStatusBarStyle))
.addRule(['statusBar', 'translucent'])
.addRule(['statusBar', 'backgroundColor'], utils_1.validateColor)
.addRule(['darkMode', 'backgroundColor'], utils_1.validateColor)
.addRule(['darkMode', 'image'], (value, config) => {
utils_1.ensurePropertyExists(config, ['darkMode', 'backgroundColor']);
return utils_1.validateFileIsPng(value);
})
.addRule(['darkMode', 'statusBar', 'style'], (value, config) => {
utils_1.ensurePropertyExists(config, ['darkMode', 'backgroundColor']);
utils_1.ensurePropertyExists(config, ['statusBar', 'style']);
return utils_1.generateValidateEnumValue(constants_1.SplashScreenStatusBarStyle)(value);
})
.addRule(['darkMode', 'statusBar', 'backgroundColor'], (value, config) => {
utils_1.ensurePropertyExists(config, ['darkMode', 'backgroundColor']);
utils_1.ensurePropertyExists(config, ['statusBar', 'backgroundColor']);
return utils_1.validateColor(value);
});
const validatedConfig = await validator.validate(config);
return validatedConfig;
}
exports.validateAndroidConfig = validateAndroidConfig;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,25 @@
/**
* Convert the given type into JSON-friendly equivalent that have the same structure,
* but for any key the value can either be string | number | boolean.
*/
export declare type JsonShape<T> = IsNever<T, never, SNB | (T extends any[] ? JsonShape<T[number]>[] : T extends object ? {
[P in keyof T]+?: JsonShape<T[P]>;
} : never)>;
/**
* Like Required but recursive for object properties
*/
export declare type DeepRequired<T> = T extends Primitive ? NonNullable<T> : T extends object ? {
[K in keyof T]-?: DeepRequired<T[K]>;
} : NonNullable<T>;
/**
* The very same as keyof, but does not count in keys of primitives and arrays (e.g. will not return String.toUpperCase)
*/
export declare type NonPrimitiveAndNonArrayKeys<T> = T extends SNB | any[] ? never : keyof T;
/**
* @see https://github.com/microsoft/TypeScript/issues/23182
*/
export declare type IsNever<T, Positive, Negative> = [T] extends [never] ? Positive : Negative;
export declare type OptionalPromise<T> = Promise<T> | T;
declare type SNB = string | number | boolean;
declare type Primitive = SNB | bigint | symbol | null | undefined;
export {};

View File

@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const conditional_type_checks_1 = require("conditional-type-checks");
conditional_type_checks_1.assert(true);
//# sourceMappingURL=types.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/validators/types.ts"],"names":[],"mappings":";;AAAA,qEAA0D;AA+D1D,gCAAM,CAkBJ,IAAI,CAAC,CAAC","sourcesContent":["import { assert, IsExact } from 'conditional-type-checks';\n\n/**\n * Convert the given type into JSON-friendly equivalent that have the same structure,\n * but for any key the value can either be string | number | boolean.\n */\nexport type JsonShape<T> = IsNever<\n T,\n never,\n | SNB\n | (T extends any[]\n ? JsonShape<T[number]>[]\n : T extends object\n ? { [P in keyof T]+?: JsonShape<T[P]> }\n : never)\n>;\n\n/**\n * Like Required but recursive for object properties\n */\nexport type DeepRequired<T> = T extends Primitive\n ? NonNullable<T>\n : T extends object\n ? { [K in keyof T]-?: DeepRequired<T[K]> }\n : NonNullable<T>;\n\n/**\n * The very same as keyof, but does not count in keys of primitives and arrays (e.g. will not return String.toUpperCase)\n */\nexport type NonPrimitiveAndNonArrayKeys<T> = T extends SNB | any[] ? never : keyof T;\n\n/**\n * @see https://github.com/microsoft/TypeScript/issues/23182\n */\nexport type IsNever<T, Positive, Negative> = [T] extends [never] ? Positive : Negative;\n\nexport type OptionalPromise<T> = Promise<T> | T;\ntype SNB = string | number | boolean;\ntype Primitive = SNB | bigint | symbol | null | undefined;\n\n// -------------------- //\n// type testing section //\n// -------------------- //\n\ntype _TestType = {\n n: number;\n s?: string;\n b?: boolean;\n a_n: number[];\n t_s: [string, string];\n o: {\n n?: number;\n };\n\n o_n?: {\n o: {\n s: string;\n };\n };\n};\n\ntype _TestTypeJSON = JsonShape<_TestType>;\n\nassert<\n IsExact<\n _TestTypeJSON,\n | SNB\n | Partial<{\n n: SNB;\n s: SNB;\n b: SNB;\n a_n: SNB | SNB[];\n t_s: SNB | SNB[];\n o: SNB | Partial<{ n: SNB }>;\n o_n:\n | SNB\n | Partial<{\n o: SNB | Partial<{ s?: SNB }>;\n }>;\n }>\n >\n>(true);\n"]}

View File

@ -0,0 +1,23 @@
import { Color } from 'color-string';
import { NonPrimitiveAndNonArrayKeys, DeepRequired } from './types';
/**
* @param value Value to be checked.
* @param availableValues Object storing all available options as values.
*/
export declare function validateEnumValue<T extends Record<string, string>>(value: string, availableValues: T): T[keyof T];
/**
* @param filePath Relative or absolute path to a file.
* @returns Absolute path to the valid image file.
*/
export declare function validateFileIsPng(filePath: string): Promise<string>;
/**
* @param filePath Relative or absolute path to a file.
* @returns Absolute path to the resolved file.
*/
export declare function validateFileExists(filePath: string): Promise<string>;
/**
* @param value Value to be checked.
*/
export declare function validateColor(value: string): Color;
export declare function generateValidateEnumValue<T extends Record<string, string>>(availableValues: T): (value: string) => T[keyof T];
export declare function ensurePropertyExists<T extends object, TK1 extends NonPrimitiveAndNonArrayKeys<DeepRequired<T>>, TK2 extends NonPrimitiveAndNonArrayKeys<DeepRequired<T>[TK1]>, TK3 extends NonPrimitiveAndNonArrayKeys<DeepRequired<T>[TK1][TK2]>>(object: T, propertyPath: [TK1] | [TK1, TK2] | [TK1, TK2, TK3]): void;

View File

@ -0,0 +1,91 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensurePropertyExists = exports.generateValidateEnumValue = exports.validateColor = exports.validateFileExists = exports.validateFileIsPng = exports.validateEnumValue = void 0;
const color_string_1 = __importDefault(require("color-string"));
const fs = __importStar(require("fs-extra"));
const lodash_1 = require("lodash");
const path_1 = __importDefault(require("path"));
/**
* @param value Value to be checked.
* @param availableValues Object storing all available options as values.
*/
function validateEnumValue(value, availableValues) {
if (!Object.values(availableValues).includes(value)) {
throw new Error(`Invalid value '${value}'. Available values are ${Object.values(availableValues)
.map(v => `"${v}"`)
.join(' | ')}.`);
}
return value;
}
exports.validateEnumValue = validateEnumValue;
/**
* @param filePath Relative or absolute path to a file.
* @returns Absolute path to the valid image file.
*/
async function validateFileIsPng(filePath) {
const resolvedPath = await validateFileExists(filePath);
// check if resolvedPath is a readable .png file
if (path_1.default.extname(resolvedPath) !== '.png') {
throw new Error(`Invalid path '${filePath}' - file is not a .png file. Provide a path to a file with .png extension.`);
}
return resolvedPath;
}
exports.validateFileIsPng = validateFileIsPng;
/**
* @param filePath Relative or absolute path to a file.
* @returns Absolute path to the resolved file.
*/
async function validateFileExists(filePath) {
const resolvedPath = path_1.default.resolve(filePath);
if (!(await fs.pathExists(resolvedPath))) {
throw new Error(`Invalid path '${filePath}' - file does not exist. Provide a path to an existing file.`);
}
return resolvedPath;
}
exports.validateFileExists = validateFileExists;
/**
* @param value Value to be checked.
*/
function validateColor(value) {
var _a;
const result = (_a = color_string_1.default.get(value)) === null || _a === void 0 ? void 0 : _a.value;
if (!result) {
throw new Error(`Invalid value '${value}' - value is not a color string. Provide a valid color string.`);
}
return result;
}
exports.validateColor = validateColor;
function generateValidateEnumValue(availableValues) {
return (value) => validateEnumValue(value, availableValues);
}
exports.generateValidateEnumValue = generateValidateEnumValue;
function ensurePropertyExists(object, propertyPath) {
const value = lodash_1.get(object, propertyPath, undefined);
if (value === undefined) {
throw new Error(`Missing a required valid value for '${propertyPath.join('.')}'. Provide a valid value for it to enable this property.`);
}
}
exports.ensurePropertyExists = ensurePropertyExists;
//# sourceMappingURL=utils.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/validators/utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gEAAkD;AAClD,6CAA+B;AAC/B,mCAA6B;AAC7B,gDAAwB;AAIxB;;;GAGG;AACH,SAAgB,iBAAiB,CAC/B,KAAa,EACb,eAAkB;IAElB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAS,eAAe,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAC3D,MAAM,IAAI,KAAK,CACb,kBAAkB,KAAK,2BAA2B,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC;aAC7E,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;aAClB,IAAI,CAAC,KAAK,CAAC,GAAG,CAClB,CAAC;KACH;IACD,OAAO,KAAmB,CAAC;AAC7B,CAAC;AAZD,8CAYC;AAED;;;GAGG;AACI,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IACtD,MAAM,YAAY,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAExD,gDAAgD;IAChD,IAAI,cAAI,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,MAAM,EAAE;QACzC,MAAM,IAAI,KAAK,CACb,iBAAiB,QAAQ,4EAA4E,CACtG,CAAC;KACH;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAVD,8CAUC;AAED;;;GAGG;AACI,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACvD,MAAM,YAAY,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,EAAE;QACxC,MAAM,IAAI,KAAK,CACb,iBAAiB,QAAQ,8DAA8D,CACxF,CAAC;KACH;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AARD,gDAQC;AAED;;GAEG;AACH,SAAgB,aAAa,CAAC,KAAa;;IACzC,MAAM,MAAM,SAAG,sBAAW,CAAC,GAAG,CAAC,KAAK,CAAC,0CAAE,KAAK,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE;QACX,MAAM,IAAI,KAAK,CACb,kBAAkB,KAAK,gEAAgE,CACxF,CAAC;KACH;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AARD,sCAQC;AAED,SAAgB,yBAAyB,CAAmC,eAAkB;IAC5F,OAAO,CAAC,KAAa,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;AACtE,CAAC;AAFD,8DAEC;AAED,SAAgB,oBAAoB,CAKlC,MAAS,EAAE,YAAkD;IAC7D,MAAM,KAAK,GAAG,YAAG,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;IACnD,IAAI,KAAK,KAAK,SAAS,EAAE;QACvB,MAAM,IAAI,KAAK,CACb,uCAAuC,YAAY,CAAC,IAAI,CACtD,GAAG,CACJ,0DAA0D,CAC5D,CAAC;KACH;AACH,CAAC;AAdD,oDAcC","sourcesContent":["import colorString, { Color } from 'color-string';\nimport * as fs from 'fs-extra';\nimport { get } from 'lodash';\nimport path from 'path';\n\nimport { NonPrimitiveAndNonArrayKeys, DeepRequired } from './types';\n\n/**\n * @param value Value to be checked.\n * @param availableValues Object storing all available options as values.\n */\nexport function validateEnumValue<T extends Record<string, string>>(\n value: string,\n availableValues: T\n): T[keyof T] {\n if (!Object.values<string>(availableValues).includes(value)) {\n throw new Error(\n `Invalid value '${value}'. Available values are ${Object.values(availableValues)\n .map(v => `\"${v}\"`)\n .join(' | ')}.`\n );\n }\n return value as T[keyof T];\n}\n\n/**\n * @param filePath Relative or absolute path to a file.\n * @returns Absolute path to the valid image file.\n */\nexport async function validateFileIsPng(filePath: string): Promise<string> {\n const resolvedPath = await validateFileExists(filePath);\n\n // check if resolvedPath is a readable .png file\n if (path.extname(resolvedPath) !== '.png') {\n throw new Error(\n `Invalid path '${filePath}' - file is not a .png file. Provide a path to a file with .png extension.`\n );\n }\n return resolvedPath;\n}\n\n/**\n * @param filePath Relative or absolute path to a file.\n * @returns Absolute path to the resolved file.\n */\nexport async function validateFileExists(filePath: string): Promise<string> {\n const resolvedPath = path.resolve(filePath);\n if (!(await fs.pathExists(resolvedPath))) {\n throw new Error(\n `Invalid path '${filePath}' - file does not exist. Provide a path to an existing file.`\n );\n }\n return resolvedPath;\n}\n\n/**\n * @param value Value to be checked.\n */\nexport function validateColor(value: string): Color {\n const result = colorString.get(value)?.value;\n if (!result) {\n throw new Error(\n `Invalid value '${value}' - value is not a color string. Provide a valid color string.`\n );\n }\n return result;\n}\n\nexport function generateValidateEnumValue<T extends Record<string, string>>(availableValues: T) {\n return (value: string) => validateEnumValue(value, availableValues);\n}\n\nexport function ensurePropertyExists<\n T extends object,\n TK1 extends NonPrimitiveAndNonArrayKeys<DeepRequired<T>>,\n TK2 extends NonPrimitiveAndNonArrayKeys<DeepRequired<T>[TK1]>,\n TK3 extends NonPrimitiveAndNonArrayKeys<DeepRequired<T>[TK1][TK2]>\n>(object: T, propertyPath: [TK1] | [TK1, TK2] | [TK1, TK2, TK3]): void {\n const value = get(object, propertyPath, undefined);\n if (value === undefined) {\n throw new Error(\n `Missing a required valid value for '${propertyPath.join(\n '.'\n )}'. Provide a valid value for it to enable this property.`\n );\n }\n}\n"]}

View File

@ -0,0 +1,10 @@
import { XcodeProject, UUID } from 'xcode';
/**
* @param filePath
* @param param1.target PBXNativeTarget reference
* @param param1.group PBXGroup reference
*/
export declare function addStoryboardFileToProject(pbxProject: XcodeProject, filePath: string, { target, group }: {
target: UUID;
group: UUID;
}): void;

View File

@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addStoryboardFileToProject = void 0;
/**
* @param filePath
* @param param1.target PBXNativeTarget reference
* @param param1.group PBXGroup reference
*/
function addStoryboardFileToProject(pbxProject, filePath, { target, group }) {
const file = pbxProject.addFile(filePath, group, {
lastKnownFileType: 'file.storyboard',
defaultEncoding: 4,
target,
});
if (!file) {
throw new Error('File already exists in the project');
}
delete pbxProject.pbxFileReferenceSection()[file.fileRef].explicitFileType;
delete pbxProject.pbxFileReferenceSection()[file.fileRef].includeInIndex;
file.uuid = pbxProject.generateUuid();
file.target = target;
pbxProject.addToPbxBuildFileSection(file);
pbxProject.addToPbxResourcesBuildPhase(file);
}
exports.addStoryboardFileToProject = addStoryboardFileToProject;
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/xcode/index.ts"],"names":[],"mappings":";;;AAEA;;;;GAIG;AACH,SAAgB,0BAA0B,CACxC,UAAwB,EACxB,QAAgB,EAChB,EAAE,MAAM,EAAE,KAAK,EAAiC;IAEhD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE;QAC/C,iBAAiB,EAAE,iBAAiB;QACpC,eAAe,EAAE,CAAC;QAClB,MAAM;KACP,CAAC,CAAC;IACH,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;KACvD;IACD,OAAO,UAAU,CAAC,uBAAuB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC;IAC3E,OAAO,UAAU,CAAC,uBAAuB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC;IAEzE,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,YAAY,EAAE,CAAC;IACtC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAErB,UAAU,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC1C,UAAU,CAAC,2BAA2B,CAAC,IAAI,CAAC,CAAC;AAC/C,CAAC;AArBD,gEAqBC","sourcesContent":["import { XcodeProject, UUID } from 'xcode';\n\n/**\n * @param filePath\n * @param param1.target PBXNativeTarget reference\n * @param param1.group PBXGroup reference\n */\nexport function addStoryboardFileToProject(\n pbxProject: XcodeProject,\n filePath: string,\n { target, group }: { target: UUID; group: UUID }\n) {\n const file = pbxProject.addFile(filePath, group, {\n lastKnownFileType: 'file.storyboard',\n defaultEncoding: 4,\n target,\n });\n if (!file) {\n throw new Error('File already exists in the project');\n }\n delete pbxProject.pbxFileReferenceSection()[file.fileRef].explicitFileType;\n delete pbxProject.pbxFileReferenceSection()[file.fileRef].includeInIndex;\n\n file.uuid = pbxProject.generateUuid();\n file.target = target;\n\n pbxProject.addToPbxBuildFileSection(file);\n pbxProject.addToPbxResourcesBuildPhase(file);\n}\n"]}

View File

@ -0,0 +1,54 @@
import { Element } from 'xml-js';
declare type ExplicitNewValue<T> = {
newValue: T;
};
declare type WithExplicitNewValue<T> = T | ExplicitNewValue<T>;
declare type ExpectedElementAttributes = Record<string, WithExplicitNewValue<string | number | undefined>>;
declare type WithExplicitIndex<T> = T & {
idx?: number;
};
declare type WithDeletionFlag<T> = T & {
deletionFlag?: boolean;
};
declare type ExpectedElements = WithExplicitNewValue<WithExplicitIndex<WithDeletionFlag<ExpectedElement>>[]>;
export declare type ExpectedElementType = {
name: string;
attributes?: ExpectedElementAttributes;
elements?: ExpectedElements;
};
export declare type ExpectedElementsType = {
elements: ExpectedElements;
};
export declare type ExpectedCommentType = {
comment: string;
};
export declare type ExpectedTextType = {
text: string | number | boolean;
};
export declare type ExpectedElement = ExpectedElementType | ExpectedElementsType | ExpectedCommentType | ExpectedTextType;
/**
* Assumption is that elements are `equal` semantically
*/
export declare function mergeXmlElements(current: Element, expected: ExpectedElement): Element;
/**
* @param filePath
* @param fallbackContent
*/
export declare function readXmlFile(filePath: string, fallbackContent?: Element | string): Promise<Element>;
export declare function writeXmlFile(filePath: string, xml: Element): Promise<void>;
/**
* Checks whether two xmlElements are equal in terms of their structure
*/
export declare function xmlElementsEqual(a: Element, b: Element, { disregardComments }?: {
disregardComments?: boolean;
}): boolean;
/**
* Check if given `element` has some meaningful data:
* - if so: write it to the file
* - if no: remove file completely
* Function assumes that the structure of the input `element` is correct (`element.elements[name = resources]`).
*/
export declare function writeXmlFileOrRemoveFileUponNoResources(filePath: string, element: Element, { disregardComments }?: {
disregardComments?: boolean;
}): Promise<void>;
export {};

View File

@ -0,0 +1,271 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeXmlFileOrRemoveFileUponNoResources = exports.xmlElementsEqual = exports.writeXmlFile = exports.readXmlFile = exports.mergeXmlElements = void 0;
const deep_equal_1 = __importDefault(require("deep-equal"));
const xml_js_1 = require("xml-js");
const file_utils_1 = require("../utils/file-utils");
function isElementType(el) {
return el.name !== undefined;
}
function isElementsType(el) {
return !el.name && Boolean(el.elements);
}
function isCommentType(el) {
return el.comment !== undefined;
}
function isTextType(el) {
return el.text !== undefined;
}
function isExplicitNewValue(el) {
// @ts-ignore
return typeof el === 'object' && el.hasOwnProperty('newValue');
}
function unboxExplicitNewValue(el) {
return isExplicitNewValue(el) ? el.newValue : el;
}
function compareElements(element, expectedElement) {
var _a;
if (isTextType(expectedElement)) {
return element.type === 'text';
}
if (isCommentType(expectedElement)) {
return element.type === 'comment' && ((_a = element.comment) === null || _a === void 0 ? void 0 : _a.trim()) === expectedElement.comment.trim();
}
if (isElementType(expectedElement) && element.type === 'element') {
if (expectedElement.name !== element.name) {
return false;
}
if (!element.attributes) {
return true;
}
for (const [key, value] of Object.entries(expectedElement.attributes || {})) {
if (isExplicitNewValue(value)) {
// this attribute has to be overridden
continue;
}
if (element.attributes[key] !== value) {
return false;
}
}
return true;
}
return false;
}
function sortWithExplicitIndex(elements) {
if (!elements) {
return;
}
const result = new Array(elements.length);
const elementsWithExplicitIndices = elements.filter(({ idx }) => idx !== undefined);
const elementsWithoutExplicitIndices = elements.filter(({ idx }) => idx === undefined);
elementsWithoutExplicitIndices.forEach((el, idx) => (result[idx] = el));
elementsWithExplicitIndices.forEach(({ idx, ...el }, i) => {
// @ts-ignore
result.splice(idx !== null && idx !== void 0 ? idx : i, 0, el);
});
return result;
}
function mergeXmlElementsLists(current, expected) {
if (isExplicitNewValue(expected) || !current) {
const sortedExpected = sortWithExplicitIndex(unboxExplicitNewValue(expected));
return sortedExpected === null || sortedExpected === void 0 ? void 0 : sortedExpected.map(convertToElement);
}
if (!expected) {
return current;
}
const result = [];
for (const currentElement of current) {
const idxInExpected = expected.findIndex(el => compareElements(currentElement, el));
if (idxInExpected !== -1) {
const { idx, ...element } = expected.splice(idxInExpected, 1)[0];
if (!element.deletionFlag) {
result.push({ idx, ...mergeXmlElements(currentElement, element) });
}
}
else {
result.push(currentElement);
}
}
result.push(...expected
.filter(({ deletionFlag }) => !deletionFlag)
.map(({ idx, ...el }) => ({ idx, ...convertToElement(el) })));
const sortedResult = sortWithExplicitIndex(result);
return sortedResult;
}
function convertToElement({ idx, ...expectedElement }) {
// @ts-ignore
if (expectedElement.deletionFlag) {
throw new Error('Cannot convert ExpectedElement to Element when deletionFlag is set');
}
if (isCommentType(expectedElement)) {
return {
...expectedElement,
type: 'comment',
};
}
if (isTextType(expectedElement)) {
return {
...expectedElement,
type: 'text',
};
}
if (isElementsType(expectedElement)) {
return {
elements: unboxExplicitNewValue(expectedElement.elements)
.filter(({ deletionFlag }) => !deletionFlag)
.map(convertToElement),
type: 'element',
};
}
const { elements, attributes, ...expectedRest } = expectedElement;
const result = {
...expectedRest,
type: 'element',
};
if (attributes) {
result.attributes = convertExpectedAttributes(attributes);
}
if (elements) {
result.elements = unboxExplicitNewValue(elements)
.filter(({ deletionFlag }) => !deletionFlag)
.map(convertToElement);
}
return result;
}
function convertExpectedAttributes(expectedAttributes) {
if (expectedAttributes) {
const result = Object.entries(expectedAttributes).reduce((acc, [key, value]) => ({
...acc,
[key]: unboxExplicitNewValue(value),
}), {});
return result;
}
return undefined;
}
function mergeAndConvertToElement({ attributes: currentAttributes, ...currentRest }, { attributes: expectedAttributes, ...expectedRest }) {
const result = {
...currentRest,
...expectedRest,
};
const attributes = (currentAttributes || expectedAttributes) && {
...currentAttributes,
...convertExpectedAttributes(expectedAttributes),
};
if (attributes) {
result.attributes = attributes;
}
return result;
}
/**
* Assumption is that elements are `equal` semantically
*/
function mergeXmlElements(current, expected) {
if (isCommentType(expected)) {
return {
...current,
...expected,
type: 'comment',
};
}
if (isTextType(expected)) {
return {
...current,
...expected,
type: 'text',
};
}
if (isElementsType(expected)) {
const result = {
...current,
type: 'element',
};
const elements = mergeXmlElementsLists(current.elements, expected.elements);
if (elements) {
result.elements = elements;
}
return result;
}
const { elements: currentElements, ...currentRest } = current;
const { elements: expectedElements, ...expectedRest } = expected;
const elements = mergeXmlElementsLists(current.elements, expected.elements);
const result = {
...mergeAndConvertToElement(currentRest, expectedRest),
type: 'element',
};
if (elements) {
result.elements = elements;
}
return result;
}
exports.mergeXmlElements = mergeXmlElements;
/**
* @param filePath
* @param fallbackContent
*/
async function readXmlFile(filePath, fallbackContent = `<?xml version="1.0" encoding="utf-8"?>`) {
const fileContent = await file_utils_1.readFileWithFallback(filePath, typeof fallbackContent === 'string' ? fallbackContent : 'fallbackToElement');
if (fileContent === 'fallbackToElement' && typeof fallbackContent === 'object') {
return fallbackContent;
}
const fileXml = xml_js_1.xml2js(fileContent);
return fileXml;
}
exports.readXmlFile = readXmlFile;
async function writeXmlFile(filePath, xml) {
const fileXml = xml_js_1.js2xml(xml, { indentAttributes: true, spaces: 2 });
const correctedFile = fileXml.replace(/(?<openTag><[^\s]+)\n *(?<firstAttribute> [^\s]+=".+?")\n *((?<secondAttribute> [^\s]+=".+?")\n *)?(?<closeTag>[/?]?>)/g, '$1$2$4$5');
await file_utils_1.createDirAndWriteFile(filePath, `${correctedFile}\n`);
}
exports.writeXmlFile = writeXmlFile;
/**
* Checks whether two xmlElements are equal in terms of their structure
*/
function xmlElementsEqual(a, b, { disregardComments = true } = {}) {
const filteredA = !disregardComments ? a : removeComments(a);
const filteredB = !disregardComments ? b : removeComments(b);
return deep_equal_1.default(filteredA, filteredB);
}
exports.xmlElementsEqual = xmlElementsEqual;
function removeComments(e) {
if (e.type === 'comment') {
return;
}
const result = Object.entries(e)
.map(([key, value]) => {
if (key === 'elements' && Array.isArray(value)) {
const filteredValue = value
.map(removeComments)
.filter((el) => el !== undefined);
return [key, filteredValue.length > 0 ? filteredValue : undefined];
}
return [key, value];
})
.filter(([_, value]) => value !== undefined)
.reduce((acc, [key, value]) => {
// @ts-ignore
acc[key] = value;
return acc;
}, {});
return result;
}
/**
* Check if given `element` has some meaningful data:
* - if so: write it to the file
* - if no: remove file completely
* Function assumes that the structure of the input `element` is correct (`element.elements[name = resources]`).
*/
async function writeXmlFileOrRemoveFileUponNoResources(filePath, element, { disregardComments } = {}) {
var _a, _b;
if (((_a = element.elements) === null || _a === void 0 ? void 0 : _a[0].name) === 'resources' &&
((_b = element.elements[0].elements) === null || _b === void 0 ? void 0 : _b.filter(({ type }) => disregardComments ? type !== 'comment' : true).length) === 0) {
await file_utils_1.removeFileIfExists(filePath);
}
else {
await writeXmlFile(filePath, element);
}
}
exports.writeXmlFileOrRemoveFileUponNoResources = writeXmlFileOrRemoveFileUponNoResources;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long