react-native-bootsplash 5.2.2 → 5.4.0

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/src/generate.ts CHANGED
@@ -1,10 +1,13 @@
1
+ import murmurhash from "@emotion/hash";
2
+ import { IOSConfig } from "@expo/config-plugins";
3
+ import plist from "@expo/plist";
4
+ import { findProjectRoot } from "@react-native-community/cli-tools";
1
5
  import {
2
6
  AndroidProjectConfig,
3
- CommandFunction,
4
7
  IOSProjectConfig,
5
8
  } from "@react-native-community/cli-types";
6
9
  import detectIndent from "detect-indent";
7
- import fs from "fs-extra";
10
+ import fs from "fs";
8
11
  import { parse as parseHtml } from "node-html-parser";
9
12
  import path from "path";
10
13
  import pc from "picocolors";
@@ -102,6 +105,42 @@ const getStoryboard = ({
102
105
  `;
103
106
  };
104
107
 
108
+ export const addFileToXcodeProject = (filePath: string) => {
109
+ const projectRoot = findProjectRoot(workingPath);
110
+
111
+ const pbxprojectPath = IOSConfig.Paths.getPBXProjectPath(projectRoot);
112
+ const project = IOSConfig.XcodeUtils.getPbxproj(projectRoot);
113
+ const xcodeProjectPath = IOSConfig.Paths.getXcodeProjectPath(projectRoot);
114
+
115
+ IOSConfig.XcodeUtils.addResourceFileToGroup({
116
+ filepath: filePath,
117
+ groupName: path.parse(xcodeProjectPath).name,
118
+ project,
119
+ });
120
+
121
+ hfs.write(pbxprojectPath, project.writeSync());
122
+ logWrite(pbxprojectPath);
123
+ };
124
+
125
+ // Freely inspired by https://github.com/humanwhocodes/humanfs
126
+ export const hfs = {
127
+ buffer: (path: string) => fs.readFileSync(path),
128
+ exists: (path: string) => fs.existsSync(path),
129
+ json: (path: string) => JSON.parse(fs.readFileSync(path, "utf-8")) as unknown,
130
+ readDir: (path: string) => fs.readdirSync(path, "utf-8"),
131
+ realPath: (path: string) => fs.realpathSync(path, "utf-8"),
132
+ rm: (path: string) => fs.rmSync(path, { force: true }),
133
+ text: (path: string) => fs.readFileSync(path, "utf-8"),
134
+
135
+ ensureDir: (dir: string) => {
136
+ fs.mkdirSync(dir, { recursive: true });
137
+ },
138
+ write: (file: string, data: string) => {
139
+ const trimmed = data.trim();
140
+ fs.writeFileSync(file, trimmed === "" ? trimmed : trimmed + "\n", "utf-8");
141
+ },
142
+ };
143
+
105
144
  export const log = {
106
145
  error: (text: string) => console.log(pc.red(`❌ ${text}`)),
107
146
  text: (text: string) => console.log(text),
@@ -120,12 +159,12 @@ export const logWrite = (
120
159
  );
121
160
 
122
161
  export const writeJson = (file: string, json: object) => {
123
- fs.writeFileSync(file, JSON.stringify(json, null, 2) + "\n", "utf-8");
162
+ hfs.write(file, JSON.stringify(json, null, 2));
124
163
  logWrite(file);
125
164
  };
126
165
 
127
166
  export const readXml = (file: string) => {
128
- const xml = fs.readFileSync(file, "utf-8");
167
+ const xml = hfs.text(file);
129
168
  const { indent } = detectIndent(xml);
130
169
 
131
170
  const formatOptions: XMLFormatterOptions = {
@@ -149,12 +188,12 @@ export const writeXml = (
149
188
  ...options,
150
189
  });
151
190
 
152
- fs.writeFileSync(file, formatted + "\n", "utf-8");
191
+ hfs.write(file, formatted);
153
192
  logWrite(file);
154
193
  };
155
194
 
156
195
  export const readHtml = (file: string) => {
157
- const html = fs.readFileSync(file, "utf-8");
196
+ const html = hfs.text(file);
158
197
  const { type, amount } = detectIndent(html);
159
198
 
160
199
  const formatOptions: PrettierOptions = {
@@ -178,10 +217,37 @@ export const writeHtml = async (
178
217
  ...options,
179
218
  });
180
219
 
181
- fs.writeFileSync(file, formatted, "utf-8");
220
+ hfs.write(file, formatted);
182
221
  logWrite(file);
183
222
  };
184
223
 
224
+ export const cleanIOSAssets = (dir: string, prefix: string) => {
225
+ hfs
226
+ .readDir(dir)
227
+ .filter((file) => file.startsWith(prefix) && file.endsWith(".png"))
228
+ .map((file) => path.join(dir, file))
229
+ .forEach((file) => hfs.rm(file));
230
+ };
231
+
232
+ export const getIOSAssetFileName = async ({
233
+ name,
234
+ image,
235
+ width,
236
+ }: {
237
+ name: string;
238
+ image: Sharp;
239
+ width: number;
240
+ }) => {
241
+ const buffer = await image
242
+ .clone()
243
+ .resize(width)
244
+ .png({ quality: 100 })
245
+ .toBuffer();
246
+
247
+ const hash = murmurhash(buffer.toString("base64"));
248
+ return `${name}-${hash}`;
249
+ };
250
+
185
251
  const ensureSupportedFormat = async (
186
252
  name: string,
187
253
  image: Sharp | undefined,
@@ -222,7 +288,7 @@ const getAndroidResPath = (
222
288
  "res",
223
289
  );
224
290
 
225
- if (!fs.existsSync(androidResPath)) {
291
+ if (!hfs.exists(androidResPath)) {
226
292
  log.warn(
227
293
  `No ${path.relative(
228
294
  workingPath,
@@ -256,7 +322,7 @@ const getIOSProjectPath = (ios: IOSProjectConfig): string | undefined => {
256
322
  .resolve(ios.sourceDir, ios.xcodeProject.name)
257
323
  .replace(/\.(xcodeproj|xcworkspace)$/, "");
258
324
 
259
- if (!fs.existsSync(iosProjectPath)) {
325
+ if (!hfs.exists(iosProjectPath)) {
260
326
  log.warn(
261
327
  `No ${path.relative(
262
328
  workingPath,
@@ -271,7 +337,7 @@ const getIOSProjectPath = (ios: IOSProjectConfig): string | undefined => {
271
337
  const getHtmlTemplatePath = (html: string): string | undefined => {
272
338
  const htmlTemplatePath = path.resolve(workingPath, html);
273
339
 
274
- if (!fs.existsSync(htmlTemplatePath)) {
340
+ if (!hfs.exists(htmlTemplatePath)) {
275
341
  log.warn(
276
342
  `No ${path.relative(
277
343
  workingPath,
@@ -314,14 +380,25 @@ const requireAddon = ():
314
380
  | { execute: (config: AddonConfig) => Promise<void> }
315
381
  | undefined => {
316
382
  try {
317
- // eslint-disable-next-line
318
- return require("./addon");
383
+ return require("./addon"); // eslint-disable-line
319
384
  } catch {
320
385
  return;
321
386
  }
322
387
  };
323
388
 
324
- export const generate: CommandFunction<{
389
+ export const generate = async ({
390
+ android,
391
+ ios,
392
+ platforms,
393
+ html,
394
+ flavor,
395
+ licenseKey,
396
+ ...args
397
+ }: {
398
+ android?: AndroidProjectConfig;
399
+ ios?: IOSProjectConfig;
400
+
401
+ logo: string;
325
402
  platforms: string[];
326
403
  background: string;
327
404
  logoWidth: number;
@@ -335,11 +412,7 @@ export const generate: CommandFunction<{
335
412
  darkBackground?: string;
336
413
  darkLogo?: string;
337
414
  darkBrand?: string;
338
- }> = async (
339
- [argsLogo],
340
- { project: { android, ios } },
341
- { platforms, html, flavor, licenseKey, ...args },
342
- ) => {
415
+ }) => {
343
416
  const [nodeStringVersion = ""] = process.versions.node.split(".");
344
417
  const nodeVersion = parseInt(nodeStringVersion, 10);
345
418
 
@@ -348,12 +421,7 @@ export const generate: CommandFunction<{
348
421
  process.exit(1);
349
422
  }
350
423
 
351
- if (argsLogo == null) {
352
- log.error("Missing required argument 'logo'");
353
- process.exit(1);
354
- }
355
-
356
- const logoPath = path.resolve(workingPath, argsLogo);
424
+ const logoPath = path.resolve(workingPath, args.logo);
357
425
 
358
426
  const darkLogoPath =
359
427
  args.darkLogo != null
@@ -472,12 +540,12 @@ export const generate: CommandFunction<{
472
540
  log.title("🤖", "Android");
473
541
 
474
542
  const valuesPath = path.resolve(androidResPath, "values");
475
- fs.ensureDirSync(valuesPath);
543
+ hfs.ensureDir(valuesPath);
476
544
 
477
545
  const colorsXmlPath = path.resolve(valuesPath, "colors.xml");
478
546
  const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
479
547
 
480
- if (fs.existsSync(colorsXmlPath)) {
548
+ if (hfs.exists(colorsXmlPath)) {
481
549
  const { root, formatOptions } = readXml(colorsXmlPath);
482
550
  const nextColor = parseHtml(colorsXmlEntry);
483
551
  const prevColor = root.querySelector(
@@ -508,7 +576,7 @@ export const generate: CommandFunction<{
508
576
  `drawable-${suffix}`,
509
577
  );
510
578
 
511
- fs.ensureDirSync(drawableDirPath);
579
+ hfs.ensureDir(drawableDirPath);
512
580
 
513
581
  // https://developer.android.com/develop/ui/views/launch/splash-screen#dimensions
514
582
  const canvasSize = 288 * ratio;
@@ -568,29 +636,60 @@ export const generate: CommandFunction<{
568
636
  { whiteSpaceAtEndOfSelfclosingTag: false },
569
637
  );
570
638
 
639
+ addFileToXcodeProject(storyboardPath);
640
+
641
+ const infoPlistPath = path.join(iosProjectPath, "Info.plist");
642
+
643
+ const infoPlist = plist.parse(hfs.text(infoPlistPath)) as Record<
644
+ string,
645
+ unknown
646
+ >;
647
+
648
+ infoPlist["UILaunchStoryboardName"] = "BootSplash.storyboard";
649
+
650
+ const formatted = formatXml(plist.build(infoPlist), {
651
+ collapseContent: true,
652
+ forceSelfClosingEmptyTag: false,
653
+ indentation: "\t",
654
+ lineSeparator: "\n",
655
+ whiteSpaceAtEndOfSelfclosingTag: false,
656
+ })
657
+ .replace(/<string\/>/gm, "<string></string>")
658
+ .replace(/^\t/gm, "");
659
+
660
+ hfs.write(infoPlistPath, formatted);
661
+ logWrite(infoPlistPath);
662
+
571
663
  const imageSetPath = path.resolve(
572
664
  iosProjectPath,
573
665
  "Images.xcassets",
574
666
  "BootSplashLogo.imageset",
575
667
  );
576
668
 
577
- fs.ensureDirSync(imageSetPath);
669
+ hfs.ensureDir(imageSetPath);
670
+ cleanIOSAssets(imageSetPath, "bootsplash_logo");
671
+
672
+ const logoFileName = await getIOSAssetFileName({
673
+ name: "bootsplash_logo",
674
+ image: logo,
675
+ width: logoWidth,
676
+ });
578
677
 
579
678
  writeJson(path.resolve(imageSetPath, "Contents.json"), {
580
679
  images: [
581
680
  {
582
681
  idiom: "universal",
583
- filename: "bootsplash_logo.png",
682
+ filename: `${logoFileName}.png`,
584
683
  scale: "1x",
585
684
  },
586
685
  {
587
686
  idiom: "universal",
588
- filename: "bootsplash_logo@2x.png",
687
+ filename: `${logoFileName}@2x.png`,
589
688
  scale: "2x",
590
689
  },
591
690
  {
592
691
  idiom: "universal",
593
- filename: "bootsplash_logo@3x.png",
692
+ filename: `${logoFileName}@3x.png`,
594
693
  scale: "3x",
595
694
  },
596
695
  ],
@@ -608,7 +707,7 @@ export const generate: CommandFunction<{
608
707
  ].map(({ ratio, suffix }) => {
609
708
  const filePath = path.resolve(
610
709
  imageSetPath,
611
- `bootsplash_logo${suffix}.png`,
710
+ `${logoFileName}${suffix}.png`,
612
711
  );
613
712
 
614
713
  return logo
@@ -632,7 +731,7 @@ export const generate: CommandFunction<{
632
731
 
633
732
  const base64 = (
634
733
  format === "svg"
635
- ? fs.readFileSync(logoPath)
734
+ ? hfs.buffer(logoPath)
636
735
  : await logo
637
736
  .clone()
638
737
  .resize(Math.round(logoWidth * 2))
@@ -690,7 +789,7 @@ export const generate: CommandFunction<{
690
789
  if (assetsOutputPath != null) {
691
790
  log.title("📄", "Assets");
692
791
 
693
- fs.ensureDirSync(assetsOutputPath);
792
+ hfs.ensureDir(assetsOutputPath);
694
793
 
695
794
  writeJson(path.resolve(assetsOutputPath, "bootsplash_manifest.json"), {
696
795
  background: background.hex,