vxrn 2.0.0-beta.131.1 → 2.0.0-beta.132.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.
@@ -1,4 +1,5 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
2
3
  import module from "node:module";
3
4
  import path from "node:path";
4
5
  import { pathToFileURL } from "node:url";
@@ -247,6 +248,54 @@ function patchIosPbxprojSceneDelegate(rendered, appName) {
247
248
  }
248
249
  return patched;
249
250
  }
251
+ function fontFileName(font) {
252
+ return path.basename(font);
253
+ }
254
+ function pbxprojId(seed) {
255
+ return createHash("sha1").update(seed).digest("hex").slice(0, 24).toUpperCase();
256
+ }
257
+ const GOOGLE_SERVICES_PLIST = "GoogleService-Info.plist";
258
+ const GOOGLE_SERVICES_VERSION = "4.4.4";
259
+ function iosResourceFiles(app) {
260
+ return [...(app.fonts ?? []).map(fontFileName), ...app.ios?.googleServicesFile ? [GOOGLE_SERVICES_PLIST] : []];
261
+ }
262
+ function patchIosPbxprojResources(rendered, appName, names) {
263
+ let patched = rendered;
264
+ for (const name of names) {
265
+ const fileRef = pbxprojId(`one-resource-file:${name}`);
266
+ const buildFile = pbxprojId(`one-resource-build:${name}`);
267
+ const fileType = name.endsWith(".plist") ? "text.plist.xml" : "file";
268
+ const edits = [
269
+ ["/* Images.xcassets in Resources */ = {isa = PBXBuildFile;", ` ${buildFile} /* ${name} in Resources */ = {isa = PBXBuildFile; fileRef = ${fileRef} /* ${name} */; };`],
270
+ ["/* Images.xcassets */ = {isa = PBXFileReference;", ` ${fileRef} /* ${name} */ = {isa = PBXFileReference; lastKnownFileType = ${fileType}; name = ${name}; path = ${appName}/${name}; sourceTree = "<group>"; };`],
271
+ ["/* Images.xcassets */,", ` ${fileRef} /* ${name} */,`],
272
+ ["/* Images.xcassets in Resources */,", ` ${buildFile} /* ${name} in Resources */,`]
273
+ ];
274
+ for (const [anchor, insertion] of edits) {
275
+ const next = insertAfterLine(patched, anchor, insertion);
276
+ if (next === patched) {
277
+ throw new Error(`[vxrn] prebuild template project.pbxproj lost its Images.xcassets anchor (${anchor})`);
278
+ }
279
+ patched = next;
280
+ }
281
+ }
282
+ return patched;
283
+ }
284
+ function copyAppResources({ root, dest, platform, app }) {
285
+ const copies = (app.fonts ?? []).map((font) => [font, platform === "ios" ? path.join(dest, app.name, fontFileName(font)) : path.join(dest, "app", "src", "main", "assets", "fonts", fontFileName(font))]);
286
+ const googleServices = platform === "ios" ? app.ios?.googleServicesFile : app.android?.googleServicesFile;
287
+ if (googleServices) {
288
+ copies.push([googleServices, platform === "ios" ? path.join(dest, app.name, GOOGLE_SERVICES_PLIST) : path.join(dest, "app", "google-services.json")]);
289
+ }
290
+ for (const [file, target] of copies) {
291
+ const source = path.resolve(root, file);
292
+ if (!FSExtra.existsSync(source)) {
293
+ throw new Error(`[vxrn] native.app names ${file}, which does not exist`);
294
+ }
295
+ FSExtra.mkdirSync(path.dirname(target), { recursive: true });
296
+ FSExtra.copyFileSync(source, target);
297
+ }
298
+ }
250
299
  function patchIosPbxprojAppEntitlements(rendered, appName) {
251
300
  const edits = [["/* AppDelegate.swift */ = {isa = PBXFileReference;", ` ${PUSH_ENTITLEMENTS_FILE_REF_ID} /* ${appName}.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ${appName}.entitlements; path = ${appName}/${appName}.entitlements; sourceTree = "<group>"; };`], ["/* AppDelegate.swift */,", ` ${PUSH_ENTITLEMENTS_FILE_REF_ID} /* ${appName}.entitlements */,`]];
252
301
  let patched = rendered;
@@ -1052,38 +1101,85 @@ ${adaptiveIcon.monochrome ? " <monochrome android:drawable=\"@mipmap/ic_launc
1052
1101
  async function generateSplashScreen(args) {
1053
1102
  const { root, dest, platform, app } = args;
1054
1103
  if (!app.splash) return;
1055
- const source = path.resolve(root, app.splash.source);
1056
- if (!FSExtra.existsSync(source)) {
1057
- throw new Error(`[vxrn] native.app.splash source does not exist: ${source}`);
1058
- }
1059
1104
  const cover = app.splash.resizeMode === "cover";
1060
- const image = sharp(source).rotate();
1061
- const { data: artwork, info: metadata } = await (cover ? image : image.trim({ background: app.splash.backgroundColor })).png().toBuffer({ resolveWithObject: true });
1062
- if (!metadata.width || !metadata.height) {
1063
- throw new Error("[vxrn] native.app.splash source must be an image");
1064
- }
1105
+ const prepareArtwork = async (sourcePath, backgroundColor) => {
1106
+ const source = path.resolve(root, sourcePath);
1107
+ if (!FSExtra.existsSync(source)) {
1108
+ throw new Error(`[vxrn] native.app.splash source does not exist: ${source}`);
1109
+ }
1110
+ const image = sharp(source).rotate();
1111
+ const prepared = await (cover ? image : image.trim({ background: backgroundColor })).png().toBuffer({ resolveWithObject: true });
1112
+ if (!prepared.info.width || !prepared.info.height) {
1113
+ throw new Error("[vxrn] native.app.splash source must be an image");
1114
+ }
1115
+ return prepared;
1116
+ };
1117
+ const { data: artwork, info: metadata } = await prepareArtwork(app.splash.source, app.splash.backgroundColor);
1118
+ const dark = app.splash.dark;
1119
+ const darkArtwork = dark?.source ? (await prepareArtwork(dark.source, dark.backgroundColor)).data : void 0;
1065
1120
  const artworkWidth = app.splash.width ?? 200;
1066
1121
  const artworkHeight = Number((artworkWidth * (metadata.height / metadata.width)).toFixed(3));
1067
1122
  if (platform === "ios") {
1068
1123
  const appDir = path.join(dest, app.name);
1069
1124
  const splashDir = path.join(appDir, "Images.xcassets", "Splash.imageset");
1070
1125
  FSExtra.mkdirSync(splashDir, { recursive: true });
1126
+ const darkAppearance = [{
1127
+ appearance: "luminosity",
1128
+ value: "dark"
1129
+ }];
1071
1130
  await sharp(artwork).toFile(path.join(splashDir, "splash.png"));
1131
+ if (darkArtwork) await sharp(darkArtwork).toFile(path.join(splashDir, "splash-dark.png"));
1072
1132
  FSExtra.writeFileSync(path.join(splashDir, "Contents.json"), `${JSON.stringify({
1073
1133
  images: [{
1074
1134
  filename: "splash.png",
1075
1135
  idiom: "universal",
1076
1136
  scale: "1x"
1077
- }],
1137
+ }, ...darkArtwork ? [{
1138
+ appearances: darkAppearance,
1139
+ filename: "splash-dark.png",
1140
+ idiom: "universal",
1141
+ scale: "1x"
1142
+ }] : []],
1078
1143
  info: {
1079
1144
  author: "xcode",
1080
1145
  version: 1
1081
1146
  }
1082
1147
  }, null, 2)}
1083
1148
  `);
1084
- const red = Number.parseInt(app.splash.backgroundColor.slice(1, 3), 16) / 255;
1085
- const green = Number.parseInt(app.splash.backgroundColor.slice(3, 5), 16) / 255;
1086
- const blue = Number.parseInt(app.splash.backgroundColor.slice(5, 7), 16) / 255;
1149
+ const srgb = (hex) => ({
1150
+ red: Number.parseInt(hex.slice(1, 3), 16) / 255,
1151
+ green: Number.parseInt(hex.slice(3, 5), 16) / 255,
1152
+ blue: Number.parseInt(hex.slice(5, 7), 16) / 255
1153
+ });
1154
+ const colorEntry = (hex) => {
1155
+ const { red: red2, green: green2, blue: blue2 } = srgb(hex);
1156
+ return {
1157
+ color: {
1158
+ "color-space": "srgb",
1159
+ components: {
1160
+ red: red2.toFixed(3),
1161
+ green: green2.toFixed(3),
1162
+ blue: blue2.toFixed(3),
1163
+ alpha: "1.000"
1164
+ }
1165
+ },
1166
+ idiom: "universal"
1167
+ };
1168
+ };
1169
+ const colorDir = path.join(appDir, "Images.xcassets", "SplashBackground.colorset");
1170
+ FSExtra.mkdirSync(colorDir, { recursive: true });
1171
+ FSExtra.writeFileSync(path.join(colorDir, "Contents.json"), `${JSON.stringify({
1172
+ colors: [colorEntry(app.splash.backgroundColor), ...dark ? [{
1173
+ appearances: darkAppearance,
1174
+ ...colorEntry(dark.backgroundColor)
1175
+ }] : []],
1176
+ info: {
1177
+ author: "xcode",
1178
+ version: 1
1179
+ }
1180
+ }, null, 2)}
1181
+ `);
1182
+ const { red, green, blue } = srgb(app.splash.backgroundColor);
1087
1183
  FSExtra.writeFileSync(path.join(appDir, "LaunchScreen.storyboard"), `<?xml version="1.0" encoding="UTF-8"?>
1088
1184
  <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="launch-controller">
1089
1185
  <device id="retina6_12" orientation="portrait" appearance="light"/>
@@ -1101,7 +1197,7 @@ async function generateSplashScreen(args) {
1101
1197
  <subviews>
1102
1198
  <imageView userInteractionEnabled="NO" contentMode="${cover ? "scaleAspectFill" : "scaleAspectFit"}" image="Splash" translatesAutoresizingMaskIntoConstraints="NO" id="splash-image"/>
1103
1199
  </subviews>
1104
- <color key="backgroundColor" red="${red}" green="${green}" blue="${blue}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
1200
+ <color key="backgroundColor" name="SplashBackground"/>
1105
1201
  <constraints>
1106
1202
  ${cover ? ` <constraint firstItem="splash-image" firstAttribute="leading" secondItem="launch-view" secondAttribute="leading" id="splash-leading"/>
1107
1203
  <constraint firstItem="splash-image" firstAttribute="trailing" secondItem="launch-view" secondAttribute="trailing" id="splash-trailing"/>
@@ -1119,6 +1215,9 @@ ${cover ? ` <constraint firstItem="splash-image" firstAttribute="le
1119
1215
  </scenes>
1120
1216
  <resources>
1121
1217
  <image name="Splash" width="${metadata.width}" height="${metadata.height}"/>
1218
+ <namedColor name="SplashBackground">
1219
+ <color red="${red}" green="${green}" blue="${blue}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
1220
+ </namedColor>
1122
1221
  </resources>
1123
1222
  </document>
1124
1223
  `);
@@ -1126,35 +1225,39 @@ ${cover ? ` <constraint firstItem="splash-image" firstAttribute="le
1126
1225
  }
1127
1226
  const mainRes = path.join(dest, "app", "src", "main", "res");
1128
1227
  const drawable = path.join(mainRes, "drawable");
1129
- for (const [density, multiplier] of Object.entries(ANDROID_DENSITIES)) {
1130
- const canvasSize = 288 * multiplier;
1131
- const imageSize = Math.round(artworkWidth * multiplier);
1132
- const contained = await sharp(artwork).resize(imageSize, imageSize, {
1133
- fit: "contain",
1134
- background: {
1135
- r: 0,
1136
- g: 0,
1137
- b: 0,
1138
- alpha: 0
1139
- }
1140
- }).png().toBuffer();
1141
- const drawableDensity = path.join(mainRes, `drawable-${density}`);
1142
- FSExtra.mkdirSync(drawableDensity, { recursive: true });
1143
- await sharp({ create: {
1144
- width: canvasSize,
1145
- height: canvasSize,
1146
- channels: 4,
1147
- background: {
1148
- r: 0,
1149
- g: 0,
1150
- b: 0,
1151
- alpha: 0
1152
- }
1153
- } }).composite([{
1154
- input: contained,
1155
- left: Math.round((canvasSize - imageSize) / 2),
1156
- top: Math.round((canvasSize - imageSize) / 2)
1157
- }]).png().toFile(path.join(drawableDensity, "splash.png"));
1228
+ const variants = [[artwork, "drawable"]];
1229
+ if (darkArtwork) variants.push([darkArtwork, "drawable-night"]);
1230
+ for (const [variantArtwork, drawablePrefix] of variants) {
1231
+ for (const [density, multiplier] of Object.entries(ANDROID_DENSITIES)) {
1232
+ const canvasSize = 288 * multiplier;
1233
+ const imageSize = Math.round(artworkWidth * multiplier);
1234
+ const contained = await sharp(variantArtwork).resize(imageSize, imageSize, {
1235
+ fit: "contain",
1236
+ background: {
1237
+ r: 0,
1238
+ g: 0,
1239
+ b: 0,
1240
+ alpha: 0
1241
+ }
1242
+ }).png().toBuffer();
1243
+ const drawableDensity = path.join(mainRes, `${drawablePrefix}-${density}`);
1244
+ FSExtra.mkdirSync(drawableDensity, { recursive: true });
1245
+ await sharp({ create: {
1246
+ width: canvasSize,
1247
+ height: canvasSize,
1248
+ channels: 4,
1249
+ background: {
1250
+ r: 0,
1251
+ g: 0,
1252
+ b: 0,
1253
+ alpha: 0
1254
+ }
1255
+ } }).composite([{
1256
+ input: contained,
1257
+ left: Math.round((canvasSize - imageSize) / 2),
1258
+ top: Math.round((canvasSize - imageSize) / 2)
1259
+ }]).png().toFile(path.join(drawableDensity, "splash.png"));
1260
+ }
1158
1261
  }
1159
1262
  FSExtra.writeFileSync(path.join(drawable, "launch_screen.xml"), `<?xml version="1.0" encoding="utf-8"?>
1160
1263
  <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
@@ -1169,6 +1272,14 @@ ${cover ? ` <constraint firstItem="splash-image" firstAttribute="le
1169
1272
  <color name="splash_background">${app.splash.backgroundColor}</color>
1170
1273
  </resources>
1171
1274
  `);
1275
+ if (dark) {
1276
+ FSExtra.mkdirSync(path.join(mainRes, "values-night"), { recursive: true });
1277
+ FSExtra.writeFileSync(path.join(mainRes, "values-night", "colors.xml"), `<?xml version="1.0" encoding="utf-8"?>
1278
+ <resources>
1279
+ <color name="splash_background">${dark.backgroundColor}</color>
1280
+ </resources>
1281
+ `);
1282
+ }
1172
1283
  const stylesPath = path.join(mainRes, "values", "styles.xml");
1173
1284
  const styles = FSExtra.readFileSync(stylesPath, "utf8").replace(" <!-- Customize your theme here. -->", " <item name=\"android:windowBackground\">@drawable/launch_screen</item>");
1174
1285
  FSExtra.writeFileSync(stylesPath, styles);
@@ -1250,6 +1361,12 @@ ${schemes.map((scheme) => ` <string>${scheme}</string>`).join("\n")}
1250
1361
  }
1251
1362
  stamps.push(` <key>${updatesHost.runtimeVersionInfoPlistKey}</key>
1252
1363
  <string>${escapeXml(app.updates.runtimeVersion)}</string>`);
1364
+ }
1365
+ if (app.fonts?.length) {
1366
+ stamps.push(` <key>UIAppFonts</key>
1367
+ <array>
1368
+ ${app.fonts.map((font) => ` <string>${escapeXml(fontFileName(font))}</string>`).join("\n")}
1369
+ </array>`);
1253
1370
  }
1254
1371
  if (app.userInterfaceStyle !== void 0) {
1255
1372
  stamps.push(` <key>UIUserInterfaceStyle</key>
@@ -1523,6 +1640,9 @@ end`);
1523
1640
  rendered = patchIosPbxprojAppEntitlements(rendered, appName);
1524
1641
  }
1525
1642
  if (app.ios?.widgets) rendered = patchIosPbxprojWidgets(rendered, app);
1643
+ if (iosResourceFiles(app).length) {
1644
+ rendered = patchIosPbxprojResources(rendered, appName, iosResourceFiles(app));
1645
+ }
1526
1646
  }
1527
1647
  if (platform === "ios" && relativePath === "Podfile") {
1528
1648
  if (app.ios?.ccache) rendered = `ENV['USE_CCACHE'] ||= '1'
@@ -1565,6 +1685,43 @@ use_frameworks! :linkage => :${app.ios.useFrameworks}`);
1565
1685
  }
1566
1686
  rendered = rendered.replace(hardcodedGradlePlugin, `includeBuild(new File(["node", "--print", "require('module').createRequire(require.resolve('react-native/package.json')).resolve('@react-native/gradle-plugin/package.json')"].execute(null, settingsDir).text.trim()).parentFile.canonicalPath)`);
1567
1687
  }
1688
+ if (platform === "android" && relativePath === "app/build.gradle" && app.android?.minify) {
1689
+ const minify = "def enableProguardInReleaseBuilds = false";
1690
+ const minifyEnabled = "minifyEnabled enableProguardInReleaseBuilds";
1691
+ if (!rendered.includes(minify) || !rendered.includes(minifyEnabled)) {
1692
+ throw new Error("[vxrn] prebuild template app/build.gradle lost its proguard switch");
1693
+ }
1694
+ rendered = rendered.replace(minify, "def enableProguardInReleaseBuilds = true");
1695
+ if (app.android.shrinkResources) {
1696
+ rendered = rendered.replace(minifyEnabled, `${minifyEnabled}
1697
+ shrinkResources true`);
1698
+ }
1699
+ }
1700
+ if (platform === "android" && app.android?.googleServicesFile) {
1701
+ const edits = [[
1702
+ "build.gradle",
1703
+ "classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin\")",
1704
+ ` classpath("com.google.gms:google-services:${GOOGLE_SERVICES_VERSION}")`
1705
+ ], [
1706
+ "app/build.gradle",
1707
+ "apply plugin: \"com.facebook.react\"",
1708
+ "apply plugin: \"com.google.gms.google-services\""
1709
+ ]];
1710
+ for (const [file, anchor, insertion] of edits) {
1711
+ if (relativePath !== file) continue;
1712
+ const next = insertAfterLine(rendered, anchor, insertion);
1713
+ if (next === rendered) {
1714
+ throw new Error(`[vxrn] prebuild template ${file} lost its ${anchor} anchor`);
1715
+ }
1716
+ rendered = next;
1717
+ }
1718
+ }
1719
+ if (platform === "android" && relativePath === "app/proguard-rules.pro" && app.android?.proguardRules) {
1720
+ rendered = `${rendered.trimEnd()}
1721
+
1722
+ ${app.android.proguardRules.trim()}
1723
+ `;
1724
+ }
1568
1725
  for (const [field, setting] of [
1569
1726
  ["minSdk", "minSdkVersion"],
1570
1727
  ["targetSdk", "targetSdkVersion"],
@@ -1640,6 +1797,12 @@ const generateForPlatform = async (root, platform, app, outDir = path.resolve(ro
1640
1797
  platform,
1641
1798
  app
1642
1799
  });
1800
+ copyAppResources({
1801
+ root,
1802
+ dest,
1803
+ platform,
1804
+ app
1805
+ });
1643
1806
  generateSceneDelegate({
1644
1807
  dest,
1645
1808
  platform,