react-native-bootsplash 6.3.11 → 7.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.
Files changed (75) hide show
  1. package/README.md +13 -41
  2. package/android/build.gradle +10 -17
  3. package/android/src/main/AndroidManifest.xml +1 -1
  4. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashModuleImpl.kt +2 -2
  5. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashPackage.kt +0 -1
  6. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashQueue.kt +1 -1
  7. package/android/src/main/res/drawable/compat_splash_screen.xml +11 -6
  8. package/android/src/main/res/drawable/compat_splash_screen_oneui_4.xml +13 -6
  9. package/android/src/main/res/values/colors.xml +1 -3
  10. package/android/src/main/res/values/public.xml +0 -2
  11. package/android/src/main/res/values/styles.xml +13 -19
  12. package/android/src/main/res/values-night/styles.xml +1 -1
  13. package/android/src/main/res/values-v27/colors.xml +4 -0
  14. package/android/src/main/res/values-v27/styles.xml +1 -6
  15. package/android/src/main/res/values-v29/styles.xml +1 -11
  16. package/android/src/main/res/values-v30/styles.xml +1 -9
  17. package/android/src/main/res/values-v31/styles.xml +1 -1
  18. package/android/src/newarch/com/zoontek/rnbootsplash/RNBootSplashModule.kt +2 -2
  19. package/android/src/oldarch/com/zoontek/rnbootsplash/RNBootSplashModule.kt +3 -3
  20. package/app.plugin.js +1 -1
  21. package/cli.js +2 -11
  22. package/dist/commonjs/addon/index.js +49 -49
  23. package/dist/commonjs/addon/index.js.map +1 -1
  24. package/dist/commonjs/generate.js +797 -553
  25. package/dist/commonjs/generate.js.map +1 -1
  26. package/dist/commonjs/index.js +1 -1
  27. package/dist/commonjs/index.js.map +1 -1
  28. package/dist/commonjs/specs/NativeRNBootSplash.js.map +1 -0
  29. package/dist/commonjs/{NativeRNBootSplash.web.js → specs/NativeRNBootSplash.web.js} +1 -1
  30. package/dist/commonjs/specs/NativeRNBootSplash.web.js.map +1 -0
  31. package/dist/module/addon/index.js +49 -49
  32. package/dist/module/addon/index.js.map +1 -1
  33. package/dist/module/generate.js +798 -551
  34. package/dist/module/generate.js.map +1 -1
  35. package/dist/module/index.js +1 -1
  36. package/dist/module/index.js.map +1 -1
  37. package/dist/module/specs/NativeRNBootSplash.js.map +1 -0
  38. package/dist/module/{NativeRNBootSplash.web.js → specs/NativeRNBootSplash.web.js} +1 -1
  39. package/dist/module/specs/NativeRNBootSplash.web.js.map +1 -0
  40. package/dist/typescript/addon/index.d.ts +22 -2
  41. package/dist/typescript/addon/index.d.ts.map +1 -1
  42. package/dist/typescript/generate.d.ts +54 -35
  43. package/dist/typescript/generate.d.ts.map +1 -1
  44. package/dist/typescript/index.d.ts +2 -2
  45. package/dist/typescript/index.d.ts.map +1 -1
  46. package/dist/typescript/{NativeRNBootSplash.d.ts → specs/NativeRNBootSplash.d.ts} +1 -1
  47. package/dist/typescript/specs/NativeRNBootSplash.d.ts.map +1 -0
  48. package/dist/typescript/{NativeRNBootSplash.web.d.ts → specs/NativeRNBootSplash.web.d.ts} +1 -1
  49. package/dist/typescript/specs/NativeRNBootSplash.web.d.ts.map +1 -0
  50. package/ios/RNBootSplash.mm +4 -10
  51. package/package.json +25 -30
  52. package/src/generate.ts +1045 -805
  53. package/src/index.ts +7 -7
  54. package/src/{NativeRNBootSplash.ts → specs/NativeRNBootSplash.ts} +1 -1
  55. package/src/{NativeRNBootSplash.web.ts → specs/NativeRNBootSplash.web.ts} +1 -1
  56. package/android/src/main/AndroidManifestNew.xml +0 -3
  57. package/android/src/main/res/drawable-v23/compat_splash_screen.xml +0 -19
  58. package/android/src/main/res/drawable-v23/compat_splash_screen_oneui_4.xml +0 -21
  59. package/android/src/main/res/values-v23/styles.xml +0 -13
  60. package/dist/commonjs/NativeRNBootSplash.js.map +0 -1
  61. package/dist/commonjs/NativeRNBootSplash.web.js.map +0 -1
  62. package/dist/commonjs/expo.js +0 -348
  63. package/dist/commonjs/expo.js.map +0 -1
  64. package/dist/module/NativeRNBootSplash.js.map +0 -1
  65. package/dist/module/NativeRNBootSplash.web.js.map +0 -1
  66. package/dist/module/expo.js +0 -341
  67. package/dist/module/expo.js.map +0 -1
  68. package/dist/typescript/NativeRNBootSplash.d.ts.map +0 -1
  69. package/dist/typescript/NativeRNBootSplash.web.d.ts.map +0 -1
  70. package/dist/typescript/expo.d.ts +0 -11
  71. package/dist/typescript/expo.d.ts.map +0 -1
  72. package/react-native.config.js +0 -101
  73. package/src/expo.ts +0 -439
  74. /package/dist/commonjs/{NativeRNBootSplash.js → specs/NativeRNBootSplash.js} +0 -0
  75. /package/dist/module/{NativeRNBootSplash.js → specs/NativeRNBootSplash.js} +0 -0
@@ -3,8 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.writeXmlLike = exports.writeJson = exports.readXmlLike = exports.log = exports.hfs = exports.getExpoConfig = exports.generate = exports.cleanIOSAssets = void 0;
6
+ exports.writeXmlLike = exports.writeJson = exports.withBootSplash = exports.readXmlLike = exports.log = exports.hfs = exports.generate = void 0;
7
7
  var Expo = _interopRequireWildcard(require("@expo/config-plugins"));
8
+ var _Colors = require("@expo/config-plugins/build/android/Colors");
9
+ var _codeMod = require("@expo/config-plugins/build/android/codeMod");
10
+ var _generateCode = require("@expo/config-plugins/build/utils/generateCode");
8
11
  var _plist = _interopRequireDefault(require("@expo/plist"));
9
12
  var _cliConfigAndroid = require("@react-native-community/cli-config-android");
10
13
  var _cliConfigApple = require("@react-native-community/cli-config-apple");
@@ -26,13 +29,10 @@ var _util = _interopRequireDefault(require("util"));
26
29
  var _xmlFormatter = _interopRequireDefault(require("xml-formatter"));
27
30
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
28
31
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
32
+ const PACKAGE_NAME = "react-native-bootsplash";
33
+ let isExpo = false;
29
34
  const workingPath = process.env.INIT_CWD ?? process.env.PWD ?? process.cwd();
30
35
  const projectRoot = (0, _cliTools.findProjectRoot)(workingPath);
31
- const getIOSProjectConfig = (0, _cliConfigApple.getProjectConfig)({
32
- platformName: "ios"
33
- });
34
- const ios = getIOSProjectConfig(projectRoot, {});
35
- const android = (0, _cliConfigAndroid.projectConfig)(projectRoot);
36
36
  const promisifiedExec = _util.default.promisify(_child_process.default.exec);
