vxrn 2.8.0 → 2.9.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/dist/exports/clean.mjs +128 -19
- package/dist/exports/clean.mjs.map +1 -1
- package/dist/exports/clean.native.js +147 -21
- package/dist/exports/clean.native.js.map +1 -1
- package/dist/exports/prebuild.mjs +2 -1
- package/dist/exports/prebuild.mjs.map +1 -1
- package/dist/exports/prebuild.native.js +2 -1
- package/dist/exports/prebuild.native.js.map +1 -1
- package/dist/exports/prebuildWithoutExpo.mjs +196 -7
- package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
- package/dist/exports/prebuildWithoutExpo.native.js +280 -42
- package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
- package/dist/plugins/reactNativeDevServer.mjs +1 -1
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +1 -1
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/dist/user-interface/index.mjs +31 -24
- package/dist/user-interface/index.mjs.map +1 -1
- package/dist/user-interface/index.native.js +32 -25
- package/dist/user-interface/index.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.mjs +111 -14
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +143 -14
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/dist/utils/scanDepsToOptimize.mjs +10 -6
- package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
- package/dist/utils/scanDepsToOptimize.native.js +12 -6
- package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
- package/expo-plugin.cjs +71 -8
- package/package.json +13 -13
- package/src/exports/clean.test.ts +502 -5
- package/src/exports/clean.ts +176 -19
- package/src/exports/expoPlugin.test.mjs +44 -1
- package/src/exports/prebuild.ts +6 -0
- package/src/exports/prebuildWithoutExpo.test.ts +134 -2
- package/src/exports/prebuildWithoutExpo.ts +235 -10
- package/src/plugins/reactNativeDevServer.ts +1 -1
- package/src/user-interface/index.ts +50 -42
- package/src/utils/createNativeDevEngine.test.ts +486 -7
- package/src/utils/createNativeDevEngine.ts +220 -17
- package/src/utils/imageSizePatch.test.ts +3 -3
- package/src/utils/scanDepsToOptimize.test.ts +35 -2
- package/src/utils/scanDepsToOptimize.ts +25 -8
- package/types/exports/clean.d.ts.map +1 -1
- package/types/exports/prebuild.d.ts +2 -1
- package/types/exports/prebuild.d.ts.map +1 -1
- package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
- package/types/user-interface/index.d.ts.map +1 -1
- package/types/utils/createNativeDevEngine.d.ts +1 -1
- package/types/utils/createNativeDevEngine.d.ts.map +1 -1
- package/types/utils/scanDepsToOptimize.d.ts +25 -0
- package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
|
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import module from "node:module";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
-
import { validateNativeApp } from "@vxrn/utils/nativeAppManifest";
|
|
6
|
+
import { androidIconSuffix, appIconPreviewResources, validateNativeApp } from "@vxrn/utils/nativeAppManifest";
|
|
7
7
|
import FSExtra from "fs-extra";
|
|
8
8
|
import sharp from "sharp";
|
|
9
9
|
import { swiftPackageDirectories } from "../utils/swiftPackageId.mjs";
|
|
@@ -1042,13 +1042,59 @@ const IPAD_APP_ICON_SIZES = [
|
|
|
1042
1042
|
["76x76", ["1x", "2x"]],
|
|
1043
1043
|
["83.5x83.5", ["2x"]]
|
|
1044
1044
|
];
|
|
1045
|
+
async function writeAppearanceIcon(args) {
|
|
1046
|
+
const { root, iconDir, icon, source } = args;
|
|
1047
|
+
const variantSource = (variant) => {
|
|
1048
|
+
if (!variant?.source) return source;
|
|
1049
|
+
const resolved = path.resolve(root, variant.source);
|
|
1050
|
+
if (!FSExtra.existsSync(resolved)) {
|
|
1051
|
+
throw new Error(`[vxrn] native.app icon appearance source does not exist: ${resolved}`);
|
|
1052
|
+
}
|
|
1053
|
+
return resolved;
|
|
1054
|
+
};
|
|
1055
|
+
const render = async (file, from, background, gray = false) => {
|
|
1056
|
+
let image = sharp(from).rotate().resize(1024, 1024, { fit: "cover" });
|
|
1057
|
+
if (background) image = image.flatten({ background });
|
|
1058
|
+
if (gray) image = image.greyscale();
|
|
1059
|
+
await image.png().toFile(path.join(iconDir, file));
|
|
1060
|
+
};
|
|
1061
|
+
const images = [];
|
|
1062
|
+
const entry = (filename, value) => ({
|
|
1063
|
+
...value ? { appearances: [{
|
|
1064
|
+
appearance: "luminosity",
|
|
1065
|
+
value
|
|
1066
|
+
}] } : {},
|
|
1067
|
+
filename,
|
|
1068
|
+
idiom: "universal",
|
|
1069
|
+
platform: "ios",
|
|
1070
|
+
size: "1024x1024"
|
|
1071
|
+
});
|
|
1072
|
+
await render("icon-1024.png", source, icon.backgroundColor);
|
|
1073
|
+
images.push(entry("icon-1024.png"));
|
|
1074
|
+
if (icon.dark) {
|
|
1075
|
+
await render("icon-1024-dark.png", variantSource(icon.dark), icon.dark.backgroundColor);
|
|
1076
|
+
images.push(entry("icon-1024-dark.png", "dark"));
|
|
1077
|
+
}
|
|
1078
|
+
if (icon.tinted) {
|
|
1079
|
+
await render("icon-1024-tinted.png", variantSource(icon.tinted), icon.tinted.backgroundColor ?? "#000000", true);
|
|
1080
|
+
images.push(entry("icon-1024-tinted.png", "tinted"));
|
|
1081
|
+
}
|
|
1082
|
+
FSExtra.writeFileSync(path.join(iconDir, "Contents.json"), `${JSON.stringify({
|
|
1083
|
+
images,
|
|
1084
|
+
info: {
|
|
1085
|
+
author: "xcode",
|
|
1086
|
+
version: 1
|
|
1087
|
+
}
|
|
1088
|
+
}, null, 2)}
|
|
1089
|
+
`);
|
|
1090
|
+
}
|
|
1045
1091
|
async function generateAppIcons(args) {
|
|
1046
1092
|
const { root, dest, platform, app } = args;
|
|
1047
1093
|
if (!app.icon) return;
|
|
1048
1094
|
if (platform === "ios") {
|
|
1049
1095
|
const assetsDir = path.join(dest, app.name, "Images.xcassets");
|
|
1050
1096
|
const template = JSON.parse(FSExtra.readFileSync(path.join(assetsDir, "AppIcon.appiconset", "Contents.json"), "utf8"));
|
|
1051
|
-
const icons = [["AppIcon", app.icon], ...Object.entries(app.
|
|
1097
|
+
const icons = [["AppIcon", app.icon], ...Object.entries(app.alternateIcons ?? {})];
|
|
1052
1098
|
for (const [name, icon] of icons) {
|
|
1053
1099
|
const source2 = path.resolve(root, icon.source);
|
|
1054
1100
|
if (!FSExtra.existsSync(source2)) {
|
|
@@ -1060,6 +1106,15 @@ async function generateAppIcons(args) {
|
|
|
1060
1106
|
}
|
|
1061
1107
|
const iconDir = path.join(assetsDir, `${name}.appiconset`);
|
|
1062
1108
|
FSExtra.mkdirSync(iconDir, { recursive: true });
|
|
1109
|
+
if (icon.dark || icon.tinted) {
|
|
1110
|
+
await writeAppearanceIcon({
|
|
1111
|
+
root,
|
|
1112
|
+
iconDir,
|
|
1113
|
+
icon,
|
|
1114
|
+
source: source2
|
|
1115
|
+
});
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1063
1118
|
const contents = structuredClone(template);
|
|
1064
1119
|
if (app.ios?.tablet) {
|
|
1065
1120
|
for (const [size, scales] of IPAD_APP_ICON_SIZES) {
|
|
@@ -1091,12 +1146,135 @@ async function generateAppIcons(args) {
|
|
|
1091
1146
|
if (metadata.width !== metadata.height || metadata.width === void 0 || metadata.width < 1024) {
|
|
1092
1147
|
throw new Error("[vxrn] native.app.icon source must be a square image at least 1024px wide");
|
|
1093
1148
|
}
|
|
1149
|
+
const launchers = [[
|
|
1150
|
+
"ic_launcher",
|
|
1151
|
+
app.icon,
|
|
1152
|
+
source
|
|
1153
|
+
], ...await Promise.all(Object.entries(app.alternateIcons ?? {}).map(async ([name, icon]) => {
|
|
1154
|
+
const alternateSource = path.resolve(root, icon.source);
|
|
1155
|
+
if (!FSExtra.existsSync(alternateSource)) {
|
|
1156
|
+
throw new Error(`[vxrn] native.app icon source does not exist: ${alternateSource}`);
|
|
1157
|
+
}
|
|
1158
|
+
const alternate = await sharp(alternateSource).metadata();
|
|
1159
|
+
if (alternate.width !== alternate.height || alternate.width === void 0 || alternate.width < 1024) {
|
|
1160
|
+
throw new Error(`[vxrn] native.app icon source must be a square image at least 1024px wide: ${alternateSource}`);
|
|
1161
|
+
}
|
|
1162
|
+
return [
|
|
1163
|
+
androidAliasIconName(name),
|
|
1164
|
+
icon,
|
|
1165
|
+
alternateSource
|
|
1166
|
+
];
|
|
1167
|
+
}))];
|
|
1094
1168
|
for (const [density, multiplier] of Object.entries(ANDROID_DENSITIES)) {
|
|
1095
1169
|
const pixels = 48 * multiplier;
|
|
1096
1170
|
const iconDir = path.join(dest, "app", "src", "main", "res", `mipmap-${density}`);
|
|
1097
|
-
for (const
|
|
1098
|
-
|
|
1171
|
+
for (const [resource, icon, iconSource] of launchers) {
|
|
1172
|
+
for (const filename of [`${resource}.png`, `${resource}_round.png`]) {
|
|
1173
|
+
await sharp(iconSource).rotate().resize(pixels, pixels, { fit: "cover" }).flatten({ background: icon.backgroundColor }).png().toFile(path.join(iconDir, filename));
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
function androidAliasIconName(name) {
|
|
1179
|
+
return `ic_launcher_${androidIconSuffix(name)}`;
|
|
1180
|
+
}
|
|
1181
|
+
const LAUNCHER_FILTER = /[ \t]*<intent-filter>(?:(?!<\/intent-filter>)[\s\S])*android\.intent\.category\.LAUNCHER[\s\S]*?<\/intent-filter>\n?/;
|
|
1182
|
+
function stampAndroidIconAliases(manifest, app) {
|
|
1183
|
+
const alternates = Object.keys(app.alternateIcons ?? {});
|
|
1184
|
+
const host = manifest.match(/<activity\b(?:(?!<\/activity>)[\s\S])*android:name="\.MainActivity"[\s\S]*?<\/activity>/)?.[0];
|
|
1185
|
+
if (!host || !LAUNCHER_FILTER.test(host)) {
|
|
1186
|
+
throw new Error("[vxrn] cannot stamp alternate icons: expected the launcher filter on .MainActivity in app/src/main/AndroidManifest.xml");
|
|
1187
|
+
}
|
|
1188
|
+
const alias = (name, icon, enabled) => ` <activity-alias
|
|
1189
|
+
android:name=".${name}"
|
|
1190
|
+
android:targetActivity=".AppIconLauncherActivity"
|
|
1191
|
+
android:enabled="${enabled}"
|
|
1192
|
+
android:exported="true"
|
|
1193
|
+
android:icon="@mipmap/${icon}"
|
|
1194
|
+
android:roundIcon="@mipmap/${icon}_round"
|
|
1195
|
+
android:label="@string/app_name">
|
|
1196
|
+
<intent-filter>
|
|
1197
|
+
<action android:name="android.intent.action.MAIN" />
|
|
1198
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
1199
|
+
</intent-filter>
|
|
1200
|
+
</activity-alias>`;
|
|
1201
|
+
const stamps = [
|
|
1202
|
+
` <activity
|
|
1203
|
+
android:name=".AppIconLauncherActivity"
|
|
1204
|
+
android:exported="false"
|
|
1205
|
+
android:taskAffinity=""
|
|
1206
|
+
android:excludeFromRecents="true"
|
|
1207
|
+
android:theme="@android:style/Theme.NoDisplay" />`,
|
|
1208
|
+
alias("Primary", "ic_launcher", true),
|
|
1209
|
+
...alternates.map((name) => alias(name, androidAliasIconName(name), false))
|
|
1210
|
+
].join("\n");
|
|
1211
|
+
const anchor = " </application>";
|
|
1212
|
+
if (!manifest.includes(anchor)) {
|
|
1213
|
+
throw new Error("[vxrn] cannot stamp alternate icons: expected </application> in app/src/main/AndroidManifest.xml");
|
|
1214
|
+
}
|
|
1215
|
+
return manifest.replace(host, host.replace(LAUNCHER_FILTER, "")).replace(anchor, `${stamps}
|
|
1216
|
+
${anchor}`);
|
|
1217
|
+
}
|
|
1218
|
+
function generateAndroidIconForwarder(dest, app) {
|
|
1219
|
+
if (!app.alternateIcons || !app.android) return;
|
|
1220
|
+
const packageName = app.android.applicationId;
|
|
1221
|
+
const file = path.join(dest, "app", "src", "main", "java", ...packageName.split("."), "AppIconLauncherActivity.kt");
|
|
1222
|
+
FSExtra.mkdirSync(path.dirname(file), { recursive: true });
|
|
1223
|
+
FSExtra.writeFileSync(file, `package ${packageName}
|
|
1224
|
+
|
|
1225
|
+
import android.app.Activity
|
|
1226
|
+
import android.content.Intent
|
|
1227
|
+
import android.os.Bundle
|
|
1228
|
+
|
|
1229
|
+
// launcher aliases finish before react native mounts in the permanent host.
|
|
1230
|
+
class AppIconLauncherActivity : Activity() {
|
|
1231
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
1232
|
+
super.onCreate(savedInstanceState)
|
|
1233
|
+
startActivity(Intent(intent).setClass(this, MainActivity::class.java)
|
|
1234
|
+
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
|
1235
|
+
Intent.FLAG_ACTIVITY_SINGLE_TOP))
|
|
1236
|
+
finish()
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
`);
|
|
1240
|
+
}
|
|
1241
|
+
async function generateAppIconPreviews(args) {
|
|
1242
|
+
const { root, dest, platform, app } = args;
|
|
1243
|
+
if (!app.icon || !app.alternateIcons) return;
|
|
1244
|
+
const icons = [[void 0, app.icon], ...Object.entries(app.alternateIcons)];
|
|
1245
|
+
for (const [name, icon] of icons) {
|
|
1246
|
+
const preview = (scale) => sharp(path.resolve(root, icon.source)).rotate().resize(64 * scale, 64 * scale, { fit: "cover" }).flatten({ background: icon.backgroundColor }).png();
|
|
1247
|
+
const resources = appIconPreviewResources(name);
|
|
1248
|
+
if (platform === "android") {
|
|
1249
|
+
const dir2 = path.join(dest, "app", "src", "main", "res", "drawable-nodpi");
|
|
1250
|
+
FSExtra.mkdirSync(dir2, { recursive: true });
|
|
1251
|
+
await preview(3).toFile(path.join(dir2, `${resources.android}.png`));
|
|
1252
|
+
continue;
|
|
1099
1253
|
}
|
|
1254
|
+
const dir = path.join(dest, app.name, "Images.xcassets", `${resources.ios}.imageset`);
|
|
1255
|
+
FSExtra.mkdirSync(dir, { recursive: true });
|
|
1256
|
+
const images = [];
|
|
1257
|
+
for (const scale of [
|
|
1258
|
+
1,
|
|
1259
|
+
2,
|
|
1260
|
+
3
|
|
1261
|
+
]) {
|
|
1262
|
+
const filename = `preview@${scale}x.png`;
|
|
1263
|
+
await preview(scale).toFile(path.join(dir, filename));
|
|
1264
|
+
images.push({
|
|
1265
|
+
filename,
|
|
1266
|
+
idiom: "universal",
|
|
1267
|
+
scale: `${scale}x`
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
FSExtra.writeFileSync(path.join(dir, "Contents.json"), `${JSON.stringify({
|
|
1271
|
+
images,
|
|
1272
|
+
info: {
|
|
1273
|
+
author: "xcode",
|
|
1274
|
+
version: 1
|
|
1275
|
+
}
|
|
1276
|
+
}, null, 2)}
|
|
1277
|
+
`);
|
|
1100
1278
|
}
|
|
1101
1279
|
}
|
|
1102
1280
|
async function generateAdaptiveIcon(args) {
|
|
@@ -1402,7 +1580,8 @@ ${background ? ` <item>
|
|
|
1402
1580
|
</style>`));
|
|
1403
1581
|
}
|
|
1404
1582
|
function renderPrebuildFile(args) {
|
|
1405
|
-
const {
|
|
1583
|
+
const { content, platform, app, nitroWebImage } = args;
|
|
1584
|
+
const relativePath = args.relativePath.replace(/\\/g, "/");
|
|
1406
1585
|
const appName = app.name;
|
|
1407
1586
|
const schemes = app.scheme === void 0 ? [] : Array.isArray(app.scheme) ? app.scheme : [app.scheme];
|
|
1408
1587
|
let destRelativePath = transformPath(relativePath);
|
|
@@ -1787,6 +1966,9 @@ ${schemes.map((scheme) => ` <data android:scheme="${scheme}" />`).joi
|
|
|
1787
1966
|
</intent-filter>
|
|
1788
1967
|
</activity>`);
|
|
1789
1968
|
}
|
|
1969
|
+
if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.alternateIcons !== void 0) {
|
|
1970
|
+
rendered = stampAndroidIconAliases(rendered, app);
|
|
1971
|
+
}
|
|
1790
1972
|
if (platform === "ios" && relativePath.endsWith(".xcodeproj/project.pbxproj")) {
|
|
1791
1973
|
rendered = rendered.replace(/TARGETED_DEVICE_FAMILY = "1,2";/g, `TARGETED_DEVICE_FAMILY = "${app.ios?.tablet ? "1,2" : "1"}";`);
|
|
1792
1974
|
}
|
|
@@ -1798,12 +1980,12 @@ ${schemes.map((scheme) => ` <data android:scheme="${scheme}" />`).joi
|
|
|
1798
1980
|
rendered = rendered.replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, `CURRENT_PROJECT_VERSION = ${app.ios.buildNumber};`);
|
|
1799
1981
|
}
|
|
1800
1982
|
}
|
|
1801
|
-
if (platform === "ios" && relativePath.endsWith(".xcodeproj/project.pbxproj") && app.
|
|
1983
|
+
if (platform === "ios" && relativePath.endsWith(".xcodeproj/project.pbxproj") && app.alternateIcons !== void 0) {
|
|
1802
1984
|
const anchor = "ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;";
|
|
1803
1985
|
if (!rendered.includes(anchor)) {
|
|
1804
1986
|
throw new Error("[vxrn] prebuild template lost its primary app icon build setting");
|
|
1805
1987
|
}
|
|
1806
|
-
const names = Object.keys(app.
|
|
1988
|
+
const names = Object.keys(app.alternateIcons).sort().join(" ");
|
|
1807
1989
|
rendered = rendered.replaceAll(anchor, `${anchor}
|
|
1808
1990
|
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "${names}";`);
|
|
1809
1991
|
}
|
|
@@ -2073,6 +2255,12 @@ const generateForPlatform = async (root, platform, app, outDir = path.resolve(ro
|
|
|
2073
2255
|
platform,
|
|
2074
2256
|
app
|
|
2075
2257
|
});
|
|
2258
|
+
await generateAppIconPreviews({
|
|
2259
|
+
root,
|
|
2260
|
+
dest,
|
|
2261
|
+
platform,
|
|
2262
|
+
app
|
|
2263
|
+
});
|
|
2076
2264
|
if (platform === "android") await generateAdaptiveIcon({
|
|
2077
2265
|
root,
|
|
2078
2266
|
dest,
|
|
@@ -2106,6 +2294,7 @@ const generateForPlatform = async (root, platform, app, outDir = path.resolve(ro
|
|
|
2106
2294
|
root,
|
|
2107
2295
|
dest
|
|
2108
2296
|
});
|
|
2297
|
+
if (platform === "android") generateAndroidIconForwarder(dest, app);
|
|
2109
2298
|
if (platform === "android") await generateKotlinSources({
|
|
2110
2299
|
root,
|
|
2111
2300
|
dest
|