vxrn 2.0.0-beta.130.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.
- package/dist/exports/prebuildWithoutExpo.mjs +295 -54
- package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
- package/dist/exports/prebuildWithoutExpo.native.js +485 -91
- package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
- package/dist/plugins/reactNativeDevServer.mjs +15 -4
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +16 -4
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/package.json +10 -10
- package/src/exports/prebuildWithoutExpo.test.ts +255 -0
- package/src/exports/prebuildWithoutExpo.ts +402 -68
- package/src/plugins/reactNativeDevServer.ts +25 -6
- package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
- package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
|
@@ -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,7 +248,55 @@ function patchIosPbxprojSceneDelegate(rendered, appName) {
|
|
|
247
248
|
}
|
|
248
249
|
return patched;
|
|
249
250
|
}
|
|
250
|
-
function
|
|
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
|
+
}
|
|
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;
|
|
253
302
|
for (const [anchor, insertion] of edits) {
|
|
@@ -264,13 +313,55 @@ function patchIosPbxprojPushEntitlements(rendered, appName) {
|
|
|
264
313
|
return patched.split(setting).join(`${setting}
|
|
265
314
|
CODE_SIGN_ENTITLEMENTS = ${appName}/${appName}.entitlements;`);
|
|
266
315
|
}
|
|
267
|
-
function
|
|
316
|
+
function renderPlistValue(value, indent) {
|
|
317
|
+
if (typeof value === "string") return `${indent}<string>${escapeXml(value)}</string>`;
|
|
318
|
+
if (typeof value === "boolean") return `${indent}<${value}/>`;
|
|
319
|
+
if (typeof value === "number") {
|
|
320
|
+
return Number.isInteger(value) ? `${indent}<integer>${value}</integer>` : `${indent}<real>${value}</real>`;
|
|
321
|
+
}
|
|
322
|
+
if (Array.isArray(value)) {
|
|
323
|
+
return `${indent}<array>
|
|
324
|
+
${value.map((item) => renderPlistValue(item, `${indent} `)).join("\n")}
|
|
325
|
+
${indent}</array>`;
|
|
326
|
+
}
|
|
327
|
+
return `${indent}<dict>
|
|
328
|
+
${renderPlistEntries(value, `${indent} `)}
|
|
329
|
+
${indent}</dict>`;
|
|
330
|
+
}
|
|
331
|
+
function renderPlistEntries(entries, indent) {
|
|
332
|
+
return Object.entries(entries).map(([key, value]) => `${indent}<key>${escapeXml(key)}</key>
|
|
333
|
+
${renderPlistValue(value, indent)}`).join("\n");
|
|
334
|
+
}
|
|
335
|
+
function appEntitlements(app) {
|
|
336
|
+
const modeled = {};
|
|
337
|
+
if (app.notifications?.push === true || app.ios?.widgets?.pushNotifications) {
|
|
338
|
+
modeled["aps-environment"] = notificationsHost.apsEnvironment;
|
|
339
|
+
}
|
|
340
|
+
if (app.ios?.widgets) {
|
|
341
|
+
modeled["com.apple.security.application-groups"] = [app.ios.widgets.appGroup];
|
|
342
|
+
}
|
|
343
|
+
if (app.ios?.associatedDomains?.length) {
|
|
344
|
+
modeled["com.apple.developer.associated-domains"] = app.ios.associatedDomains;
|
|
345
|
+
}
|
|
346
|
+
if (app.ios?.usesAppleSignIn) {
|
|
347
|
+
modeled["com.apple.developer.applesignin"] = ["Default"];
|
|
348
|
+
}
|
|
349
|
+
for (const key of Object.keys(app.ios?.entitlements ?? {})) {
|
|
350
|
+
if (key in modeled) {
|
|
351
|
+
throw new Error(`[vxrn] native.app.ios.entitlements sets ${key}, which native.app already writes: use its field`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
...modeled,
|
|
356
|
+
...app.ios?.entitlements
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function renderEntitlements(entries) {
|
|
268
360
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
269
361
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
270
362
|
<plist version="1.0">
|
|
271
363
|
<dict>
|
|
272
|
-
|
|
273
|
-
<string>${notificationsHost.apsEnvironment}</string>
|
|
364
|
+
${renderPlistEntries(entries, " ")}
|
|
274
365
|
</dict>
|
|
275
366
|
</plist>
|
|
276
367
|
`;
|
|
@@ -409,8 +500,9 @@ function generateSceneDelegate(args) {
|
|
|
409
500
|
const { dest, platform, app } = args;
|
|
410
501
|
if (platform !== "ios") return;
|
|
411
502
|
FSExtra.writeFileSync(path.join(dest, app.name, "SceneDelegate.swift"), renderSceneDelegateSwift(app.name));
|
|
412
|
-
|
|
413
|
-
|
|
503
|
+
const entitlements = appEntitlements(app);
|
|
504
|
+
if (Object.keys(entitlements).length && !app.ios?.widgets) {
|
|
505
|
+
FSExtra.writeFileSync(path.join(dest, app.name, `${app.name}.entitlements`), renderEntitlements(entitlements));
|
|
414
506
|
}
|
|
415
507
|
}
|
|
416
508
|
function generateIosWidgets(dest, app) {
|
|
@@ -423,7 +515,7 @@ function generateIosWidgets(dest, app) {
|
|
|
423
515
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
424
516
|
<plist version="1.0"><dict><key>com.apple.security.application-groups</key><array><string>${escapeXml(widgets.appGroup)}</string></array></dict></plist>
|
|
425
517
|
`;
|
|
426
|
-
FSExtra.writeFileSync(path.join(appDir, "OneAppWidgets.entitlements"),
|
|
518
|
+
FSExtra.writeFileSync(path.join(appDir, "OneAppWidgets.entitlements"), renderEntitlements(appEntitlements(app)));
|
|
427
519
|
FSExtra.writeFileSync(path.join(extensionDir, "OneWidgets.entitlements"), entitlements);
|
|
428
520
|
FSExtra.writeFileSync(path.join(extensionDir, "WidgetInfo.plist"), `<?xml version="1.0" encoding="UTF-8"?>
|
|
429
521
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -1009,38 +1101,85 @@ ${adaptiveIcon.monochrome ? " <monochrome android:drawable=\"@mipmap/ic_launc
|
|
|
1009
1101
|
async function generateSplashScreen(args) {
|
|
1010
1102
|
const { root, dest, platform, app } = args;
|
|
1011
1103
|
if (!app.splash) return;
|
|
1012
|
-
const source = path.resolve(root, app.splash.source);
|
|
1013
|
-
if (!FSExtra.existsSync(source)) {
|
|
1014
|
-
throw new Error(`[vxrn] native.app.splash source does not exist: ${source}`);
|
|
1015
|
-
}
|
|
1016
1104
|
const cover = app.splash.resizeMode === "cover";
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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;
|
|
1022
1120
|
const artworkWidth = app.splash.width ?? 200;
|
|
1023
1121
|
const artworkHeight = Number((artworkWidth * (metadata.height / metadata.width)).toFixed(3));
|
|
1024
1122
|
if (platform === "ios") {
|
|
1025
1123
|
const appDir = path.join(dest, app.name);
|
|
1026
1124
|
const splashDir = path.join(appDir, "Images.xcassets", "Splash.imageset");
|
|
1027
1125
|
FSExtra.mkdirSync(splashDir, { recursive: true });
|
|
1126
|
+
const darkAppearance = [{
|
|
1127
|
+
appearance: "luminosity",
|
|
1128
|
+
value: "dark"
|
|
1129
|
+
}];
|
|
1028
1130
|
await sharp(artwork).toFile(path.join(splashDir, "splash.png"));
|
|
1131
|
+
if (darkArtwork) await sharp(darkArtwork).toFile(path.join(splashDir, "splash-dark.png"));
|
|
1029
1132
|
FSExtra.writeFileSync(path.join(splashDir, "Contents.json"), `${JSON.stringify({
|
|
1030
1133
|
images: [{
|
|
1031
1134
|
filename: "splash.png",
|
|
1032
1135
|
idiom: "universal",
|
|
1033
1136
|
scale: "1x"
|
|
1034
|
-
}
|
|
1137
|
+
}, ...darkArtwork ? [{
|
|
1138
|
+
appearances: darkAppearance,
|
|
1139
|
+
filename: "splash-dark.png",
|
|
1140
|
+
idiom: "universal",
|
|
1141
|
+
scale: "1x"
|
|
1142
|
+
}] : []],
|
|
1143
|
+
info: {
|
|
1144
|
+
author: "xcode",
|
|
1145
|
+
version: 1
|
|
1146
|
+
}
|
|
1147
|
+
}, null, 2)}
|
|
1148
|
+
`);
|
|
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
|
+
}] : []],
|
|
1035
1176
|
info: {
|
|
1036
1177
|
author: "xcode",
|
|
1037
1178
|
version: 1
|
|
1038
1179
|
}
|
|
1039
1180
|
}, null, 2)}
|
|
1040
1181
|
`);
|
|
1041
|
-
const red =
|
|
1042
|
-
const green = Number.parseInt(app.splash.backgroundColor.slice(3, 5), 16) / 255;
|
|
1043
|
-
const blue = Number.parseInt(app.splash.backgroundColor.slice(5, 7), 16) / 255;
|
|
1182
|
+
const { red, green, blue } = srgb(app.splash.backgroundColor);
|
|
1044
1183
|
FSExtra.writeFileSync(path.join(appDir, "LaunchScreen.storyboard"), `<?xml version="1.0" encoding="UTF-8"?>
|
|
1045
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">
|
|
1046
1185
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
|
@@ -1058,7 +1197,7 @@ async function generateSplashScreen(args) {
|
|
|
1058
1197
|
<subviews>
|
|
1059
1198
|
<imageView userInteractionEnabled="NO" contentMode="${cover ? "scaleAspectFill" : "scaleAspectFit"}" image="Splash" translatesAutoresizingMaskIntoConstraints="NO" id="splash-image"/>
|
|
1060
1199
|
</subviews>
|
|
1061
|
-
<color key="backgroundColor"
|
|
1200
|
+
<color key="backgroundColor" name="SplashBackground"/>
|
|
1062
1201
|
<constraints>
|
|
1063
1202
|
${cover ? ` <constraint firstItem="splash-image" firstAttribute="leading" secondItem="launch-view" secondAttribute="leading" id="splash-leading"/>
|
|
1064
1203
|
<constraint firstItem="splash-image" firstAttribute="trailing" secondItem="launch-view" secondAttribute="trailing" id="splash-trailing"/>
|
|
@@ -1076,6 +1215,9 @@ ${cover ? ` <constraint firstItem="splash-image" firstAttribute="le
|
|
|
1076
1215
|
</scenes>
|
|
1077
1216
|
<resources>
|
|
1078
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>
|
|
1079
1221
|
</resources>
|
|
1080
1222
|
</document>
|
|
1081
1223
|
`);
|
|
@@ -1083,35 +1225,39 @@ ${cover ? ` <constraint firstItem="splash-image" firstAttribute="le
|
|
|
1083
1225
|
}
|
|
1084
1226
|
const mainRes = path.join(dest, "app", "src", "main", "res");
|
|
1085
1227
|
const drawable = path.join(mainRes, "drawable");
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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
|
+
}
|
|
1115
1261
|
}
|
|
1116
1262
|
FSExtra.writeFileSync(path.join(drawable, "launch_screen.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
1117
1263
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
@@ -1126,6 +1272,14 @@ ${cover ? ` <constraint firstItem="splash-image" firstAttribute="le
|
|
|
1126
1272
|
<color name="splash_background">${app.splash.backgroundColor}</color>
|
|
1127
1273
|
</resources>
|
|
1128
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
|
+
}
|
|
1129
1283
|
const stylesPath = path.join(mainRes, "values", "styles.xml");
|
|
1130
1284
|
const styles = FSExtra.readFileSync(stylesPath, "utf8").replace(" <!-- Customize your theme here. -->", " <item name=\"android:windowBackground\">@drawable/launch_screen</item>");
|
|
1131
1285
|
FSExtra.writeFileSync(stylesPath, styles);
|
|
@@ -1207,6 +1361,12 @@ ${schemes.map((scheme) => ` <string>${scheme}</string>`).join("\n")}
|
|
|
1207
1361
|
}
|
|
1208
1362
|
stamps.push(` <key>${updatesHost.runtimeVersionInfoPlistKey}</key>
|
|
1209
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>`);
|
|
1210
1370
|
}
|
|
1211
1371
|
if (app.userInterfaceStyle !== void 0) {
|
|
1212
1372
|
stamps.push(` <key>UIUserInterfaceStyle</key>
|
|
@@ -1223,6 +1383,12 @@ ${schemes.map((scheme) => ` <string>${scheme}</string>`).join("\n")}
|
|
|
1223
1383
|
${phoneOrientations.map((orientation) => ` <string>${orientation}</string>`).join("\n")}
|
|
1224
1384
|
</array>`);
|
|
1225
1385
|
}
|
|
1386
|
+
for (const [key, value] of Object.entries(app.ios?.infoPlist ?? {})) {
|
|
1387
|
+
if (rendered.includes(`<key>${key}</key>`) || stamps.some((stamp) => stamp.includes(`<key>${key}</key>`))) {
|
|
1388
|
+
throw new Error(`[vxrn] native.app.ios.infoPlist sets ${key}, which the template or native.app already writes: use its field`);
|
|
1389
|
+
}
|
|
1390
|
+
stamps.push(renderPlistEntries({ [key]: value }, " "));
|
|
1391
|
+
}
|
|
1226
1392
|
if (stamps.length) {
|
|
1227
1393
|
const anchor = " <key>LSRequiresIPhoneOS</key>";
|
|
1228
1394
|
if (!rendered.includes(anchor)) throw new Error(`[vxrn] prebuild template ${relativePath} lost its LSRequiresIPhoneOS anchor`);
|
|
@@ -1334,6 +1500,29 @@ ${line}
|
|
|
1334
1500
|
}
|
|
1335
1501
|
rendered = rendered.replace(dayNight, app.userInterfaceStyle === "light" ? "parent=\"Theme.AppCompat.Light.NoActionBar\"" : "parent=\"Theme.AppCompat.NoActionBar\"");
|
|
1336
1502
|
}
|
|
1503
|
+
if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && (app.android?.permissions?.length || app.android?.blockedPermissions?.length)) {
|
|
1504
|
+
const anchor = "<uses-permission android:name=\"android.permission.INTERNET\" />";
|
|
1505
|
+
if (!rendered.includes(anchor)) {
|
|
1506
|
+
throw new Error("[vxrn] cannot stamp permissions: expected the INTERNET permission in app/src/main/AndroidManifest.xml");
|
|
1507
|
+
}
|
|
1508
|
+
const permissionName = (name) => name.includes(".") ? name : `android.permission.${name}`;
|
|
1509
|
+
const lines = [...(app.android?.permissions ?? []).map((name) => ` <uses-permission android:name="${permissionName(name)}" />`), ...(app.android?.blockedPermissions ?? []).map((name) => ` <uses-permission android:name="${permissionName(name)}" tools:node="remove" />`)];
|
|
1510
|
+
rendered = rendered.replace(anchor, `${anchor}
|
|
1511
|
+
${lines.join("\n")}`);
|
|
1512
|
+
if (app.android?.blockedPermissions?.length && !rendered.includes("xmlns:tools=")) {
|
|
1513
|
+
rendered = rendered.replace("xmlns:android=\"http://schemas.android.com/apk/res/android\"", "xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/tools\"");
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.android?.appLinks?.length) {
|
|
1517
|
+
rendered = rendered.replace(" </activity>", ` <intent-filter android:autoVerify="true">
|
|
1518
|
+
<action android:name="android.intent.action.VIEW" />
|
|
1519
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
1520
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
1521
|
+
<data android:scheme="https" />
|
|
1522
|
+
${app.android.appLinks.map((link) => ` <data android:host="${link.host}"${link.pathPrefix ? ` android:pathPrefix="${link.pathPrefix}"` : ""} />`).join("\n")}
|
|
1523
|
+
</intent-filter>
|
|
1524
|
+
</activity>`);
|
|
1525
|
+
}
|
|
1337
1526
|
if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.orientation !== void 0) {
|
|
1338
1527
|
const anchor = "android:name=\".MainActivity\"";
|
|
1339
1528
|
if (!rendered.includes(anchor)) {
|
|
@@ -1447,10 +1636,13 @@ end`);
|
|
|
1447
1636
|
rendered = patchIosBundlePhase(rendered, app.updates?.runtimeVersion);
|
|
1448
1637
|
rendered = patchIosPbxprojSceneDelegate(rendered, appName);
|
|
1449
1638
|
rendered = patchIosPbxprojOneBridgingHeader(rendered, appName);
|
|
1450
|
-
if (app.
|
|
1451
|
-
rendered =
|
|
1639
|
+
if (Object.keys(appEntitlements(app)).length && !app.ios?.widgets) {
|
|
1640
|
+
rendered = patchIosPbxprojAppEntitlements(rendered, appName);
|
|
1452
1641
|
}
|
|
1453
1642
|
if (app.ios?.widgets) rendered = patchIosPbxprojWidgets(rendered, app);
|
|
1643
|
+
if (iosResourceFiles(app).length) {
|
|
1644
|
+
rendered = patchIosPbxprojResources(rendered, appName, iosResourceFiles(app));
|
|
1645
|
+
}
|
|
1454
1646
|
}
|
|
1455
1647
|
if (platform === "ios" && relativePath === "Podfile") {
|
|
1456
1648
|
if (app.ios?.ccache) rendered = `ENV['USE_CCACHE'] ||= '1'
|
|
@@ -1493,8 +1685,51 @@ use_frameworks! :linkage => :${app.ios.useFrameworks}`);
|
|
|
1493
1685
|
}
|
|
1494
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)`);
|
|
1495
1687
|
}
|
|
1496
|
-
if (platform === "android" && app.android?.
|
|
1497
|
-
|
|
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
|
+
}
|
|
1725
|
+
for (const [field, setting] of [
|
|
1726
|
+
["minSdk", "minSdkVersion"],
|
|
1727
|
+
["targetSdk", "targetSdkVersion"],
|
|
1728
|
+
["compileSdk", "compileSdkVersion"]
|
|
1729
|
+
]) {
|
|
1730
|
+
const value = platform === "android" ? app.android?.[field] : void 0;
|
|
1731
|
+
if (value === void 0) continue;
|
|
1732
|
+
rendered = rendered.replace(new RegExp(`${setting} = \\d+`, "g"), `${setting} = ${value}`);
|
|
1498
1733
|
}
|
|
1499
1734
|
}
|
|
1500
1735
|
return {
|
|
@@ -1562,6 +1797,12 @@ const generateForPlatform = async (root, platform, app, outDir = path.resolve(ro
|
|
|
1562
1797
|
platform,
|
|
1563
1798
|
app
|
|
1564
1799
|
});
|
|
1800
|
+
copyAppResources({
|
|
1801
|
+
root,
|
|
1802
|
+
dest,
|
|
1803
|
+
platform,
|
|
1804
|
+
app
|
|
1805
|
+
});
|
|
1565
1806
|
generateSceneDelegate({
|
|
1566
1807
|
dest,
|
|
1567
1808
|
platform,
|