37
37
  const exec = cmd => promisifiedExec(cmd).then(({
38
38
  stdout,
@@ -40,16 +40,20 @@ const exec = cmd => promisifiedExec(cmd).then(({
40
40
  }) => stdout || stderr);
41
41
  const log = exports.log = {
42
42
  error: text => {
43
- console.log(_picocolors.default.red(`❌ ${text}`));
43
+ console.log(_picocolors.default.red(isExpo ? `❌ [${PACKAGE_NAME}] ${text}` : `❌ ${text}`));
44
44
  },
45
45
  title: (emoji, text) => {
46
- console.log(`\n${emoji} ${_picocolors.default.underline(_picocolors.default.bold(text))}`);
46
+ if (!isExpo) {
47
+ console.log(`\n${emoji} ${_picocolors.default.underline(_picocolors.default.bold(text))}`);
48
+ }
47
49
  },
48
50
  warn: text => {
49
- console.log(_picocolors.default.yellow(`⚠️ ${text}`));
51
+ console.log(_picocolors.default.yellow(isExpo ? `⚠️ [${PACKAGE_NAME}] ${text}` : `⚠️ ${text}`));
50
52
  },
51
53
  write: (filePath, dimensions) => {
52
- console.log(` ${_path.default.relative(workingPath, filePath)}` + (dimensions != null ? ` (${dimensions.width}x${dimensions.height})` : ""));
54
+ if (!isExpo) {
55
+ console.log(` ${_path.default.relative(workingPath, filePath)}` + (dimensions != null ? ` (${dimensions.width}x${dimensions.height})` : ""));
56
+ }
53
57
  }
54
58
  };
55
59
  const parseColor = value => {
@@ -69,20 +73,19 @@ const parseColor = value => {
69
73
  rgb
70
74
  };
71
75
  };
72
- const getStoryboard = ({
73
- logoHeight,
74
- logoWidth,
75
- background: {
76
+ const getStoryboard = props => {
77
+ const {
78
+ background,
79
+ logo,
80
+ fileNameSuffix
81
+ } = props;
82
+ const {
76
83
  R,
77
84
  G,
78
85
  B
79
- },
80
- fileNameSuffix
81
- }) => {
86
+ } = background.rgb;
82
87
  const frameWidth = 375;
83
88
  const frameHeight = 667;
84
- const logoX = (frameWidth - logoWidth) / 2;
85
- const logoY = (frameHeight - logoHeight) / 2;
86
89
  return (0, _tsDedent.dedent)`
87
90
  <?xml version="1.0" encoding="UTF-8"?>
88
91
  <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
@@ -104,7 +107,7 @@ const getStoryboard = ({
104
107
  <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
105
108
  <subviews>
106
109
  <imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="BootSplashLogo-${fileNameSuffix}" translatesAutoresizingMaskIntoConstraints="NO" id="3lX-Ut-9ad">
107
- <rect key="frame" x="${logoX}" y="${logoY}" width="${logoWidth}" height="${logoHeight}"/>
110
+ <rect key="frame" x="${(frameWidth - logo.width) / 2}" y="${(frameHeight - logo.height) / 2}" width="${logo.width}" height="${logo.height}"/>
108
111
  <accessibility key="accessibilityConfiguration">
109
112
  <accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
110
113
  </accessibility>
@@ -124,7 +127,7 @@ const getStoryboard = ({
124
127
  </scene>
125
128
  </scenes>
126
129
  <resources>
127
- <image name="BootSplashLogo-${fileNameSuffix}" width="${logoWidth}" height="${logoHeight}"/>
130
+ <image name="BootSplashLogo-${fileNameSuffix}" width="${logo.width}" height="${logo.height}"/>
128
131
  <namedColor name="BootSplashBackground-${fileNameSuffix}">
129
132
  <color red="${R}" green="${G}" blue="${B}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
130
133
  </namedColor>
@@ -146,20 +149,6 @@ const hfs = exports.hfs = {
146
149
  recursive: true
147
150
  }),
148
151
  text: path => _fsExtra.default.readFileSync(path, "utf-8"),
149
- copy: (src, dest) => {
150
- if (hfs.isDir(src) || !hfs.exists(dest)) {
151
- return _fsExtra.default.copySync(src, dest, {
152
- overwrite: true
153
- });
154
- }
155
- const srcBuffer = _fsExtra.default.readFileSync(src);
156
- const destBuffer = _fsExtra.default.readFileSync(dest);
157
- if (!srcBuffer.equals(destBuffer)) {
158
- return _fsExtra.default.copySync(src, dest, {
159
- overwrite: true
160
- });
161
- }
162
- },
163
152
  ensureDir: dir => {
164
153
  _fsExtra.default.mkdirSync(dir, {
165
154
  recursive: true
@@ -170,53 +159,6 @@ const hfs = exports.hfs = {
170
159
  _fsExtra.default.writeFileSync(path, trimmed === "" ? trimmed : trimmed + "\n", "utf-8");
171
160
  }
172
161
  };
173
-
174
- // Adapted from https://github.com/square/find-yarn-workspace-root
175
- const findUp = (from, matcher) => {
176
- let previous;
177
- let current = _path.default.normalize(from);
178
- do {
179
- const found = matcher(current);
180
- if (typeof found !== "undefined") {
181
- return found;
182
- }
183
- previous = current;
184
- current = _path.default.dirname(current);
185
- } while (current !== previous);
186
- };
187
- const getExpoConfig = from => {
188
- const hasDependency = findUp(from, dir => {
189
- const pkgPath = _path.default.resolve(dir, "package.json");
190
- if (_fsExtra.default.existsSync(pkgPath)) {
191
- try {
192
- const pkg = hfs.json(pkgPath);
193
- return pkg.dependencies?.expo != null;
194
- } catch {} // eslint-disable-line no-empty
195
- }
196
- }) ?? false;
197
- if (!hasDependency) {
198
- return {
199
- isExpo: false
200
- };
201
- }
202
- const version = findUp(from, dir => {
203
- const pkgPath = _path.default.resolve(dir, "node_modules", "expo", "package.json");
204
- if (_fsExtra.default.existsSync(pkgPath)) {
205
- try {
206
- const pkg = hfs.json(pkgPath);
207
- return pkg.version;
208
- } catch {} // eslint-disable-line no-empty
209
- }
210
- });
211
- if (version == null || _semver.default.lt(version, "51.0.20")) {
212
- log.error("Requires Expo 51.0.20 (or higher)");
213
- process.exit(1);
214
- }
215
- return {
216
- isExpo: true
217
- };
218
- };
219
- exports.getExpoConfig = getExpoConfig;
220
162
  const writeJson = (filePath, content) => {
221
163
  hfs.write(filePath, JSON.stringify(content, null, 2));
222
164
  log.write(filePath);
@@ -272,106 +214,27 @@ const writeXmlLike = async (filePath, content, {
272
214
  }
273
215
  };
274
216
  exports.writeXmlLike = writeXmlLike;
275
- const cleanIOSAssets = dir => {
276
- hfs.readDir(dir).filter(file => file === "Colors.xcassets" || file === "Images.xcassets").map(file => _path.default.join(dir, file)).flatMap(dir => hfs.readDir(dir).filter(file => file.startsWith("BootSplash")).map(file => _path.default.join(dir, file))).forEach(file => {
277
- hfs.rm(file);
278
- });
279
- };
280
- exports.cleanIOSAssets = cleanIOSAssets;
281
- const getImageBase64 = async (image, width) => {
282
- if (image == null) {
283
- return "";
284
- }
285
- const buffer = await image.clone().resize(width).png({
286
- quality: 100
287
- }).toBuffer();
288
- return buffer.toString("base64");
289
- };
290
- const getFileNameSuffix = async ({
291
- background,
292
- brand,
293
- brandWidth,
294
- darkBackground,
295
- darkBrand,
296
- darkLogo,
297
- logo,
298
- logoWidth
299
- }) => {
300
- const [logoHash, darkLogoHash, brandHash, darkBrandHash] = await Promise.all([getImageBase64(logo, logoWidth), getImageBase64(darkLogo, logoWidth), getImageBase64(brand, brandWidth), getImageBase64(darkBrand, brandWidth)]);
301
- const record = {
302
- background: background.hex,
303
- darkBackground: darkBackground?.hex ?? "",
304
- logo: logoHash,
305
- darkLogo: darkLogoHash,
306
- brand: brandHash,
307
- darkBrand: darkBrandHash
308
- };
309
- const stableKey = Object.keys(record).sort().map(key => record[key]).join();
310
- return _crypto.default.createHash("shake256", {
311
- outputLength: 3
312
- }).update(stableKey).digest("hex").toLowerCase();
313
- };
314
- const ensureSupportedFormat = async (name, image) => {
315
- if (image == null) {
316
- return;
317
- }
318
- const {
319
- format
320
- } = await image.metadata();
321
- if (format !== "png" && format !== "svg") {
322
- log.error(`${name} image file format (${format}) is not supported`);
323
- process.exit(1);
324
- }
325
- };
326
217
  const getAndroidOutputPath = ({
327
- assetsOutputPath,
328
- brandHeight,
329
- brandWidth,
330
218
  flavor,
331
- isExpo,
332
- logoHeight,
333
- logoWidth,
334
219
  platforms
335
220
  }) => {
336
- if (!platforms.includes("android")) {
337
- return;
338
- }
339
- const withSizeChecks = assetsOutputPath => {
340
- if (logoWidth > 192 || logoHeight > 192) {
341
- return log.warn("Logo size exceeding 192x192dp will be cropped by Android. Skipping Android assets generation…");
342
- }
343
- if (brandWidth > 200 || brandHeight > 80) {
344
- return log.warn("Brand size exceeding 200x80dp will be cropped by Android. Skipping Android assets generation…");
345
- }
346
- if (logoWidth > 134 || logoHeight > 134) {
347
- log.warn("Logo size exceeds 134x134dp. It might be cropped by Android.");
348
- }
349
- return assetsOutputPath;
350
- };
351
- if (isExpo) {
352
- return withSizeChecks(_path.default.resolve(assetsOutputPath, "android"));
353
- }
354
- if (android == null) {
221
+ const android = (0, _cliConfigAndroid.projectConfig)(projectRoot);
222
+ if (!platforms.includes("android") || android == null) {
355
223
  return;
356
224
  }
357
225
  const androidOutputPath = _path.default.resolve(android.sourceDir, android.appName, "src", flavor, "res");
358
226
  if (!hfs.exists(androidOutputPath)) {
359
227
  return log.warn(`No ${_path.default.relative(workingPath, androidOutputPath)} directory found. Skipping Android assets generation…`);
360
228
  }
361
- return withSizeChecks(androidOutputPath);
229
+ return androidOutputPath;
362
230
  };
363
231
  const getIOSOutputPath = ({
364
- assetsOutputPath,
365
- isExpo,
366
232
  platforms
367
233
  }) => {
368
- if (!platforms.includes("ios")) {
369
- return;
370
- }
371
- if (isExpo) {
372
- return _path.default.resolve(assetsOutputPath, "ios");
373
- }
374
- if (ios == null) {
234
+ const ios = (0, _cliConfigApple.getProjectConfig)({
235
+ platformName: "ios"
236
+ })(projectRoot, {});
237
+ if (!platforms.includes("ios") || ios == null) {
375
238
  return;
376
239
  }
377
240
  if (ios.xcodeProject == null) {
@@ -383,428 +246,361 @@ const getIOSOutputPath = ({
383
246
  }
384
247
  return iosOutputPath;
385
248
  };
386
- const getHtmlTemplatePath = async ({
387
- isExpo,
249
+ const getHtmlTemplatePath = ({
388
250
  html,
389
251
  platforms
390
252
  }) => {
391
253
  if (!platforms.includes("web")) {
392
254
  return;
393
255
  }
394
- if (isExpo) {
395
- const htmlTemplatePath = _path.default.resolve(workingPath, html);
396
- const htmlTemplateRelativePath = _path.default.relative(workingPath, htmlTemplatePath);
397
- if (htmlTemplateRelativePath === "public/index.html" && !hfs.exists(htmlTemplatePath)) {
398
- const cmd = `npx expo customize ${htmlTemplateRelativePath}`;
399
- console.log(_picocolors.default.dim(`Running ${cmd}`));
400
- await exec(cmd);
401
- }
402
- }
403
256
  const htmlTemplatePath = _path.default.resolve(workingPath, html);
404
257
  if (!hfs.exists(htmlTemplatePath)) {
405
- return log.warn(`No ${_path.default.relative(workingPath, htmlTemplatePath)} file found. Skipping HTML + CSS generation…`);
258
+ return log.warn(`No ${_path.default.relative(workingPath, htmlTemplatePath)} found. Skipping HTML + CSS generation…`);
406
259
  }
407
260
  return htmlTemplatePath;
408
261
  };
409
- const getImageHeight = (image, width) => {
410
- if (image == null) {
411
- return Promise.resolve(0);
262
+ const getInfoPlistPath = ({
263
+ iosOutputPath,
264
+ plist
265
+ }) => {
266
+ if (plist != null) {
267
+ const infoPlistPath = _path.default.resolve(workingPath, plist);
268
+ if (!hfs.exists(infoPlistPath)) {
269
+ return log.warn(`No ${_path.default.relative(workingPath, infoPlistPath)} found`);
270
+ }
271
+ return infoPlistPath;
412
272
  }
413
- return image.clone().resize(width).toBuffer().then(buffer => (0, _sharp.default)(buffer).metadata()).then(({
414
- height = 0
415
- }) => Math.round(height));
273
+ return _path.default.resolve(iosOutputPath, "Info.plist");
416
274
  };
417
- const requireAddon = () => {
418
- try {
419
- return require("./addon"); // eslint-disable-line
420
- } catch {
421
- return;
275
+ const toAsset = async (filePath, width) => {
276
+ const image = (0, _sharp.default)(filePath);
277
+ const {
278
+ format
279
+ } = await image.metadata();
280
+ if (format !== "png" && format !== "svg") {
281
+ log.error(`${_path.default.basename(filePath)} image file format (${format}) is not supported`);
282
+ process.exit(1);
422
283
  }
284
+ const [height, hash] = await Promise.all([image.clone().resize(width).toBuffer().then(buffer => (0, _sharp.default)(buffer).metadata()).then(({
285
+ height = 0
286
+ }) => Math.round(height)), image.clone().resize(width).png({
287
+ quality: 100
288
+ }).toBuffer().then(buffer => buffer.toString("base64"))]);
289
+ return {
290
+ path: filePath,
291
+ image,
292
+ hash,
293
+ height,
294
+ width
295
+ };
423
296
  };
424
- const generate = async ({
425
- projectType,
426
- platforms,
427
- html,
428
- flavor,
297
+ const transformProps = async (rootPath, {
298
+ android = {},
429
299
  licenseKey,
430
- ...args
300
+ ...rawProps
431
301
  }) => {
432
- const isExpo = projectType === "expo" || projectType === "detect" && getExpoConfig(workingPath).isExpo;
433
- if (_semver.default.lt(process.versions.node, "18.0.0")) {
434
- log.error("Requires Node 18 (or higher)");
302
+ if (_semver.default.lt(process.versions.node, "20.0.0")) {
303
+ log.error("Requires Node 20 (or higher)");
435
304
  process.exit(1);
436
305
  }
437
- const logoPath = _path.default.resolve(workingPath, args.logo);
438
- const darkLogoPath = args.darkLogo != null ? _path.default.resolve(workingPath, args.darkLogo) : undefined;
439
- const brandPath = args.brand != null ? _path.default.resolve(workingPath, args.brand) : undefined;
440
- const darkBrandPath = args.darkBrand != null ? _path.default.resolve(workingPath, args.darkBrand) : undefined;
441
- const assetsOutputPath = _path.default.resolve(workingPath, args.assetsOutput);
442
- const logo = (0, _sharp.default)(logoPath);
443
- const darkLogo = darkLogoPath != null ? (0, _sharp.default)(darkLogoPath) : undefined;
444
- const brand = brandPath != null ? (0, _sharp.default)(brandPath) : undefined;
445
- const darkBrand = darkBrandPath != null ? (0, _sharp.default)(darkBrandPath) : undefined;
446
- const background = parseColor(args.background);
447
- const logoWidth = args.logoWidth - args.logoWidth % 2;
448
- const brandWidth = args.brandWidth - args.brandWidth % 2;
449
- const darkBackground = args.darkBackground != null ? parseColor(args.darkBackground) : undefined;
306
+ const assetsOutputPath = _path.default.resolve(rootPath, rawProps.assetsOutput);
307
+ const logoPath = _path.default.resolve(rootPath, rawProps.logo);
308
+ const darkLogoPath = rawProps.darkLogo != null ? _path.default.resolve(rootPath, rawProps.darkLogo) : undefined;
309
+ const brandPath = rawProps.brand != null ? _path.default.resolve(rootPath, rawProps.brand) : undefined;
310
+ const darkBrandPath = rawProps.darkBrand != null ? _path.default.resolve(rootPath, rawProps.darkBrand) : undefined;
311
+ const logoWidth = rawProps.logoWidth - rawProps.logoWidth % 2;
312
+ const brandWidth = rawProps.brandWidth - rawProps.brandWidth % 2;
313
+ const [logo, darkLogo, brand, darkBrand] = await Promise.all([toAsset(logoPath, logoWidth), darkLogoPath != null ? toAsset(darkLogoPath, logoWidth) : undefined, brandPath != null ? toAsset(brandPath, brandWidth) : undefined, darkBrandPath != null ? toAsset(darkBrandPath, brandWidth) : undefined]);
314
+ const background = parseColor(rawProps.background);
315
+ const darkBackground = rawProps.darkBackground != null ? parseColor(rawProps.darkBackground) : undefined;
450
316
  const executeAddon = brand != null || darkBackground != null || darkLogo != null || darkBrand != null;
451
317
  if (licenseKey != null && !executeAddon) {
452
318
  log.warn("You specified a license key but none of the options that requires it.");
453
319
  }
320
+ const optionNames = {
321
+ brand: isExpo ? "brand" : "--brand",
322
+ darkBackground: isExpo ? "darkBackground" : "--dark-background",
323
+ darkLogo: isExpo ? "darkLogo" : "--dark-logo",
324
+ darkBrand: isExpo ? "darkBrand" : "--dark-brand"
325
+ };
454
326
  if (licenseKey == null && executeAddon) {
455
- const options = [brand != null ? "brand" : "", darkBackground != null ? "dark-background" : "", darkLogo != null ? "dark-logo" : "", darkBrand != null ? "dark-brand" : ""].filter(option => option !== "").map(option => `--${option}`).join(", ");
327
+ const options = [brand != null ? optionNames.brand : "", darkBackground != null ? optionNames.darkBackground : "", darkLogo != null ? optionNames.darkLogo : "", darkBrand != null ? optionNames.darkBrand : ""].filter(option => option !== "").join(", ");
456
328
  log.error(`You need to specify a license key in order to use ${options}.`);
457
329
  process.exit(1);
458
330
  }
459
331
  if (brand == null && darkBrand != null) {
460
- log.error("--dark-brand option couldn't be used without --brand.");
332
+ log.error(`${optionNames.darkBrand} option couldn't be used without ${optionNames.brand}.`);
461
333
  process.exit(1);
462
334
  }
463
- await ensureSupportedFormat("Logo", logo);
464
- await ensureSupportedFormat("Dark logo", darkLogo);
465
- await ensureSupportedFormat("Brand", brand);
466
- await ensureSupportedFormat("Dark brand", darkBrand);
467
- const logoHeight = await getImageHeight(logo, logoWidth);
468
- const brandHeight = await getImageHeight(brand, brandWidth);
469
- if (logoWidth < args.logoWidth) {
335
+ if (logoWidth < rawProps.logoWidth) {
470
336
  log.warn(`Logo width must be a multiple of 2. It has been rounded to ${logoWidth}dp.`);
471
337
  }
472
- if (brandWidth < args.brandWidth) {
338
+ if (brandWidth < rawProps.brandWidth) {
473
339
  log.warn(`Brand width must be a multiple of 2. It has been rounded to ${brandWidth}dp.`);
474
340
  }
475
- const fileNameSuffix = await getFileNameSuffix({
341
+ const record = {
342
+ background: background.hex,
343
+ darkBackground: darkBackground?.hex ?? "",
344
+ logo: logo.hash,
345
+ darkLogo: darkLogo?.hash ?? "",
346
+ brand: brand?.hash ?? "",
347
+ darkBrand: darkBrand?.hash ?? ""
348
+ };
349
+ const stableKey = Object.keys(record).sort().map(key => record[key]).join();
350
+ const fileNameSuffix = _crypto.default.createHash("shake256", {
351
+ outputLength: 3
352
+ }).update(stableKey).digest("hex").toLowerCase();
353
+ return {
354
+ android,
355
+ assetsOutputPath,
356
+ licenseKey,
357
+ executeAddon,
476
358
  background,
477
- brand,
478
- brandWidth,
479
359
  darkBackground,
480
- darkBrand,
360
+ logo,
481
361
  darkLogo,
362
+ brand,
363
+ darkBrand,
364
+ fileNameSuffix
365
+ };
366
+ };
367
+ const writeAndroidAssets = async ({
368
+ androidOutputPath,
369
+ props
370
+ }) => {
371
+ const {
482
372
  logo,
483
- logoWidth
484
- });
485
- const androidOutputPath = getAndroidOutputPath({
486
- assetsOutputPath,
487
- brandHeight,
488
- brandWidth,
489
- flavor,
490
- isExpo,
491
- logoHeight,
492
- logoWidth,
493
- platforms
494
- });
495
- const iosOutputPath = getIOSOutputPath({
496
- assetsOutputPath,
497
- isExpo,
498
- platforms
499
- });
500
- const htmlTemplatePath = await getHtmlTemplatePath({
501
- isExpo,
502
- html,
503
- platforms
504
- });
505
- if (androidOutputPath != null) {
506
- log.title("🤖", "Android");
507
- hfs.ensureDir(androidOutputPath);
508
- await Promise.all([{
509
- ratio: 1,
510
- suffix: "mdpi"
511
- }, {
512
- ratio: 1.5,
513
- suffix: "hdpi"
514
- }, {
515
- ratio: 2,
516
- suffix: "xhdpi"
517
- }, {
518
- ratio: 3,
519
- suffix: "xxhdpi"
520
- }, {
521
- ratio: 4,
522
- suffix: "xxxhdpi"
523
- }].map(({
524
- ratio,
525
- suffix
526
- }) => {
527
- const drawableDirPath = _path.default.resolve(androidOutputPath, `drawable-${suffix}`);
528
- hfs.ensureDir(drawableDirPath);
373
+ brand
374
+ } = props;
375
+ if (logo.width > 192 || logo.height > 192) {
376
+ return log.warn("Logo size exceeding 192x192dp will be cropped by Android. Skipping Android assets generation…");
377
+ }
378
+ if (brand != null && (brand.width > 200 || brand.height > 80)) {
379
+ return log.warn("Brand size exceeding 200x80dp will be cropped by Android. Skipping Android assets generation…");
380
+ }
381
+ if (logo.width > 134 || logo.height > 134) {
382
+ log.warn("Logo size exceeds 134x134dp. It might be cropped by Android.");
383
+ }
384
+ log.title("🤖", "Android");
385
+ hfs.ensureDir(androidOutputPath);
386
+ await Promise.all([{
387
+ ratio: 1,
388
+ suffix: "mdpi"
389
+ }, {
390
+ ratio: 1.5,
391
+ suffix: "hdpi"
392
+ }, {
393
+ ratio: 2,
394
+ suffix: "xhdpi"
395
+ }, {
396
+ ratio: 3,
397
+ suffix: "xxhdpi"
398
+ }, {
399
+ ratio: 4,
400
+ suffix: "xxxhdpi"
401
+ }].map(({
402
+ ratio,
403
+ suffix
404
+ }) => {
405
+ const drawableDirPath = _path.default.resolve(androidOutputPath, `drawable-${suffix}`);
406
+ hfs.ensureDir(drawableDirPath);
529
407
 
530
- // https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions
531
- const canvasSize = 288 * ratio;
408
+ // https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions
409
+ const canvasSize = 288 * ratio;
532
410
 
533
- // https://sharp.pixelplumbing.com/api-constructor
534
- const canvas = (0, _sharp.default)({
535
- create: {
536
- width: canvasSize,
537
- height: canvasSize,
538
- channels: 4,
539
- background: {
540
- r: 255,
541
- g: 255,
542
- b: 255,
543
- alpha: 0
544
- }
545
- }
546
- });
547
- const filePath = _path.default.resolve(drawableDirPath, "bootsplash_logo.png");
548
- return logo.clone().resize(logoWidth * ratio).toBuffer().then(input => canvas.composite([{
549
- input
550
- }]).png({
551
- quality: 100
552
- }).toFile(filePath)).then(() => {
553
- log.write(filePath, {
554
- width: canvasSize,
555
- height: canvasSize
556
- });
557
- });
558
- }));
559
- if (!isExpo) {
560
- const manifestXmlPath = _path.default.resolve(androidOutputPath, "..", "AndroidManifest.xml");
561
- if (hfs.exists(manifestXmlPath)) {
562
- const manifestXml = readXmlLike(manifestXmlPath);
563
- const activities = manifestXml.root.querySelectorAll("activity");
564
- for (const activity of activities) {
565
- if (activity.getAttribute("android:name") === ".MainActivity") {
566
- activity.setAttribute("android:theme", "@style/BootTheme");
567
- }
411
+ // https://sharp.pixelplumbing.com/api-constructor
412
+ const canvas = (0, _sharp.default)({
413
+ create: {
414
+ width: canvasSize,
415
+ height: canvasSize,
416
+ channels: 4,
417
+ background: {
418
+ r: 255,
419
+ g: 255,
420
+ b: 255,
421
+ alpha: 0
568
422
  }
569
- await writeXmlLike(manifestXmlPath, manifestXml.root.toString(), {
570
- ...manifestXml.formatOptions,
571
- formatter: "prettier",
572
- htmlWhitespaceSensitivity: "ignore",
573
- selfClosingTags: true,
574
- singleAttributePerLine: true
575
- });
576
- } else {
577
- log.warn("No AndroidManifest.xml found. Skipping…");
578
- }
579
- const valuesPath = _path.default.resolve(androidOutputPath, "values");
580
- hfs.ensureDir(valuesPath);
581
- const colorsXmlPath = _path.default.resolve(valuesPath, "colors.xml");
582
- const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
583
- if (hfs.exists(colorsXmlPath)) {
584
- const colorsXml = readXmlLike(colorsXmlPath);
585
- const nextColor = (0, _nodeHtmlParser.parse)(colorsXmlEntry);
586
- const prevColor = colorsXml.root.querySelector('color[name="bootsplash_background"]');
587
- if (prevColor != null) {
588
- prevColor.replaceWith(nextColor);
589
- } else {
590
- colorsXml.root.querySelector("resources")?.appendChild(nextColor);
591
- }
592
- await writeXmlLike(colorsXmlPath, colorsXml.root.toString(), {
593
- ...colorsXml.formatOptions,
594
- formatter: "xmlFormatter"
595
- });
596
- } else {
597
- await writeXmlLike(colorsXmlPath, `<resources>${colorsXmlEntry}</resources>`, {
598
- formatter: "xmlFormatter"
599
- });
600
- }
601
- const stylesXmlPath = _path.default.resolve(valuesPath, "styles.xml");
602
- if (hfs.exists(stylesXmlPath)) {
603
- const stylesXml = readXmlLike(stylesXmlPath);
604
- const prevStyle = stylesXml.root.querySelector('style[name="BootTheme"]');
605
- const parent = prevStyle?.getAttribute("parent") ?? "Theme.BootSplash";
606
- const extraItems = (0, _nodeHtmlParser.parse)(prevStyle?.text.split("\n").map(line => line.trim()).join("") ?? "").childNodes.filter(node => {
607
- if (!(node instanceof _nodeHtmlParser.HTMLElement)) {
608
- return true;
609
- }
610
- const name = node.getAttribute("name");
611
- return name !== "bootSplashBackground" && name !== "bootSplashLogo" && name !== "bootSplashBrand" && name !== "postBootSplashTheme";
612
- }).map(node => node.toString());
613
- const styleItems = [...(extraItems.length > 0 ? [...extraItems, ""] : []), '<item name="bootSplashBackground">@color/bootsplash_background</item>', '<item name="bootSplashLogo">@drawable/bootsplash_logo</item>', ...(brand != null && brandPath != null ? ['<item name="bootSplashBrand">@drawable/bootsplash_brand</item>'] : []), '<item name="postBootSplashTheme">@style/AppTheme</item>'];
614
- const nextStyle = (0, _nodeHtmlParser.parse)((0, _tsDedent.dedent)`
615
- <style name="BootTheme" parent="${parent}">
616
- ${styleItems.join("\n")}
617
- </style>
618
- `);
619
- prevStyle?.remove(); // remove the existing style
620
- stylesXml.root.querySelector("resources")?.appendChild(nextStyle);
621
- await writeXmlLike(stylesXmlPath, stylesXml.root.toString(), {
622
- ...stylesXml.formatOptions,
623
- formatter: "prettier",
624
- htmlWhitespaceSensitivity: "ignore"
625
- });
626
- } else {
627
- log.warn("No styles.xml found. Skipping…");
628
423
  }
629
- }
630
- }
631
- if (iosOutputPath != null) {
632
- log.title("🍏", "iOS");
633
- hfs.ensureDir(iosOutputPath);
634
- cleanIOSAssets(iosOutputPath);
635
- const storyboardPath = _path.default.resolve(iosOutputPath, "BootSplash.storyboard");
636
- const colorsSetPath = _path.default.resolve(iosOutputPath, "Colors.xcassets", `BootSplashBackground-${fileNameSuffix}.colorset`);
637
- const imageSetPath = _path.default.resolve(iosOutputPath, "Images.xcassets", `BootSplashLogo-${fileNameSuffix}.imageset`);
638
- hfs.ensureDir(colorsSetPath);
639
- hfs.ensureDir(imageSetPath);
640
- await writeXmlLike(storyboardPath, getStoryboard({
641
- logoHeight,
642
- logoWidth,
643
- background: background.rgb,
644
- fileNameSuffix
645
- }), {
646
- formatter: "xmlFormatter",
647
- whiteSpaceAtEndOfSelfclosingTag: false
648
424
  });
649
- writeJson(_path.default.resolve(colorsSetPath, "Contents.json"), {
650
- colors: [{
651
- idiom: "universal",
652
- color: {
653
- "color-space": "srgb",
654
- components: {
655
- blue: background.rgb.B,
656
- green: background.rgb.G,
657
- red: background.rgb.R,
658
- alpha: "1.000"
659
- }
660
- }
661
- }],
662
- info: {
663
- author: "xcode",
664
- version: 1
665
- }
425
+ const filePath = _path.default.resolve(drawableDirPath, "bootsplash_logo.png");
426
+ return logo.image.clone().resize(logo.width * ratio).toBuffer().then(input => canvas.composite([{
427
+ input
428
+ }]).png({
429
+ quality: 100
430
+ }).toFile(filePath)).then(() => {
431
+ log.write(filePath, {
432
+ width: canvasSize,
433
+ height: canvasSize
434
+ });
666
435
  });
667
- const logoFileName = `logo-${fileNameSuffix}`;
668
- writeJson(_path.default.resolve(imageSetPath, "Contents.json"), {
669
- images: [{
670
- idiom: "universal",
671
- filename: `${logoFileName}.png`,
672
- scale: "1x"
673
- }, {
674
- idiom: "universal",
675
- filename: `${logoFileName}@2x.png`,
676
- scale: "2x"
677
- }, {
678
- idiom: "universal",
679
- filename: `${logoFileName}@3x.png`,
680
- scale: "3x"
681
- }],
682
- info: {
683
- author: "xcode",
684
- version: 1
436
+ }));
437
+ };
438
+ const writeIOSAssets = async ({
439
+ iosOutputPath,
440
+ props
441
+ }) => {
442
+ const {
443
+ background,
444
+ logo,
445
+ fileNameSuffix
446
+ } = props;
447
+ log.title("🍏", "iOS");
448
+ hfs.ensureDir(iosOutputPath);
449
+
450
+ // clean existing assets
451
+ hfs.readDir(iosOutputPath).filter(file => file === "Colors.xcassets" || file === "Images.xcassets").map(file => _path.default.join(iosOutputPath, file)).flatMap(dir => hfs.readDir(dir).filter(file => file.startsWith("BootSplash")).map(file => _path.default.join(dir, file))).forEach(file => {
452
+ hfs.rm(file);
453
+ });
454
+ const storyboardPath = _path.default.resolve(iosOutputPath, "BootSplash.storyboard");
455
+ await writeXmlLike(storyboardPath, getStoryboard(props), {
456
+ formatter: "xmlFormatter",
457
+ whiteSpaceAtEndOfSelfclosingTag: false
458
+ });
459
+ const colorsSetPath = _path.default.resolve(iosOutputPath, "Colors.xcassets", `BootSplashBackground-${fileNameSuffix}.colorset`);
460
+ hfs.ensureDir(colorsSetPath);
461
+ writeJson(_path.default.resolve(colorsSetPath, "Contents.json"), {
462
+ colors: [{
463
+ idiom: "universal",
464
+ color: {
465
+ "color-space": "srgb",
466
+ components: {
467
+ blue: background.rgb.B,
468
+ green: background.rgb.G,
469
+ red: background.rgb.R,
470
+ alpha: "1.000"
471
+ }
685
472
  }
686
- });
687
- await Promise.all([{
688
- ratio: 1,
689
- suffix: ""
473
+ }],
474
+ info: {
475
+ author: "xcode",
476
+ version: 1
477
+ }
478
+ });
479
+ const logoFileName = `logo-${fileNameSuffix}`;
480
+ const imagesSetPath = _path.default.resolve(iosOutputPath, "Images.xcassets", `BootSplashLogo-${fileNameSuffix}.imageset`);
481
+ hfs.ensureDir(imagesSetPath);
482
+ writeJson(_path.default.resolve(imagesSetPath, "Contents.json"), {
483
+ images: [{
484
+ idiom: "universal",
485
+ filename: `${logoFileName}.png`,
486
+ scale: "1x"
690
487
  }, {
691
- ratio: 2,
692
- suffix: "@2x"
488
+ idiom: "universal",
489
+ filename: `${logoFileName}@2x.png`,
490
+ scale: "2x"
693
491
  }, {
694
- ratio: 3,
695
- suffix: "@3x"
696
- }].map(({
697
- ratio,
698
- suffix
492
+ idiom: "universal",
493
+ filename: `${logoFileName}@3x.png`,
494
+ scale: "3x"
495
+ }],
496
+ info: {
497
+ author: "xcode",
498
+ version: 1
499
+ }
500
+ });
501
+ await Promise.all([{
502
+ ratio: 1,
503
+ suffix: ""
504
+ }, {
505
+ ratio: 2,
506
+ suffix: "@2x"
507
+ }, {
508
+ ratio: 3,
509
+ suffix: "@3x"
510
+ }].map(({
511
+ ratio,
512
+ suffix
513
+ }) => {
514
+ const filePath = _path.default.resolve(imagesSetPath, `${logoFileName}${suffix}.png`);
515
+ return logo.image.clone().resize(logo.width * ratio).png({
516
+ quality: 100
517
+ }).toFile(filePath).then(({
518
+ width,
519
+ height
699
520
  }) => {
700
- const filePath = _path.default.resolve(imageSetPath, `${logoFileName}${suffix}.png`);
701
- return logo.clone().resize(logoWidth * ratio).png({
702
- quality: 100
703
- }).toFile(filePath).then(({
521
+ log.write(filePath, {
704
522
  width,
705
523
  height
706
- }) => {
707
- log.write(filePath, {
708
- width,
709
- height
710
- });
711
- });
712
- }));
713
- if (!isExpo) {
714
- const infoPlistPath = _path.default.resolve(iosOutputPath, "Info.plist");
715
- const infoPlist = _plist.default.parse(hfs.text(infoPlistPath));
716
- infoPlist["UILaunchStoryboardName"] = "BootSplash";
717
- const formatted = (0, _xmlFormatter.default)(_plist.default.build(infoPlist), {
718
- collapseContent: true,
719
- forceSelfClosingEmptyTag: false,
720
- indentation: "\t",
721
- lineSeparator: "\n",
722
- whiteSpaceAtEndOfSelfclosingTag: false
723
- }).replace(/<string\/>/gm, "<string></string>").replace(/^\t/gm, "");
724
- hfs.write(infoPlistPath, formatted);
725
- log.write(infoPlistPath);
726
- const pbxprojectPath = Expo.IOSConfig.Paths.getPBXProjectPath(projectRoot);
727
- const xcodeProjectPath = Expo.IOSConfig.Paths.getXcodeProjectPath(projectRoot);
728
- const project = Expo.IOSConfig.XcodeUtils.getPbxproj(projectRoot);
729
- const projectName = _path.default.basename(iosOutputPath);
730
- const groupName = _path.default.parse(xcodeProjectPath).name;
731
- Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
732
- project,
733
- filepath: _path.default.join(projectName, "BootSplash.storyboard"),
734
- groupName,
735
- isBuildFile: true
736
524
  });
737
- Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
738
- project,
739
- filepath: _path.default.join(projectName, "Colors.xcassets"),
740
- groupName,
741
- isBuildFile: true
742
- });
743
- hfs.write(pbxprojectPath, project.writeSync());
744
- log.write(pbxprojectPath);
745
- }
746
- }
747
- if (htmlTemplatePath != null) {
748
- log.title("🌐", "Web");
749
- const htmlTemplate = readXmlLike(htmlTemplatePath);
750
- const {
751
- format
752
- } = await logo.metadata();
753
- const prevStyle = htmlTemplate.root.querySelector("#bootsplash-style");
754
- const base64 = (format === "svg" ? hfs.buffer(logoPath) : await logo.clone().resize(Math.round(logoWidth * 2)).png({
755
- quality: 100
756
- }).toBuffer()).toString("base64");
757
- const dataURI = `data:image/${format ? "svg+xml" : "png"};base64,${base64}`;
758
- const nextStyle = (0, _nodeHtmlParser.parse)((0, _tsDedent.dedent)`
759
- <style id="bootsplash-style">
760
- #bootsplash {
761
- position: absolute;
762
- top: 0;
763
- bottom: 0;
764
- left: 0;
765
- right: 0;
766
- overflow: hidden;
767
- display: flex;
768
- justify-content: center;
769
- align-items: center;
770
- background-color: ${background.hex};
771
- }
772
- #bootsplash-logo {
773
- content: url("${dataURI}");
774
- width: ${logoWidth}px;
775
- height: ${logoHeight}px;
776
- }
777
- </style>
778
- `);
779
- if (prevStyle != null) {
780
- prevStyle.replaceWith(nextStyle);
781
- } else {
782
- htmlTemplate.root.querySelector("head")?.appendChild(nextStyle);
783
- }
784
- const prevDiv = htmlTemplate.root.querySelector("#bootsplash");
785
- const nextDiv = (0, _nodeHtmlParser.parse)((0, _tsDedent.dedent)`
786
- <div id="bootsplash">
787
- <div id="bootsplash-logo"></div>
788
- </div>
789
- `);
790
- if (prevDiv != null) {
791
- prevDiv.replaceWith(nextDiv);
792
- } else {
793
- htmlTemplate.root.querySelector("body")?.appendChild(nextDiv);
794
- }
795
- await writeXmlLike(htmlTemplatePath, htmlTemplate.root.toString(), {
796
- ...htmlTemplate.formatOptions,
797
- formatter: "prettier",
798
- useCssPlugin: true
799
525
  });
526
+ }));
527
+ };
528
+ const writeWebAssets = async ({
529
+ htmlTemplatePath,
530
+ props
531
+ }) => {
532
+ const {
533
+ background,
534
+ logo
535
+ } = props;
536
+ log.title("🌐", "Web");
537
+ const htmlTemplate = readXmlLike(htmlTemplatePath);
538
+ const {
539
+ format
540
+ } = await logo.image.metadata();
541
+ const prevStyle = htmlTemplate.root.querySelector("#bootsplash-style");
542
+ const base64 = (format === "svg" ? hfs.buffer(logo.path) : await logo.image.clone().resize(Math.round(logo.width * 2)).png({
543
+ quality: 100
544
+ }).toBuffer()).toString("base64");
545
+ const dataURI = `data:image/${format ? "svg+xml" : "png"};base64,${base64}`;
546
+ const nextStyle = (0, _nodeHtmlParser.parse)((0, _tsDedent.dedent)`
547
+ <style id="bootsplash-style">
548
+ #bootsplash {
549
+ position: absolute;
550
+ top: 0;
551
+ bottom: 0;
552
+ left: 0;
553
+ right: 0;
554
+ overflow: hidden;
555
+ display: flex;
556
+ justify-content: center;
557
+ align-items: center;
558
+ background-color: ${background.hex};
559
+ }
560
+ #bootsplash-logo {
561
+ content: url("${dataURI}");
562
+ width: ${logo.width}px;
563
+ height: ${logo.height}px;
564
+ }
565
+ </style>
566
+ `);
567
+ if (prevStyle != null) {
568
+ prevStyle.replaceWith(nextStyle);
569
+ } else {
570
+ htmlTemplate.root.querySelector("head")?.appendChild(nextStyle);
571
+ }
572
+ const prevDiv = htmlTemplate.root.querySelector("#bootsplash");
573
+ const nextDiv = (0, _nodeHtmlParser.parse)((0, _tsDedent.dedent)`
574
+ <div id="bootsplash">
575
+ <div id="bootsplash-logo"></div>
576
+ </div>
577
+ `);
578
+ if (prevDiv != null) {
579
+ prevDiv.replaceWith(nextDiv);
580
+ } else {
581
+ htmlTemplate.root.querySelector("body")?.appendChild(nextDiv);
800
582
  }
583
+ await writeXmlLike(htmlTemplatePath, htmlTemplate.root.toString(), {
584
+ ...htmlTemplate.formatOptions,
585
+ formatter: "prettier",
586
+ useCssPlugin: true
587
+ });
588
+ };
589
+ const writeGenericAssets = async ({
590
+ props
591
+ }) => {
592
+ const {
593
+ assetsOutputPath,
594
+ background,
595
+ logo
596
+ } = props;
801
597
  log.title("📄", "Assets");
802
598
  hfs.ensureDir(assetsOutputPath);
803
599
  writeJson(_path.default.resolve(assetsOutputPath, "manifest.json"), {
804
600
  background: background.hex,
805
601
  logo: {
806
- width: logoWidth,
807
- height: logoHeight
602
+ width: logo.width,
603
+ height: logo.height
808
604
  }
809
605
  });
810
606
  await Promise.all([{
@@ -827,7 +623,7 @@ const generate = async ({
827
623
  suffix
828
624
  }) => {
829
625
  const filePath = _path.default.resolve(assetsOutputPath, `logo${suffix}.png`);
830
- return logo.clone().resize(Math.round(logoWidth * ratio)).png({
626
+ return logo.image.clone().resize(Math.round(logo.width * ratio)).png({
831
627
  quality: 100
832
628
  }).toFile(filePath).then(({
833
629
  width,
@@ -839,30 +635,175 @@ const generate = async ({
839
635
  });
840
636
  });
841
637
  }));
638
+ };
639
+ const requireAddon = ({
640
+ executeAddon,
641
+ licenseKey
642
+ }) => {
842
643
  if (licenseKey != null && executeAddon) {
843
- const addon = requireAddon();
844
- await addon?.execute({
845
- licenseKey,
846
- isExpo,
847
- fileNameSuffix,
644
+ try {
645
+ return require("./addon");
646
+ } catch {
647
+ return;
648
+ }
649
+ }
650
+ };
651
+ const generate = async ({
652
+ platforms,
653
+ html,
654
+ flavor,
655
+ plist,
656
+ ...rawProps
657
+ }) => {
658
+ const props = await transformProps(workingPath, rawProps);
659
+ const addon = requireAddon(props);
660
+ const {
661
+ background,
662
+ brand
663
+ } = props;
664
+ const androidOutputPath = getAndroidOutputPath({
665
+ flavor,
666
+ platforms
667
+ });
668
+ const iosOutputPath = getIOSOutputPath({
669
+ platforms
670
+ });
671
+ const htmlTemplatePath = getHtmlTemplatePath({
672
+ html,
673
+ platforms
674
+ });
675
+ if (androidOutputPath != null) {
676
+ await writeAndroidAssets({
848
677
  androidOutputPath,
678
+ props
679
+ });
680
+ const manifestXmlPath = _path.default.resolve(androidOutputPath, "..", "AndroidManifest.xml");
681
+ if (hfs.exists(manifestXmlPath)) {
682
+ const manifestXml = readXmlLike(manifestXmlPath);
683
+ const activities = manifestXml.root.querySelectorAll("activity");
684
+ for (const activity of activities) {
685
+ if (activity.getAttribute("android:name") === ".MainActivity") {
686
+ activity.setAttribute("android:theme", "@style/BootTheme");
687
+ }
688
+ }
689
+ await writeXmlLike(manifestXmlPath, manifestXml.root.toString(), {
690
+ ...manifestXml.formatOptions,
691
+ formatter: "prettier",
692
+ htmlWhitespaceSensitivity: "ignore",
693
+ selfClosingTags: true,
694
+ singleAttributePerLine: true
695
+ });
696
+ } else {
697
+ log.warn("No AndroidManifest.xml found");
698
+ }
699
+ const valuesPath = _path.default.resolve(androidOutputPath, "values");
700
+ hfs.ensureDir(valuesPath);
701
+ const colorsXmlPath = _path.default.resolve(valuesPath, "colors.xml");
702
+ const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
703
+ if (hfs.exists(colorsXmlPath)) {
704
+ const colorsXml = readXmlLike(colorsXmlPath);
705
+ const nextColor = (0, _nodeHtmlParser.parse)(colorsXmlEntry);
706
+ const prevColor = colorsXml.root.querySelector('color[name="bootsplash_background"]');
707
+ if (prevColor != null) {
708
+ prevColor.replaceWith(nextColor);
709
+ } else {
710
+ colorsXml.root.querySelector("resources")?.appendChild(nextColor);
711
+ }
712
+ await writeXmlLike(colorsXmlPath, colorsXml.root.toString(), {
713
+ ...colorsXml.formatOptions,
714
+ formatter: "xmlFormatter"
715
+ });
716
+ } else {
717
+ await writeXmlLike(colorsXmlPath, `<resources>${colorsXmlEntry}</resources>`, {
718
+ formatter: "xmlFormatter"
719
+ });
720
+ }
721
+ const stylesXmlPath = _path.default.resolve(valuesPath, "styles.xml");
722
+ if (hfs.exists(stylesXmlPath)) {
723
+ const stylesXml = readXmlLike(stylesXmlPath);
724
+ const prevStyle = stylesXml.root.querySelector('style[name="BootTheme"]');
725
+ const parent = prevStyle?.getAttribute("parent") ?? "Theme.BootSplash";
726
+ const extraItems = (0, _nodeHtmlParser.parse)(prevStyle?.text.split("\n").map(line => line.trim()).join("") ?? "").childNodes.filter(node => {
727
+ if (!(node instanceof _nodeHtmlParser.HTMLElement)) {
728
+ return true;
729
+ }
730
+ const name = node.getAttribute("name");
731
+ return name !== "bootSplashBackground" && name !== "bootSplashLogo" && name !== "bootSplashBrand" && name !== "postBootSplashTheme";
732
+ }).map(node => node.toString());
733
+ const styleItems = [...(extraItems.length > 0 ? [...extraItems, ""] : []), '<item name="bootSplashBackground">@color/bootsplash_background</item>', '<item name="bootSplashLogo">@drawable/bootsplash_logo</item>', ...(brand != null ? ['<item name="bootSplashBrand">@drawable/bootsplash_brand</item>'] : []), '<item name="postBootSplashTheme">@style/AppTheme</item>'];
734
+ const nextStyle = (0, _nodeHtmlParser.parse)((0, _tsDedent.dedent)`
735
+ <style name="BootTheme" parent="${parent}">
736
+ ${styleItems.join("\n")}
737
+ </style>
738
+ `);
739
+ prevStyle?.remove(); // remove the existing style
740
+ stylesXml.root.querySelector("resources")?.appendChild(nextStyle);
741
+ await writeXmlLike(stylesXmlPath, stylesXml.root.toString(), {
742
+ ...stylesXml.formatOptions,
743
+ formatter: "prettier",
744
+ htmlWhitespaceSensitivity: "ignore"
745
+ });
746
+ } else {
747
+ log.warn("No styles.xml found");
748
+ }
749
+ }
750
+ if (iosOutputPath != null) {
751
+ await writeIOSAssets({
849
752
  iosOutputPath,
753
+ props
754
+ });
755
+ const infoPlistPath = getInfoPlistPath({
756
+ iosOutputPath,
757
+ plist
758
+ });
759
+ if (infoPlistPath != null) {
760
+ const infoPlist = _plist.default.parse(hfs.text(infoPlistPath));
761
+ infoPlist["UILaunchStoryboardName"] = "BootSplash";
762
+ const formatted = (0, _xmlFormatter.default)(_plist.default.build(infoPlist), {
763
+ collapseContent: true,
764
+ forceSelfClosingEmptyTag: false,
765
+ indentation: "\t",
766
+ lineSeparator: "\n",
767
+ whiteSpaceAtEndOfSelfclosingTag: false
768
+ }).replace(/<string\/>/gm, "<string></string>").replace(/^\t/gm, "");
769
+ hfs.write(infoPlistPath, formatted);
770
+ log.write(infoPlistPath);
771
+ }
772
+ const pbxprojectPath = Expo.IOSConfig.Paths.getPBXProjectPath(projectRoot);
773
+ const xcodeProjectPath = Expo.IOSConfig.Paths.getXcodeProjectPath(projectRoot);
774
+ const project = Expo.IOSConfig.XcodeUtils.getPbxproj(projectRoot);
775
+ const projectName = _path.default.basename(iosOutputPath);
776
+ const groupName = _path.default.parse(xcodeProjectPath).name;
777
+ Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
778
+ project,
779
+ filepath: _path.default.join(projectName, "BootSplash.storyboard"),
780
+ groupName,
781
+ isBuildFile: true
782
+ });
783
+ Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
784
+ project,
785
+ filepath: _path.default.join(projectName, "Colors.xcassets"),
786
+ groupName,
787
+ isBuildFile: true
788
+ });
789
+ hfs.write(pbxprojectPath, project.writeSync());
790
+ log.write(pbxprojectPath);
791
+ }
792
+ if (htmlTemplatePath != null) {
793
+ await writeWebAssets({
850
794
  htmlTemplatePath,
851
- assetsOutputPath,
852
- logoHeight,
853
- logoWidth,
854
- brandHeight,
855
- brandWidth,
856
- logoPath,
857
- darkLogoPath,
858
- brandPath,
859
- darkBrandPath,
860
- background,
861
- logo,
862
- brand,
863
- darkBackground,
864
- darkLogo,
865
- darkBrand
795
+ props
796
+ });
797
+ }
798
+ await writeGenericAssets({
799
+ props
800
+ });
801
+ if (addon != null) {
802
+ await addon.execute({
803
+ props,
804
+ androidOutputPath,
805
+ iosOutputPath,
806
+ htmlTemplatePath
866
807
  });
867
808
  } else {
868
809
  console.log(`
@@ -873,5 +814,308 @@ ${_picocolors.default.blue("┗━━━━━━━━━━━━━━━━
873
814
  }
874
815
  console.log(`\n💖 Thanks for using ${_picocolors.default.underline("react-native-bootsplash")}`);
875
816
  };
817
+
818
+ // Expo plugin
876
819
  exports.generate = generate;
820
+ const withoutExpoSplashScreen = Expo.createRunOncePlugin(expoConfig => expoConfig, "expo-splash-screen", "skip");
821
+ const withAndroidAssets = (expoConfig, rawProps) => Expo.withDangerousMod(expoConfig, ["android", async config => {
822
+ const {
823
+ platformProjectRoot,
824
+ projectRoot
825
+ } = config.modRequest;
826
+ const props = await transformProps(projectRoot, rawProps);
827
+ const addon = requireAddon(props);
828
+ const androidOutputPath = _path.default.resolve(platformProjectRoot, "app", "src", "main", "res");
829
+ await writeAndroidAssets({
830
+ androidOutputPath,
831
+ props
832
+ });
833
+ await addon?.writeAndroidAssets({
834
+ androidOutputPath,
835
+ props
836
+ });
837
+ return config;
838
+ }]);
839
+ const withAndroidManifest = expoConfig => Expo.withAndroidManifest(expoConfig, config => {
840
+ config.modResults.manifest.application?.forEach(application => {
841
+ if (application.$["android:name"] === ".MainApplication") {
842
+ const {
843
+ activity
844
+ } = application;
845
+ activity?.forEach(activity => {
846
+ if (activity.$["android:name"] === ".MainActivity") {
847
+ activity.$["android:theme"] = "@style/BootTheme";
848
+ }
849
+ });
850
+ }
851
+ });
852
+ return config;
853
+ });
854
+ const withMainActivity = expoConfig => Expo.withMainActivity(expoConfig, config => {
855
+ const {
856
+ modResults
857
+ } = config;
858
+ const withImports = (0, _codeMod.addImports)(modResults.contents.replace(/(\/\/ )?setTheme\(R\.style\.AppTheme\)/, "// setTheme(R.style.AppTheme)"), ["android.os.Bundle", "com.zoontek.rnbootsplash.RNBootSplash"], false);
859
+
860
+ // indented with 4 spaces
861
+ const withInit = (0, _generateCode.mergeContents)({
862
+ src: withImports,
863
+ comment: " //",
864
+ tag: "bootsplash-init",
865
+ offset: 0,
866
+ anchor: /super\.onCreate\(null\)/,
867
+ newSrc: " RNBootSplash.init(this, R.style.BootTheme)"
868
+ });
869
+ return {
870
+ ...config,
871
+ modResults: {
872
+ ...modResults,
873
+ contents: withInit.contents
874
+ }
875
+ };
876
+ });
877
+ const withAndroidStyles = (expoConfig, rawProps) => Expo.withAndroidStyles(expoConfig, async config => {
878
+ const {
879
+ projectRoot
880
+ } = config.modRequest;
881
+ const {
882
+ android,
883
+ brand
884
+ } = await transformProps(projectRoot, rawProps);
885
+ const {
886
+ darkContentBarsStyle
887
+ } = android;
888
+ const {
889
+ modResults
890
+ } = config;
891
+ const {
892
+ resources
893
+ } = modResults;
894
+ const {
895
+ style = []
896
+ } = resources;
897
+ const item = [{
898
+ $: {
899
+ name: "postBootSplashTheme"
900
+ },
901
+ _: "@style/AppTheme"
902
+ }, {
903
+ $: {
904
+ name: "bootSplashBackground"
905
+ },
906
+ _: "@color/bootsplash_background"
907
+ }, {
908
+ $: {
909
+ name: "bootSplashLogo"
910
+ },
911
+ _: "@drawable/bootsplash_logo"
912
+ }];
913
+ if (brand != null) {
914
+ item.push({
915
+ $: {
916
+ name: "bootSplashBrand"
917
+ },
918
+ _: "@drawable/bootsplash_brand"
919
+ });
920
+ }
921
+ if (darkContentBarsStyle != null) {
922
+ item.push({
923
+ $: {
924
+ name: "darkContentBarsStyle"
925
+ },
926
+ _: String(darkContentBarsStyle)
927
+ });
928
+ }
929
+ const withBootTheme = [...style.filter(({
930
+ $
931
+ }) => $.name !== "BootTheme"), {
932
+ $: {
933
+ name: "BootTheme",
934
+ parent: "Theme.BootSplash"
935
+ },
936
+ item
937
+ }];
938
+ return {
939
+ ...config,
940
+ modResults: {
941
+ ...modResults,
942
+ resources: {
943
+ ...resources,
944
+ style: withBootTheme
945
+ }
946
+ }
947
+ };
948
+ });
949
+ const withAndroidColors = (expoConfig, rawProps) => Expo.withAndroidColors(expoConfig, async config => {
950
+ const {
951
+ projectRoot
952
+ } = config.modRequest;
953
+ const {
954
+ background
955
+ } = await transformProps(projectRoot, rawProps);
956
+ config.modResults = (0, _Colors.assignColorValue)(config.modResults, {
957
+ name: "bootsplash_background",
958
+ value: background.hex
959
+ });
960
+ return config;
961
+ });
962
+ const withAndroidColorsNight = (expoConfig, rawProps) => Expo.withAndroidColorsNight(expoConfig, async config => {
963
+ const {
964
+ projectRoot
965
+ } = config.modRequest;
966
+ const props = await transformProps(projectRoot, rawProps);
967
+ const addon = requireAddon(props);
968
+ return addon != null ? await addon.withAndroidColorsNight({
969
+ config,
970
+ props
971
+ }) : config;
972
+ });
973
+ const withIOSAssets = (expoConfig, rawProps) => Expo.withDangerousMod(expoConfig, ["ios", async config => {
974
+ const {
975
+ platformProjectRoot,
976
+ projectName = "",
977
+ projectRoot
978
+ } = config.modRequest;
979
+ const props = await transformProps(projectRoot, rawProps);
980
+ const addon = requireAddon(props);
981
+ const iosOutputPath = _path.default.resolve(platformProjectRoot, projectName);
982
+ await writeIOSAssets({
983
+ iosOutputPath,
984
+ props
985
+ });
986
+ await addon?.writeIOSAssets({
987
+ iosOutputPath,
988
+ props
989
+ });
990
+ return config;
991
+ }]);
992
+ const withAppDelegate = expoConfig => Expo.withAppDelegate(expoConfig, config => {
993
+ const {
994
+ modResults
995
+ } = config;
996
+ const {
997
+ language
998
+ } = modResults;
999
+ if (language !== "swift") {
1000
+ throw new Error(`Cannot modify the project AppDelegate as it's not in a supported language: ${language}`);
1001
+ }
1002
+ const withHeader = (0, _generateCode.mergeContents)({
1003
+ src: modResults.contents,
1004
+ comment: "//",
1005
+ tag: "bootsplash-header",
1006
+ offset: 1,
1007
+ anchor: /import Expo/,
1008
+ newSrc: "import RNBootSplash"
1009
+ });
1010
+ const withRootView = (0, _generateCode.mergeContents)({
1011
+ src: withHeader.contents,
1012
+ comment: "//",
1013
+ tag: "bootsplash-init",
1014
+ offset: 1,
1015
+ anchor: /class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {/,
1016
+ newSrc: (0, _tsDedent.dedent)`
1017
+ public override func customize(_ rootView: UIView) {
1018
+ super.customize(rootView)
1019
+ RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView)
1020
+ }
1021
+ `
1022
+ });
1023
+ return {
1024
+ ...config,
1025
+ modResults: {
1026
+ ...modResults,
1027
+ contents: withRootView.contents
1028
+ }
1029
+ };
1030
+ });
1031
+ const withInfoPlist = expoConfig => Expo.withInfoPlist(expoConfig, config => {
1032
+ config.modResults["UILaunchStoryboardName"] = "BootSplash";
1033
+ return config;
1034
+ });
1035
+ const withXcodeProject = expoConfig => Expo.withXcodeProject(expoConfig, config => {
1036
+ const {
1037
+ projectName = ""
1038
+ } = config.modRequest;
1039
+ Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
1040
+ filepath: _path.default.join(projectName, "BootSplash.storyboard"),
1041
+ groupName: projectName,
1042
+ project: config.modResults,
1043
+ isBuildFile: true
1044
+ });
1045
+ Expo.IOSConfig.XcodeUtils.addResourceFileToGroup({
1046
+ filepath: _path.default.join(projectName, "Colors.xcassets"),
1047
+ groupName: projectName,
1048
+ project: config.modResults,
1049
+ isBuildFile: true
1050
+ });
1051
+ return config;
1052
+ });
1053
+ const withWebAssets = (expoConfig, rawProps) => Expo.withDangerousMod(expoConfig, [expoConfig.platforms?.includes("ios") ? "ios" : "android", async config => {
1054
+ const {
1055
+ projectRoot
1056
+ } = config.modRequest;
1057
+ const props = await transformProps(projectRoot, rawProps);
1058
+ const addon = requireAddon(props);
1059
+ const fileName = "public/index.html";
1060
+ const htmlTemplatePath = _path.default.resolve(projectRoot, fileName);
1061
+ if (!hfs.exists(htmlTemplatePath)) {
1062
+ await exec(`npx expo customize ${fileName}`);
1063
+ }
1064
+ await writeWebAssets({
1065
+ htmlTemplatePath,
1066
+ props
1067
+ });
1068
+ await addon?.writeWebAssets({
1069
+ htmlTemplatePath,
1070
+ props
1071
+ });
1072
+ return config;
1073
+ }]);
1074
+ const withGenericAssets = (expoConfig, rawProps) => Expo.withDangerousMod(expoConfig, [expoConfig.platforms?.includes("ios") ? "ios" : "android", async config => {
1075
+ const {
1076
+ projectRoot
1077
+ } = config.modRequest;
1078
+ const props = await transformProps(projectRoot, rawProps);
1079
+ const addon = requireAddon(props);
1080
+ await writeGenericAssets({
1081
+ props
1082
+ });
1083
+ await addon?.writeGenericAssets({
1084
+ props
1085
+ });
1086
+ return config;
1087
+ }]);
1088
+ const withBootSplash = exports.withBootSplash = Expo.createRunOncePlugin((config, baseProps) => {
1089
+ const {
1090
+ platforms = [],
1091
+ sdkVersion = "0.1.0"
1092
+ } = config;
1093
+ isExpo = true;
1094
+ if (_semver.default.lt(sdkVersion, "54.0.0")) {
1095
+ log.error("Requires Expo 54.0.0 (or higher)");
1096
+ process.exit(1);
1097
+ }
1098
+ if (!baseProps?.logo) {
1099
+ log.error("Missing required parameter 'logo'");
1100
+ process.exit(1);
1101
+ }
1102
+ const rawProps = {
1103
+ assetsOutput: "assets/bootsplash",
1104
+ background: "#fff",
1105
+ brandWidth: 80,
1106
+ logoWidth: 100,
1107
+ ...baseProps
1108
+ };
1109
+ const plugins = [withoutExpoSplashScreen, withGenericAssets];
1110
+ if (platforms.includes("android")) {
1111
+ plugins.push(withAndroidAssets, withAndroidManifest, withMainActivity, withAndroidStyles, withAndroidColors, withAndroidColorsNight);
1112
+ }
1113
+ if (platforms.includes("ios")) {
1114
+ plugins.push(withIOSAssets, withAppDelegate, withInfoPlist, withXcodeProject);
1115
+ }
1116
+ if (platforms.includes("web")) {
1117
+ plugins.push(withWebAssets);
1118
+ }
1119
+ return Expo.withPlugins(config, plugins.map(plugin => [plugin, rawProps]));
1120
+ }, PACKAGE_NAME);
877
1121
  //# sourceMappingURL=generate.js.map