react-native-bootsplash 5.5.3 → 6.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -38
- package/app.plugin.js +2 -0
- package/dist/commonjs/addon/index.js +38 -30
- package/dist/commonjs/addon/index.js.map +1 -1
- package/dist/commonjs/generate.js +588 -203
- package/dist/commonjs/generate.js.map +1 -1
- package/dist/commonjs/index.js +19 -13
- package/dist/commonjs/index.js.map +1 -1
- package/dist/module/addon/index.js +38 -30
- package/dist/module/addon/index.js.map +1 -1
- package/dist/module/generate.js +577 -190
- package/dist/module/generate.js.map +1 -1
- package/dist/module/index.js +19 -13
- package/dist/module/index.js.map +1 -1
- package/dist/typescript/addon/index.d.ts +1 -1
- package/dist/typescript/addon/index.d.ts.map +1 -1
- package/dist/typescript/generate.d.ts +47 -41
- package/dist/typescript/generate.d.ts.map +1 -1
- package/dist/typescript/index.d.ts +1 -0
- package/dist/typescript/index.d.ts.map +1 -1
- package/package.json +16 -12
- package/react-native.config.js +16 -10
- package/src/generate.ts +792 -279
- package/src/index.ts +26 -16
package/src/generate.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import murmurhash from "@emotion/hash";
|
|
2
|
-
import {
|
|
2
|
+
import { getConfig as getExpoConfig } from "@expo/config";
|
|
3
|
+
import * as Expo from "@expo/config-plugins";
|
|
4
|
+
import { assignColorValue } from "@expo/config-plugins/build/android/Colors";
|
|
5
|
+
import { addImports } from "@expo/config-plugins/build/android/codeMod";
|
|
6
|
+
import { mergeContents } from "@expo/config-plugins/build/utils/generateCode";
|
|
3
7
|
import plist from "@expo/plist";
|
|
4
8
|
import { findProjectRoot } from "@react-native-community/cli-tools";
|
|
5
9
|
import {
|
|
@@ -7,7 +11,7 @@ import {
|
|
|
7
11
|
IOSProjectConfig,
|
|
8
12
|
} from "@react-native-community/cli-types";
|
|
9
13
|
import detectIndent from "detect-indent";
|
|
10
|
-
import fs from "fs";
|
|
14
|
+
import fs from "fs-extra";
|
|
11
15
|
import { parse as parseHtml } from "node-html-parser";
|
|
12
16
|
import path from "path";
|
|
13
17
|
import pc from "picocolors";
|
|
@@ -21,14 +25,39 @@ import formatXml, { XMLFormatterOptions } from "xml-formatter";
|
|
|
21
25
|
import { Manifest } from ".";
|
|
22
26
|
|
|
23
27
|
const workingPath = process.env.INIT_CWD ?? process.env.PWD ?? process.cwd();
|
|
28
|
+
const projectRoot = findProjectRoot(workingPath);
|
|
29
|
+
|
|
30
|
+
export type Platforms = ("android" | "ios" | "web")[];
|
|
31
|
+
|
|
32
|
+
export type RGBColor = {
|
|
33
|
+
R: string;
|
|
34
|
+
G: string;
|
|
35
|
+
B: string;
|
|
36
|
+
};
|
|
24
37
|
|
|
25
38
|
export type Color = {
|
|
26
39
|
hex: string;
|
|
27
|
-
rgb:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
rgb: RGBColor;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const log = {
|
|
44
|
+
error: (text: string) => {
|
|
45
|
+
console.log(pc.red(`❌ ${text}`));
|
|
46
|
+
},
|
|
47
|
+
title: (emoji: string, text: string) => {
|
|
48
|
+
console.log(`\n${emoji} ${pc.underline(pc.bold(text))}`);
|
|
49
|
+
},
|
|
50
|
+
warn: (text: string) => {
|
|
51
|
+
console.log(pc.yellow(`⚠️ ${text}`));
|
|
52
|
+
},
|
|
53
|
+
write: (filePath: string, dimensions?: { width: number; height: number }) => {
|
|
54
|
+
console.log(
|
|
55
|
+
` ${path.relative(workingPath, filePath)}` +
|
|
56
|
+
(dimensions != null
|
|
57
|
+
? ` (${dimensions.width}x${dimensions.height})`
|
|
58
|
+
: ""),
|
|
59
|
+
);
|
|
60
|
+
},
|
|
32
61
|
};
|
|
33
62
|
|
|
34
63
|
export const parseColor = (value: string): Color => {
|
|
@@ -45,22 +74,24 @@ export const parseColor = (value: string): Color => {
|
|
|
45
74
|
: "#" + up;
|
|
46
75
|
|
|
47
76
|
const rgb: Color["rgb"] = {
|
|
48
|
-
R: (parseInt("" + hex[1] + hex[2], 16) / 255).toPrecision(15),
|
|
49
|
-
G: (parseInt("" + hex[3] + hex[4], 16) / 255).toPrecision(15),
|
|
50
|
-
B: (parseInt("" + hex[5] + hex[6], 16) / 255).toPrecision(15),
|
|
77
|
+
R: (Number.parseInt("" + hex[1] + hex[2], 16) / 255).toPrecision(15),
|
|
78
|
+
G: (Number.parseInt("" + hex[3] + hex[4], 16) / 255).toPrecision(15),
|
|
79
|
+
B: (Number.parseInt("" + hex[5] + hex[6], 16) / 255).toPrecision(15),
|
|
51
80
|
};
|
|
52
81
|
|
|
53
|
-
return { hex, rgb };
|
|
82
|
+
return { hex: hex.toLowerCase(), rgb };
|
|
54
83
|
};
|
|
55
84
|
|
|
56
85
|
const getStoryboard = ({
|
|
57
86
|
logoHeight,
|
|
58
87
|
logoWidth,
|
|
59
88
|
background: { R, G, B },
|
|
89
|
+
fileNameSuffix,
|
|
60
90
|
}: {
|
|
61
91
|
logoHeight: number;
|
|
62
92
|
logoWidth: number;
|
|
63
93
|
background: Color["rgb"];
|
|
94
|
+
fileNameSuffix: string;
|
|
64
95
|
}) => {
|
|
65
96
|
const frameWidth = 375;
|
|
66
97
|
const frameHeight = 667;
|
|
@@ -74,6 +105,7 @@ const getStoryboard = ({
|
|
|
74
105
|
<dependencies>
|
|
75
106
|
<deployment identifier="iOS"/>
|
|
76
107
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
|
108
|
+
<capability name="Named colors" minToolsVersion="9.0"/>
|
|
77
109
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
78
110
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
79
111
|
</dependencies>
|
|
@@ -86,7 +118,7 @@ const getStoryboard = ({
|
|
|
86
118
|
<rect key="frame" x="0.0" y="0.0" width="${frameWidth}" height="${frameHeight}"/>
|
|
87
119
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
88
120
|
<subviews>
|
|
89
|
-
<imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="BootSplashLogo" translatesAutoresizingMaskIntoConstraints="NO" id="3lX-Ut-9ad">
|
|
121
|
+
<imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="BootSplashLogo-${fileNameSuffix}" translatesAutoresizingMaskIntoConstraints="NO" id="3lX-Ut-9ad">
|
|
90
122
|
<rect key="frame" x="${logoX}" y="${logoY}" width="${logoWidth}" height="${logoHeight}"/>
|
|
91
123
|
<accessibility key="accessibilityConfiguration">
|
|
92
124
|
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
|
|
@@ -94,7 +126,7 @@ const getStoryboard = ({
|
|
|
94
126
|
</imageView>
|
|
95
127
|
</subviews>
|
|
96
128
|
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
|
97
|
-
<color key="backgroundColor"
|
|
129
|
+
<color key="backgroundColor" name="BootSplashBackground-${fileNameSuffix}"/>
|
|
98
130
|
<constraints>
|
|
99
131
|
<constraint firstItem="3lX-Ut-9ad" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="Fh9-Fy-1nT"/>
|
|
100
132
|
<constraint firstItem="3lX-Ut-9ad" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="nvB-Ic-PnI"/>
|
|
@@ -107,73 +139,54 @@ const getStoryboard = ({
|
|
|
107
139
|
</scene>
|
|
108
140
|
</scenes>
|
|
109
141
|
<resources>
|
|
110
|
-
<image name="BootSplashLogo" width="${logoWidth}" height="${logoHeight}"/>
|
|
142
|
+
<image name="BootSplashLogo-${fileNameSuffix}" width="${logoWidth}" height="${logoHeight}"/>
|
|
143
|
+
<namedColor name="BootSplashBackground-${fileNameSuffix}">
|
|
144
|
+
<color red="${R}" green="${G}" blue="${B}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
145
|
+
</namedColor>
|
|
111
146
|
</resources>
|
|
112
147
|
</document>
|
|
113
148
|
`;
|
|
114
149
|
};
|
|
115
150
|
|
|
116
|
-
export const addFileToXcodeProject = (filePath: string) => {
|
|
117
|
-
const projectRoot = findProjectRoot(workingPath);
|
|
118
|
-
|
|
119
|
-
const pbxprojectPath = IOSConfig.Paths.getPBXProjectPath(projectRoot);
|
|
120
|
-
const project = IOSConfig.XcodeUtils.getPbxproj(projectRoot);
|
|
121
|
-
const xcodeProjectPath = IOSConfig.Paths.getXcodeProjectPath(projectRoot);
|
|
122
|
-
|
|
123
|
-
IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
124
|
-
filepath: filePath,
|
|
125
|
-
groupName: path.parse(xcodeProjectPath).name,
|
|
126
|
-
project,
|
|
127
|
-
isBuildFile: true,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
hfs.write(pbxprojectPath, project.writeSync());
|
|
131
|
-
logWrite(pbxprojectPath);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
151
|
// Freely inspired by https://github.com/humanwhocodes/humanfs
|
|
135
152
|
export const hfs = {
|
|
136
153
|
buffer: (path: string) => fs.readFileSync(path),
|
|
137
154
|
exists: (path: string) => fs.existsSync(path),
|
|
155
|
+
isDir: (path: string) => fs.lstatSync(path).isDirectory(),
|
|
138
156
|
json: (path: string) => JSON.parse(fs.readFileSync(path, "utf-8")) as unknown,
|
|
139
157
|
readDir: (path: string) => fs.readdirSync(path, "utf-8"),
|
|
140
158
|
realPath: (path: string) => fs.realpathSync(path, "utf-8"),
|
|
141
|
-
rm: (path: string) => fs.rmSync(path, { force: true }),
|
|
159
|
+
rm: (path: string) => fs.rmSync(path, { force: true, recursive: true }),
|
|
142
160
|
text: (path: string) => fs.readFileSync(path, "utf-8"),
|
|
143
161
|
|
|
162
|
+
copy: (src: string, dest: string) => {
|
|
163
|
+
if (hfs.isDir(src) || !hfs.exists(dest)) {
|
|
164
|
+
return fs.copySync(src, dest, { overwrite: true });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const srcBuffer = fs.readFileSync(src);
|
|
168
|
+
const destBuffer = fs.readFileSync(dest);
|
|
169
|
+
|
|
170
|
+
if (!srcBuffer.equals(destBuffer)) {
|
|
171
|
+
return fs.copySync(src, dest, { overwrite: true });
|
|
172
|
+
}
|
|
173
|
+
},
|
|
144
174
|
ensureDir: (dir: string) => {
|
|
145
175
|
fs.mkdirSync(dir, { recursive: true });
|
|
146
176
|
},
|
|
147
|
-
write: (
|
|
148
|
-
const trimmed =
|
|
149
|
-
fs.writeFileSync(
|
|
177
|
+
write: (path: string, content: string) => {
|
|
178
|
+
const trimmed = content.trim();
|
|
179
|
+
fs.writeFileSync(path, trimmed === "" ? trimmed : trimmed + "\n", "utf-8");
|
|
150
180
|
},
|
|
151
181
|
};
|
|
152
182
|
|
|
153
|
-
export const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
title: (emoji: string, text: string) =>
|
|
157
|
-
console.log(`\n${emoji} ${pc.underline(pc.bold(text))}`),
|
|
158
|
-
warn: (text: string) => console.log(pc.yellow(`⚠️ ${text}`)),
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
export const logWrite = (
|
|
162
|
-
filePath: string,
|
|
163
|
-
dimensions?: { width: number; height: number },
|
|
164
|
-
) =>
|
|
165
|
-
console.log(
|
|
166
|
-
` ${path.relative(workingPath, filePath)}` +
|
|
167
|
-
(dimensions != null ? ` (${dimensions.width}x${dimensions.height})` : ""),
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
export const writeJson = (file: string, json: object) => {
|
|
171
|
-
hfs.write(file, JSON.stringify(json, null, 2));
|
|
172
|
-
logWrite(file);
|
|
183
|
+
export const writeJson = (filePath: string, content: object) => {
|
|
184
|
+
hfs.write(filePath, JSON.stringify(content, null, 2));
|
|
185
|
+
log.write(filePath);
|
|
173
186
|
};
|
|
174
187
|
|
|
175
|
-
export const readXml = (
|
|
176
|
-
const xml = hfs.text(
|
|
188
|
+
export const readXml = (filePath: string) => {
|
|
189
|
+
const xml = hfs.text(filePath);
|
|
177
190
|
const { indent } = detectIndent(xml);
|
|
178
191
|
|
|
179
192
|
const formatOptions: XMLFormatterOptions = {
|
|
@@ -184,11 +197,11 @@ export const readXml = (file: string) => {
|
|
|
184
197
|
};
|
|
185
198
|
|
|
186
199
|
export const writeXml = (
|
|
187
|
-
|
|
188
|
-
|
|
200
|
+
filePath: string,
|
|
201
|
+
content: string,
|
|
189
202
|
options?: XMLFormatterOptions,
|
|
190
203
|
) => {
|
|
191
|
-
const formatted = formatXml(
|
|
204
|
+
const formatted = formatXml(content, {
|
|
192
205
|
collapseContent: true,
|
|
193
206
|
forceSelfClosingEmptyTag: true,
|
|
194
207
|
indentation: " ",
|
|
@@ -197,12 +210,12 @@ export const writeXml = (
|
|
|
197
210
|
...options,
|
|
198
211
|
});
|
|
199
212
|
|
|
200
|
-
hfs.write(
|
|
201
|
-
|
|
213
|
+
hfs.write(filePath, formatted);
|
|
214
|
+
log.write(filePath);
|
|
202
215
|
};
|
|
203
216
|
|
|
204
|
-
export const readHtml = (
|
|
205
|
-
const html = hfs.text(
|
|
217
|
+
export const readHtml = (filePath: string) => {
|
|
218
|
+
const html = hfs.text(filePath);
|
|
206
219
|
const { type, amount } = detectIndent(html);
|
|
207
220
|
|
|
208
221
|
const formatOptions: PrettierOptions = {
|
|
@@ -214,11 +227,11 @@ export const readHtml = (file: string) => {
|
|
|
214
227
|
};
|
|
215
228
|
|
|
216
229
|
export const writeHtml = async (
|
|
217
|
-
|
|
218
|
-
|
|
230
|
+
filePath: string,
|
|
231
|
+
content: string,
|
|
219
232
|
options?: Omit<PrettierOptions, "parser" | "plugins">,
|
|
220
233
|
) => {
|
|
221
|
-
const formatted = await prettier.format(
|
|
234
|
+
const formatted = await prettier.format(content, {
|
|
222
235
|
parser: "html",
|
|
223
236
|
plugins: [htmlPlugin, cssPlugin],
|
|
224
237
|
tabWidth: 2,
|
|
@@ -226,35 +239,84 @@ export const writeHtml = async (
|
|
|
226
239
|
...options,
|
|
227
240
|
});
|
|
228
241
|
|
|
229
|
-
hfs.write(
|
|
230
|
-
|
|
242
|
+
hfs.write(filePath, formatted);
|
|
243
|
+
log.write(filePath);
|
|
231
244
|
};
|
|
232
245
|
|
|
233
|
-
|
|
246
|
+
const cleanIOS = (dir: string) => {
|
|
234
247
|
hfs
|
|
235
248
|
.readDir(dir)
|
|
236
|
-
.filter((file) => file.
|
|
249
|
+
.filter((file) => file === "Colors.xcassets" || file === "Images.xcassets")
|
|
237
250
|
.map((file) => path.join(dir, file))
|
|
238
|
-
.
|
|
251
|
+
.flatMap((dir) =>
|
|
252
|
+
hfs
|
|
253
|
+
.readDir(dir)
|
|
254
|
+
.filter((file) => file.startsWith("BootSplash"))
|
|
255
|
+
.map((file) => path.join(dir, file)),
|
|
256
|
+
)
|
|
257
|
+
.forEach((file) => {
|
|
258
|
+
hfs.rm(file);
|
|
259
|
+
});
|
|
239
260
|
};
|
|
240
261
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}) => {
|
|
262
|
+
const getImageBase64 = async (
|
|
263
|
+
image: Sharp | undefined,
|
|
264
|
+
width: number,
|
|
265
|
+
): Promise<string> => {
|
|
266
|
+
if (image == null) {
|
|
267
|
+
return "";
|
|
268
|
+
}
|
|
269
|
+
|
|
250
270
|
const buffer = await image
|
|
251
271
|
.clone()
|
|
252
272
|
.resize(width)
|
|
253
273
|
.png({ quality: 100 })
|
|
254
274
|
.toBuffer();
|
|
255
275
|
|
|
256
|
-
|
|
257
|
-
|
|
276
|
+
return buffer.toString("base64");
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const getFileNameSuffix = async ({
|
|
280
|
+
background,
|
|
281
|
+
brand,
|
|
282
|
+
brandWidth,
|
|
283
|
+
darkBackground,
|
|
284
|
+
darkBrand,
|
|
285
|
+
darkLogo,
|
|
286
|
+
logo,
|
|
287
|
+
logoWidth,
|
|
288
|
+
}: {
|
|
289
|
+
background: Color;
|
|
290
|
+
brand: Sharp | undefined;
|
|
291
|
+
brandWidth: number;
|
|
292
|
+
darkBackground: Color | undefined;
|
|
293
|
+
darkBrand: Sharp | undefined;
|
|
294
|
+
darkLogo: Sharp | undefined;
|
|
295
|
+
logo: Sharp;
|
|
296
|
+
logoWidth: number;
|
|
297
|
+
}) => {
|
|
298
|
+
const [logoHash, darkLogoHash, brandHash, darkBrandHash] = await Promise.all([
|
|
299
|
+
getImageBase64(logo, logoWidth),
|
|
300
|
+
getImageBase64(darkLogo, logoWidth),
|
|
301
|
+
getImageBase64(brand, brandWidth),
|
|
302
|
+
getImageBase64(darkBrand, brandWidth),
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
const record: Record<string, string> = {
|
|
306
|
+
background: background.hex,
|
|
307
|
+
darkBackground: darkBackground?.hex ?? "",
|
|
308
|
+
logo: logoHash,
|
|
309
|
+
darkLogo: darkLogoHash,
|
|
310
|
+
brand: brandHash,
|
|
311
|
+
darkBrand: darkBrandHash,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const stableKey = Object.keys(record)
|
|
315
|
+
.sort()
|
|
316
|
+
.map((key) => record[key])
|
|
317
|
+
.join();
|
|
318
|
+
|
|
319
|
+
return murmurhash(stableKey);
|
|
258
320
|
};
|
|
259
321
|
|
|
260
322
|
const ensureSupportedFormat = async (
|
|
@@ -273,23 +335,38 @@ const ensureSupportedFormat = async (
|
|
|
273
335
|
}
|
|
274
336
|
};
|
|
275
337
|
|
|
276
|
-
const
|
|
277
|
-
android
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
338
|
+
const getAndroidOutputPath = ({
|
|
339
|
+
android,
|
|
340
|
+
assetsOutputPath,
|
|
341
|
+
brandHeight,
|
|
342
|
+
brandWidth,
|
|
343
|
+
flavor,
|
|
344
|
+
isExpo,
|
|
345
|
+
logoHeight,
|
|
346
|
+
logoWidth,
|
|
347
|
+
platforms,
|
|
348
|
+
}: {
|
|
349
|
+
android: AndroidProjectConfig | undefined;
|
|
350
|
+
assetsOutputPath: string;
|
|
351
|
+
brandHeight: number;
|
|
352
|
+
brandWidth: number;
|
|
353
|
+
flavor: string;
|
|
354
|
+
isExpo: boolean;
|
|
355
|
+
logoHeight: number;
|
|
356
|
+
logoWidth: number;
|
|
357
|
+
platforms: Platforms;
|
|
358
|
+
}) => {
|
|
359
|
+
if (!platforms.includes("android")) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (isExpo) {
|
|
363
|
+
return path.resolve(assetsOutputPath, "android");
|
|
364
|
+
}
|
|
365
|
+
if (android == null) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const androidOutputPath = path.resolve(
|
|
293
370
|
android.sourceDir,
|
|
294
371
|
android.appName,
|
|
295
372
|
"src",
|
|
@@ -297,74 +374,123 @@ const getAndroidResPath = (
|
|
|
297
374
|
"res",
|
|
298
375
|
);
|
|
299
376
|
|
|
300
|
-
if (!hfs.exists(
|
|
301
|
-
log.warn(
|
|
377
|
+
if (!hfs.exists(androidOutputPath)) {
|
|
378
|
+
return log.warn(
|
|
302
379
|
`No ${path.relative(
|
|
303
380
|
workingPath,
|
|
304
|
-
|
|
381
|
+
androidOutputPath,
|
|
305
382
|
)} directory found. Skipping Android assets generation…`,
|
|
306
383
|
);
|
|
307
|
-
}
|
|
308
|
-
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (logoWidth > 288 || logoHeight > 288) {
|
|
387
|
+
return log.warn(
|
|
309
388
|
"Logo size exceeding 288x288dp will be cropped by Android. Skipping Android assets generation…",
|
|
310
389
|
);
|
|
311
|
-
}
|
|
312
|
-
|
|
390
|
+
}
|
|
391
|
+
if (brandWidth > 200 || brandHeight > 80) {
|
|
392
|
+
return log.warn(
|
|
313
393
|
"Brand size exceeding 200x80dp will be cropped by Android. Skipping Android assets generation…",
|
|
314
394
|
);
|
|
315
|
-
}
|
|
316
|
-
if (logoWidth > 192 || logoHeight > 192) {
|
|
317
|
-
log.warn(`Logo size exceeds 192x192dp. It might be cropped by Android.`);
|
|
318
|
-
}
|
|
395
|
+
}
|
|
319
396
|
|
|
320
|
-
|
|
397
|
+
if (logoWidth > 192 || logoHeight > 192) {
|
|
398
|
+
log.warn("Logo size exceeds 192x192dp. It might be cropped by Android.");
|
|
321
399
|
}
|
|
400
|
+
|
|
401
|
+
return androidOutputPath;
|
|
322
402
|
};
|
|
323
403
|
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
404
|
+
const getIOSOutputPath = ({
|
|
405
|
+
assetsOutputPath,
|
|
406
|
+
ios,
|
|
407
|
+
isExpo,
|
|
408
|
+
platforms,
|
|
409
|
+
}: {
|
|
410
|
+
ios: IOSProjectConfig | undefined;
|
|
411
|
+
assetsOutputPath: string;
|
|
412
|
+
isExpo: boolean;
|
|
413
|
+
platforms: Platforms;
|
|
414
|
+
}) => {
|
|
415
|
+
if (!platforms.includes("ios")) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (isExpo) {
|
|
419
|
+
return path.resolve(assetsOutputPath, "ios");
|
|
420
|
+
}
|
|
421
|
+
if (ios == null) {
|
|
327
422
|
return;
|
|
328
423
|
}
|
|
424
|
+
if (ios.xcodeProject == null) {
|
|
425
|
+
return log.warn("No Xcode project found. Skipping iOS assets generation…");
|
|
426
|
+
}
|
|
329
427
|
|
|
330
|
-
const
|
|
428
|
+
const iosOutputPath = path
|
|
331
429
|
.resolve(ios.sourceDir, ios.xcodeProject.name)
|
|
332
430
|
.replace(/\.(xcodeproj|xcworkspace)$/, "");
|
|
333
431
|
|
|
334
|
-
if (!hfs.exists(
|
|
335
|
-
log.warn(
|
|
432
|
+
if (!hfs.exists(iosOutputPath)) {
|
|
433
|
+
return log.warn(
|
|
336
434
|
`No ${path.relative(
|
|
337
435
|
workingPath,
|
|
338
|
-
|
|
436
|
+
iosOutputPath,
|
|
339
437
|
)} directory found. Skipping iOS assets generation…`,
|
|
340
438
|
);
|
|
341
|
-
} else {
|
|
342
|
-
return iosProjectPath;
|
|
343
439
|
}
|
|
440
|
+
|
|
441
|
+
return iosOutputPath;
|
|
344
442
|
};
|
|
345
443
|
|
|
346
|
-
const getHtmlTemplatePath = (
|
|
444
|
+
const getHtmlTemplatePath = ({
|
|
445
|
+
html,
|
|
446
|
+
platforms,
|
|
447
|
+
}: {
|
|
448
|
+
html: string;
|
|
449
|
+
platforms: Platforms;
|
|
450
|
+
}) => {
|
|
451
|
+
if (!platforms.includes("web")) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
347
455
|
const htmlTemplatePath = path.resolve(workingPath, html);
|
|
348
456
|
|
|
349
457
|
if (!hfs.exists(htmlTemplatePath)) {
|
|
350
|
-
log.warn(
|
|
458
|
+
return log.warn(
|
|
351
459
|
`No ${path.relative(
|
|
352
460
|
workingPath,
|
|
353
461
|
htmlTemplatePath,
|
|
354
462
|
)} file found. Skipping HTML + CSS generation…`,
|
|
355
463
|
);
|
|
356
|
-
} else {
|
|
357
|
-
return htmlTemplatePath;
|
|
358
464
|
}
|
|
465
|
+
|
|
466
|
+
return htmlTemplatePath;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
export const getImageHeight = (
|
|
470
|
+
image: Sharp | undefined,
|
|
471
|
+
width: number,
|
|
472
|
+
): Promise<number> => {
|
|
473
|
+
if (image == null) {
|
|
474
|
+
return Promise.resolve(0);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return image
|
|
478
|
+
.clone()
|
|
479
|
+
.resize(width)
|
|
480
|
+
.toBuffer()
|
|
481
|
+
.then((buffer) => sharp(buffer).metadata())
|
|
482
|
+
.then(({ height = 0 }) => Math.round(height));
|
|
359
483
|
};
|
|
360
484
|
|
|
361
485
|
export type AddonConfig = {
|
|
362
486
|
licenseKey: string;
|
|
487
|
+
isExpo: boolean;
|
|
488
|
+
fileNameSuffix: string;
|
|
363
489
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
htmlTemplatePath: string |
|
|
367
|
-
assetsOutputPath: string
|
|
490
|
+
androidOutputPath: string | void;
|
|
491
|
+
iosOutputPath: string | void;
|
|
492
|
+
htmlTemplatePath: string | void;
|
|
493
|
+
assetsOutputPath: string;
|
|
368
494
|
|
|
369
495
|
logoPath: string;
|
|
370
496
|
darkLogoPath: string | undefined;
|
|
@@ -408,10 +534,10 @@ export const generate = async ({
|
|
|
408
534
|
ios?: IOSProjectConfig;
|
|
409
535
|
|
|
410
536
|
logo: string;
|
|
411
|
-
platforms:
|
|
537
|
+
platforms: Platforms;
|
|
412
538
|
background: string;
|
|
413
539
|
logoWidth: number;
|
|
414
|
-
assetsOutput
|
|
540
|
+
assetsOutput: string;
|
|
415
541
|
html: string;
|
|
416
542
|
flavor: string;
|
|
417
543
|
|
|
@@ -422,10 +548,14 @@ export const generate = async ({
|
|
|
422
548
|
darkLogo?: string;
|
|
423
549
|
darkBrand?: string;
|
|
424
550
|
}) => {
|
|
551
|
+
const isExpo =
|
|
552
|
+
getExpoConfig(projectRoot, { skipSDKVersionRequirement: true }).exp
|
|
553
|
+
.sdkVersion != null;
|
|
554
|
+
|
|
425
555
|
const [nodeStringVersion = ""] = process.versions.node.split(".");
|
|
426
|
-
const nodeVersion = parseInt(nodeStringVersion, 10);
|
|
556
|
+
const nodeVersion = Number.parseInt(nodeStringVersion, 10);
|
|
427
557
|
|
|
428
|
-
if (!isNaN(nodeVersion) && nodeVersion < 18) {
|
|
558
|
+
if (!Number.isNaN(nodeVersion) && nodeVersion < 18) {
|
|
429
559
|
log.error("Requires Node 18 (or higher)");
|
|
430
560
|
process.exit(1);
|
|
431
561
|
}
|
|
@@ -445,10 +575,7 @@ export const generate = async ({
|
|
|
445
575
|
? path.resolve(workingPath, args.darkBrand)
|
|
446
576
|
: undefined;
|
|
447
577
|
|
|
448
|
-
const assetsOutputPath =
|
|
449
|
-
args.assetsOutput != null
|
|
450
|
-
? path.resolve(workingPath, args.assetsOutput)
|
|
451
|
-
: undefined;
|
|
578
|
+
const assetsOutputPath = path.resolve(workingPath, args.assetsOutput);
|
|
452
579
|
|
|
453
580
|
const logo = sharp(logoPath);
|
|
454
581
|
const darkLogo = darkLogoPath != null ? sharp(darkLogoPath) : undefined;
|
|
@@ -470,7 +597,7 @@ export const generate = async ({
|
|
|
470
597
|
|
|
471
598
|
if (licenseKey != null && !executeAddon) {
|
|
472
599
|
log.warn(
|
|
473
|
-
|
|
600
|
+
"You specified a license key but none of the options that requires it.",
|
|
474
601
|
);
|
|
475
602
|
}
|
|
476
603
|
|
|
@@ -499,20 +626,8 @@ export const generate = async ({
|
|
|
499
626
|
await ensureSupportedFormat("Brand", brand);
|
|
500
627
|
await ensureSupportedFormat("Dark brand", darkBrand);
|
|
501
628
|
|
|
502
|
-
const logoHeight = await logo
|
|
503
|
-
|
|
504
|
-
.resize(logoWidth)
|
|
505
|
-
.toBuffer()
|
|
506
|
-
.then((buffer) => sharp(buffer).metadata())
|
|
507
|
-
.then(({ height = 0 }) => Math.round(height));
|
|
508
|
-
|
|
509
|
-
const brandHeight =
|
|
510
|
-
(await brand
|
|
511
|
-
?.clone()
|
|
512
|
-
.resize(brandWidth)
|
|
513
|
-
.toBuffer()
|
|
514
|
-
.then((buffer) => sharp(buffer).metadata())
|
|
515
|
-
.then(({ height = 0 }) => Math.round(height))) ?? 0;
|
|
629
|
+
const logoHeight = await getImageHeight(logo, logoWidth);
|
|
630
|
+
const brandHeight = await getImageHeight(brand, brandWidth);
|
|
516
631
|
|
|
517
632
|
if (logoWidth < args.logoWidth) {
|
|
518
633
|
log.warn(
|
|
@@ -525,52 +640,45 @@ export const generate = async ({
|
|
|
525
640
|
);
|
|
526
641
|
}
|
|
527
642
|
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const iosProjectPath =
|
|
540
|
-
platforms.includes("ios") && ios != null
|
|
541
|
-
? getIOSProjectPath(ios)
|
|
542
|
-
: undefined;
|
|
543
|
-
|
|
544
|
-
const htmlTemplatePath = platforms.includes("web")
|
|
545
|
-
? getHtmlTemplatePath(html)
|
|
546
|
-
: undefined;
|
|
547
|
-
|
|
548
|
-
if (androidResPath != null) {
|
|
549
|
-
log.title("🤖", "Android");
|
|
643
|
+
const fileNameSuffix = await getFileNameSuffix({
|
|
644
|
+
background,
|
|
645
|
+
brand,
|
|
646
|
+
brandWidth,
|
|
647
|
+
darkBackground,
|
|
648
|
+
darkBrand,
|
|
649
|
+
darkLogo,
|
|
650
|
+
logo,
|
|
651
|
+
logoWidth,
|
|
652
|
+
});
|
|
550
653
|
|
|
551
|
-
|
|
552
|
-
|
|
654
|
+
const androidOutputPath = getAndroidOutputPath({
|
|
655
|
+
android,
|
|
656
|
+
assetsOutputPath,
|
|
657
|
+
brandHeight,
|
|
658
|
+
brandWidth,
|
|
659
|
+
flavor,
|
|
660
|
+
isExpo,
|
|
661
|
+
logoHeight,
|
|
662
|
+
logoWidth,
|
|
663
|
+
platforms,
|
|
664
|
+
});
|
|
553
665
|
|
|
554
|
-
|
|
555
|
-
|
|
666
|
+
const iosOutputPath = getIOSOutputPath({
|
|
667
|
+
assetsOutputPath,
|
|
668
|
+
ios,
|
|
669
|
+
isExpo,
|
|
670
|
+
platforms,
|
|
671
|
+
});
|
|
556
672
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
'color[name="bootsplash_background"]',
|
|
562
|
-
);
|
|
673
|
+
const htmlTemplatePath = getHtmlTemplatePath({
|
|
674
|
+
html,
|
|
675
|
+
platforms,
|
|
676
|
+
});
|
|
563
677
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
} else {
|
|
567
|
-
root.querySelector("resources")?.appendChild(nextColor);
|
|
568
|
-
}
|
|
678
|
+
if (androidOutputPath != null) {
|
|
679
|
+
log.title("🤖", "Android");
|
|
569
680
|
|
|
570
|
-
|
|
571
|
-
} else {
|
|
572
|
-
writeXml(colorsXmlPath, `<resources>${colorsXmlEntry}</resources>`);
|
|
573
|
-
}
|
|
681
|
+
hfs.ensureDir(androidOutputPath);
|
|
574
682
|
|
|
575
683
|
await Promise.all(
|
|
576
684
|
[
|
|
@@ -581,7 +689,7 @@ export const generate = async ({
|
|
|
581
689
|
{ ratio: 4, suffix: "xxxhdpi" },
|
|
582
690
|
].map(({ ratio, suffix }) => {
|
|
583
691
|
const drawableDirPath = path.resolve(
|
|
584
|
-
|
|
692
|
+
androidOutputPath,
|
|
585
693
|
`drawable-${suffix}`,
|
|
586
694
|
);
|
|
587
695
|
|
|
@@ -618,74 +726,99 @@ export const generate = async ({
|
|
|
618
726
|
.toFile(filePath),
|
|
619
727
|
)
|
|
620
728
|
.then(() => {
|
|
621
|
-
|
|
729
|
+
log.write(filePath, {
|
|
622
730
|
width: canvasSize,
|
|
623
731
|
height: canvasSize,
|
|
624
732
|
});
|
|
625
733
|
});
|
|
626
734
|
}),
|
|
627
735
|
);
|
|
628
|
-
}
|
|
629
736
|
|
|
630
|
-
|
|
631
|
-
|
|
737
|
+
if (!isExpo) {
|
|
738
|
+
const valuesPath = path.resolve(androidOutputPath, "values");
|
|
739
|
+
hfs.ensureDir(valuesPath);
|
|
632
740
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
"BootSplash.storyboard",
|
|
636
|
-
);
|
|
741
|
+
const colorsXmlPath = path.resolve(valuesPath, "colors.xml");
|
|
742
|
+
const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
|
|
637
743
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
logoHeight,
|
|
642
|
-
logoWidth,
|
|
643
|
-
background: background.rgb,
|
|
644
|
-
}),
|
|
645
|
-
{ whiteSpaceAtEndOfSelfclosingTag: false },
|
|
646
|
-
);
|
|
744
|
+
if (hfs.exists(colorsXmlPath)) {
|
|
745
|
+
const { root, formatOptions } = readXml(colorsXmlPath);
|
|
746
|
+
const nextColor = parseHtml(colorsXmlEntry);
|
|
647
747
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
748
|
+
const prevColor = root.querySelector(
|
|
749
|
+
'color[name="bootsplash_background"]',
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
if (prevColor != null) {
|
|
753
|
+
prevColor.replaceWith(nextColor);
|
|
754
|
+
} else {
|
|
755
|
+
root.querySelector("resources")?.appendChild(nextColor);
|
|
756
|
+
}
|
|
651
757
|
|
|
652
|
-
|
|
758
|
+
writeXml(colorsXmlPath, root.toString(), formatOptions);
|
|
759
|
+
} else {
|
|
760
|
+
writeXml(colorsXmlPath, `<resources>${colorsXmlEntry}</resources>`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
653
764
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
unknown
|
|
657
|
-
>;
|
|
765
|
+
if (iosOutputPath != null) {
|
|
766
|
+
log.title("🍏", "iOS");
|
|
658
767
|
|
|
659
|
-
|
|
768
|
+
hfs.ensureDir(iosOutputPath);
|
|
769
|
+
cleanIOS(iosOutputPath);
|
|
660
770
|
|
|
661
|
-
const
|
|
662
|
-
collapseContent: true,
|
|
663
|
-
forceSelfClosingEmptyTag: false,
|
|
664
|
-
indentation: "\t",
|
|
665
|
-
lineSeparator: "\n",
|
|
666
|
-
whiteSpaceAtEndOfSelfclosingTag: false,
|
|
667
|
-
})
|
|
668
|
-
.replace(/<string\/>/gm, "<string></string>")
|
|
669
|
-
.replace(/^\t/gm, "");
|
|
771
|
+
const storyboardPath = path.resolve(iosOutputPath, "BootSplash.storyboard");
|
|
670
772
|
|
|
671
|
-
|
|
672
|
-
|
|
773
|
+
const colorsSetPath = path.resolve(
|
|
774
|
+
iosOutputPath,
|
|
775
|
+
"Colors.xcassets",
|
|
776
|
+
`BootSplashBackground-${fileNameSuffix}.colorset`,
|
|
777
|
+
);
|
|
673
778
|
|
|
674
779
|
const imageSetPath = path.resolve(
|
|
675
|
-
|
|
780
|
+
iosOutputPath,
|
|
676
781
|
"Images.xcassets",
|
|
677
|
-
|
|
782
|
+
`BootSplashLogo-${fileNameSuffix}.imageset`,
|
|
678
783
|
);
|
|
679
784
|
|
|
785
|
+
hfs.ensureDir(colorsSetPath);
|
|
680
786
|
hfs.ensureDir(imageSetPath);
|
|
681
|
-
cleanIOSAssets(imageSetPath, "bootsplash_logo");
|
|
682
787
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
788
|
+
writeXml(
|
|
789
|
+
storyboardPath,
|
|
790
|
+
getStoryboard({
|
|
791
|
+
logoHeight,
|
|
792
|
+
logoWidth,
|
|
793
|
+
background: background.rgb,
|
|
794
|
+
fileNameSuffix,
|
|
795
|
+
}),
|
|
796
|
+
{ whiteSpaceAtEndOfSelfclosingTag: false },
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
writeJson(path.resolve(colorsSetPath, "Contents.json"), {
|
|
800
|
+
colors: [
|
|
801
|
+
{
|
|
802
|
+
idiom: "universal",
|
|
803
|
+
color: {
|
|
804
|
+
"color-space": "srgb",
|
|
805
|
+
components: {
|
|
806
|
+
blue: background.rgb.B,
|
|
807
|
+
green: background.rgb.G,
|
|
808
|
+
red: background.rgb.R,
|
|
809
|
+
alpha: "1.000",
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
],
|
|
814
|
+
info: {
|
|
815
|
+
author: "xcode",
|
|
816
|
+
version: 1,
|
|
817
|
+
},
|
|
687
818
|
});
|
|
688
819
|
|
|
820
|
+
const logoFileName = `logo-${fileNameSuffix}`;
|
|
821
|
+
|
|
689
822
|
writeJson(path.resolve(imageSetPath, "Contents.json"), {
|
|
690
823
|
images: [
|
|
691
824
|
{
|
|
@@ -727,10 +860,60 @@ export const generate = async ({
|
|
|
727
860
|
.png({ quality: 100 })
|
|
728
861
|
.toFile(filePath)
|
|
729
862
|
.then(({ width, height }) => {
|
|
730
|
-
|
|
863
|
+
log.write(filePath, { width, height });
|
|
731
864
|
});
|
|
732
865
|
}),
|
|
733
866
|
);
|
|
867
|
+
|
|
868
|
+
if (!isExpo) {
|
|
869
|
+
const infoPlistPath = path.resolve(iosOutputPath, "Info.plist");
|
|
870
|
+
|
|
871
|
+
const infoPlist = plist.parse(hfs.text(infoPlistPath)) as Record<
|
|
872
|
+
string,
|
|
873
|
+
unknown
|
|
874
|
+
>;
|
|
875
|
+
|
|
876
|
+
infoPlist["UILaunchStoryboardName"] = "BootSplash";
|
|
877
|
+
|
|
878
|
+
const formatted = formatXml(plist.build(infoPlist), {
|
|
879
|
+
collapseContent: true,
|
|
880
|
+
forceSelfClosingEmptyTag: false,
|
|
881
|
+
indentation: "\t",
|
|
882
|
+
lineSeparator: "\n",
|
|
883
|
+
whiteSpaceAtEndOfSelfclosingTag: false,
|
|
884
|
+
})
|
|
885
|
+
.replace(/<string\/>/gm, "<string></string>")
|
|
886
|
+
.replace(/^\t/gm, "");
|
|
887
|
+
|
|
888
|
+
hfs.write(infoPlistPath, formatted);
|
|
889
|
+
log.write(infoPlistPath);
|
|
890
|
+
|
|
891
|
+
const pbxprojectPath =
|
|
892
|
+
Expo.IOSConfig.Paths.getPBXProjectPath(projectRoot);
|
|
893
|
+
|
|
894
|
+
const xcodeProjectPath =
|
|
895
|
+
Expo.IOSConfig.Paths.getXcodeProjectPath(projectRoot);
|
|
896
|
+
|
|
897
|
+
const project = Expo.IOSConfig.XcodeUtils.getPbxproj(projectRoot);
|
|
898
|
+
const projectName = path.basename(iosOutputPath);
|
|
899
|
+
|
|
900
|
+
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
901
|
+
filepath: path.join(projectName, "BootSplash.storyboard"),
|
|
902
|
+
groupName: path.parse(xcodeProjectPath).name,
|
|
903
|
+
project,
|
|
904
|
+
isBuildFile: true,
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
908
|
+
filepath: path.join(projectName, "Colors.xcassets"),
|
|
909
|
+
groupName: path.parse(xcodeProjectPath).name,
|
|
910
|
+
project,
|
|
911
|
+
isBuildFile: true,
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
hfs.write(pbxprojectPath, project.writeSync());
|
|
915
|
+
log.write(pbxprojectPath);
|
|
916
|
+
}
|
|
734
917
|
}
|
|
735
918
|
|
|
736
919
|
if (htmlTemplatePath != null) {
|
|
@@ -797,52 +980,49 @@ export const generate = async ({
|
|
|
797
980
|
await writeHtml(htmlTemplatePath, root.toString(), formatOptions);
|
|
798
981
|
}
|
|
799
982
|
|
|
800
|
-
|
|
801
|
-
log.title("📄", "Assets");
|
|
802
|
-
|
|
803
|
-
hfs.ensureDir(assetsOutputPath);
|
|
983
|
+
log.title("📄", "Assets");
|
|
804
984
|
|
|
805
|
-
|
|
806
|
-
background: background.hex,
|
|
807
|
-
logo: {
|
|
808
|
-
width: logoWidth,
|
|
809
|
-
height: logoHeight,
|
|
810
|
-
},
|
|
811
|
-
} satisfies Manifest);
|
|
812
|
-
|
|
813
|
-
await Promise.all(
|
|
814
|
-
[
|
|
815
|
-
{ ratio: 1, suffix: "" },
|
|
816
|
-
{ ratio: 1.5, suffix: "@1,5x" },
|
|
817
|
-
{ ratio: 2, suffix: "@2x" },
|
|
818
|
-
{ ratio: 3, suffix: "@3x" },
|
|
819
|
-
{ ratio: 4, suffix: "@4x" },
|
|
820
|
-
].map(({ ratio, suffix }) => {
|
|
821
|
-
const filePath = path.resolve(
|
|
822
|
-
assetsOutputPath,
|
|
823
|
-
`bootsplash_logo${suffix}.png`,
|
|
824
|
-
);
|
|
985
|
+
hfs.ensureDir(assetsOutputPath);
|
|
825
986
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
987
|
+
writeJson(path.resolve(assetsOutputPath, "manifest.json"), {
|
|
988
|
+
background: background.hex,
|
|
989
|
+
logo: {
|
|
990
|
+
width: logoWidth,
|
|
991
|
+
height: logoHeight,
|
|
992
|
+
},
|
|
993
|
+
} satisfies Manifest);
|
|
994
|
+
|
|
995
|
+
await Promise.all(
|
|
996
|
+
[
|
|
997
|
+
{ ratio: 1, suffix: "" },
|
|
998
|
+
{ ratio: 1.5, suffix: "@1,5x" },
|
|
999
|
+
{ ratio: 2, suffix: "@2x" },
|
|
1000
|
+
{ ratio: 3, suffix: "@3x" },
|
|
1001
|
+
{ ratio: 4, suffix: "@4x" },
|
|
1002
|
+
].map(({ ratio, suffix }) => {
|
|
1003
|
+
const filePath = path.resolve(assetsOutputPath, `logo${suffix}.png`);
|
|
1004
|
+
|
|
1005
|
+
return logo
|
|
1006
|
+
.clone()
|
|
1007
|
+
.resize(Math.round(logoWidth * ratio))
|
|
1008
|
+
.png({ quality: 100 })
|
|
1009
|
+
.toFile(filePath)
|
|
1010
|
+
.then(({ width, height }) => {
|
|
1011
|
+
log.write(filePath, { width, height });
|
|
1012
|
+
});
|
|
1013
|
+
}),
|
|
1014
|
+
);
|
|
837
1015
|
|
|
838
1016
|
if (licenseKey != null && executeAddon) {
|
|
839
1017
|
const addon = requireAddon();
|
|
840
1018
|
|
|
841
1019
|
await addon?.execute({
|
|
842
1020
|
licenseKey,
|
|
1021
|
+
isExpo,
|
|
1022
|
+
fileNameSuffix,
|
|
843
1023
|
|
|
844
|
-
|
|
845
|
-
|
|
1024
|
+
androidOutputPath,
|
|
1025
|
+
iosOutputPath,
|
|
846
1026
|
htmlTemplatePath,
|
|
847
1027
|
assetsOutputPath,
|
|
848
1028
|
|
|
@@ -865,7 +1045,7 @@ export const generate = async ({
|
|
|
865
1045
|
darkBrand,
|
|
866
1046
|
});
|
|
867
1047
|
} else {
|
|
868
|
-
log
|
|
1048
|
+
console.log(`
|
|
869
1049
|
${pc.blue("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")}
|
|
870
1050
|
${pc.blue("┃")} 🔑 ${pc.bold(
|
|
871
1051
|
"Get a license key for brand image / dark mode support",
|
|
@@ -876,5 +1056,338 @@ ${pc.blue("┃")} ${pc.underline(
|
|
|
876
1056
|
${pc.blue("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")}`);
|
|
877
1057
|
}
|
|
878
1058
|
|
|
879
|
-
log
|
|
1059
|
+
console.log(
|
|
1060
|
+
`\n💖 Thanks for using ${pc.underline("react-native-bootsplash")}`,
|
|
1061
|
+
);
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
export type ExpoProps = {
|
|
1065
|
+
assetsDir?: string;
|
|
1066
|
+
edgeToEdge?: boolean;
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
export type ExpoPlugin = Expo.ConfigPlugin<ExpoProps>;
|
|
1070
|
+
|
|
1071
|
+
const withAndroidAssets: ExpoPlugin = (config, props) =>
|
|
1072
|
+
Expo.withDangerousMod(config, [
|
|
1073
|
+
"android",
|
|
1074
|
+
(config) => {
|
|
1075
|
+
const { assetsDir = "assets/bootsplash" } = props;
|
|
1076
|
+
const { platformProjectRoot } = config.modRequest;
|
|
1077
|
+
|
|
1078
|
+
const srcDir = path.resolve(workingPath, assetsDir, "android");
|
|
1079
|
+
|
|
1080
|
+
const destDir = path.resolve(
|
|
1081
|
+
platformProjectRoot,
|
|
1082
|
+
"app",
|
|
1083
|
+
"src",
|
|
1084
|
+
"main",
|
|
1085
|
+
"res",
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
for (const drawableDir of hfs.readDir(srcDir)) {
|
|
1089
|
+
const srcDrawableDir = path.join(srcDir, drawableDir);
|
|
1090
|
+
const destDrawableDir = path.join(destDir, drawableDir);
|
|
1091
|
+
|
|
1092
|
+
hfs.ensureDir(destDrawableDir);
|
|
1093
|
+
|
|
1094
|
+
for (const file of hfs.readDir(srcDrawableDir)) {
|
|
1095
|
+
hfs.copy(
|
|
1096
|
+
path.join(srcDrawableDir, file),
|
|
1097
|
+
path.join(destDrawableDir, file),
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
return config;
|
|
1103
|
+
},
|
|
1104
|
+
]);
|
|
1105
|
+
|
|
1106
|
+
const withAndroidManifest: ExpoPlugin = (config) =>
|
|
1107
|
+
Expo.withAndroidManifest(config, (config) => {
|
|
1108
|
+
config.modResults.manifest.application?.forEach((application) => {
|
|
1109
|
+
if (application.$["android:name"] === ".MainApplication") {
|
|
1110
|
+
const { activity } = application;
|
|
1111
|
+
|
|
1112
|
+
activity?.forEach((activity) => {
|
|
1113
|
+
if (activity.$["android:name"] === ".MainActivity") {
|
|
1114
|
+
activity.$["android:theme"] = "@style/BootTheme";
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
return config;
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
const withMainActivity: ExpoPlugin = (config) =>
|
|
1124
|
+
Expo.withMainActivity(config, (config) => {
|
|
1125
|
+
const { modResults } = config;
|
|
1126
|
+
const { language } = modResults;
|
|
1127
|
+
|
|
1128
|
+
const withImports = addImports(
|
|
1129
|
+
modResults.contents.replace(
|
|
1130
|
+
/(\/\/ )?setTheme\(R\.style\.AppTheme\)/,
|
|
1131
|
+
"// setTheme(R.style.AppTheme)",
|
|
1132
|
+
),
|
|
1133
|
+
["android.os.Bundle", "com.zoontek.rnbootsplash.RNBootSplash"],
|
|
1134
|
+
language === "java",
|
|
1135
|
+
);
|
|
1136
|
+
|
|
1137
|
+
// indented with 4 spaces
|
|
1138
|
+
const withInit = mergeContents({
|
|
1139
|
+
src: withImports,
|
|
1140
|
+
comment: " //",
|
|
1141
|
+
tag: "bootsplash-init",
|
|
1142
|
+
offset: 0,
|
|
1143
|
+
anchor: /super\.onCreate\(null\)/,
|
|
1144
|
+
newSrc:
|
|
1145
|
+
" RNBootSplash.init(this, R.style.BootTheme)" +
|
|
1146
|
+
(language === "java" ? ";" : ""),
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
return {
|
|
1150
|
+
...config,
|
|
1151
|
+
modResults: {
|
|
1152
|
+
...modResults,
|
|
1153
|
+
contents: withInit.contents,
|
|
1154
|
+
},
|
|
1155
|
+
};
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
const withAndroidStyles: ExpoPlugin = (config, props) =>
|
|
1159
|
+
Expo.withAndroidStyles(config, async (config) => {
|
|
1160
|
+
const { assetsDir = "assets/bootsplash", edgeToEdge = false } = props;
|
|
1161
|
+
const { modResults } = config;
|
|
1162
|
+
const { resources } = modResults;
|
|
1163
|
+
const { style = [] } = resources;
|
|
1164
|
+
|
|
1165
|
+
const manifest = (await hfs.json(
|
|
1166
|
+
path.resolve(workingPath, assetsDir, "manifest.json"),
|
|
1167
|
+
)) as Manifest;
|
|
1168
|
+
|
|
1169
|
+
const item = [
|
|
1170
|
+
{
|
|
1171
|
+
$: { name: "postBootSplashTheme" },
|
|
1172
|
+
_: "@style/AppTheme",
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
$: { name: "bootSplashBackground" },
|
|
1176
|
+
_: "@color/bootsplash_background",
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
$: { name: "bootSplashLogo" },
|
|
1180
|
+
_: "@drawable/bootsplash_logo",
|
|
1181
|
+
},
|
|
1182
|
+
];
|
|
1183
|
+
|
|
1184
|
+
if (manifest.brand != null) {
|
|
1185
|
+
item.push({
|
|
1186
|
+
$: { name: "bootSplashBrand" },
|
|
1187
|
+
_: "@drawable/bootsplash_brand",
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
const withBootTheme = [
|
|
1192
|
+
...style.filter(({ $ }) => $.name !== "BootTheme"),
|
|
1193
|
+
{
|
|
1194
|
+
$: {
|
|
1195
|
+
name: "BootTheme",
|
|
1196
|
+
parent: edgeToEdge
|
|
1197
|
+
? "Theme.BootSplash.EdgeToEdge"
|
|
1198
|
+
: "Theme.BootSplash",
|
|
1199
|
+
},
|
|
1200
|
+
item,
|
|
1201
|
+
},
|
|
1202
|
+
];
|
|
1203
|
+
|
|
1204
|
+
return {
|
|
1205
|
+
...config,
|
|
1206
|
+
modResults: {
|
|
1207
|
+
...modResults,
|
|
1208
|
+
resources: {
|
|
1209
|
+
...resources,
|
|
1210
|
+
style: withBootTheme,
|
|
1211
|
+
},
|
|
1212
|
+
},
|
|
1213
|
+
};
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
const withAndroidColors: ExpoPlugin = (config, props) =>
|
|
1217
|
+
Expo.withAndroidColors(config, async (config) => {
|
|
1218
|
+
const { assetsDir = "assets/bootsplash" } = props;
|
|
1219
|
+
|
|
1220
|
+
const manifest = (await hfs.json(
|
|
1221
|
+
path.resolve(workingPath, assetsDir, "manifest.json"),
|
|
1222
|
+
)) as Manifest;
|
|
1223
|
+
|
|
1224
|
+
config.modResults = assignColorValue(config.modResults, {
|
|
1225
|
+
name: "bootsplash_background",
|
|
1226
|
+
value: manifest.background,
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
return config;
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
const withAndroidColorsNight: ExpoPlugin = (config, props) =>
|
|
1233
|
+
Expo.withAndroidColorsNight(config, async (config) => {
|
|
1234
|
+
const { assetsDir = "assets/bootsplash" } = props;
|
|
1235
|
+
|
|
1236
|
+
const manifest = (await hfs.json(
|
|
1237
|
+
path.resolve(workingPath, assetsDir, "manifest.json"),
|
|
1238
|
+
)) as Manifest;
|
|
1239
|
+
|
|
1240
|
+
if (manifest.darkBackground != null) {
|
|
1241
|
+
config.modResults = assignColorValue(config.modResults, {
|
|
1242
|
+
name: "bootsplash_background",
|
|
1243
|
+
value: manifest.darkBackground,
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
return config;
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
const withIOSAssets: ExpoPlugin = (config, props) =>
|
|
1251
|
+
Expo.withDangerousMod(config, [
|
|
1252
|
+
"ios",
|
|
1253
|
+
(config) => {
|
|
1254
|
+
const { assetsDir = "assets/bootsplash" } = props;
|
|
1255
|
+
const { platformProjectRoot, projectName = "" } = config.modRequest;
|
|
1256
|
+
|
|
1257
|
+
const srcDir = path.resolve(workingPath, assetsDir, "ios");
|
|
1258
|
+
const destDir = path.resolve(platformProjectRoot, projectName);
|
|
1259
|
+
|
|
1260
|
+
cleanIOS(destDir);
|
|
1261
|
+
|
|
1262
|
+
hfs.copy(
|
|
1263
|
+
path.join(srcDir, "BootSplash.storyboard"),
|
|
1264
|
+
path.join(destDir, "BootSplash.storyboard"),
|
|
1265
|
+
);
|
|
1266
|
+
|
|
1267
|
+
for (const xcassetsDir of ["Colors.xcassets", "Images.xcassets"]) {
|
|
1268
|
+
const srcXcassetsDir = path.join(srcDir, xcassetsDir);
|
|
1269
|
+
const destXcassetsDir = path.join(destDir, xcassetsDir);
|
|
1270
|
+
|
|
1271
|
+
hfs.ensureDir(destXcassetsDir);
|
|
1272
|
+
|
|
1273
|
+
for (const file of hfs.readDir(srcXcassetsDir)) {
|
|
1274
|
+
hfs.copy(
|
|
1275
|
+
path.join(srcXcassetsDir, file),
|
|
1276
|
+
path.join(destXcassetsDir, file),
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
return config;
|
|
1282
|
+
},
|
|
1283
|
+
]);
|
|
1284
|
+
|
|
1285
|
+
const withAppDelegate: ExpoPlugin = (config) =>
|
|
1286
|
+
Expo.withAppDelegate(config, (config) => {
|
|
1287
|
+
const [sdkStringVersion = ""] = config.sdkVersion?.split(".") ?? "";
|
|
1288
|
+
const isAtLeastExpo51 = Number.parseInt(sdkStringVersion, 10) >= 51;
|
|
1289
|
+
|
|
1290
|
+
const { modResults } = config;
|
|
1291
|
+
const { language } = modResults;
|
|
1292
|
+
|
|
1293
|
+
if (language !== "objc" && language !== "objcpp") {
|
|
1294
|
+
throw new Error(
|
|
1295
|
+
`Cannot modify the project AppDelegate as it's not in a supported language: ${language}`,
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
const withHeader = mergeContents({
|
|
1300
|
+
src: modResults.contents,
|
|
1301
|
+
comment: "//",
|
|
1302
|
+
tag: "bootsplash-header",
|
|
1303
|
+
offset: 1,
|
|
1304
|
+
anchor: /#import "AppDelegate\.h"/,
|
|
1305
|
+
newSrc: '#import "RNBootSplash.h"',
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
const withRootView = mergeContents({
|
|
1309
|
+
src: withHeader.contents,
|
|
1310
|
+
comment: "//",
|
|
1311
|
+
tag: "bootsplash-init",
|
|
1312
|
+
offset: 0,
|
|
1313
|
+
anchor: /@end/,
|
|
1314
|
+
newSrc: isAtLeastExpo51
|
|
1315
|
+
? dedent`
|
|
1316
|
+
- (void)customizeRootView:(RCTRootView *)rootView {
|
|
1317
|
+
[RNBootSplash initWithStoryboard:@"BootSplash" rootView:rootView];
|
|
1318
|
+
}
|
|
1319
|
+
`
|
|
1320
|
+
: dedent`
|
|
1321
|
+
- (UIView *)createRootViewWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initProps:(NSDictionary *)initProps {
|
|
1322
|
+
UIView *rootView = [super createRootViewWithBridge:bridge moduleName:moduleName initProps:initProps];
|
|
1323
|
+
[RNBootSplash initWithStoryboard:@"BootSplash" rootView:rootView];
|
|
1324
|
+
return rootView;
|
|
1325
|
+
}
|
|
1326
|
+
`,
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
return {
|
|
1330
|
+
...config,
|
|
1331
|
+
modResults: {
|
|
1332
|
+
...modResults,
|
|
1333
|
+
contents: withRootView.contents,
|
|
1334
|
+
},
|
|
1335
|
+
};
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
const withInfoPlist: ExpoPlugin = (config) =>
|
|
1339
|
+
Expo.withInfoPlist(config, (config) => {
|
|
1340
|
+
config.modResults["UILaunchStoryboardName"] = "BootSplash";
|
|
1341
|
+
return config;
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
const withXcodeProject: ExpoPlugin = (config) =>
|
|
1345
|
+
Expo.withXcodeProject(config, (config) => {
|
|
1346
|
+
const { projectName = "" } = config.modRequest;
|
|
1347
|
+
|
|
1348
|
+
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
1349
|
+
filepath: path.join(projectName, "BootSplash.storyboard"),
|
|
1350
|
+
groupName: projectName,
|
|
1351
|
+
project: config.modResults,
|
|
1352
|
+
isBuildFile: true,
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
|
|
1356
|
+
filepath: path.join(projectName, "Colors.xcassets"),
|
|
1357
|
+
groupName: projectName,
|
|
1358
|
+
project: config.modResults,
|
|
1359
|
+
isBuildFile: true,
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
return config;
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
export const withGenerate: ExpoPlugin = (config, props = {}) => {
|
|
1366
|
+
const plugins: ExpoPlugin[] = [];
|
|
1367
|
+
const { platforms = [] } = config;
|
|
1368
|
+
|
|
1369
|
+
if (platforms.includes("android")) {
|
|
1370
|
+
plugins.push(
|
|
1371
|
+
withAndroidAssets,
|
|
1372
|
+
withAndroidManifest,
|
|
1373
|
+
withMainActivity,
|
|
1374
|
+
withAndroidStyles,
|
|
1375
|
+
withAndroidColors,
|
|
1376
|
+
withAndroidColorsNight,
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (platforms.includes("ios")) {
|
|
1381
|
+
plugins.push(
|
|
1382
|
+
withIOSAssets,
|
|
1383
|
+
withAppDelegate,
|
|
1384
|
+
withInfoPlist,
|
|
1385
|
+
withXcodeProject,
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
return Expo.withPlugins(
|
|
1390
|
+
config,
|
|
1391
|
+
plugins.map((plugin) => [plugin, props]),
|
|
1392
|
+
);
|
|
880
1393
|
};
|