@symbiote-native/cli 0.3.0 → 0.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/bundle.js CHANGED
@@ -3,9 +3,9 @@ import { createRequire } from "node:module";
3
3
  import { styleText } from "node:util";
4
4
  import process$1, { stdin, stdout } from "node:process";
5
5
  import l__default from "node:readline";
6
- import * as fs$1 from "node:fs";
6
+ import * as fs$6 from "node:fs";
7
7
  import { readFileSync } from "node:fs";
8
- import * as path$1 from "node:path";
8
+ import * as path$5 from "node:path";
9
9
  import { join } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { execFileSync } from "node:child_process";
@@ -132,6 +132,11 @@ const EXPO_PACKAGE_LAYERS = [
132
132
  label: "App metrics",
133
133
  symbiotePackage: "@symbiote-native/app-metrics"
134
134
  },
135
+ {
136
+ id: "apple-authentication",
137
+ label: "Sign in with Apple",
138
+ symbiotePackage: "@symbiote-native/apple-authentication"
139
+ },
135
140
  {
136
141
  id: "application",
137
142
  label: "Application info",
@@ -183,6 +188,11 @@ const EXPO_PACKAGE_LAYERS = [
183
188
  label: "Blob",
184
189
  symbiotePackage: "@symbiote-native/blob"
185
190
  },
191
+ {
192
+ id: "blur",
193
+ label: "Blur",
194
+ symbiotePackage: "@symbiote-native/blur"
195
+ },
186
196
  {
187
197
  id: "brightness",
188
198
  label: "Brightness",
@@ -193,6 +203,11 @@ const EXPO_PACKAGE_LAYERS = [
193
203
  label: "Calendar",
194
204
  symbiotePackage: "@symbiote-native/calendar"
195
205
  },
206
+ {
207
+ id: "camera",
208
+ label: "Camera",
209
+ symbiotePackage: "@symbiote-native/camera"
210
+ },
196
211
  {
197
212
  id: "cellular",
198
213
  label: "Cellular info",
@@ -238,11 +253,26 @@ const EXPO_PACKAGE_LAYERS = [
238
253
  label: "Font",
239
254
  symbiotePackage: "@symbiote-native/font"
240
255
  },
256
+ {
257
+ id: "gl",
258
+ label: "GL",
259
+ symbiotePackage: "@symbiote-native/gl"
260
+ },
261
+ {
262
+ id: "glass-effect",
263
+ label: "Glass effect",
264
+ symbiotePackage: "@symbiote-native/glass-effect"
265
+ },
241
266
  {
242
267
  id: "haptics",
243
268
  label: "Haptics",
244
269
  symbiotePackage: "@symbiote-native/haptics"
245
270
  },
271
+ {
272
+ id: "image",
273
+ label: "Image",
274
+ symbiotePackage: "@symbiote-native/image"
275
+ },
246
276
  {
247
277
  id: "image-manipulator",
248
278
  label: "Image manipulator",
@@ -263,6 +293,16 @@ const EXPO_PACKAGE_LAYERS = [
263
293
  label: "Keep awake",
264
294
  symbiotePackage: "@symbiote-native/keep-awake"
265
295
  },
296
+ {
297
+ id: "linear-gradient",
298
+ label: "Linear gradient",
299
+ symbiotePackage: "@symbiote-native/linear-gradient"
300
+ },
301
+ {
302
+ id: "live-photo",
303
+ label: "Live Photo",
304
+ symbiotePackage: "@symbiote-native/live-photo"
305
+ },
266
306
  {
267
307
  id: "local-auth",
268
308
  label: "Local authentication",
@@ -369,6 +409,11 @@ const EXPO_PACKAGE_LAYERS = [
369
409
  label: "Store review prompt",
370
410
  symbiotePackage: "@symbiote-native/store-review"
371
411
  },
412
+ {
413
+ id: "symbols",
414
+ label: "Symbols",
415
+ symbiotePackage: "@symbiote-native/symbols"
416
+ },
372
417
  {
373
418
  id: "system-ui",
374
419
  label: "System UI",
@@ -384,6 +429,23 @@ const EXPO_PACKAGE_LAYERS = [
384
429
  label: "Tracking transparency",
385
430
  symbiotePackage: "@symbiote-native/tracking-transparency"
386
431
  },
432
+ {
433
+ id: "video",
434
+ label: "Video",
435
+ symbiotePackage: "@symbiote-native/video",
436
+ optionalManifestBundles: [{
437
+ id: "backgroundPlayback",
438
+ label: "Background video playback",
439
+ warning: "Requesting FOREGROUND_SERVICE_MEDIA_PLAYBACK triggers Play Console policy review for a foreground service, Google asks for a justification before you can publish.",
440
+ nextSteps: "Set `staysActiveInBackground` and `showNowPlayingNotification` on the player. On iOS add `audio` to UIBackgroundModes in Info.plist yourself.",
441
+ manifestPermissions: ["android.permission.FOREGROUND_SERVICE", "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"],
442
+ manifestServices: [{
443
+ name: "expo.modules.video.playbackService.ExpoVideoPlaybackService",
444
+ foregroundServiceType: "mediaPlayback",
445
+ intentFilterActions: ["androidx.media3.session.MediaSessionService"]
446
+ }]
447
+ }]
448
+ },
387
449
  {
388
450
  id: "video-thumbnails",
389
451
  label: "Video thumbnails",
@@ -1885,44 +1947,44 @@ const TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
1885
1947
  ""
1886
1948
  ]);
1887
1949
  function replaceTextContent(filePath, identity) {
1888
- const content = fs$1.readFileSync(filePath, "utf8");
1950
+ const content = fs$6.readFileSync(filePath, "utf8");
1889
1951
  const replaced = content.split("com.canary").join(identity.bundleId).split("Canary").join(identity.nativeAppName);
1890
- if (replaced !== content) fs$1.writeFileSync(filePath, replaced);
1952
+ if (replaced !== content) fs$6.writeFileSync(filePath, replaced);
1891
1953
  }
1892
1954
  function walkTextFiles(dir, identity) {
1893
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
1894
- const entryPath = path$1.join(dir, entry.name);
1955
+ for (const entry of fs$6.readdirSync(dir, { withFileTypes: true })) {
1956
+ const entryPath = path$5.join(dir, entry.name);
1895
1957
  if (entry.isDirectory()) {
1896
1958
  walkTextFiles(entryPath, identity);
1897
1959
  continue;
1898
1960
  }
1899
- if (TEXT_FILE_EXTENSIONS.has(path$1.extname(entry.name))) replaceTextContent(entryPath, identity);
1961
+ if (TEXT_FILE_EXTENSIONS.has(path$5.extname(entry.name))) replaceTextContent(entryPath, identity);
1900
1962
  }
1901
1963
  }
1902
1964
  function renameCanaryPaths(dir, nativeAppName) {
1903
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) if (entry.isDirectory()) renameCanaryPaths(path$1.join(dir, entry.name), nativeAppName);
1904
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
1965
+ for (const entry of fs$6.readdirSync(dir, { withFileTypes: true })) if (entry.isDirectory()) renameCanaryPaths(path$5.join(dir, entry.name), nativeAppName);
1966
+ for (const entry of fs$6.readdirSync(dir, { withFileTypes: true })) {
1905
1967
  if (!entry.name.startsWith("Canary")) continue;
1906
- const from = path$1.join(dir, entry.name);
1907
- const to = path$1.join(dir, entry.name.replace("Canary", nativeAppName));
1908
- fs$1.renameSync(from, to);
1968
+ const from = path$5.join(dir, entry.name);
1969
+ const to = path$5.join(dir, entry.name.replace("Canary", nativeAppName));
1970
+ fs$6.renameSync(from, to);
1909
1971
  }
1910
1972
  }
1911
1973
  function moveAndroidPackageDir(javaSrcDir, bundleId) {
1912
- const oldDir = path$1.join(javaSrcDir, "com", "canary");
1913
- if (!fs$1.existsSync(oldDir)) return;
1914
- const newDir = path$1.join(javaSrcDir, ...bundleId.split("."));
1915
- fs$1.mkdirSync(path$1.dirname(newDir), { recursive: true });
1916
- fs$1.renameSync(oldDir, newDir);
1917
- const oldTopDir = path$1.join(javaSrcDir, "com");
1918
- if (fs$1.existsSync(oldTopDir) && fs$1.readdirSync(oldTopDir).length === 0) fs$1.rmdirSync(oldTopDir);
1974
+ const oldDir = path$5.join(javaSrcDir, "com", "canary");
1975
+ if (!fs$6.existsSync(oldDir)) return;
1976
+ const newDir = path$5.join(javaSrcDir, ...bundleId.split("."));
1977
+ fs$6.mkdirSync(path$5.dirname(newDir), { recursive: true });
1978
+ fs$6.renameSync(oldDir, newDir);
1979
+ const oldTopDir = path$5.join(javaSrcDir, "com");
1980
+ if (fs$6.existsSync(oldTopDir) && fs$6.readdirSync(oldTopDir).length === 0) fs$6.rmdirSync(oldTopDir);
1919
1981
  }
1920
1982
  function applyAppIdentity(root, identity) {
1921
1983
  walkTextFiles(root, identity);
1922
- const iosDir = path$1.join(root, "ios");
1923
- if (fs$1.existsSync(iosDir)) renameCanaryPaths(iosDir, identity.nativeAppName);
1924
- const androidAppSrc = path$1.join(root, "android", "app", "src");
1925
- if (fs$1.existsSync(androidAppSrc)) for (const sourceSet of fs$1.readdirSync(androidAppSrc)) moveAndroidPackageDir(path$1.join(androidAppSrc, sourceSet, "java"), identity.bundleId);
1984
+ const iosDir = path$5.join(root, "ios");
1985
+ if (fs$6.existsSync(iosDir)) renameCanaryPaths(iosDir, identity.nativeAppName);
1986
+ const androidAppSrc = path$5.join(root, "android", "app", "src");
1987
+ if (fs$6.existsSync(androidAppSrc)) for (const sourceSet of fs$6.readdirSync(androidAppSrc)) moveAndroidPackageDir(path$5.join(androidAppSrc, sourceSet, "java"), identity.bundleId);
1926
1988
  }
1927
1989
  //#endregion
1928
1990
  //#region src/utils/json.ts
@@ -1969,7 +2031,7 @@ function sortDependencies(packageJson) {
1969
2031
  //#endregion
1970
2032
  //#region src/utils/render-template.ts
1971
2033
  function readJsonObject(filePath) {
1972
- const parsed = JSON.parse(fs$1.readFileSync(filePath, "utf8"));
2034
+ const parsed = JSON.parse(fs$6.readFileSync(filePath, "utf8"));
1973
2035
  if (!isJsonObject(parsed)) throw new Error(`${filePath}: expected a JSON object`);
1974
2036
  return parsed;
1975
2037
  }
@@ -1999,37 +2061,37 @@ function stripTypescriptInfix(filename) {
1999
2061
  }
2000
2062
  const DEFAULT_OPTIONS = { hasTypescript: true };
2001
2063
  function renderTemplate(src, dest, options = DEFAULT_OPTIONS) {
2002
- if (fs$1.statSync(src).isDirectory()) {
2003
- fs$1.mkdirSync(dest, { recursive: true });
2004
- for (const entry of fs$1.readdirSync(src)) renderTemplate(path$1.resolve(src, entry), path$1.resolve(dest, entry), options);
2064
+ if (fs$6.statSync(src).isDirectory()) {
2065
+ fs$6.mkdirSync(dest, { recursive: true });
2066
+ for (const entry of fs$6.readdirSync(src)) renderTemplate(path$5.resolve(src, entry), path$5.resolve(dest, entry), options);
2005
2067
  return;
2006
2068
  }
2007
- const filename = path$1.basename(src);
2069
+ const filename = path$5.basename(src);
2008
2070
  if (filename === "README.md") return;
2009
2071
  if (!options.hasTypescript && (isTypescriptOnlyFile(filename) || filename.includes(".typescript."))) return;
2010
2072
  if (options.hasTypescript && isJavascriptOnlyFile(filename)) return;
2011
2073
  const destFilename = filename.includes(".typescript.") ? stripTypescriptInfix(filename) : filename;
2012
- const resolvedDest = path$1.join(path$1.dirname(dest), destFilename);
2074
+ const resolvedDest = path$5.join(path$5.dirname(dest), destFilename);
2013
2075
  if (destFilename === "package.json.fragment.json") {
2014
- const destPackageJsonPath = path$1.resolve(path$1.dirname(resolvedDest), "package.json");
2076
+ const destPackageJsonPath = path$5.resolve(path$5.dirname(resolvedDest), "package.json");
2015
2077
  const fragment = readJsonObject(src);
2016
- const merged = sortDependencies(deepMerge(fs$1.existsSync(destPackageJsonPath) ? readJsonObject(destPackageJsonPath) : {}, fragment));
2017
- fs$1.writeFileSync(destPackageJsonPath, `${JSON.stringify(merged, null, 2)}\n`);
2078
+ const merged = sortDependencies(deepMerge(fs$6.existsSync(destPackageJsonPath) ? readJsonObject(destPackageJsonPath) : {}, fragment));
2079
+ fs$6.writeFileSync(destPackageJsonPath, `${JSON.stringify(merged, null, 2)}\n`);
2018
2080
  return;
2019
2081
  }
2020
- if (destFilename === ".gitignore" && fs$1.existsSync(resolvedDest)) {
2021
- const existing = fs$1.readFileSync(resolvedDest, "utf8");
2022
- const addition = fs$1.readFileSync(src, "utf8");
2023
- fs$1.writeFileSync(resolvedDest, `${existing}\n${addition}`);
2082
+ if (destFilename === ".gitignore" && fs$6.existsSync(resolvedDest)) {
2083
+ const existing = fs$6.readFileSync(resolvedDest, "utf8");
2084
+ const addition = fs$6.readFileSync(src, "utf8");
2085
+ fs$6.writeFileSync(resolvedDest, `${existing}\n${addition}`);
2024
2086
  return;
2025
2087
  }
2026
- fs$1.copyFileSync(src, resolvedDest);
2088
+ fs$6.copyFileSync(src, resolvedDest);
2027
2089
  }
2028
2090
  //#endregion
2029
2091
  //#region src/utils/apply-bootsplash-logo.ts
2030
2092
  function applyBootsplashLogo(root, templatesRoot, framework) {
2031
- const overlayRoot = path$1.join(templatesRoot, "bootsplash-logo", framework);
2032
- if (fs$1.existsSync(overlayRoot)) renderTemplate(overlayRoot, root);
2093
+ const overlayRoot = path$5.join(templatesRoot, "bootsplash-logo", framework);
2094
+ if (fs$6.existsSync(overlayRoot)) renderTemplate(overlayRoot, root);
2033
2095
  }
2034
2096
  //#endregion
2035
2097
  //#region src/utils/apply-expo-modules-manifest.ts
@@ -2044,54 +2106,54 @@ const EXPO_MODULES_BACKUP_ATTRS = `android:supportsRtl="true"
2044
2106
  android:dataExtractionRules="@xml/secure_store_data_extraction_rules"
2045
2107
  android:fullBackupContent="@xml/secure_store_backup_rules">`;
2046
2108
  function applyExpoModulesAndroidManifest(root) {
2047
- const manifestPath = path$1.join(root, "android/app/src/main/AndroidManifest.xml");
2048
- const manifest = fs$1.readFileSync(manifestPath, "utf8");
2109
+ const manifestPath = path$5.join(root, "android/app/src/main/AndroidManifest.xml");
2110
+ const manifest = fs$6.readFileSync(manifestPath, "utf8");
2049
2111
  const withPermissions = manifest.includes("USE_BIOMETRIC") ? manifest : manifest.replace(" <uses-permission android:name=\"android.permission.VIBRATE\" />", EXPO_MODULES_PERMISSIONS);
2050
2112
  const withBackupAttrs = withPermissions.includes("dataExtractionRules") ? withPermissions : withPermissions.replace("android:supportsRtl=\"true\">", EXPO_MODULES_BACKUP_ATTRS);
2051
- fs$1.writeFileSync(manifestPath, withBackupAttrs);
2113
+ fs$6.writeFileSync(manifestPath, withBackupAttrs);
2052
2114
  }
2053
2115
  //#endregion
2054
2116
  //#region src/utils/apply-expo-modules-podfile-autolinking.ts
2055
2117
  const AUTOLINKING_REQUIRE = "require File.join(File.dirname(`node --print \"require.resolve('expo/package.json')\"`), \"scripts/autolinking\")";
2056
2118
  const NATIVE_MODULES_LINE = "config = use_native_modules!";
2057
2119
  function applyExpoModulesPodfileAutolinking(root) {
2058
- const podfilePath = path$1.join(root, "ios", "Podfile");
2059
- if (!fs$1.existsSync(podfilePath)) return;
2060
- let podfile = fs$1.readFileSync(podfilePath, "utf8");
2120
+ const podfilePath = path$5.join(root, "ios", "Podfile");
2121
+ if (!fs$6.existsSync(podfilePath)) return;
2122
+ let podfile = fs$6.readFileSync(podfilePath, "utf8");
2061
2123
  if (podfile.includes("use_expo_modules!")) return;
2062
2124
  if (!podfile.includes(AUTOLINKING_REQUIRE)) podfile = `${AUTOLINKING_REQUIRE}\n\n${podfile}`;
2063
2125
  podfile = podfile.replace(NATIVE_MODULES_LINE, `${NATIVE_MODULES_LINE}\n\n use_expo_modules!`);
2064
- fs$1.writeFileSync(podfilePath, podfile);
2126
+ fs$6.writeFileSync(podfilePath, podfile);
2065
2127
  }
2066
2128
  //#endregion
2067
2129
  //#region src/utils/apply-expo-modules-podfile.ts
2068
2130
  const DEFAULT_PLATFORM_LINE = "platform :ios, min_ios_version_supported.to_s";
2069
2131
  const RAISED_PLATFORM_LINE = "platform :ios, [min_ios_version_supported.to_f, 16.4].max.to_s";
2070
2132
  function applyExpoModulesPodfilePlatform(root) {
2071
- const podfilePath = path$1.join(root, "ios", "Podfile");
2072
- if (!fs$1.existsSync(podfilePath)) return;
2073
- const podfile = fs$1.readFileSync(podfilePath, "utf8");
2133
+ const podfilePath = path$5.join(root, "ios", "Podfile");
2134
+ if (!fs$6.existsSync(podfilePath)) return;
2135
+ const podfile = fs$6.readFileSync(podfilePath, "utf8");
2074
2136
  if (!podfile.includes(DEFAULT_PLATFORM_LINE)) return;
2075
- fs$1.writeFileSync(podfilePath, podfile.replace(DEFAULT_PLATFORM_LINE, RAISED_PLATFORM_LINE));
2137
+ fs$6.writeFileSync(podfilePath, podfile.replace(DEFAULT_PLATFORM_LINE, RAISED_PLATFORM_LINE));
2076
2138
  }
2077
2139
  //#endregion
2078
2140
  //#region src/utils/apply-expo-modules-xcode-deployment-target.ts
2079
2141
  const MIN_EXPO_IOS_VERSION = 16.4;
2080
2142
  const DEPLOYMENT_TARGET_LINE = /IPHONEOS_DEPLOYMENT_TARGET = (\d+(?:\.\d+)?);/g;
2081
2143
  function findPbxprojPath(root) {
2082
- const iosDir = path$1.join(root, "ios");
2083
- if (!fs$1.existsSync(iosDir)) return void 0;
2084
- const xcodeproj = fs$1.readdirSync(iosDir).find((entry) => entry.endsWith(".xcodeproj"));
2144
+ const iosDir = path$5.join(root, "ios");
2145
+ if (!fs$6.existsSync(iosDir)) return void 0;
2146
+ const xcodeproj = fs$6.readdirSync(iosDir).find((entry) => entry.endsWith(".xcodeproj"));
2085
2147
  if (!xcodeproj) return void 0;
2086
- const pbxprojPath = path$1.join(iosDir, xcodeproj, "project.pbxproj");
2087
- return fs$1.existsSync(pbxprojPath) ? pbxprojPath : void 0;
2148
+ const pbxprojPath = path$5.join(iosDir, xcodeproj, "project.pbxproj");
2149
+ return fs$6.existsSync(pbxprojPath) ? pbxprojPath : void 0;
2088
2150
  }
2089
2151
  function applyExpoModulesXcodeDeploymentTarget(root) {
2090
2152
  const pbxprojPath = findPbxprojPath(root);
2091
2153
  if (!pbxprojPath) return;
2092
- const pbxproj = fs$1.readFileSync(pbxprojPath, "utf8");
2154
+ const pbxproj = fs$6.readFileSync(pbxprojPath, "utf8");
2093
2155
  const raised = pbxproj.replace(DEPLOYMENT_TARGET_LINE, (line, version) => Number.parseFloat(version) < MIN_EXPO_IOS_VERSION ? `IPHONEOS_DEPLOYMENT_TARGET = ${MIN_EXPO_IOS_VERSION};` : line);
2094
- if (raised !== pbxproj) fs$1.writeFileSync(pbxprojPath, raised);
2156
+ if (raised !== pbxproj) fs$6.writeFileSync(pbxprojPath, raised);
2095
2157
  }
2096
2158
  //#endregion
2097
2159
  //#region src/utils/apply-splash-screen-hide.ts
@@ -2108,8 +2170,8 @@ const APP_COMPONENT_FILENAMES = /* @__PURE__ */ new Set([
2108
2170
  "App.svelte"
2109
2171
  ]);
2110
2172
  function findAppComponentFile(dir) {
2111
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2112
- const entryPath = path$1.join(dir, entry.name);
2173
+ for (const entry of fs$6.readdirSync(dir, { withFileTypes: true })) {
2174
+ const entryPath = path$5.join(dir, entry.name);
2113
2175
  if (entry.isDirectory()) {
2114
2176
  if (SKIPPED_DIR_NAMES$1.has(entry.name)) continue;
2115
2177
  const found = findAppComponentFile(entryPath);
@@ -2155,9 +2217,9 @@ function patchSvelte(source) {
2155
2217
  function applySplashScreenAppHide(root, options) {
2156
2218
  const appFilePath = findAppComponentFile(root);
2157
2219
  if (appFilePath === void 0) return;
2158
- const source = fs$1.readFileSync(appFilePath, "utf8");
2220
+ const source = fs$6.readFileSync(appFilePath, "utf8");
2159
2221
  if (/\bhide\(\)/.test(source)) return;
2160
- fs$1.writeFileSync(appFilePath, patch(source, options.framework, appFilePath));
2222
+ fs$6.writeFileSync(appFilePath, patch(source, options.framework, appFilePath));
2161
2223
  }
2162
2224
  function patch(source, framework, appFilePath) {
2163
2225
  switch (framework) {
@@ -2173,16 +2235,16 @@ function patch(source, framework, appFilePath) {
2173
2235
  const INLINE_STYLE_BLOCK_FRAMEWORKS = /* @__PURE__ */ new Set(["vue-sfc", "svelte"]);
2174
2236
  const STYLE_TAG_PATTERN = /<style(?=[ >])/;
2175
2237
  function addStyleLangAttribute(dir, lang) {
2176
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2177
- const entryPath = path$1.join(dir, entry.name);
2238
+ for (const entry of fs$6.readdirSync(dir, { withFileTypes: true })) {
2239
+ const entryPath = path$5.join(dir, entry.name);
2178
2240
  if (entry.isDirectory()) {
2179
2241
  if (SKIPPED_DIR_NAMES.has(entry.name)) continue;
2180
2242
  addStyleLangAttribute(entryPath, lang);
2181
2243
  continue;
2182
2244
  }
2183
2245
  if (!entry.name.endsWith(".vue") && !entry.name.endsWith(".svelte")) continue;
2184
- const content = fs$1.readFileSync(entryPath, "utf8");
2185
- if (STYLE_TAG_PATTERN.test(content)) fs$1.writeFileSync(entryPath, content.replace(STYLE_TAG_PATTERN, `<style lang="${lang}"`));
2246
+ const content = fs$6.readFileSync(entryPath, "utf8");
2247
+ if (STYLE_TAG_PATTERN.test(content)) fs$6.writeFileSync(entryPath, content.replace(STYLE_TAG_PATTERN, `<style lang="${lang}"`));
2186
2248
  }
2187
2249
  }
2188
2250
  const PREPROCESSOR_EXTENSIONS = {
@@ -2196,42 +2258,42 @@ const SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
2196
2258
  "node_modules"
2197
2259
  ]);
2198
2260
  function findAppCssFile(dir) {
2199
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2261
+ for (const entry of fs$6.readdirSync(dir, { withFileTypes: true })) {
2200
2262
  if (entry.isDirectory()) {
2201
2263
  if (SKIPPED_DIR_NAMES.has(entry.name)) continue;
2202
- const found = findAppCssFile(path$1.join(dir, entry.name));
2264
+ const found = findAppCssFile(path$5.join(dir, entry.name));
2203
2265
  if (found !== void 0) return found;
2204
2266
  continue;
2205
2267
  }
2206
- if (entry.name === "App.css") return path$1.join(dir, entry.name);
2268
+ if (entry.name === "App.css") return path$5.join(dir, entry.name);
2207
2269
  }
2208
2270
  }
2209
2271
  function replaceInTextFiles(dir, from, to) {
2210
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2211
- const entryPath = path$1.join(dir, entry.name);
2272
+ for (const entry of fs$6.readdirSync(dir, { withFileTypes: true })) {
2273
+ const entryPath = path$5.join(dir, entry.name);
2212
2274
  if (entry.isDirectory()) {
2213
2275
  if (SKIPPED_DIR_NAMES.has(entry.name)) continue;
2214
2276
  replaceInTextFiles(entryPath, from, to);
2215
2277
  continue;
2216
2278
  }
2217
- const content = fs$1.readFileSync(entryPath, "utf8");
2218
- if (content.includes(from)) fs$1.writeFileSync(entryPath, content.split(from).join(to));
2279
+ const content = fs$6.readFileSync(entryPath, "utf8");
2280
+ if (content.includes(from)) fs$6.writeFileSync(entryPath, content.split(from).join(to));
2219
2281
  }
2220
2282
  }
2221
2283
  function renameStylesheetExtension(root, extension) {
2222
2284
  const cssFile = findAppCssFile(root);
2223
2285
  if (cssFile === void 0) return;
2224
- fs$1.renameSync(cssFile, path$1.join(path$1.dirname(cssFile), `App.${extension}`));
2286
+ fs$6.renameSync(cssFile, path$5.join(path$5.dirname(cssFile), `App.${extension}`));
2225
2287
  replaceInTextFiles(root, "'./App.css'", `'./App.${extension}'`);
2226
2288
  }
2227
2289
  function applyStyling(root, options) {
2228
2290
  const { styling, jsTemplateDir, hasNavigation, hasTypescript, templatesRoot } = options;
2229
2291
  if (styling === "css") return;
2230
2292
  if (styling === "css-modules" || styling === "stylesheet") {
2231
- renderTemplate(path$1.join(templatesRoot, "styling", "js", jsTemplateDir, styling), root, { hasTypescript });
2232
- if (hasNavigation) renderTemplate(path$1.join(templatesRoot, "styling", "navigation", jsTemplateDir, styling), root, { hasTypescript });
2293
+ renderTemplate(path$5.join(templatesRoot, "styling", "js", jsTemplateDir, styling), root, { hasTypescript });
2294
+ if (hasNavigation) renderTemplate(path$5.join(templatesRoot, "styling", "navigation", jsTemplateDir, styling), root, { hasTypescript });
2233
2295
  const orphanedCss = findAppCssFile(root);
2234
- if (orphanedCss !== void 0) fs$1.rmSync(orphanedCss, { force: true });
2296
+ if (orphanedCss !== void 0) fs$6.rmSync(orphanedCss, { force: true });
2235
2297
  return;
2236
2298
  }
2237
2299
  if (INLINE_STYLE_BLOCK_FRAMEWORKS.has(jsTemplateDir)) {
@@ -2270,50 +2332,50 @@ var TargetDirNotEmptyError = class extends Error {
2270
2332
  }
2271
2333
  };
2272
2334
  function resolveTemplatesRoot() {
2273
- const packageDir = path$1.dirname(fileURLToPath(import.meta.url));
2274
- const sibling = path$1.join(packageDir, "templates");
2275
- return fs$1.existsSync(sibling) ? sibling : path$1.resolve(packageDir, "../templates");
2335
+ const packageDir = path$5.dirname(fileURLToPath(import.meta.url));
2336
+ const sibling = path$5.join(packageDir, "templates");
2337
+ return fs$6.existsSync(sibling) ? sibling : path$5.resolve(packageDir, "../templates");
2276
2338
  }
2277
2339
  const templatesRoot = resolveTemplatesRoot();
2278
2340
  function isEmptyDir(dir) {
2279
- if (!fs$1.existsSync(dir)) return true;
2280
- const entries = fs$1.readdirSync(dir);
2341
+ if (!fs$6.existsSync(dir)) return true;
2342
+ const entries = fs$6.readdirSync(dir);
2281
2343
  return entries.length === 0 || entries.length === 1 && entries[0] === ".git";
2282
2344
  }
2283
2345
  function scaffoldApp(options) {
2284
- const root = path$1.resolve(process.cwd(), options.appName);
2346
+ const root = path$5.resolve(process.cwd(), options.appName);
2285
2347
  const jsTemplateDir = resolveJsTemplateDir(options.framework, options.vueFlavor);
2286
2348
  if (!isEmptyDir(root)) throw new TargetDirNotEmptyError(root);
2287
- fs$1.mkdirSync(root, { recursive: true });
2288
- const packageNameSource = options.appName === "." ? path$1.basename(root) : options.appName;
2289
- fs$1.writeFileSync(path$1.join(root, "package.json"), `${JSON.stringify({
2349
+ fs$6.mkdirSync(root, { recursive: true });
2350
+ const packageNameSource = options.appName === "." ? path$5.basename(root) : options.appName;
2351
+ fs$6.writeFileSync(path$5.join(root, "package.json"), `${JSON.stringify({
2290
2352
  name: toValidPackageName(packageNameSource),
2291
2353
  version: "0.0.1",
2292
2354
  private: true
2293
2355
  }, null, 2)}\n`);
2294
- renderTemplate(path$1.join(templatesRoot, "native"), root, { hasTypescript: options.hasTypescript });
2295
- renderTemplate(path$1.join(templatesRoot, "js", jsTemplateDir), root, { hasTypescript: options.hasTypescript });
2356
+ renderTemplate(path$5.join(templatesRoot, "native"), root, { hasTypescript: options.hasTypescript });
2357
+ renderTemplate(path$5.join(templatesRoot, "js", jsTemplateDir), root, { hasTypescript: options.hasTypescript });
2296
2358
  applyBootsplashLogo(root, templatesRoot, options.framework);
2297
2359
  const hasExpoModules = options.hasExpoModules || (options.expoPackages?.size ?? 0) > 0;
2298
2360
  if (options.hasNavigation) {
2299
- const navigationLayerRoot = path$1.join(templatesRoot, "layers", "navigation");
2300
- renderTemplate(path$1.join(navigationLayerRoot, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2301
- renderTemplate(path$1.join(navigationLayerRoot, "app", jsTemplateDir), root, { hasTypescript: options.hasTypescript });
2361
+ const navigationLayerRoot = path$5.join(templatesRoot, "layers", "navigation");
2362
+ renderTemplate(path$5.join(navigationLayerRoot, "package.json.fragment.json"), path$5.join(root, "package.json.fragment.json"));
2363
+ renderTemplate(path$5.join(navigationLayerRoot, "app", jsTemplateDir), root, { hasTypescript: options.hasTypescript });
2302
2364
  }
2303
- if (hasExpoModules) renderTemplate(path$1.join(templatesRoot, "layers", "expo-modules"), root);
2365
+ if (hasExpoModules) renderTemplate(path$5.join(templatesRoot, "layers", "expo-modules"), root);
2304
2366
  if (options.hasSplashScreen) {
2305
- const splashScreenLayerRoot = path$1.join(templatesRoot, "layers", "splash-screen");
2306
- renderTemplate(path$1.join(splashScreenLayerRoot, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2307
- renderTemplate(path$1.join(splashScreenLayerRoot, "native"), root);
2367
+ const splashScreenLayerRoot = path$5.join(templatesRoot, "layers", "splash-screen");
2368
+ renderTemplate(path$5.join(splashScreenLayerRoot, "package.json.fragment.json"), path$5.join(root, "package.json.fragment.json"));
2369
+ renderTemplate(path$5.join(splashScreenLayerRoot, "native"), root);
2308
2370
  }
2309
- if (options.hasSlider) renderTemplate(path$1.join(templatesRoot, "layers", "slider", "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2310
- for (const layerName of options.expoPackages ?? []) renderTemplate(path$1.join(templatesRoot, "layers", layerName, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2371
+ if (options.hasSlider) renderTemplate(path$5.join(templatesRoot, "layers", "slider", "package.json.fragment.json"), path$5.join(root, "package.json.fragment.json"));
2372
+ for (const layerName of options.expoPackages ?? []) renderTemplate(path$5.join(templatesRoot, "layers", layerName, "package.json.fragment.json"), path$5.join(root, "package.json.fragment.json"));
2311
2373
  if (options.hasTesting) {
2312
- const testingLayerRoot = path$1.join(templatesRoot, "layers", "testing");
2374
+ const testingLayerRoot = path$5.join(templatesRoot, "layers", "testing");
2313
2375
  const fragmentName = options.framework === "angular" ? "angular" : "base";
2314
- renderTemplate(path$1.join(testingLayerRoot, "fragment", fragmentName, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2315
- renderTemplate(path$1.join(testingLayerRoot, "detox.config.js"), path$1.join(root, "detox.config.js"));
2316
- renderTemplate(path$1.join(testingLayerRoot, "e2e"), path$1.join(root, "e2e"), { hasTypescript: true });
2376
+ renderTemplate(path$5.join(testingLayerRoot, "fragment", fragmentName, "package.json.fragment.json"), path$5.join(root, "package.json.fragment.json"));
2377
+ renderTemplate(path$5.join(testingLayerRoot, "detox.config.js"), path$5.join(root, "detox.config.js"));
2378
+ renderTemplate(path$5.join(testingLayerRoot, "e2e"), path$5.join(root, "e2e"), { hasTypescript: true });
2317
2379
  }
2318
2380
  if (hasExpoModules) {
2319
2381
  applyExpoModulesAndroidManifest(root);
@@ -2338,9 +2400,9 @@ function scaffoldApp(options) {
2338
2400
  //#endregion
2339
2401
  //#region src/utils/find-ios-app-dir.ts
2340
2402
  function findIosAppDir(root) {
2341
- const iosDir = path$1.join(root, "ios");
2342
- if (!fs$1.existsSync(iosDir)) return void 0;
2343
- for (const entry of fs$1.readdirSync(iosDir, { withFileTypes: true })) if (entry.isDirectory() && entry.name !== "Pods") return path$1.join(iosDir, entry.name);
2403
+ const iosDir = path$5.join(root, "ios");
2404
+ if (!fs$6.existsSync(iosDir)) return void 0;
2405
+ for (const entry of fs$6.readdirSync(iosDir, { withFileTypes: true })) if (entry.isDirectory() && entry.name !== "Pods") return path$5.join(iosDir, entry.name);
2344
2406
  }
2345
2407
  //#endregion
2346
2408
  //#region src/utils/apply-expo-modules-plist.ts
@@ -2352,14 +2414,14 @@ const USAGE_DESCRIPTIONS = [
2352
2414
  function applyExpoModulesInfoPlist(root) {
2353
2415
  const iosAppDir = findIosAppDir(root);
2354
2416
  if (iosAppDir === void 0) return;
2355
- const plistPath = path$1.join(iosAppDir, "Info.plist");
2356
- if (!fs$1.existsSync(plistPath)) return;
2357
- const plist = fs$1.readFileSync(plistPath, "utf8");
2417
+ const plistPath = path$5.join(iosAppDir, "Info.plist");
2418
+ if (!fs$6.existsSync(plistPath)) return;
2419
+ const plist = fs$6.readFileSync(plistPath, "utf8");
2358
2420
  const updated = USAGE_DESCRIPTIONS.reduce((current, [key, description]) => {
2359
2421
  if (current.includes(`<key>${key}</key>`)) return current;
2360
2422
  return current.replace("</dict>\n</plist>", `\t<key>${key}</key>\n\t<string>${description}</string>\n</dict>\n</plist>`);
2361
2423
  }, plist);
2362
- if (updated !== plist) fs$1.writeFileSync(plistPath, updated);
2424
+ if (updated !== plist) fs$6.writeFileSync(plistPath, updated);
2363
2425
  }
2364
2426
  //#endregion
2365
2427
  //#region src/utils/apply-splash-screen-app-delegate.ts
@@ -2368,9 +2430,9 @@ const REACT_IMPORT = "import React\n";
2368
2430
  function applySplashScreenAppDelegate(root) {
2369
2431
  const iosAppDir = findIosAppDir(root);
2370
2432
  if (iosAppDir === void 0) return;
2371
- const appDelegatePath = path$1.join(iosAppDir, "AppDelegate.swift");
2372
- if (!fs$1.existsSync(appDelegatePath)) return;
2373
- const source = fs$1.readFileSync(appDelegatePath, "utf8");
2433
+ const appDelegatePath = path$5.join(iosAppDir, "AppDelegate.swift");
2434
+ if (!fs$6.existsSync(appDelegatePath)) return;
2435
+ const source = fs$6.readFileSync(appDelegatePath, "utf8");
2374
2436
  if (source.includes("RNBootSplash.initWithStoryboard(")) return;
2375
2437
  if (!source.includes(REACT_IMPORT)) return;
2376
2438
  const updated = source.includes("import RNBootSplash") ? source : source.replace(REACT_IMPORT, `${REACT_IMPORT}import RNBootSplash\n`);
@@ -2379,13 +2441,13 @@ function applySplashScreenAppDelegate(root) {
2379
2441
  }
2380
2442
 
2381
2443
  `);
2382
- fs$1.writeFileSync(appDelegatePath, withInit);
2444
+ fs$6.writeFileSync(appDelegatePath, withInit);
2383
2445
  }
2384
2446
  //#endregion
2385
2447
  //#region src/utils/apply-splash-screen-main-activity.ts
2386
2448
  function findMainActivityKt(dir) {
2387
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2388
- const entryPath = path$1.join(dir, entry.name);
2449
+ for (const entry of fs$6.readdirSync(dir, { withFileTypes: true })) {
2450
+ const entryPath = path$5.join(dir, entry.name);
2389
2451
  if (entry.isDirectory()) {
2390
2452
  const found = findMainActivityKt(entryPath);
2391
2453
  if (found !== void 0) return found;
@@ -2397,32 +2459,32 @@ function findMainActivityKt(dir) {
2397
2459
  const ONCREATE_OPEN = /override fun onCreate\(savedInstanceState: Bundle\?\) \{\n/;
2398
2460
  const REACT_ACTIVITY_IMPORT = "import com.facebook.react.ReactActivity\n";
2399
2461
  function applySplashScreenMainActivity(root) {
2400
- const javaDir = path$1.join(root, "android/app/src/main/java");
2401
- if (!fs$1.existsSync(javaDir)) return;
2462
+ const javaDir = path$5.join(root, "android/app/src/main/java");
2463
+ if (!fs$6.existsSync(javaDir)) return;
2402
2464
  const mainActivityPath = findMainActivityKt(javaDir);
2403
2465
  if (mainActivityPath === void 0) return;
2404
- const source = fs$1.readFileSync(mainActivityPath, "utf8");
2466
+ const source = fs$6.readFileSync(mainActivityPath, "utf8");
2405
2467
  if (source.includes("RNBootSplash.init(")) return;
2406
2468
  if (!source.includes(REACT_ACTIVITY_IMPORT)) return;
2407
2469
  let updated = source.includes("import com.zoontek.rnbootsplash.RNBootSplash") ? source : source.replace(REACT_ACTIVITY_IMPORT, `${REACT_ACTIVITY_IMPORT}import com.zoontek.rnbootsplash.RNBootSplash\n`);
2408
2470
  if (!updated.includes("import android.os.Bundle")) updated = updated.replace(REACT_ACTIVITY_IMPORT, `import android.os.Bundle\n${REACT_ACTIVITY_IMPORT}`);
2409
2471
  updated = ONCREATE_OPEN.test(updated) ? updated.replace(ONCREATE_OPEN, (match) => `${match} RNBootSplash.init(this, R.style.BootTheme)\n`) : updated.replace("class MainActivity : ReactActivity() {\n", "class MainActivity : ReactActivity() {\n\n override fun onCreate(savedInstanceState: Bundle?) {\n RNBootSplash.init(this, R.style.BootTheme)\n super.onCreate(savedInstanceState)\n }\n");
2410
- fs$1.writeFileSync(mainActivityPath, updated);
2472
+ fs$6.writeFileSync(mainActivityPath, updated);
2411
2473
  }
2412
2474
  //#endregion
2413
2475
  //#region src/utils/apply-splash-screen-manifest.ts
2414
2476
  const MAIN_ACTIVITY_BLOCK = /<activity\b[^>]*android:name="\.MainActivity"[^>]*>/;
2415
2477
  function applySplashScreenManifest(root) {
2416
- const manifestPath = path$1.join(root, "android/app/src/main/AndroidManifest.xml");
2417
- if (!fs$1.existsSync(manifestPath)) return;
2418
- const manifest = fs$1.readFileSync(manifestPath, "utf8");
2478
+ const manifestPath = path$5.join(root, "android/app/src/main/AndroidManifest.xml");
2479
+ if (!fs$6.existsSync(manifestPath)) return;
2480
+ const manifest = fs$6.readFileSync(manifestPath, "utf8");
2419
2481
  const match = manifest.match(MAIN_ACTIVITY_BLOCK);
2420
2482
  if (match === null) return;
2421
2483
  const block = match[0];
2422
2484
  if (block.includes("@style/BootTheme")) return;
2423
2485
  if (!block.includes("android:theme=\"@style/AppTheme\"")) return;
2424
2486
  const updatedBlock = block.replace("android:theme=\"@style/AppTheme\"", "android:theme=\"@style/BootTheme\"");
2425
- fs$1.writeFileSync(manifestPath, manifest.replace(block, updatedBlock));
2487
+ fs$6.writeFileSync(manifestPath, manifest.replace(block, updatedBlock));
2426
2488
  }
2427
2489
  //#endregion
2428
2490
  //#region src/utils/apply-splash-screen-styles.ts
@@ -2433,22 +2495,22 @@ const BOOT_THEME_STYLE = ` <style name="BootTheme" parent="Theme.BootSplash">
2433
2495
  </style>
2434
2496
  `;
2435
2497
  function applySplashScreenStyles(root) {
2436
- const stylesPath = path$1.join(root, "android/app/src/main/res/values/styles.xml");
2437
- if (!fs$1.existsSync(stylesPath)) return;
2438
- const styles = fs$1.readFileSync(stylesPath, "utf8");
2498
+ const stylesPath = path$5.join(root, "android/app/src/main/res/values/styles.xml");
2499
+ if (!fs$6.existsSync(stylesPath)) return;
2500
+ const styles = fs$6.readFileSync(stylesPath, "utf8");
2439
2501
  if (styles.includes("name=\"BootTheme\"")) return;
2440
2502
  if (!styles.includes("</resources>")) return;
2441
- fs$1.writeFileSync(stylesPath, styles.replace("</resources>", `${BOOT_THEME_STYLE}</resources>`));
2503
+ fs$6.writeFileSync(stylesPath, styles.replace("</resources>", `${BOOT_THEME_STYLE}</resources>`));
2442
2504
  }
2443
2505
  //#endregion
2444
2506
  //#region src/add-layers.ts
2445
2507
  function applyDependencyOnlyLayer(root, layerName) {
2446
- renderTemplate(path$1.join(templatesRoot, "layers", layerName, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2508
+ renderTemplate(path$5.join(templatesRoot, "layers", layerName, "package.json.fragment.json"), path$5.join(root, "package.json.fragment.json"));
2447
2509
  }
2448
2510
  function applyExpoModulesLayer(root) {
2449
2511
  applyDependencyOnlyLayer(root, "expo-modules");
2450
- if (fs$1.existsSync(path$1.join(root, "android/app/src/main/AndroidManifest.xml"))) applyExpoModulesAndroidManifest(root);
2451
- if (fs$1.existsSync(path$1.join(root, "ios"))) {
2512
+ if (fs$6.existsSync(path$5.join(root, "android/app/src/main/AndroidManifest.xml"))) applyExpoModulesAndroidManifest(root);
2513
+ if (fs$6.existsSync(path$5.join(root, "ios"))) {
2452
2514
  applyExpoModulesInfoPlist(root);
2453
2515
  applyExpoModulesPodfilePlatform(root);
2454
2516
  applyExpoModulesPodfileAutolinking(root);
@@ -2465,14 +2527,14 @@ function applySplashScreenLayer(root, framework) {
2465
2527
  applyBootsplashLogo(root, templatesRoot, framework);
2466
2528
  }
2467
2529
  async function applyTestingLayer(root, framework, confirmOverwrite) {
2468
- const testingLayerRoot = path$1.join(templatesRoot, "layers", "testing");
2530
+ const testingLayerRoot = path$5.join(templatesRoot, "layers", "testing");
2469
2531
  const fragmentName = framework === "angular" ? "angular" : "base";
2470
- renderTemplate(path$1.join(testingLayerRoot, "fragment", fragmentName, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2471
- const detoxConfigPath = path$1.join(root, "detox.config.js");
2472
- const e2eDirPath = path$1.join(root, "e2e");
2473
- if ((fs$1.existsSync(detoxConfigPath) || fs$1.existsSync(e2eDirPath)) && !await confirmOverwrite()) return false;
2474
- renderTemplate(path$1.join(testingLayerRoot, "detox.config.js"), detoxConfigPath);
2475
- renderTemplate(path$1.join(testingLayerRoot, "e2e"), e2eDirPath, { hasTypescript: true });
2532
+ renderTemplate(path$5.join(testingLayerRoot, "fragment", fragmentName, "package.json.fragment.json"), path$5.join(root, "package.json.fragment.json"));
2533
+ const detoxConfigPath = path$5.join(root, "detox.config.js");
2534
+ const e2eDirPath = path$5.join(root, "e2e");
2535
+ if ((fs$6.existsSync(detoxConfigPath) || fs$6.existsSync(e2eDirPath)) && !await confirmOverwrite()) return false;
2536
+ renderTemplate(path$5.join(testingLayerRoot, "detox.config.js"), detoxConfigPath);
2537
+ renderTemplate(path$5.join(testingLayerRoot, "e2e"), e2eDirPath, { hasTypescript: true });
2476
2538
  return true;
2477
2539
  }
2478
2540
  async function addLayersToApp(options) {
@@ -2547,105 +2609,114 @@ function getCommand(packageManager, script) {
2547
2609
  return packageManager === "npm" ? `npm run ${script}` : `${packageManager} ${script}`;
2548
2610
  }
2549
2611
  //#endregion
2550
- //#region src/grant-bundles.ts
2551
- var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2552
- const fs = __require("node:fs");
2553
- const path = __require("node:path");
2554
- const BEGIN_TAG = "SYMBIOTE-EXPO-LINK:BEGIN";
2555
- const END_TAG = "SYMBIOTE-EXPO-LINK:END";
2556
- const REGION_NOTE = "generated by symbiote-expo-link - do not edit, regenerated on every install";
2557
- const REACT_ANDROID_ANCHOR = "implementation(\"com.facebook.react:react-android\")";
2558
- const MODULES_MAP_ANCHOR = "override fun getModulesMap(): Map<Class<out Module>, String?> = mapOf(";
2559
- const MODULES_PROVIDER_CLASS_ANCHOR = ": ModulesProvider {";
2560
- const MANIFEST_FILENAME = "native-link.json";
2612
+ //#region ../expo-modules-link/src/common.cjs
2613
+ var require_common = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2614
+ const fs$5 = __require("node:fs");
2615
+ const path$4 = __require("node:path");
2616
+ const DEBUG_ENV_VALUES = ["1", "true"];
2561
2617
  function isDebug() {
2562
- return process.env.DEBUG === "1" || process.env.DEBUG === "true" || globalThis.__SYMBIOTE_DEBUG__ === true;
2618
+ return DEBUG_ENV_VALUES.includes(process.env.DEBUG) || globalThis.__SYMBIOTE_DEBUG__ === true;
2563
2619
  }
2564
2620
  function dlog(...args) {
2565
- if (isDebug()) console.log("[symbiote-expo-link]", ...args);
2621
+ if (isDebug()) process.stderr.write(`[symbiote-expo-link] ${args.join(" ")}\n`);
2566
2622
  }
2567
2623
  function warn(message) {
2568
2624
  console.warn(`[symbiote-expo-link] ${message}`);
2569
2625
  }
2570
2626
  function writeFileAtomic(filePath, content) {
2571
2627
  const tmpPath = `${filePath}.symbiote-expo-link.${process.pid}.tmp`;
2572
- fs.writeFileSync(tmpPath, content);
2573
- fs.renameSync(tmpPath, filePath);
2628
+ fs$5.writeFileSync(tmpPath, content);
2629
+ fs$5.renameSync(tmpPath, filePath);
2574
2630
  }
2575
- function findAppRoot() {
2576
- const candidates = [process.cwd(), process.env.INIT_CWD].filter(Boolean);
2577
- for (const candidate of candidates) {
2578
- if (fs.existsSync(path.join(candidate, "android", "app", "build.gradle"))) return candidate;
2579
- if (findInfoPlistFile(candidate)) return candidate;
2580
- }
2581
- return null;
2631
+ function applyIfChanged(filePath, content, nextContent) {
2632
+ if (nextContent === null || nextContent === content) return;
2633
+ writeFileAtomic(filePath, nextContent);
2634
+ dlog(`updated ${filePath}`);
2582
2635
  }
2583
- function findInfoPlistFile(appRoot) {
2584
- const iosRoot = path.join(appRoot, "ios");
2585
- if (!fs.existsSync(iosRoot)) return null;
2586
- const skip = /Pods|build|DerivedData|Tests/i;
2587
- const stack = [iosRoot];
2636
+ function escapeXml(value) {
2637
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2638
+ }
2639
+ function findFileInTree(root, fileName, skip = null) {
2640
+ if (!fs$5.existsSync(root)) return null;
2641
+ const stack = [root];
2588
2642
  while (stack.length > 0) {
2589
2643
  const dir = stack.pop();
2590
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2591
- if (skip.test(entry.name)) continue;
2592
- const full = path.join(dir, entry.name);
2644
+ for (const entry of fs$5.readdirSync(dir, { withFileTypes: true })) {
2645
+ if (skip && skip.test(entry.name)) continue;
2646
+ const full = path$4.join(dir, entry.name);
2593
2647
  if (entry.isDirectory()) stack.push(full);
2594
- else if (entry.name === "Info.plist") return full;
2648
+ else if (entry.name === fileName) return full;
2595
2649
  }
2596
2650
  }
2597
2651
  return null;
2598
2652
  }
2653
+ module.exports = {
2654
+ isDebug,
2655
+ dlog,
2656
+ warn,
2657
+ writeFileAtomic,
2658
+ applyIfChanged,
2659
+ escapeXml,
2660
+ findFileInTree
2661
+ };
2662
+ }));
2663
+ //#endregion
2664
+ //#region ../expo-modules-link/src/android-paths.cjs
2665
+ var require_android_paths = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2666
+ const path$3 = __require("node:path");
2667
+ const { findFileInTree } = require_common();
2668
+ const ANDROID_DIR = "android";
2669
+ const MAIN_APPLICATION_FILE = "MainApplication.kt";
2670
+ function buildGradlePath(appRoot) {
2671
+ return path$3.join(appRoot, ANDROID_DIR, "app", "build.gradle");
2672
+ }
2673
+ function rootBuildGradlePath(appRoot) {
2674
+ return path$3.join(appRoot, ANDROID_DIR, "build.gradle");
2675
+ }
2676
+ function kspLookupPath(appRoot) {
2677
+ return path$3.join(appRoot, "node_modules", "expo-modules-autolinking", "android", "expo-gradle-plugin", "expo-autolinking-plugin", "src", "main", "kotlin", "expo", "modules", "plugin", "KSPLookup.kt");
2678
+ }
2679
+ function androidManifestPath(appRoot) {
2680
+ return path$3.join(appRoot, ANDROID_DIR, "app", "src", "main", "AndroidManifest.xml");
2681
+ }
2599
2682
  function findMainApplicationFile(appRoot) {
2600
- const javaRoot = path.join(appRoot, "android", "app", "src", "main", "java");
2601
- if (!fs.existsSync(javaRoot)) return null;
2602
- const stack = [javaRoot];
2603
- while (stack.length > 0) {
2604
- const dir = stack.pop();
2605
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2606
- const full = path.join(dir, entry.name);
2607
- if (entry.isDirectory()) stack.push(full);
2608
- else if (entry.name === "MainApplication.kt") return full;
2609
- }
2610
- }
2611
- return null;
2683
+ const javaRoot = path$3.join(appRoot, ANDROID_DIR, "app", "src", "main", "java");
2684
+ return findFileInTree(javaRoot, MAIN_APPLICATION_FILE);
2612
2685
  }
2613
- function readManifest(packageDir, packageName) {
2614
- const manifestPath = path.join(packageDir, MANIFEST_FILENAME);
2615
- if (!fs.existsSync(manifestPath)) return null;
2616
- try {
2617
- return {
2618
- packageName,
2619
- manifest: JSON.parse(fs.readFileSync(manifestPath, "utf8"))
2620
- };
2621
- } catch (error) {
2622
- warn(`skipping ${packageName}: its ${MANIFEST_FILENAME} is not valid JSON (${error.message})`);
2686
+ module.exports = {
2687
+ MAIN_APPLICATION_FILE,
2688
+ buildGradlePath,
2689
+ rootBuildGradlePath,
2690
+ kspLookupPath,
2691
+ androidManifestPath,
2692
+ findMainApplicationFile
2693
+ };
2694
+ }));
2695
+ //#endregion
2696
+ //#region ../expo-modules-link/src/android-regions.cjs
2697
+ var require_android_regions = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2698
+ const fs$4 = __require("node:fs");
2699
+ const { dlog, warn, applyIfChanged } = require_common();
2700
+ const { MAIN_APPLICATION_FILE, buildGradlePath, rootBuildGradlePath, kspLookupPath, findMainApplicationFile } = require_android_paths();
2701
+ const BEGIN_TAG = "SYMBIOTE-EXPO-LINK:BEGIN";
2702
+ const END_TAG = "SYMBIOTE-EXPO-LINK:END";
2703
+ const REGION_NOTE = "generated by symbiote-expo-link - do not edit, regenerated on every install";
2704
+ const REACT_ANDROID_ANCHOR = "implementation(\"com.facebook.react:react-android\")";
2705
+ const MODULES_MAP_ANCHOR = "override fun getModulesMap(): Map<Class<out Module>, String?> = mapOf(";
2706
+ const MODULES_PROVIDER_CLASS_ANCHOR = ": ModulesProvider {";
2707
+ const SERVICE_IMPORT = "import expo.modules.kotlin.services.Service";
2708
+ function locateRegion(lines, name, filePath) {
2709
+ const begin = lines.findIndex((line) => line.includes(`${BEGIN_TAG} ${name}`));
2710
+ if (begin === -1) return void 0;
2711
+ const end = lines.findIndex((line, i) => i > begin && line.includes(`${END_TAG} ${name}`));
2712
+ if (end === -1) {
2713
+ warn(`${filePath} has "${BEGIN_TAG} ${name}" but no matching "${END_TAG} ${name}". Restore the end marker (or delete the whole block) and reinstall.`);
2623
2714
  return null;
2624
2715
  }
2625
- }
2626
- function collectManifests(appRoot) {
2627
- const modulesRoot = path.join(appRoot, "node_modules");
2628
- if (!fs.existsSync(modulesRoot)) {
2629
- warn(`no node_modules in ${appRoot}, nothing to link`);
2630
- return [];
2631
- }
2632
- const found = [];
2633
- for (const entry of fs.readdirSync(modulesRoot, { withFileTypes: true })) {
2634
- if (entry.name.startsWith(".")) continue;
2635
- if (entry.name.startsWith("@")) {
2636
- const scopeDir = path.join(modulesRoot, entry.name);
2637
- for (const scoped of fs.readdirSync(scopeDir, { withFileTypes: true })) {
2638
- const entryFound = readManifest(path.join(scopeDir, scoped.name), `${entry.name}/${scoped.name}`);
2639
- if (entryFound) found.push(entryFound);
2640
- }
2641
- continue;
2642
- }
2643
- const entryFound = readManifest(path.join(modulesRoot, entry.name), entry.name);
2644
- if (entryFound) found.push(entryFound);
2645
- }
2646
- found.sort((a, b) => a.packageName < b.packageName ? -1 : a.packageName > b.packageName ? 1 : 0);
2647
- dlog(`found ${found.length} linkable package(s): ${found.map((f) => f.packageName).join(", ")}`);
2648
- return found;
2716
+ return {
2717
+ begin,
2718
+ end
2719
+ };
2649
2720
  }
2650
2721
  function writeRegion({ filePath, content, name, indent, bodyLines, findAnchorIndex, anchorLabel }) {
2651
2722
  const lines = content.split("\n");
@@ -2654,14 +2725,10 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2654
2725
  ...bodyLines.map((line) => `${indent}${line}`),
2655
2726
  `${indent}// ${END_TAG} ${name}`
2656
2727
  ];
2657
- const beginIndex = lines.findIndex((line) => line.includes(`${BEGIN_TAG} ${name}`));
2658
- if (beginIndex !== -1) {
2659
- const endIndex = lines.findIndex((line, i) => i > beginIndex && line.includes(`${END_TAG} ${name}`));
2660
- if (endIndex === -1) {
2661
- warn(`${filePath} has "${BEGIN_TAG} ${name}" but no matching "${END_TAG} ${name}". Restore the end marker (or delete the whole block) and reinstall.`);
2662
- return null;
2663
- }
2664
- lines.splice(beginIndex, endIndex - beginIndex + 1, ...block);
2728
+ const region = locateRegion(lines, name, filePath);
2729
+ if (region === null) return null;
2730
+ if (region !== void 0) {
2731
+ lines.splice(region.begin, region.end - region.begin + 1, ...block);
2665
2732
  return lines.join("\n");
2666
2733
  }
2667
2734
  const anchorIndex = findAnchorIndex(lines);
@@ -2672,22 +2739,23 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2672
2739
  lines.splice(anchorIndex + 1, 0, ...block);
2673
2740
  return lines.join("\n");
2674
2741
  }
2675
- function applyIfChanged(filePath, content, nextContent) {
2676
- if (nextContent === null || nextContent === content) return;
2677
- writeFileAtomic(filePath, nextContent);
2678
- dlog(`updated ${filePath}`);
2742
+ function androidModulesOf(entries) {
2743
+ return entries.flatMap((entry) => entry.manifest.android && Array.isArray(entry.manifest.android.modules) ? entry.manifest.android.modules : []);
2744
+ }
2745
+ function androidServicesOf(entries) {
2746
+ return entries.flatMap((entry) => entry.manifest.android && Array.isArray(entry.manifest.android.services) ? entry.manifest.android.services : []);
2679
2747
  }
2680
2748
  function patchBuildGradle(appRoot, entries) {
2681
- const gradlePath = path.join(appRoot, "android", "app", "build.gradle");
2682
- if (!fs.existsSync(gradlePath)) {
2749
+ const gradlePath = buildGradlePath(appRoot);
2750
+ if (!fs$4.existsSync(gradlePath)) {
2683
2751
  dlog("android/app/build.gradle not found, skipping");
2684
2752
  return;
2685
2753
  }
2686
2754
  const primaryProjectNames = entries.filter((entry) => entry.manifest.android && entry.manifest.android.gradleProjectName).map((entry) => entry.manifest.android.gradleProjectName);
2687
2755
  const serviceProjectNames = androidServicesOf(entries).map((service) => service.gradleProjectName);
2688
2756
  const deps = [.../* @__PURE__ */ new Set([...primaryProjectNames, ...serviceProjectNames])].sort().map((name) => `implementation project(':${name}')`);
2689
- const content = fs.readFileSync(gradlePath, "utf8");
2690
- applyIfChanged(gradlePath, content, writeRegion({
2757
+ const content = fs$4.readFileSync(gradlePath, "utf8");
2758
+ const next = writeRegion({
2691
2759
  filePath: gradlePath,
2692
2760
  content,
2693
2761
  name: "DEPENDENCIES",
@@ -2695,41 +2763,102 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2695
2763
  bodyLines: deps,
2696
2764
  findAnchorIndex: (lines) => lines.findIndex((line) => line.includes(REACT_ANDROID_ANCHOR)),
2697
2765
  anchorLabel: `no \`${REACT_ANDROID_ANCHOR}\` line to anchor below`
2698
- }));
2766
+ });
2767
+ applyIfChanged(gradlePath, content, next);
2699
2768
  }
2700
- function androidModulesOf(entries) {
2701
- return entries.flatMap((entry) => entry.manifest.android && Array.isArray(entry.manifest.android.modules) ? entry.manifest.android.modules : []);
2769
+ function removeRegion(content, name, filePath) {
2770
+ const lines = content.split("\n");
2771
+ const region = locateRegion(lines, name, filePath);
2772
+ if (region === null) return null;
2773
+ if (region === void 0) return content;
2774
+ lines.splice(region.begin, region.end - region.begin + 1);
2775
+ return lines.join("\n");
2702
2776
  }
2703
- function androidServicesOf(entries) {
2704
- return entries.flatMap((entry) => entry.manifest.android && Array.isArray(entry.manifest.android.services) ? entry.manifest.android.services : []);
2777
+ const KOTLIN_VERSION_PATTERN = /^\s*kotlinVersion\s*=\s*"([^"]+)"/;
2778
+ const KSP_VERSION_PATTERN = /\bkspVersion\b/;
2779
+ const KSP_PAIR_PATTERN = /"([^"]+)"\s+to\s+"([^"]+)"/g;
2780
+ const LATEST_KSP_PATTERN = /latestKspVersion\s*=\s*"([^"]+)"/;
2781
+ const KSP_UNTIED_FROM_KOTLIN = [2, 3];
2782
+ function isKotlinAtLeast(version, [major, minor]) {
2783
+ const [versionMajor, versionMinor] = version.split(".").map((part) => parseInt(part, 10) || 0);
2784
+ return versionMajor > major || versionMajor === major && versionMinor >= minor;
2785
+ }
2786
+ function resolveKspVersion(appRoot, kotlinVersion, gradlePath) {
2787
+ const lookupPath = kspLookupPath(appRoot);
2788
+ const byHand = `Set kspVersion in ${gradlePath} by hand.`;
2789
+ if (!fs$4.existsSync(lookupPath)) {
2790
+ warn(`cannot read ${lookupPath} to pick the KSP build for Kotlin ${kotlinVersion}. ${byHand}`);
2791
+ return null;
2792
+ }
2793
+ const source = fs$4.readFileSync(lookupPath, "utf8");
2794
+ const table = new Map([...source.matchAll(KSP_PAIR_PATTERN)].map((pair) => [pair[1], pair[2]]));
2795
+ const latest = LATEST_KSP_PATTERN.exec(source);
2796
+ const version = table.get(kotlinVersion) ?? (isKotlinAtLeast(kotlinVersion, KSP_UNTIED_FROM_KOTLIN) && latest ? latest[1] : null);
2797
+ if (version === null) warn(`Kotlin ${kotlinVersion} has no KSP build in Expo's KSPLookup.kt. ${byHand}`);
2798
+ return version;
2799
+ }
2800
+ function patchRootBuildGradle(appRoot, entries) {
2801
+ const gradlePath = rootBuildGradlePath(appRoot);
2802
+ if (!fs$4.existsSync(gradlePath)) {
2803
+ dlog("android/build.gradle not found, skipping");
2804
+ return;
2805
+ }
2806
+ const content = fs$4.readFileSync(gradlePath, "utf8");
2807
+ const isRequired = entries.some((entry) => entry.manifest.android && entry.manifest.android.requiresKsp === true);
2808
+ const withoutRegion = removeRegion(content, "KSP", gradlePath);
2809
+ if (withoutRegion === null) return;
2810
+ if (!isRequired || KSP_VERSION_PATTERN.test(withoutRegion)) {
2811
+ applyIfChanged(gradlePath, content, withoutRegion);
2812
+ return;
2813
+ }
2814
+ const lines = content.split("\n");
2815
+ const anchorIndex = lines.findIndex((line) => KOTLIN_VERSION_PATTERN.test(line));
2816
+ if (anchorIndex === -1) {
2817
+ warn(`cannot create the KSP block in ${gradlePath}: no \`kotlinVersion = "..."\` line to read the Kotlin version from`);
2818
+ return;
2819
+ }
2820
+ const kotlinVersion = KOTLIN_VERSION_PATTERN.exec(lines[anchorIndex])[1];
2821
+ const kspVersion = resolveKspVersion(appRoot, kotlinVersion, gradlePath);
2822
+ if (kspVersion === null) return;
2823
+ const next = writeRegion({
2824
+ filePath: gradlePath,
2825
+ content,
2826
+ name: "KSP",
2827
+ indent: /^\s*/.exec(lines[anchorIndex])[0],
2828
+ bodyLines: [`kspVersion = "${kspVersion}"`],
2829
+ findAnchorIndex: (current) => current.findIndex((line) => KOTLIN_VERSION_PATTERN.test(line)),
2830
+ anchorLabel: "no `kotlinVersion = \"...\"` line to anchor below"
2831
+ });
2832
+ applyIfChanged(gradlePath, content, next);
2833
+ }
2834
+ function lastImportIndex(lines) {
2835
+ return lines.reduce((last, line, i) => line.trim().startsWith("import ") ? i : last, -1);
2836
+ }
2837
+ function mainApplicationImports(modules, services) {
2838
+ return [.../* @__PURE__ */ new Set([
2839
+ ...modules.map((module$1) => `import ${module$1.importPath}`),
2840
+ ...services.map((service) => `import ${service.importPath}`),
2841
+ SERVICE_IMPORT
2842
+ ])].sort();
2705
2843
  }
2706
2844
  function patchMainApplication(appRoot, entries) {
2707
2845
  const filePath = findMainApplicationFile(appRoot);
2708
2846
  if (!filePath) {
2709
- dlog("MainApplication.kt not found, skipping");
2847
+ dlog(`${MAIN_APPLICATION_FILE} not found, skipping`);
2710
2848
  return;
2711
2849
  }
2712
2850
  const modules = androidModulesOf(entries);
2713
2851
  const services = androidServicesOf(entries);
2714
- const imports = [.../* @__PURE__ */ new Set([
2715
- ...modules.map((module$1) => `import ${module$1.importPath}`),
2716
- ...services.map((service) => `import ${service.importPath}`),
2717
- "import expo.modules.kotlin.services.Service"
2718
- ])].sort();
2719
2852
  const mapEntries = [...new Set(modules.map((module$2) => `${module$2.className}::class.java to "${module$2.nativeName}",`))].sort();
2720
2853
  const serviceEntries = [...new Set(services.map((service) => ` ${service.className}::class.java,`))].sort();
2721
- const content = fs.readFileSync(filePath, "utf8");
2854
+ const content = fs$4.readFileSync(filePath, "utf8");
2722
2855
  const withImports = writeRegion({
2723
2856
  filePath,
2724
2857
  content,
2725
2858
  name: "IMPORTS",
2726
2859
  indent: "",
2727
- bodyLines: imports,
2728
- findAnchorIndex: (lines) => {
2729
- let lastImport = -1;
2730
- for (let i = 0; i < lines.length; i += 1) if (lines[i].trim().startsWith("import ")) lastImport = i;
2731
- return lastImport;
2732
- },
2860
+ bodyLines: mainApplicationImports(modules, services),
2861
+ findAnchorIndex: lastImportIndex,
2733
2862
  anchorLabel: "no import statements to anchor below"
2734
2863
  });
2735
2864
  if (withImports === null) return;
@@ -2743,7 +2872,7 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2743
2872
  anchorLabel: "no `getModulesMap()` declaration to anchor below"
2744
2873
  });
2745
2874
  if (withModules === null) return;
2746
- applyIfChanged(filePath, content, writeRegion({
2875
+ const next = writeRegion({
2747
2876
  filePath,
2748
2877
  content: withModules,
2749
2878
  name: "SERVICES",
@@ -2755,31 +2884,45 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2755
2884
  ],
2756
2885
  findAnchorIndex: (lines) => lines.findIndex((line) => line.includes(MODULES_PROVIDER_CLASS_ANCHOR)),
2757
2886
  anchorLabel: "no `ExpoModulesProvider : ModulesProvider` class declaration to anchor below"
2758
- }));
2759
- }
2760
- function escapeXml(value) {
2761
- return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2887
+ });
2888
+ applyIfChanged(filePath, content, next);
2762
2889
  }
2890
+ module.exports = {
2891
+ androidServicesOf,
2892
+ patchBuildGradle,
2893
+ patchRootBuildGradle,
2894
+ patchMainApplication
2895
+ };
2896
+ }));
2897
+ //#endregion
2898
+ //#region ../expo-modules-link/src/android-manifest.cjs
2899
+ var require_android_manifest = /* @__PURE__ */ __commonJSMin(((exports, module) => {
2900
+ const fs$3 = __require("node:fs");
2901
+ const { dlog, warn, applyIfChanged, escapeXml } = require_common();
2902
+ const { androidManifestPath } = require_android_paths();
2903
+ const CONFIG_CHANGES_ATTRIBUTE = "android:configChanges";
2763
2904
  function escapeXmlAttribute(value) {
2764
2905
  return escapeXml(value).replace(/"/g, "&quot;");
2765
2906
  }
2766
- function findTag(content, tagName) {
2767
- const match = new RegExp(`<${tagName}(?=[\\s>/])`).exec(content);
2768
- if (!match) return null;
2907
+ function scanTagEnd(content, from) {
2769
2908
  let quote = null;
2770
- for (let i = match.index; i < content.length; i += 1) {
2909
+ for (let i = from; i < content.length; i += 1) {
2771
2910
  const char = content[i];
2772
2911
  if (quote !== null) {
2773
2912
  if (char === quote) quote = null;
2774
- continue;
2775
- }
2776
- if (char === "\"" || char === "'") quote = char;
2777
- else if (char === ">") return {
2778
- start: match.index,
2779
- end: i
2780
- };
2913
+ } else if (char === "\"" || char === "'") quote = char;
2914
+ else if (char === ">") return i;
2781
2915
  }
2782
- return null;
2916
+ return -1;
2917
+ }
2918
+ function findTag(content, tagName) {
2919
+ const match = new RegExp(`<${tagName}(?=[\\s>/])`).exec(content);
2920
+ if (!match) return null;
2921
+ const end = scanTagEnd(content, match.index);
2922
+ return end === -1 ? null : {
2923
+ start: match.index,
2924
+ end
2925
+ };
2783
2926
  }
2784
2927
  function findApplicationTag(content) {
2785
2928
  return findTag(content, "application");
@@ -2789,27 +2932,22 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2789
2932
  const re = new RegExp(`<${tagName}(?=[\\s>/])`, "g");
2790
2933
  let match = re.exec(content);
2791
2934
  while (match) {
2792
- let quote = null;
2793
- for (let i = match.index; i < content.length; i += 1) {
2794
- const char = content[i];
2795
- if (quote !== null) {
2796
- if (char === quote) quote = null;
2797
- continue;
2798
- }
2799
- if (char === "\"" || char === "'") quote = char;
2800
- else if (char === ">") {
2801
- tags.push({
2802
- start: match.index,
2803
- end: i
2804
- });
2805
- re.lastIndex = i + 1;
2806
- break;
2807
- }
2935
+ const end = scanTagEnd(content, match.index);
2936
+ if (end !== -1) {
2937
+ tags.push({
2938
+ start: match.index,
2939
+ end
2940
+ });
2941
+ re.lastIndex = end + 1;
2808
2942
  }
2809
2943
  match = re.exec(content);
2810
2944
  }
2811
2945
  return tags;
2812
2946
  }
2947
+ function readXmlAttribute(tagText, name) {
2948
+ const match = new RegExp(`\\s${name}\\s*=\\s*"([^"]*)"`).exec(tagText);
2949
+ return match ? match[1] : null;
2950
+ }
2813
2951
  function findMainActivityTag(content) {
2814
2952
  for (const tag of findAllTags(content, "activity")) {
2815
2953
  const name = readXmlAttribute(content.slice(tag.start, tag.end), "android:name");
@@ -2817,26 +2955,48 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2817
2955
  }
2818
2956
  return null;
2819
2957
  }
2820
- function readXmlAttribute(tagText, name) {
2821
- const match = new RegExp(`\\s${name}\\s*=\\s*"([^"]*)"`).exec(tagText);
2822
- return match ? match[1] : null;
2958
+ function attributeSeparator(tagText) {
2959
+ const lastNewline = tagText.lastIndexOf("\n");
2960
+ return lastNewline === -1 ? " " : `\n${/^[ \t]*/.exec(tagText.slice(lastNewline + 1))[0]}`;
2823
2961
  }
2824
- function patchAndroidManifest(appRoot, entries) {
2962
+ function appendAttribute(content, tag, name, escapedValue) {
2963
+ const separator = attributeSeparator(content.slice(tag.start, tag.end));
2964
+ const insertAt = content[tag.end - 1] === "/" ? tag.end - 1 : tag.end;
2965
+ return `${content.slice(0, insertAt)}${separator}${name}="${escapedValue}"${content.slice(insertAt)}`;
2966
+ }
2967
+ function androidListOf(entries, field) {
2968
+ return entries.flatMap((entry) => entry.manifest.android && entry.manifest.android[field] || []);
2969
+ }
2970
+ function loadAndroidManifest(appRoot, skipLabel) {
2971
+ const manifestPath = androidManifestPath(appRoot);
2972
+ if (!fs$3.existsSync(manifestPath)) {
2973
+ dlog(`AndroidManifest.xml not found, skipping ${skipLabel}`);
2974
+ return null;
2975
+ }
2976
+ return {
2977
+ manifestPath,
2978
+ content: fs$3.readFileSync(manifestPath, "utf8")
2979
+ };
2980
+ }
2981
+ function wantedApplicationAttributes(entries) {
2825
2982
  const wanted = /* @__PURE__ */ new Map();
2826
2983
  for (const entry of entries) {
2827
2984
  const attributes = entry.manifest.android && entry.manifest.android.manifestApplicationAttributes;
2828
- if (!attributes) continue;
2829
- for (const [name, value] of Object.entries(attributes)) if (!wanted.has(name)) wanted.set(name, value);
2985
+ for (const [name, value] of Object.entries(attributes || {})) if (!wanted.has(name)) wanted.set(name, value);
2830
2986
  }
2831
- if (wanted.size === 0) return;
2832
- const manifestPath = path.join(appRoot, "android", "app", "src", "main", "AndroidManifest.xml");
2833
- if (!fs.existsSync(manifestPath)) {
2834
- dlog("AndroidManifest.xml not found, skipping application attributes");
2835
- return;
2836
- }
2837
- const content = fs.readFileSync(manifestPath, "utf8");
2987
+ return [...wanted].sort((a, b) => a[0] < b[0] ? -1 : 1);
2988
+ }
2989
+ function warnOnAttributeDrift(manifestPath, name, existing, value) {
2990
+ warn(`${name} in ${manifestPath} reads "${existing}" but the package manifest says "${value}". Keeping the file as-is; edit it by hand if you want the manifest's.`);
2991
+ }
2992
+ function patchAndroidManifest(appRoot, entries) {
2993
+ const wanted = wantedApplicationAttributes(entries);
2994
+ if (wanted.length === 0) return;
2995
+ const loaded = loadAndroidManifest(appRoot, "application attributes");
2996
+ if (!loaded) return;
2997
+ const { manifestPath, content } = loaded;
2838
2998
  let next = content;
2839
- for (const [name, value] of [...wanted].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
2999
+ for (const [name, value] of wanted) {
2840
3000
  if (!/^[\w.:-]+$/.test(name)) {
2841
3001
  warn(`skipping the manifest attribute "${name}": not a plain XML attribute name`);
2842
3002
  continue;
@@ -2846,79 +3006,55 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2846
3006
  warn(`${manifestPath} has no <application> element, skipping ${name}`);
2847
3007
  return;
2848
3008
  }
2849
- const tagText = next.slice(tag.start, tag.end);
2850
3009
  const escaped = escapeXmlAttribute(value);
2851
- const existing = readXmlAttribute(tagText, name);
2852
- if (existing !== null) {
2853
- if (existing !== escaped) warn(`${name} in ${manifestPath} reads "${existing}" but the package manifest says "${value}". Keeping the file as-is; edit it by hand if you want the manifest's.`);
2854
- continue;
2855
- }
2856
- const lastNewline = tagText.lastIndexOf("\n");
2857
- const separator = lastNewline === -1 ? " " : `\n${/^[ \t]*/.exec(tagText.slice(lastNewline + 1))[0]}`;
2858
- const insertAt = next[tag.end - 1] === "/" ? tag.end - 1 : tag.end;
2859
- next = `${next.slice(0, insertAt)}${separator}${name}="${escaped}"${next.slice(insertAt)}`;
3010
+ const existing = readXmlAttribute(next.slice(tag.start, tag.end), name);
3011
+ if (existing === null) next = appendAttribute(next, tag, name, escaped);
3012
+ else if (existing !== escaped) warnOnAttributeDrift(manifestPath, name, existing, value);
2860
3013
  }
2861
3014
  applyIfChanged(manifestPath, content, next);
2862
3015
  }
3016
+ function replaceConfigChangesValue(content, tag, merged) {
3017
+ const tagText = content.slice(tag.start, tag.end);
3018
+ const valueMatch = new RegExp(`${CONFIG_CHANGES_ATTRIBUTE}\\s*=\\s*"`).exec(tagText);
3019
+ const valueStart = valueMatch.index + valueMatch[0].length;
3020
+ const valueEnd = tagText.indexOf("\"", valueStart);
3021
+ const nextTagText = tagText.slice(0, valueStart) + merged + tagText.slice(valueEnd);
3022
+ return content.slice(0, tag.start) + nextTagText + content.slice(tag.end);
3023
+ }
3024
+ function addConfigChangesAttribute(content, tag, merged) {
3025
+ const selfClosing = content[tag.end - 1] === "/";
3026
+ const insertAt = selfClosing ? tag.end - 1 : tag.end;
3027
+ const separator = attributeSeparator(content.slice(tag.start, tag.end));
3028
+ return `${content.slice(0, insertAt).replace(/[ \t]+$/, "")}${separator}${CONFIG_CHANGES_ATTRIBUTE}="${merged}"${selfClosing ? " " : ""}${content.slice(insertAt)}`;
3029
+ }
2863
3030
  function patchMainActivityConfigChanges(appRoot, entries) {
2864
- const wanted = /* @__PURE__ */ new Set();
2865
- for (const entry of entries) {
2866
- const values = entry.manifest.android && entry.manifest.android.mainActivityConfigChanges;
2867
- if (!values) continue;
2868
- for (const value of values) wanted.add(value);
2869
- }
3031
+ const wanted = new Set(androidListOf(entries, "mainActivityConfigChanges"));
2870
3032
  if (wanted.size === 0) return;
2871
- const manifestPath = path.join(appRoot, "android", "app", "src", "main", "AndroidManifest.xml");
2872
- if (!fs.existsSync(manifestPath)) {
2873
- dlog("AndroidManifest.xml not found, skipping MainActivity configChanges");
2874
- return;
2875
- }
2876
- const content = fs.readFileSync(manifestPath, "utf8");
3033
+ const loaded = loadAndroidManifest(appRoot, "MainActivity configChanges");
3034
+ if (!loaded) return;
3035
+ const { manifestPath, content } = loaded;
2877
3036
  const tag = findMainActivityTag(content);
2878
3037
  if (!tag) {
2879
3038
  warn(`${manifestPath} has no MainActivity <activity> element, skipping configChanges`);
2880
3039
  return;
2881
3040
  }
2882
- const tagText = content.slice(tag.start, tag.end);
2883
- const existingValue = readXmlAttribute(tagText, "android:configChanges");
3041
+ const existingValue = readXmlAttribute(content.slice(tag.start, tag.end), CONFIG_CHANGES_ATTRIBUTE);
2884
3042
  const existingTokens = existingValue ? existingValue.split("|").map((token) => token.trim()).filter(Boolean) : [];
2885
- const existingSet = new Set(existingTokens);
2886
- const missing = [...wanted].filter((token) => !existingSet.has(token)).sort();
3043
+ const missing = [...wanted].filter((token) => !existingTokens.includes(token)).sort();
2887
3044
  if (missing.length === 0) return;
2888
3045
  const merged = [...existingTokens, ...missing].join("|");
2889
- let next;
2890
- if (existingValue !== null) {
2891
- const valueMatch = /android:configChanges\s*=\s*"/.exec(tagText);
2892
- const valueStart = valueMatch.index + valueMatch[0].length;
2893
- const valueEnd = tagText.indexOf("\"", valueStart);
2894
- const nextTagText = tagText.slice(0, valueStart) + merged + tagText.slice(valueEnd);
2895
- next = content.slice(0, tag.start) + nextTagText + content.slice(tag.end);
2896
- } else {
2897
- const lastNewline = tagText.lastIndexOf("\n");
2898
- const separator = lastNewline === -1 ? " " : `\n${/^[ \t]*/.exec(tagText.slice(lastNewline + 1))[0]}`;
2899
- const selfClosing = content[tag.end - 1] === "/";
2900
- const insertAt = selfClosing ? tag.end - 1 : tag.end;
2901
- next = `${content.slice(0, insertAt).replace(/[ \t]+$/, "")}${separator}android:configChanges="${merged}"${selfClosing ? " " : ""}${content.slice(insertAt)}`;
2902
- }
3046
+ const next = existingValue !== null ? replaceConfigChangesValue(content, tag, merged) : addConfigChangesAttribute(content, tag, merged);
2903
3047
  applyIfChanged(manifestPath, content, next);
2904
3048
  }
2905
3049
  function readManifestPermissions(content) {
2906
3050
  return new Set([...content.matchAll(/<uses-permission\s[^>]*android:name="([^"]+)"/g)].map((match) => match[1]));
2907
3051
  }
2908
3052
  function patchAndroidManifestPermissions(appRoot, entries) {
2909
- const wanted = /* @__PURE__ */ new Set();
2910
- for (const entry of entries) {
2911
- const permissions = entry.manifest.android && entry.manifest.android.manifestPermissions;
2912
- if (!permissions) continue;
2913
- for (const permission of permissions) wanted.add(permission);
2914
- }
3053
+ const wanted = new Set(androidListOf(entries, "manifestPermissions"));
2915
3054
  if (wanted.size === 0) return;
2916
- const manifestPath = path.join(appRoot, "android", "app", "src", "main", "AndroidManifest.xml");
2917
- if (!fs.existsSync(manifestPath)) {
2918
- dlog("AndroidManifest.xml not found, skipping permissions");
2919
- return;
2920
- }
2921
- const content = fs.readFileSync(manifestPath, "utf8");
3055
+ const loaded = loadAndroidManifest(appRoot, "permissions");
3056
+ if (!loaded) return;
3057
+ const { manifestPath, content } = loaded;
2922
3058
  const existing = readManifestPermissions(content);
2923
3059
  const missing = [...wanted].filter((permission) => !existing.has(permission)).sort();
2924
3060
  if (missing.length === 0) return;
@@ -2941,21 +3077,13 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2941
3077
  return `${openTag}>\n${indent} <intent-filter>\n${service.intentFilterActions.map((action) => `${indent} <action android:name="${escapeXmlAttribute(action)}" />`).join("\n")}\n${indent} </intent-filter>\n${indent}</service>`;
2942
3078
  }
2943
3079
  function patchAndroidManifestServices(appRoot, entries) {
2944
- const wanted = /* @__PURE__ */ new Map();
2945
- for (const entry of entries) {
2946
- const services = entry.manifest.android && entry.manifest.android.manifestServices;
2947
- if (!services) continue;
2948
- for (const service of services) if (!wanted.has(service.name)) wanted.set(service.name, service);
2949
- }
2950
- if (wanted.size === 0) return;
2951
- const manifestPath = path.join(appRoot, "android", "app", "src", "main", "AndroidManifest.xml");
2952
- if (!fs.existsSync(manifestPath)) {
2953
- dlog("AndroidManifest.xml not found, skipping services");
2954
- return;
2955
- }
2956
- const content = fs.readFileSync(manifestPath, "utf8");
3080
+ const declared = androidListOf(entries, "manifestServices");
3081
+ if (declared.length === 0) return;
3082
+ const loaded = loadAndroidManifest(appRoot, "services");
3083
+ if (!loaded) return;
3084
+ const { manifestPath, content } = loaded;
2957
3085
  const existing = readManifestServiceNames(content);
2958
- const missing = [...wanted.values()].filter((service) => !existing.has(service.name)).sort((a, b) => a.name < b.name ? -1 : 1);
3086
+ const missing = [...new Map(declared.map((service) => [service.name, service])).values()].filter((service) => !existing.has(service.name)).sort((a, b) => a.name < b.name ? -1 : 1);
2959
3087
  if (missing.length === 0) return;
2960
3088
  const tag = findApplicationTag(content);
2961
3089
  if (!tag) {
@@ -2967,50 +3095,130 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2967
3095
  return;
2968
3096
  }
2969
3097
  const lastNewline = content.lastIndexOf("\n", tag.start);
2970
- const indent = `${/^[ \t]*/.exec(content.slice(lastNewline + 1, tag.start))[0]} `;
2971
- const blocks = missing.map((service) => buildServiceXml(service, indent)).join("\n");
3098
+ const tagIndent = /^[ \t]*/.exec(content.slice(lastNewline + 1, tag.start))[0];
3099
+ const blocks = missing.map((service) => buildServiceXml(service, `${tagIndent} `)).join("\n");
2972
3100
  const insertAt = tag.end + 1;
2973
3101
  applyIfChanged(manifestPath, content, `${content.slice(0, insertAt)}\n${blocks}${content.slice(insertAt)}`);
2974
3102
  }
3103
+ module.exports = {
3104
+ patchAndroidManifest,
3105
+ patchAndroidManifestPermissions,
3106
+ patchAndroidManifestServices,
3107
+ patchMainActivityConfigChanges
3108
+ };
3109
+ }));
3110
+ //#endregion
3111
+ //#region ../expo-modules-link/src/ios-xcode-project.cjs
3112
+ var require_ios_xcode_project = /* @__PURE__ */ __commonJSMin(((exports, module) => {
3113
+ const crypto = __require("node:crypto");
3114
+ const fs$2 = __require("node:fs");
3115
+ const path$2 = __require("node:path");
3116
+ const { dlog, warn, writeFileAtomic } = require_common();
3117
+ const XCODEPROJ_SUFFIX = ".xcodeproj";
3118
+ const PODS_PROJECT = "Pods.xcodeproj";
3119
+ const PBXPROJ = "project.pbxproj";
3120
+ const OBJECT_ID_LENGTH = 24;
3121
+ const EMPTY_ENTITLEMENTS = `<?xml version="1.0" encoding="UTF-8"?>
3122
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3123
+ <plist version="1.0">
3124
+ <dict>
3125
+ </dict>
3126
+ </plist>
3127
+ `;
3128
+ function escapeRegExp(text) {
3129
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3130
+ }
3131
+ function pbxLabel(name) {
3132
+ return [
3133
+ "/*",
3134
+ name,
3135
+ "*/"
3136
+ ].join(" ");
3137
+ }
3138
+ function projectRelative(iosDir, filePath) {
3139
+ return path$2.relative(iosDir, filePath).split(path$2.sep).join("/");
3140
+ }
3141
+ function findAppPbxproj(iosDir) {
3142
+ if (!fs$2.existsSync(iosDir)) return null;
3143
+ const project = fs$2.readdirSync(iosDir).find((name) => name.endsWith(XCODEPROJ_SUFFIX) && name !== PODS_PROJECT);
3144
+ return project ? path$2.join(iosDir, project, PBXPROJ) : null;
3145
+ }
3146
+ function newObjectId(content, seed) {
3147
+ for (let salt = 0;; salt += 1) {
3148
+ const id = crypto.createHash("sha1").update(`${seed}:${salt}`).digest("hex").slice(0, OBJECT_ID_LENGTH).toUpperCase();
3149
+ if (!content.includes(id)) return id;
3150
+ }
3151
+ }
3152
+ function withSigningSetting(content, plistRef, entitlementsRef) {
3153
+ const settings = new RegExp(`^(\\t+)INFOPLIST_FILE = "?${escapeRegExp(plistRef)}"?;\\n`, "gm");
3154
+ if (!settings.test(content)) return null;
3155
+ return content.replace(settings, (line, indent, offset) => {
3156
+ return content.slice(content.lastIndexOf("buildSettings", offset), offset).includes("CODE_SIGN_ENTITLEMENTS") ? line : `${indent}CODE_SIGN_ENTITLEMENTS = ${entitlementsRef};\n${line}`;
3157
+ });
3158
+ }
3159
+ function withFileReference(content, plistRef, entitlementsRef) {
3160
+ const plist = new RegExp(`^(\\t+)(\\w+) /\\* ([^*]+) \\*/ = \\{isa = PBXFileReference;[^\\n]*path = "?${escapeRegExp(plistRef)}"?;[^\\n]*\\n`, "m").exec(content);
3161
+ if (!plist) return null;
3162
+ const [line, indent, plistId, plistName] = plist;
3163
+ const fileName = path$2.posix.basename(entitlementsRef);
3164
+ const label = pbxLabel(fileName);
3165
+ const id = newObjectId(content, entitlementsRef);
3166
+ const attributes = `isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; name = ${fileName}; path = ${entitlementsRef}; sourceTree = "<group>";`;
3167
+ const withReference = content.replace(line, () => `${line}${indent}${id} ${label} = {${attributes} };\n`);
3168
+ const inGroup = new RegExp(`^(\\t+)${plistId} ${escapeRegExp(pbxLabel(plistName))},\\n`, "m").exec(withReference);
3169
+ if (!inGroup) return null;
3170
+ return withReference.replace(inGroup[0], () => `${inGroup[0]}${inGroup[1]}${id} ${label},\n`);
3171
+ }
3172
+ function addEntitlementsToProject(content, plistRef, entitlementsRef) {
3173
+ const withSigning = withSigningSetting(content, plistRef, entitlementsRef);
3174
+ return withSigning === null ? null : withFileReference(withSigning, plistRef, entitlementsRef);
3175
+ }
3176
+ function createEntitlementsFile(appRoot, plistPath) {
3177
+ const iosDir = path$2.join(appRoot, "ios");
3178
+ const pbxprojPath = findAppPbxproj(iosDir);
3179
+ if (!pbxprojPath) {
3180
+ dlog("no Xcode project found, not creating an .entitlements file");
3181
+ return null;
3182
+ }
3183
+ const appName = path$2.basename(path$2.dirname(plistPath));
3184
+ const filePath = path$2.join(path$2.dirname(plistPath), `${appName}.entitlements`);
3185
+ const next = addEntitlementsToProject(fs$2.readFileSync(pbxprojPath, "utf8"), projectRelative(iosDir, plistPath), projectRelative(iosDir, filePath));
3186
+ if (next === null) {
3187
+ warn(`could not wire an .entitlements file into ${pbxprojPath}, add one and set CODE_SIGN_ENTITLEMENTS by hand`);
3188
+ return null;
3189
+ }
3190
+ writeFileAtomic(pbxprojPath, next);
3191
+ writeFileAtomic(filePath, EMPTY_ENTITLEMENTS);
3192
+ dlog(`created ${filePath} and wired it into ${pbxprojPath}`);
3193
+ return filePath;
3194
+ }
3195
+ module.exports = { createEntitlementsFile };
3196
+ }));
3197
+ //#endregion
3198
+ //#region ../expo-modules-link/src/ios-plist.cjs
3199
+ var require_ios_plist = /* @__PURE__ */ __commonJSMin(((exports, module) => {
3200
+ const fs$1 = __require("node:fs");
3201
+ const path$1 = __require("node:path");
3202
+ const { dlog, warn, applyIfChanged, escapeXml, findFileInTree } = require_common();
3203
+ const { createEntitlementsFile } = require_ios_xcode_project();
3204
+ const INFO_PLIST = "Info.plist";
3205
+ const NON_APP_DIRS = /Pods|build|DerivedData|Tests/i;
3206
+ function findInfoPlistFile(appRoot) {
3207
+ return findFileInTree(path$1.join(appRoot, "ios"), INFO_PLIST, NON_APP_DIRS);
3208
+ }
3209
+ function findEntitlementsFile(appRoot) {
3210
+ const plistPath = findInfoPlistFile(appRoot);
3211
+ if (!plistPath) return null;
3212
+ const dir = path$1.dirname(plistPath);
3213
+ const name = fs$1.readdirSync(dir).find((file) => file.endsWith(".entitlements"));
3214
+ return name ? path$1.join(dir, name) : null;
3215
+ }
2975
3216
  function readPlistString(content, key) {
2976
3217
  const keyIndex = content.indexOf(`<key>${key}</key>`);
2977
3218
  if (keyIndex === -1) return null;
2978
3219
  const match = /<string>([\s\S]*?)<\/string>/.exec(content.slice(keyIndex));
2979
3220
  return match ? match[1] : null;
2980
3221
  }
2981
- function patchInfoPlist(appRoot, entries) {
2982
- const wanted = /* @__PURE__ */ new Map();
2983
- for (const entry of entries) {
2984
- const keys = entry.manifest.ios && entry.manifest.ios.infoPlistKeys;
2985
- if (!keys) continue;
2986
- for (const [key, description] of Object.entries(keys)) if (!wanted.has(key)) wanted.set(key, description);
2987
- }
2988
- if (wanted.size === 0) return;
2989
- const plistPath = findInfoPlistFile(appRoot);
2990
- if (!plistPath) {
2991
- dlog("Info.plist not found, skipping iOS permission strings");
2992
- return;
2993
- }
2994
- const content = fs.readFileSync(plistPath, "utf8");
2995
- let next = content;
2996
- for (const [key, description] of [...wanted].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
2997
- const escaped = escapeXml(description);
2998
- const existing = readPlistString(next, key);
2999
- if (existing !== null) {
3000
- if (existing !== escaped) warn(`${key} in ${plistPath} reads "${existing}" but the package manifest says "${description}". Keeping the file as-is; edit it by hand if you want the manifest's.`);
3001
- continue;
3002
- }
3003
- const plistCloseIndex = next.lastIndexOf("</plist>");
3004
- const dictCloseIndex = next.lastIndexOf("</dict>", plistCloseIndex === -1 ? void 0 : plistCloseIndex);
3005
- if (dictCloseIndex === -1) {
3006
- warn(`${plistPath} has no closing </dict>, skipping ${key}`);
3007
- continue;
3008
- }
3009
- const block = `\t<key>${key}</key>\n\t<string>${escaped}</string>\n`;
3010
- next = next.slice(0, dictCloseIndex) + block + next.slice(dictCloseIndex);
3011
- }
3012
- applyIfChanged(plistPath, content, next);
3013
- }
3014
3222
  function readPlistArray(content, key) {
3015
3223
  const keyTag = `<key>${key}</key>`;
3016
3224
  const keyIndex = content.indexOf(keyTag);
@@ -3023,44 +3231,151 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3023
3231
  items: [...content.slice(arrayOpenIndex + 7, arrayCloseIndex).matchAll(/<string>([\s\S]*?)<\/string>/g)].map((match) => match[1])
3024
3232
  };
3025
3233
  }
3026
- function patchInfoPlistArrays(appRoot, entries) {
3234
+ function insertPlistKey(source, plistPath, key, valueXml) {
3235
+ const content = source.replace(/<dict\/>(\s*<\/plist>)/, (_, tail) => `<dict>\n</dict>${tail}`);
3236
+ const plistCloseIndex = content.lastIndexOf("</plist>");
3237
+ const dictCloseIndex = content.lastIndexOf("</dict>", plistCloseIndex === -1 ? void 0 : plistCloseIndex);
3238
+ if (dictCloseIndex === -1) {
3239
+ warn(`${plistPath} has no closing </dict>, skipping ${key}`);
3240
+ return content;
3241
+ }
3242
+ const block = `\t<key>${key}</key>\n${valueXml}\n`;
3243
+ return content.slice(0, dictCloseIndex) + block + content.slice(dictCloseIndex);
3244
+ }
3245
+ function mergePlistArray(content, plistPath, key, items) {
3246
+ const existing = readPlistArray(content, key);
3247
+ if (existing === null) return insertPlistKey(content, plistPath, key, `\t<array>\n${items.map((item) => `\t\t<string>${escapeXml(item)}</string>`).join("\n")}\n\t</array>`);
3248
+ const missing = items.filter((item) => !existing.items.includes(item));
3249
+ if (missing.length === 0) return content;
3250
+ const missingLines = missing.map((item) => `\t\t<string>${escapeXml(item)}</string>\n`).join("");
3251
+ return content.slice(0, existing.arrayCloseIndex) + missingLines + content.slice(existing.arrayCloseIndex);
3252
+ }
3253
+ function scalarXml(value) {
3254
+ return typeof value === "boolean" ? `\t<${value}/>` : `\t<string>${escapeXml(value)}</string>`;
3255
+ }
3256
+ function warnOnStringDrift(content, plistPath, key, value) {
3257
+ const existing = readPlistString(content, key);
3258
+ if (typeof value !== "string" || existing === null || existing === escapeXml(value)) return;
3259
+ warn(`${key} in ${plistPath} reads "${existing}" but the package manifest says "${value}". Keeping the file as-is; edit it by hand if you want the manifest's.`);
3260
+ }
3261
+ function applyKey(content, plistPath, key, value) {
3262
+ if (value instanceof Set) return mergePlistArray(content, plistPath, key, [...value]);
3263
+ if (content.includes(`<key>${key}</key>`)) {
3264
+ warnOnStringDrift(content, plistPath, key, value);
3265
+ return content;
3266
+ }
3267
+ return insertPlistKey(content, plistPath, key, scalarXml(value));
3268
+ }
3269
+ function addWanted(wanted, key, value) {
3270
+ if (Array.isArray(value)) {
3271
+ wanted.set(key, /* @__PURE__ */ new Set([...wanted.get(key) || [], ...value]));
3272
+ return;
3273
+ }
3274
+ if (!wanted.has(key)) wanted.set(key, value);
3275
+ }
3276
+ function wantedKeys(entries, field) {
3027
3277
  const wanted = /* @__PURE__ */ new Map();
3028
- for (const entry of entries) {
3029
- const arrayKeys = entry.manifest.ios && entry.manifest.ios.infoPlistArrayKeys;
3030
- if (!arrayKeys) continue;
3031
- for (const [key, items] of Object.entries(arrayKeys)) {
3032
- if (!wanted.has(key)) wanted.set(key, /* @__PURE__ */ new Set());
3033
- for (const item of items) wanted.get(key).add(item);
3034
- }
3278
+ const declaredPerPackage = entries.map((entry) => entry.manifest.ios && entry.manifest.ios[field]);
3279
+ for (const declared of declaredPerPackage) for (const [key, value] of Object.entries(declared || {})) addWanted(wanted, key, value);
3280
+ return [...wanted].sort((a, b) => a[0] < b[0] ? -1 : 1);
3281
+ }
3282
+ function patchPlistFile(filePath, wanted) {
3283
+ const content = fs$1.readFileSync(filePath, "utf8");
3284
+ const next = wanted.reduce((acc, [key, value]) => applyKey(acc, filePath, key, value), content);
3285
+ applyIfChanged(filePath, content, next);
3286
+ }
3287
+ function patchInfoPlistField(appRoot, entries, field) {
3288
+ const wanted = wantedKeys(entries, field);
3289
+ if (wanted.length === 0) return;
3290
+ const plistPath = findInfoPlistFile(appRoot);
3291
+ if (!plistPath) {
3292
+ dlog(`${INFO_PLIST} not found, skipping iOS ${field}`);
3293
+ return;
3035
3294
  }
3036
- if (wanted.size === 0) return;
3295
+ patchPlistFile(plistPath, wanted);
3296
+ }
3297
+ function patchInfoPlist(appRoot, entries) {
3298
+ patchInfoPlistField(appRoot, entries, "infoPlistKeys");
3299
+ }
3300
+ function patchInfoPlistArrays(appRoot, entries) {
3301
+ patchInfoPlistField(appRoot, entries, "infoPlistArrayKeys");
3302
+ }
3303
+ function patchInfoPlistBooleans(appRoot, entries) {
3304
+ patchInfoPlistField(appRoot, entries, "infoPlistBooleanKeys");
3305
+ }
3306
+ function patchEntitlements(appRoot, entries) {
3307
+ const wanted = wantedKeys(entries, "entitlements");
3308
+ if (wanted.length === 0) return;
3037
3309
  const plistPath = findInfoPlistFile(appRoot);
3038
3310
  if (!plistPath) {
3039
- dlog("Info.plist not found, skipping iOS array keys");
3311
+ dlog(`${INFO_PLIST} not found, skipping iOS entitlements`);
3040
3312
  return;
3041
3313
  }
3042
- const content = fs.readFileSync(plistPath, "utf8");
3043
- let next = content;
3044
- for (const [key, itemsSet] of [...wanted].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
3045
- const wantedItems = [...itemsSet];
3046
- const existing = readPlistArray(next, key);
3047
- if (existing === null) {
3048
- const plistCloseIndex = next.lastIndexOf("</plist>");
3049
- const dictCloseIndex = next.lastIndexOf("</dict>", plistCloseIndex === -1 ? void 0 : plistCloseIndex);
3050
- if (dictCloseIndex === -1) {
3051
- warn(`${plistPath} has no closing </dict>, skipping ${key}`);
3052
- continue;
3053
- }
3054
- const block = `\t<key>${key}</key>\n\t<array>\n${wantedItems.map((item) => `\t\t<string>${escapeXml(item)}</string>`).join("\n")}\n\t</array>\n`;
3055
- next = next.slice(0, dictCloseIndex) + block + next.slice(dictCloseIndex);
3056
- continue;
3057
- }
3058
- const missing = wantedItems.filter((item) => !existing.items.includes(item));
3059
- if (missing.length === 0) continue;
3060
- const missingLines = missing.map((item) => `\t\t<string>${escapeXml(item)}</string>\n`).join("");
3061
- next = next.slice(0, existing.arrayCloseIndex) + missingLines + next.slice(existing.arrayCloseIndex);
3314
+ const filePath = findEntitlementsFile(appRoot) || createEntitlementsFile(appRoot, plistPath);
3315
+ if (!filePath) {
3316
+ warn(`no .entitlements file next to ${INFO_PLIST}, add one to enable ${wanted.map(([key]) => key).join(", ")}`);
3317
+ return;
3318
+ }
3319
+ patchPlistFile(filePath, wanted);
3320
+ }
3321
+ module.exports = {
3322
+ findInfoPlistFile,
3323
+ patchInfoPlist,
3324
+ patchInfoPlistArrays,
3325
+ patchInfoPlistBooleans,
3326
+ patchEntitlements
3327
+ };
3328
+ }));
3329
+ //#endregion
3330
+ //#region src/grant-bundles.ts
3331
+ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3332
+ const fs = __require("node:fs");
3333
+ const path = __require("node:path");
3334
+ const { dlog, warn } = require_common();
3335
+ const { buildGradlePath, findMainApplicationFile } = require_android_paths();
3336
+ const { patchBuildGradle, patchRootBuildGradle, patchMainApplication } = require_android_regions();
3337
+ const { patchAndroidManifest, patchAndroidManifestPermissions, patchAndroidManifestServices, patchMainActivityConfigChanges } = require_android_manifest();
3338
+ const { findInfoPlistFile, patchInfoPlist, patchInfoPlistArrays, patchInfoPlistBooleans, patchEntitlements } = require_ios_plist();
3339
+ const MANIFEST_FILENAME = "native-link.json";
3340
+ function findAppRoot() {
3341
+ const candidates = [process.cwd(), process.env.INIT_CWD].filter(Boolean);
3342
+ for (const candidate of candidates) {
3343
+ if (fs.existsSync(buildGradlePath(candidate))) return candidate;
3344
+ if (findInfoPlistFile(candidate)) return candidate;
3062
3345
  }
3063
- applyIfChanged(plistPath, content, next);
3346
+ return null;
3347
+ }
3348
+ function readManifest(packageDir, packageName) {
3349
+ const manifestPath = path.join(packageDir, MANIFEST_FILENAME);
3350
+ if (!fs.existsSync(manifestPath)) return null;
3351
+ try {
3352
+ return {
3353
+ packageName,
3354
+ manifest: JSON.parse(fs.readFileSync(manifestPath, "utf8"))
3355
+ };
3356
+ } catch (error) {
3357
+ warn(`skipping ${packageName}: its ${MANIFEST_FILENAME} is not valid JSON (${error.message})`);
3358
+ return null;
3359
+ }
3360
+ }
3361
+ function readScopedManifests(modulesRoot, scopeName) {
3362
+ const scopeDir = path.join(modulesRoot, scopeName);
3363
+ return fs.readdirSync(scopeDir, { withFileTypes: true }).map((scoped) => readManifest(path.join(scopeDir, scoped.name), `${scopeName}/${scoped.name}`));
3364
+ }
3365
+ function comparePackageNames(a, b) {
3366
+ if (a.packageName < b.packageName) return -1;
3367
+ return a.packageName > b.packageName ? 1 : 0;
3368
+ }
3369
+ function collectManifests(appRoot) {
3370
+ const modulesRoot = path.join(appRoot, "node_modules");
3371
+ if (!fs.existsSync(modulesRoot)) {
3372
+ warn(`no node_modules in ${appRoot}, nothing to link`);
3373
+ return [];
3374
+ }
3375
+ const found = fs.readdirSync(modulesRoot, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".")).flatMap((entry) => entry.name.startsWith("@") ? readScopedManifests(modulesRoot, entry.name) : [readManifest(path.join(modulesRoot, entry.name), entry.name)]).filter(Boolean);
3376
+ found.sort(comparePackageNames);
3377
+ dlog(`found ${found.length} linkable package(s): ${found.map((f) => f.packageName).join(", ")}`);
3378
+ return found;
3064
3379
  }
3065
3380
  function linkApp(explicitAppRoot) {
3066
3381
  const appRoot = explicitAppRoot || findAppRoot();
@@ -3070,6 +3385,7 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3070
3385
  }
3071
3386
  const entries = collectManifests(appRoot);
3072
3387
  patchBuildGradle(appRoot, entries);
3388
+ patchRootBuildGradle(appRoot, entries);
3073
3389
  patchMainApplication(appRoot, entries);
3074
3390
  patchAndroidManifest(appRoot, entries);
3075
3391
  patchAndroidManifestPermissions(appRoot, entries);
@@ -3077,6 +3393,8 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3077
3393
  patchMainActivityConfigChanges(appRoot, entries);
3078
3394
  patchInfoPlist(appRoot, entries);
3079
3395
  patchInfoPlistArrays(appRoot, entries);
3396
+ patchInfoPlistBooleans(appRoot, entries);
3397
+ patchEntitlements(appRoot, entries);
3080
3398
  }
3081
3399
  module.exports = {
3082
3400
  linkApp,
@@ -3085,13 +3403,16 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3085
3403
  findInfoPlistFile,
3086
3404
  findMainApplicationFile,
3087
3405
  patchBuildGradle,
3406
+ patchRootBuildGradle,
3088
3407
  patchMainApplication,
3089
3408
  patchAndroidManifest,
3090
3409
  patchAndroidManifestPermissions,
3091
3410
  patchAndroidManifestServices,
3092
3411
  patchMainActivityConfigChanges,
3093
3412
  patchInfoPlist,
3094
- patchInfoPlistArrays
3413
+ patchInfoPlistArrays,
3414
+ patchInfoPlistBooleans,
3415
+ patchEntitlements
3095
3416
  };
3096
3417
  })))();
3097
3418
  function discoveredBundlesFromLayers(layers) {
@@ -3582,13 +3903,13 @@ function tryGitInit(root) {
3582
3903
  async function runNew(parsed) {
3583
3904
  intro("@symbiote-native/cli new");
3584
3905
  const appName = await resolveAppName(parsed.appName);
3585
- const targetDir = path$1.resolve(process.cwd(), appName);
3906
+ const targetDir = path$5.resolve(process.cwd(), appName);
3586
3907
  if (!isEmptyDir(targetDir)) {
3587
3908
  if (!await resolveOverwrite(targetDir, parsed.hasForce)) {
3588
3909
  cancel("Cancelled.");
3589
3910
  process.exit(1);
3590
3911
  }
3591
- fs$1.rmSync(targetDir, {
3912
+ fs$6.rmSync(targetDir, {
3592
3913
  recursive: true,
3593
3914
  force: true
3594
3915
  });
@@ -3614,7 +3935,7 @@ async function runNew(parsed) {
3614
3935
  styling,
3615
3936
  packageManager
3616
3937
  });
3617
- const hasGitDir = fs$1.existsSync(path$1.join(root, ".git"));
3938
+ const hasGitDir = fs$6.existsSync(path$5.join(root, ".git"));
3618
3939
  const lines = [
3619
3940
  `Scaffolded "${appName}" at ${root}.`,
3620
3941
  "",
@@ -3645,7 +3966,7 @@ async function runNew(parsed) {
3645
3966
  }
3646
3967
  //#endregion
3647
3968
  //#region package.json
3648
- var version = "0.3.0";
3969
+ var version = "0.4.0";
3649
3970
  //#endregion
3650
3971
  //#region src/index.ts
3651
3972
  function printHelp() {