@symbiote-native/cli 0.2.1 → 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.
Files changed (33) hide show
  1. package/bundle.js +851 -430
  2. package/package.json +2 -2
  3. package/templates/layers/README.md +1 -1
  4. package/templates/layers/age-range/package.json.fragment.json +5 -0
  5. package/templates/layers/app-integrity/package.json.fragment.json +5 -0
  6. package/templates/layers/app-metrics/package.json.fragment.json +5 -0
  7. package/templates/layers/apple-authentication/package.json.fragment.json +5 -0
  8. package/templates/layers/asset/package.json.fragment.json +5 -0
  9. package/templates/layers/auth-session/package.json.fragment.json +5 -0
  10. package/templates/layers/blob/package.json.fragment.json +5 -0
  11. package/templates/layers/blur/package.json.fragment.json +5 -0
  12. package/templates/layers/calendar/package.json.fragment.json +5 -0
  13. package/templates/layers/camera/package.json.fragment.json +5 -0
  14. package/templates/layers/constants/package.json.fragment.json +5 -0
  15. package/templates/layers/contacts/package.json.fragment.json +5 -0
  16. package/templates/layers/document-picker/package.json.fragment.json +5 -0
  17. package/templates/layers/font/package.json.fragment.json +5 -0
  18. package/templates/layers/gl/package.json.fragment.json +5 -0
  19. package/templates/layers/glass-effect/package.json.fragment.json +5 -0
  20. package/templates/layers/image/package.json.fragment.json +5 -0
  21. package/templates/layers/image-manipulator/package.json.fragment.json +5 -0
  22. package/templates/layers/image-picker/package.json.fragment.json +5 -0
  23. package/templates/layers/intent-launcher/package.json.fragment.json +5 -0
  24. package/templates/layers/linear-gradient/package.json.fragment.json +5 -0
  25. package/templates/layers/live-photo/package.json.fragment.json +5 -0
  26. package/templates/layers/mail-composer/package.json.fragment.json +5 -0
  27. package/templates/layers/navigation-bar/package.json.fragment.json +5 -0
  28. package/templates/layers/print/package.json.fragment.json +5 -0
  29. package/templates/layers/screen-capture/package.json.fragment.json +5 -0
  30. package/templates/layers/speech/package.json.fragment.json +5 -0
  31. package/templates/layers/symbols/package.json.fragment.json +5 -0
  32. package/templates/layers/video/package.json.fragment.json +5 -0
  33. package/templates/layers/video-thumbnails/package.json.fragment.json +5 -0
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";
@@ -117,11 +117,36 @@ var NotSymbioteAppError = class extends Error {
117
117
  //#endregion
118
118
  //#region src/expo-package-layers.ts
119
119
  const EXPO_PACKAGE_LAYERS = [
120
+ {
121
+ id: "age-range",
122
+ label: "Age range",
123
+ symbiotePackage: "@symbiote-native/age-range"
124
+ },
125
+ {
126
+ id: "app-integrity",
127
+ label: "App integrity",
128
+ symbiotePackage: "@symbiote-native/app-integrity"
129
+ },
130
+ {
131
+ id: "app-metrics",
132
+ label: "App metrics",
133
+ symbiotePackage: "@symbiote-native/app-metrics"
134
+ },
135
+ {
136
+ id: "apple-authentication",
137
+ label: "Sign in with Apple",
138
+ symbiotePackage: "@symbiote-native/apple-authentication"
139
+ },
120
140
  {
121
141
  id: "application",
122
142
  label: "Application info",
123
143
  symbiotePackage: "@symbiote-native/application"
124
144
  },
145
+ {
146
+ id: "asset",
147
+ label: "Asset",
148
+ symbiotePackage: "@symbiote-native/asset"
149
+ },
125
150
  {
126
151
  id: "audio",
127
152
  label: "Audio",
@@ -138,6 +163,11 @@ const EXPO_PACKAGE_LAYERS = [
138
163
  }]
139
164
  }]
140
165
  },
166
+ {
167
+ id: "auth-session",
168
+ label: "Auth session (OAuth2/OIDC)",
169
+ symbiotePackage: "@symbiote-native/auth-session"
170
+ },
141
171
  {
142
172
  id: "background-fetch",
143
173
  label: "Background fetch",
@@ -153,11 +183,31 @@ const EXPO_PACKAGE_LAYERS = [
153
183
  label: "Battery",
154
184
  symbiotePackage: "@symbiote-native/battery"
155
185
  },
186
+ {
187
+ id: "blob",
188
+ label: "Blob",
189
+ symbiotePackage: "@symbiote-native/blob"
190
+ },
191
+ {
192
+ id: "blur",
193
+ label: "Blur",
194
+ symbiotePackage: "@symbiote-native/blur"
195
+ },
156
196
  {
157
197
  id: "brightness",
158
198
  label: "Brightness",
159
199
  symbiotePackage: "@symbiote-native/brightness"
160
200
  },
201
+ {
202
+ id: "calendar",
203
+ label: "Calendar",
204
+ symbiotePackage: "@symbiote-native/calendar"
205
+ },
206
+ {
207
+ id: "camera",
208
+ label: "Camera",
209
+ symbiotePackage: "@symbiote-native/camera"
210
+ },
161
211
  {
162
212
  id: "cellular",
163
213
  label: "Cellular info",
@@ -168,6 +218,16 @@ const EXPO_PACKAGE_LAYERS = [
168
218
  label: "Clipboard",
169
219
  symbiotePackage: "@symbiote-native/clipboard"
170
220
  },
221
+ {
222
+ id: "constants",
223
+ label: "Constants",
224
+ symbiotePackage: "@symbiote-native/constants"
225
+ },
226
+ {
227
+ id: "contacts",
228
+ label: "Contacts",
229
+ symbiotePackage: "@symbiote-native/contacts"
230
+ },
171
231
  {
172
232
  id: "crypto",
173
233
  label: "Crypto",
@@ -178,21 +238,71 @@ const EXPO_PACKAGE_LAYERS = [
178
238
  label: "Device info",
179
239
  symbiotePackage: "@symbiote-native/device"
180
240
  },
241
+ {
242
+ id: "document-picker",
243
+ label: "Document picker",
244
+ symbiotePackage: "@symbiote-native/document-picker"
245
+ },
181
246
  {
182
247
  id: "file-system",
183
248
  label: "File system",
184
249
  symbiotePackage: "@symbiote-native/file-system"
185
250
  },
251
+ {
252
+ id: "font",
253
+ label: "Font",
254
+ symbiotePackage: "@symbiote-native/font"
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
+ },
186
266
  {
187
267
  id: "haptics",
188
268
  label: "Haptics",
189
269
  symbiotePackage: "@symbiote-native/haptics"
190
270
  },
271
+ {
272
+ id: "image",
273
+ label: "Image",
274
+ symbiotePackage: "@symbiote-native/image"
275
+ },
276
+ {
277
+ id: "image-manipulator",
278
+ label: "Image manipulator",
279
+ symbiotePackage: "@symbiote-native/image-manipulator"
280
+ },
281
+ {
282
+ id: "image-picker",
283
+ label: "Image picker",
284
+ symbiotePackage: "@symbiote-native/image-picker"
285
+ },
286
+ {
287
+ id: "intent-launcher",
288
+ label: "Intent launcher",
289
+ symbiotePackage: "@symbiote-native/intent-launcher"
290
+ },
191
291
  {
192
292
  id: "keep-awake",
193
293
  label: "Keep awake",
194
294
  symbiotePackage: "@symbiote-native/keep-awake"
195
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
+ },
196
306
  {
197
307
  id: "local-auth",
198
308
  label: "Local authentication",
@@ -219,11 +329,21 @@ const EXPO_PACKAGE_LAYERS = [
219
329
  ]
220
330
  }]
221
331
  },
332
+ {
333
+ id: "mail-composer",
334
+ label: "Mail composer",
335
+ symbiotePackage: "@symbiote-native/mail-composer"
336
+ },
222
337
  {
223
338
  id: "media-library",
224
339
  label: "Media library",
225
340
  symbiotePackage: "@symbiote-native/media-library"
226
341
  },
342
+ {
343
+ id: "navigation-bar",
344
+ label: "Navigation bar",
345
+ symbiotePackage: "@symbiote-native/navigation-bar"
346
+ },
227
347
  {
228
348
  id: "network",
229
349
  label: "Network info",
@@ -234,6 +354,16 @@ const EXPO_PACKAGE_LAYERS = [
234
354
  label: "Notifications",
235
355
  symbiotePackage: "@symbiote-native/notifications"
236
356
  },
357
+ {
358
+ id: "print",
359
+ label: "Print",
360
+ symbiotePackage: "@symbiote-native/print"
361
+ },
362
+ {
363
+ id: "screen-capture",
364
+ label: "Screen capture",
365
+ symbiotePackage: "@symbiote-native/screen-capture"
366
+ },
237
367
  {
238
368
  id: "screen-orientation",
239
369
  label: "Screen orientation",
@@ -259,6 +389,11 @@ const EXPO_PACKAGE_LAYERS = [
259
389
  label: "SMS",
260
390
  symbiotePackage: "@symbiote-native/sms"
261
391
  },
392
+ {
393
+ id: "speech",
394
+ label: "Speech",
395
+ symbiotePackage: "@symbiote-native/speech"
396
+ },
262
397
  {
263
398
  id: "sqlite",
264
399
  label: "SQLite",
@@ -274,6 +409,11 @@ const EXPO_PACKAGE_LAYERS = [
274
409
  label: "Store review prompt",
275
410
  symbiotePackage: "@symbiote-native/store-review"
276
411
  },
412
+ {
413
+ id: "symbols",
414
+ label: "Symbols",
415
+ symbiotePackage: "@symbiote-native/symbols"
416
+ },
277
417
  {
278
418
  id: "system-ui",
279
419
  label: "System UI",
@@ -289,6 +429,28 @@ const EXPO_PACKAGE_LAYERS = [
289
429
  label: "Tracking transparency",
290
430
  symbiotePackage: "@symbiote-native/tracking-transparency"
291
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
+ },
449
+ {
450
+ id: "video-thumbnails",
451
+ label: "Video thumbnails",
452
+ symbiotePackage: "@symbiote-native/video-thumbnails"
453
+ },
292
454
  {
293
455
  id: "web-browser",
294
456
  label: "Web browser",
@@ -1785,44 +1947,44 @@ const TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
1785
1947
  ""
1786
1948
  ]);
1787
1949
  function replaceTextContent(filePath, identity) {
1788
- const content = fs$1.readFileSync(filePath, "utf8");
1950
+ const content = fs$6.readFileSync(filePath, "utf8");
1789
1951
  const replaced = content.split("com.canary").join(identity.bundleId).split("Canary").join(identity.nativeAppName);
1790
- if (replaced !== content) fs$1.writeFileSync(filePath, replaced);
1952
+ if (replaced !== content) fs$6.writeFileSync(filePath, replaced);
1791
1953
  }
1792
1954
  function walkTextFiles(dir, identity) {
1793
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
1794
- 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);
1795
1957
  if (entry.isDirectory()) {
1796
1958
  walkTextFiles(entryPath, identity);
1797
1959
  continue;
1798
1960
  }
1799
- 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);
1800
1962
  }
1801
1963
  }
1802
1964
  function renameCanaryPaths(dir, nativeAppName) {
1803
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) if (entry.isDirectory()) renameCanaryPaths(path$1.join(dir, entry.name), nativeAppName);
1804
- 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 })) {
1805
1967
  if (!entry.name.startsWith("Canary")) continue;
1806
- const from = path$1.join(dir, entry.name);
1807
- const to = path$1.join(dir, entry.name.replace("Canary", nativeAppName));
1808
- 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);
1809
1971
  }
1810
1972
  }
1811
1973
  function moveAndroidPackageDir(javaSrcDir, bundleId) {
1812
- const oldDir = path$1.join(javaSrcDir, "com", "canary");
1813
- if (!fs$1.existsSync(oldDir)) return;
1814
- const newDir = path$1.join(javaSrcDir, ...bundleId.split("."));
1815
- fs$1.mkdirSync(path$1.dirname(newDir), { recursive: true });
1816
- fs$1.renameSync(oldDir, newDir);
1817
- const oldTopDir = path$1.join(javaSrcDir, "com");
1818
- 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);
1819
1981
  }
1820
1982
  function applyAppIdentity(root, identity) {
1821
1983
  walkTextFiles(root, identity);
1822
- const iosDir = path$1.join(root, "ios");
1823
- if (fs$1.existsSync(iosDir)) renameCanaryPaths(iosDir, identity.nativeAppName);
1824
- const androidAppSrc = path$1.join(root, "android", "app", "src");
1825
- 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);
1826
1988
  }
1827
1989
  //#endregion
1828
1990
  //#region src/utils/json.ts
@@ -1869,7 +2031,7 @@ function sortDependencies(packageJson) {
1869
2031
  //#endregion
1870
2032
  //#region src/utils/render-template.ts
1871
2033
  function readJsonObject(filePath) {
1872
- const parsed = JSON.parse(fs$1.readFileSync(filePath, "utf8"));
2034
+ const parsed = JSON.parse(fs$6.readFileSync(filePath, "utf8"));
1873
2035
  if (!isJsonObject(parsed)) throw new Error(`${filePath}: expected a JSON object`);
1874
2036
  return parsed;
1875
2037
  }
@@ -1899,37 +2061,37 @@ function stripTypescriptInfix(filename) {
1899
2061
  }
1900
2062
  const DEFAULT_OPTIONS = { hasTypescript: true };
1901
2063
  function renderTemplate(src, dest, options = DEFAULT_OPTIONS) {
1902
- if (fs$1.statSync(src).isDirectory()) {
1903
- fs$1.mkdirSync(dest, { recursive: true });
1904
- 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);
1905
2067
  return;
1906
2068
  }
1907
- const filename = path$1.basename(src);
2069
+ const filename = path$5.basename(src);
1908
2070
  if (filename === "README.md") return;
1909
2071
  if (!options.hasTypescript && (isTypescriptOnlyFile(filename) || filename.includes(".typescript."))) return;
1910
2072
  if (options.hasTypescript && isJavascriptOnlyFile(filename)) return;
1911
2073
  const destFilename = filename.includes(".typescript.") ? stripTypescriptInfix(filename) : filename;
1912
- const resolvedDest = path$1.join(path$1.dirname(dest), destFilename);
2074
+ const resolvedDest = path$5.join(path$5.dirname(dest), destFilename);
1913
2075
  if (destFilename === "package.json.fragment.json") {
1914
- const destPackageJsonPath = path$1.resolve(path$1.dirname(resolvedDest), "package.json");
2076
+ const destPackageJsonPath = path$5.resolve(path$5.dirname(resolvedDest), "package.json");
1915
2077
  const fragment = readJsonObject(src);
1916
- const merged = sortDependencies(deepMerge(fs$1.existsSync(destPackageJsonPath) ? readJsonObject(destPackageJsonPath) : {}, fragment));
1917
- 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`);
1918
2080
  return;
1919
2081
  }
1920
- if (destFilename === ".gitignore" && fs$1.existsSync(resolvedDest)) {
1921
- const existing = fs$1.readFileSync(resolvedDest, "utf8");
1922
- const addition = fs$1.readFileSync(src, "utf8");
1923
- 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}`);
1924
2086
  return;
1925
2087
  }
1926
- fs$1.copyFileSync(src, resolvedDest);
2088
+ fs$6.copyFileSync(src, resolvedDest);
1927
2089
  }
1928
2090
  //#endregion
1929
2091
  //#region src/utils/apply-bootsplash-logo.ts
1930
2092
  function applyBootsplashLogo(root, templatesRoot, framework) {
1931
- const overlayRoot = path$1.join(templatesRoot, "bootsplash-logo", framework);
1932
- 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);
1933
2095
  }
1934
2096
  //#endregion
1935
2097
  //#region src/utils/apply-expo-modules-manifest.ts
@@ -1944,54 +2106,54 @@ const EXPO_MODULES_BACKUP_ATTRS = `android:supportsRtl="true"
1944
2106
  android:dataExtractionRules="@xml/secure_store_data_extraction_rules"
1945
2107
  android:fullBackupContent="@xml/secure_store_backup_rules">`;
1946
2108
  function applyExpoModulesAndroidManifest(root) {
1947
- const manifestPath = path$1.join(root, "android/app/src/main/AndroidManifest.xml");
1948
- 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");
1949
2111
  const withPermissions = manifest.includes("USE_BIOMETRIC") ? manifest : manifest.replace(" <uses-permission android:name=\"android.permission.VIBRATE\" />", EXPO_MODULES_PERMISSIONS);
1950
2112
  const withBackupAttrs = withPermissions.includes("dataExtractionRules") ? withPermissions : withPermissions.replace("android:supportsRtl=\"true\">", EXPO_MODULES_BACKUP_ATTRS);
1951
- fs$1.writeFileSync(manifestPath, withBackupAttrs);
2113
+ fs$6.writeFileSync(manifestPath, withBackupAttrs);
1952
2114
  }
1953
2115
  //#endregion
1954
2116
  //#region src/utils/apply-expo-modules-podfile-autolinking.ts
1955
2117
  const AUTOLINKING_REQUIRE = "require File.join(File.dirname(`node --print \"require.resolve('expo/package.json')\"`), \"scripts/autolinking\")";
1956
2118
  const NATIVE_MODULES_LINE = "config = use_native_modules!";
1957
2119
  function applyExpoModulesPodfileAutolinking(root) {
1958
- const podfilePath = path$1.join(root, "ios", "Podfile");
1959
- if (!fs$1.existsSync(podfilePath)) return;
1960
- 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");
1961
2123
  if (podfile.includes("use_expo_modules!")) return;
1962
2124
  if (!podfile.includes(AUTOLINKING_REQUIRE)) podfile = `${AUTOLINKING_REQUIRE}\n\n${podfile}`;
1963
2125
  podfile = podfile.replace(NATIVE_MODULES_LINE, `${NATIVE_MODULES_LINE}\n\n use_expo_modules!`);
1964
- fs$1.writeFileSync(podfilePath, podfile);
2126
+ fs$6.writeFileSync(podfilePath, podfile);
1965
2127
  }
1966
2128
  //#endregion
1967
2129
  //#region src/utils/apply-expo-modules-podfile.ts
1968
2130
  const DEFAULT_PLATFORM_LINE = "platform :ios, min_ios_version_supported.to_s";
1969
2131
  const RAISED_PLATFORM_LINE = "platform :ios, [min_ios_version_supported.to_f, 16.4].max.to_s";
1970
2132
  function applyExpoModulesPodfilePlatform(root) {
1971
- const podfilePath = path$1.join(root, "ios", "Podfile");
1972
- if (!fs$1.existsSync(podfilePath)) return;
1973
- 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");
1974
2136
  if (!podfile.includes(DEFAULT_PLATFORM_LINE)) return;
1975
- 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));
1976
2138
  }
1977
2139
  //#endregion
1978
2140
  //#region src/utils/apply-expo-modules-xcode-deployment-target.ts
1979
2141
  const MIN_EXPO_IOS_VERSION = 16.4;
1980
2142
  const DEPLOYMENT_TARGET_LINE = /IPHONEOS_DEPLOYMENT_TARGET = (\d+(?:\.\d+)?);/g;
1981
2143
  function findPbxprojPath(root) {
1982
- const iosDir = path$1.join(root, "ios");
1983
- if (!fs$1.existsSync(iosDir)) return void 0;
1984
- 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"));
1985
2147
  if (!xcodeproj) return void 0;
1986
- const pbxprojPath = path$1.join(iosDir, xcodeproj, "project.pbxproj");
1987
- 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;
1988
2150
  }
1989
2151
  function applyExpoModulesXcodeDeploymentTarget(root) {
1990
2152
  const pbxprojPath = findPbxprojPath(root);
1991
2153
  if (!pbxprojPath) return;
1992
- const pbxproj = fs$1.readFileSync(pbxprojPath, "utf8");
2154
+ const pbxproj = fs$6.readFileSync(pbxprojPath, "utf8");
1993
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);
1994
- if (raised !== pbxproj) fs$1.writeFileSync(pbxprojPath, raised);
2156
+ if (raised !== pbxproj) fs$6.writeFileSync(pbxprojPath, raised);
1995
2157
  }
1996
2158
  //#endregion
1997
2159
  //#region src/utils/apply-splash-screen-hide.ts
@@ -2008,8 +2170,8 @@ const APP_COMPONENT_FILENAMES = /* @__PURE__ */ new Set([
2008
2170
  "App.svelte"
2009
2171
  ]);
2010
2172
  function findAppComponentFile(dir) {
2011
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2012
- 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);
2013
2175
  if (entry.isDirectory()) {
2014
2176
  if (SKIPPED_DIR_NAMES$1.has(entry.name)) continue;
2015
2177
  const found = findAppComponentFile(entryPath);
@@ -2055,9 +2217,9 @@ function patchSvelte(source) {
2055
2217
  function applySplashScreenAppHide(root, options) {
2056
2218
  const appFilePath = findAppComponentFile(root);
2057
2219
  if (appFilePath === void 0) return;
2058
- const source = fs$1.readFileSync(appFilePath, "utf8");
2220
+ const source = fs$6.readFileSync(appFilePath, "utf8");
2059
2221
  if (/\bhide\(\)/.test(source)) return;
2060
- fs$1.writeFileSync(appFilePath, patch(source, options.framework, appFilePath));
2222
+ fs$6.writeFileSync(appFilePath, patch(source, options.framework, appFilePath));
2061
2223
  }
2062
2224
  function patch(source, framework, appFilePath) {
2063
2225
  switch (framework) {
@@ -2073,16 +2235,16 @@ function patch(source, framework, appFilePath) {
2073
2235
  const INLINE_STYLE_BLOCK_FRAMEWORKS = /* @__PURE__ */ new Set(["vue-sfc", "svelte"]);
2074
2236
  const STYLE_TAG_PATTERN = /<style(?=[ >])/;
2075
2237
  function addStyleLangAttribute(dir, lang) {
2076
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2077
- 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);
2078
2240
  if (entry.isDirectory()) {
2079
2241
  if (SKIPPED_DIR_NAMES.has(entry.name)) continue;
2080
2242
  addStyleLangAttribute(entryPath, lang);
2081
2243
  continue;
2082
2244
  }
2083
2245
  if (!entry.name.endsWith(".vue") && !entry.name.endsWith(".svelte")) continue;
2084
- const content = fs$1.readFileSync(entryPath, "utf8");
2085
- 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}"`));
2086
2248
  }
2087
2249
  }
2088
2250
  const PREPROCESSOR_EXTENSIONS = {
@@ -2096,42 +2258,42 @@ const SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
2096
2258
  "node_modules"
2097
2259
  ]);
2098
2260
  function findAppCssFile(dir) {
2099
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2261
+ for (const entry of fs$6.readdirSync(dir, { withFileTypes: true })) {
2100
2262
  if (entry.isDirectory()) {
2101
2263
  if (SKIPPED_DIR_NAMES.has(entry.name)) continue;
2102
- const found = findAppCssFile(path$1.join(dir, entry.name));
2264
+ const found = findAppCssFile(path$5.join(dir, entry.name));
2103
2265
  if (found !== void 0) return found;
2104
2266
  continue;
2105
2267
  }
2106
- 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);
2107
2269
  }
2108
2270
  }
2109
2271
  function replaceInTextFiles(dir, from, to) {
2110
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2111
- 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);
2112
2274
  if (entry.isDirectory()) {
2113
2275
  if (SKIPPED_DIR_NAMES.has(entry.name)) continue;
2114
2276
  replaceInTextFiles(entryPath, from, to);
2115
2277
  continue;
2116
2278
  }
2117
- const content = fs$1.readFileSync(entryPath, "utf8");
2118
- 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));
2119
2281
  }
2120
2282
  }
2121
2283
  function renameStylesheetExtension(root, extension) {
2122
2284
  const cssFile = findAppCssFile(root);
2123
2285
  if (cssFile === void 0) return;
2124
- 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}`));
2125
2287
  replaceInTextFiles(root, "'./App.css'", `'./App.${extension}'`);
2126
2288
  }
2127
2289
  function applyStyling(root, options) {
2128
2290
  const { styling, jsTemplateDir, hasNavigation, hasTypescript, templatesRoot } = options;
2129
2291
  if (styling === "css") return;
2130
2292
  if (styling === "css-modules" || styling === "stylesheet") {
2131
- renderTemplate(path$1.join(templatesRoot, "styling", "js", jsTemplateDir, styling), root, { hasTypescript });
2132
- 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 });
2133
2295
  const orphanedCss = findAppCssFile(root);
2134
- if (orphanedCss !== void 0) fs$1.rmSync(orphanedCss, { force: true });
2296
+ if (orphanedCss !== void 0) fs$6.rmSync(orphanedCss, { force: true });
2135
2297
  return;
2136
2298
  }
2137
2299
  if (INLINE_STYLE_BLOCK_FRAMEWORKS.has(jsTemplateDir)) {
@@ -2170,50 +2332,50 @@ var TargetDirNotEmptyError = class extends Error {
2170
2332
  }
2171
2333
  };
2172
2334
  function resolveTemplatesRoot() {
2173
- const packageDir = path$1.dirname(fileURLToPath(import.meta.url));
2174
- const sibling = path$1.join(packageDir, "templates");
2175
- 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");
2176
2338
  }
2177
2339
  const templatesRoot = resolveTemplatesRoot();
2178
2340
  function isEmptyDir(dir) {
2179
- if (!fs$1.existsSync(dir)) return true;
2180
- const entries = fs$1.readdirSync(dir);
2341
+ if (!fs$6.existsSync(dir)) return true;
2342
+ const entries = fs$6.readdirSync(dir);
2181
2343
  return entries.length === 0 || entries.length === 1 && entries[0] === ".git";
2182
2344
  }
2183
2345
  function scaffoldApp(options) {
2184
- const root = path$1.resolve(process.cwd(), options.appName);
2346
+ const root = path$5.resolve(process.cwd(), options.appName);
2185
2347
  const jsTemplateDir = resolveJsTemplateDir(options.framework, options.vueFlavor);
2186
2348
  if (!isEmptyDir(root)) throw new TargetDirNotEmptyError(root);
2187
- fs$1.mkdirSync(root, { recursive: true });
2188
- const packageNameSource = options.appName === "." ? path$1.basename(root) : options.appName;
2189
- 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({
2190
2352
  name: toValidPackageName(packageNameSource),
2191
2353
  version: "0.0.1",
2192
2354
  private: true
2193
2355
  }, null, 2)}\n`);
2194
- renderTemplate(path$1.join(templatesRoot, "native"), root, { hasTypescript: options.hasTypescript });
2195
- 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 });
2196
2358
  applyBootsplashLogo(root, templatesRoot, options.framework);
2197
2359
  const hasExpoModules = options.hasExpoModules || (options.expoPackages?.size ?? 0) > 0;
2198
2360
  if (options.hasNavigation) {
2199
- const navigationLayerRoot = path$1.join(templatesRoot, "layers", "navigation");
2200
- renderTemplate(path$1.join(navigationLayerRoot, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2201
- 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 });
2202
2364
  }
2203
- if (hasExpoModules) renderTemplate(path$1.join(templatesRoot, "layers", "expo-modules"), root);
2365
+ if (hasExpoModules) renderTemplate(path$5.join(templatesRoot, "layers", "expo-modules"), root);
2204
2366
  if (options.hasSplashScreen) {
2205
- const splashScreenLayerRoot = path$1.join(templatesRoot, "layers", "splash-screen");
2206
- renderTemplate(path$1.join(splashScreenLayerRoot, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2207
- 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);
2208
2370
  }
2209
- if (options.hasSlider) renderTemplate(path$1.join(templatesRoot, "layers", "slider", "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2210
- 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"));
2211
2373
  if (options.hasTesting) {
2212
- const testingLayerRoot = path$1.join(templatesRoot, "layers", "testing");
2374
+ const testingLayerRoot = path$5.join(templatesRoot, "layers", "testing");
2213
2375
  const fragmentName = options.framework === "angular" ? "angular" : "base";
2214
- renderTemplate(path$1.join(testingLayerRoot, "fragment", fragmentName, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2215
- renderTemplate(path$1.join(testingLayerRoot, "detox.config.js"), path$1.join(root, "detox.config.js"));
2216
- 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 });
2217
2379
  }
2218
2380
  if (hasExpoModules) {
2219
2381
  applyExpoModulesAndroidManifest(root);
@@ -2238,9 +2400,9 @@ function scaffoldApp(options) {
2238
2400
  //#endregion
2239
2401
  //#region src/utils/find-ios-app-dir.ts
2240
2402
  function findIosAppDir(root) {
2241
- const iosDir = path$1.join(root, "ios");
2242
- if (!fs$1.existsSync(iosDir)) return void 0;
2243
- 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);
2244
2406
  }
2245
2407
  //#endregion
2246
2408
  //#region src/utils/apply-expo-modules-plist.ts
@@ -2252,14 +2414,14 @@ const USAGE_DESCRIPTIONS = [
2252
2414
  function applyExpoModulesInfoPlist(root) {
2253
2415
  const iosAppDir = findIosAppDir(root);
2254
2416
  if (iosAppDir === void 0) return;
2255
- const plistPath = path$1.join(iosAppDir, "Info.plist");
2256
- if (!fs$1.existsSync(plistPath)) return;
2257
- 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");
2258
2420
  const updated = USAGE_DESCRIPTIONS.reduce((current, [key, description]) => {
2259
2421
  if (current.includes(`<key>${key}</key>`)) return current;
2260
2422
  return current.replace("</dict>\n</plist>", `\t<key>${key}</key>\n\t<string>${description}</string>\n</dict>\n</plist>`);
2261
2423
  }, plist);
2262
- if (updated !== plist) fs$1.writeFileSync(plistPath, updated);
2424
+ if (updated !== plist) fs$6.writeFileSync(plistPath, updated);
2263
2425
  }
2264
2426
  //#endregion
2265
2427
  //#region src/utils/apply-splash-screen-app-delegate.ts
@@ -2268,9 +2430,9 @@ const REACT_IMPORT = "import React\n";
2268
2430
  function applySplashScreenAppDelegate(root) {
2269
2431
  const iosAppDir = findIosAppDir(root);
2270
2432
  if (iosAppDir === void 0) return;
2271
- const appDelegatePath = path$1.join(iosAppDir, "AppDelegate.swift");
2272
- if (!fs$1.existsSync(appDelegatePath)) return;
2273
- 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");
2274
2436
  if (source.includes("RNBootSplash.initWithStoryboard(")) return;
2275
2437
  if (!source.includes(REACT_IMPORT)) return;
2276
2438
  const updated = source.includes("import RNBootSplash") ? source : source.replace(REACT_IMPORT, `${REACT_IMPORT}import RNBootSplash\n`);
@@ -2279,13 +2441,13 @@ function applySplashScreenAppDelegate(root) {
2279
2441
  }
2280
2442
 
2281
2443
  `);
2282
- fs$1.writeFileSync(appDelegatePath, withInit);
2444
+ fs$6.writeFileSync(appDelegatePath, withInit);
2283
2445
  }
2284
2446
  //#endregion
2285
2447
  //#region src/utils/apply-splash-screen-main-activity.ts
2286
2448
  function findMainActivityKt(dir) {
2287
- for (const entry of fs$1.readdirSync(dir, { withFileTypes: true })) {
2288
- 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);
2289
2451
  if (entry.isDirectory()) {
2290
2452
  const found = findMainActivityKt(entryPath);
2291
2453
  if (found !== void 0) return found;
@@ -2297,32 +2459,32 @@ function findMainActivityKt(dir) {
2297
2459
  const ONCREATE_OPEN = /override fun onCreate\(savedInstanceState: Bundle\?\) \{\n/;
2298
2460
  const REACT_ACTIVITY_IMPORT = "import com.facebook.react.ReactActivity\n";
2299
2461
  function applySplashScreenMainActivity(root) {
2300
- const javaDir = path$1.join(root, "android/app/src/main/java");
2301
- 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;
2302
2464
  const mainActivityPath = findMainActivityKt(javaDir);
2303
2465
  if (mainActivityPath === void 0) return;
2304
- const source = fs$1.readFileSync(mainActivityPath, "utf8");
2466
+ const source = fs$6.readFileSync(mainActivityPath, "utf8");
2305
2467
  if (source.includes("RNBootSplash.init(")) return;
2306
2468
  if (!source.includes(REACT_ACTIVITY_IMPORT)) return;
2307
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`);
2308
2470
  if (!updated.includes("import android.os.Bundle")) updated = updated.replace(REACT_ACTIVITY_IMPORT, `import android.os.Bundle\n${REACT_ACTIVITY_IMPORT}`);
2309
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");
2310
- fs$1.writeFileSync(mainActivityPath, updated);
2472
+ fs$6.writeFileSync(mainActivityPath, updated);
2311
2473
  }
2312
2474
  //#endregion
2313
2475
  //#region src/utils/apply-splash-screen-manifest.ts
2314
2476
  const MAIN_ACTIVITY_BLOCK = /<activity\b[^>]*android:name="\.MainActivity"[^>]*>/;
2315
2477
  function applySplashScreenManifest(root) {
2316
- const manifestPath = path$1.join(root, "android/app/src/main/AndroidManifest.xml");
2317
- if (!fs$1.existsSync(manifestPath)) return;
2318
- 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");
2319
2481
  const match = manifest.match(MAIN_ACTIVITY_BLOCK);
2320
2482
  if (match === null) return;
2321
2483
  const block = match[0];
2322
2484
  if (block.includes("@style/BootTheme")) return;
2323
2485
  if (!block.includes("android:theme=\"@style/AppTheme\"")) return;
2324
2486
  const updatedBlock = block.replace("android:theme=\"@style/AppTheme\"", "android:theme=\"@style/BootTheme\"");
2325
- fs$1.writeFileSync(manifestPath, manifest.replace(block, updatedBlock));
2487
+ fs$6.writeFileSync(manifestPath, manifest.replace(block, updatedBlock));
2326
2488
  }
2327
2489
  //#endregion
2328
2490
  //#region src/utils/apply-splash-screen-styles.ts
@@ -2333,22 +2495,22 @@ const BOOT_THEME_STYLE = ` <style name="BootTheme" parent="Theme.BootSplash">
2333
2495
  </style>
2334
2496
  `;
2335
2497
  function applySplashScreenStyles(root) {
2336
- const stylesPath = path$1.join(root, "android/app/src/main/res/values/styles.xml");
2337
- if (!fs$1.existsSync(stylesPath)) return;
2338
- 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");
2339
2501
  if (styles.includes("name=\"BootTheme\"")) return;
2340
2502
  if (!styles.includes("</resources>")) return;
2341
- fs$1.writeFileSync(stylesPath, styles.replace("</resources>", `${BOOT_THEME_STYLE}</resources>`));
2503
+ fs$6.writeFileSync(stylesPath, styles.replace("</resources>", `${BOOT_THEME_STYLE}</resources>`));
2342
2504
  }
2343
2505
  //#endregion
2344
2506
  //#region src/add-layers.ts
2345
2507
  function applyDependencyOnlyLayer(root, layerName) {
2346
- 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"));
2347
2509
  }
2348
2510
  function applyExpoModulesLayer(root) {
2349
2511
  applyDependencyOnlyLayer(root, "expo-modules");
2350
- if (fs$1.existsSync(path$1.join(root, "android/app/src/main/AndroidManifest.xml"))) applyExpoModulesAndroidManifest(root);
2351
- 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"))) {
2352
2514
  applyExpoModulesInfoPlist(root);
2353
2515
  applyExpoModulesPodfilePlatform(root);
2354
2516
  applyExpoModulesPodfileAutolinking(root);
@@ -2365,14 +2527,14 @@ function applySplashScreenLayer(root, framework) {
2365
2527
  applyBootsplashLogo(root, templatesRoot, framework);
2366
2528
  }
2367
2529
  async function applyTestingLayer(root, framework, confirmOverwrite) {
2368
- const testingLayerRoot = path$1.join(templatesRoot, "layers", "testing");
2530
+ const testingLayerRoot = path$5.join(templatesRoot, "layers", "testing");
2369
2531
  const fragmentName = framework === "angular" ? "angular" : "base";
2370
- renderTemplate(path$1.join(testingLayerRoot, "fragment", fragmentName, "package.json.fragment.json"), path$1.join(root, "package.json.fragment.json"));
2371
- const detoxConfigPath = path$1.join(root, "detox.config.js");
2372
- const e2eDirPath = path$1.join(root, "e2e");
2373
- if ((fs$1.existsSync(detoxConfigPath) || fs$1.existsSync(e2eDirPath)) && !await confirmOverwrite()) return false;
2374
- renderTemplate(path$1.join(testingLayerRoot, "detox.config.js"), detoxConfigPath);
2375
- 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 });
2376
2538
  return true;
2377
2539
  }
2378
2540
  async function addLayersToApp(options) {
@@ -2447,105 +2609,114 @@ function getCommand(packageManager, script) {
2447
2609
  return packageManager === "npm" ? `npm run ${script}` : `${packageManager} ${script}`;
2448
2610
  }
2449
2611
  //#endregion
2450
- //#region src/grant-bundles.ts
2451
- var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2452
- const fs = __require("node:fs");
2453
- const path = __require("node:path");
2454
- const BEGIN_TAG = "SYMBIOTE-EXPO-LINK:BEGIN";
2455
- const END_TAG = "SYMBIOTE-EXPO-LINK:END";
2456
- const REGION_NOTE = "generated by symbiote-expo-link - do not edit, regenerated on every install";
2457
- const REACT_ANDROID_ANCHOR = "implementation(\"com.facebook.react:react-android\")";
2458
- const MODULES_MAP_ANCHOR = "override fun getModulesMap(): Map<Class<out Module>, String?> = mapOf(";
2459
- const MODULES_PROVIDER_CLASS_ANCHOR = ": ModulesProvider {";
2460
- 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"];
2461
2617
  function isDebug() {
2462
- 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;
2463
2619
  }
2464
2620
  function dlog(...args) {
2465
- if (isDebug()) console.log("[symbiote-expo-link]", ...args);
2621
+ if (isDebug()) process.stderr.write(`[symbiote-expo-link] ${args.join(" ")}\n`);
2466
2622
  }
2467
2623
  function warn(message) {
2468
2624
  console.warn(`[symbiote-expo-link] ${message}`);
2469
2625
  }
2470
2626
  function writeFileAtomic(filePath, content) {
2471
2627
  const tmpPath = `${filePath}.symbiote-expo-link.${process.pid}.tmp`;
2472
- fs.writeFileSync(tmpPath, content);
2473
- fs.renameSync(tmpPath, filePath);
2628
+ fs$5.writeFileSync(tmpPath, content);
2629
+ fs$5.renameSync(tmpPath, filePath);
2474
2630
  }
2475
- function findAppRoot() {
2476
- const candidates = [process.cwd(), process.env.INIT_CWD].filter(Boolean);
2477
- for (const candidate of candidates) {
2478
- if (fs.existsSync(path.join(candidate, "android", "app", "build.gradle"))) return candidate;
2479
- if (findInfoPlistFile(candidate)) return candidate;
2480
- }
2481
- return null;
2631
+ function applyIfChanged(filePath, content, nextContent) {
2632
+ if (nextContent === null || nextContent === content) return;
2633
+ writeFileAtomic(filePath, nextContent);
2634
+ dlog(`updated ${filePath}`);
2482
2635
  }
2483
- function findInfoPlistFile(appRoot) {
2484
- const iosRoot = path.join(appRoot, "ios");
2485
- if (!fs.existsSync(iosRoot)) return null;
2486
- const skip = /Pods|build|DerivedData|Tests/i;
2487
- 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];
2488
2642
  while (stack.length > 0) {
2489
2643
  const dir = stack.pop();
2490
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2491
- if (skip.test(entry.name)) continue;
2492
- 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);
2493
2647
  if (entry.isDirectory()) stack.push(full);
2494
- else if (entry.name === "Info.plist") return full;
2648
+ else if (entry.name === fileName) return full;
2495
2649
  }
2496
2650
  }
2497
2651
  return null;
2498
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
+ }
2499
2682
  function findMainApplicationFile(appRoot) {
2500
- const javaRoot = path.join(appRoot, "android", "app", "src", "main", "java");
2501
- if (!fs.existsSync(javaRoot)) return null;
2502
- const stack = [javaRoot];
2503
- while (stack.length > 0) {
2504
- const dir = stack.pop();
2505
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2506
- const full = path.join(dir, entry.name);
2507
- if (entry.isDirectory()) stack.push(full);
2508
- else if (entry.name === "MainApplication.kt") return full;
2509
- }
2510
- }
2511
- return null;
2683
+ const javaRoot = path$3.join(appRoot, ANDROID_DIR, "app", "src", "main", "java");
2684
+ return findFileInTree(javaRoot, MAIN_APPLICATION_FILE);
2512
2685
  }
2513
- function readManifest(packageDir, packageName) {
2514
- const manifestPath = path.join(packageDir, MANIFEST_FILENAME);
2515
- if (!fs.existsSync(manifestPath)) return null;
2516
- try {
2517
- return {
2518
- packageName,
2519
- manifest: JSON.parse(fs.readFileSync(manifestPath, "utf8"))
2520
- };
2521
- } catch (error) {
2522
- 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.`);
2523
2714
  return null;
2524
2715
  }
2525
- }
2526
- function collectManifests(appRoot) {
2527
- const modulesRoot = path.join(appRoot, "node_modules");
2528
- if (!fs.existsSync(modulesRoot)) {
2529
- warn(`no node_modules in ${appRoot}, nothing to link`);
2530
- return [];
2531
- }
2532
- const found = [];
2533
- for (const entry of fs.readdirSync(modulesRoot, { withFileTypes: true })) {
2534
- if (entry.name.startsWith(".")) continue;
2535
- if (entry.name.startsWith("@")) {
2536
- const scopeDir = path.join(modulesRoot, entry.name);
2537
- for (const scoped of fs.readdirSync(scopeDir, { withFileTypes: true })) {
2538
- const entryFound = readManifest(path.join(scopeDir, scoped.name), `${entry.name}/${scoped.name}`);
2539
- if (entryFound) found.push(entryFound);
2540
- }
2541
- continue;
2542
- }
2543
- const entryFound = readManifest(path.join(modulesRoot, entry.name), entry.name);
2544
- if (entryFound) found.push(entryFound);
2545
- }
2546
- found.sort((a, b) => a.packageName < b.packageName ? -1 : a.packageName > b.packageName ? 1 : 0);
2547
- dlog(`found ${found.length} linkable package(s): ${found.map((f) => f.packageName).join(", ")}`);
2548
- return found;
2716
+ return {
2717
+ begin,
2718
+ end
2719
+ };
2549
2720
  }
2550
2721
  function writeRegion({ filePath, content, name, indent, bodyLines, findAnchorIndex, anchorLabel }) {
2551
2722
  const lines = content.split("\n");
@@ -2554,14 +2725,10 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2554
2725
  ...bodyLines.map((line) => `${indent}${line}`),
2555
2726
  `${indent}// ${END_TAG} ${name}`
2556
2727
  ];
2557
- const beginIndex = lines.findIndex((line) => line.includes(`${BEGIN_TAG} ${name}`));
2558
- if (beginIndex !== -1) {
2559
- const endIndex = lines.findIndex((line, i) => i > beginIndex && line.includes(`${END_TAG} ${name}`));
2560
- if (endIndex === -1) {
2561
- warn(`${filePath} has "${BEGIN_TAG} ${name}" but no matching "${END_TAG} ${name}". Restore the end marker (or delete the whole block) and reinstall.`);
2562
- return null;
2563
- }
2564
- 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);
2565
2732
  return lines.join("\n");
2566
2733
  }
2567
2734
  const anchorIndex = findAnchorIndex(lines);
@@ -2572,22 +2739,23 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2572
2739
  lines.splice(anchorIndex + 1, 0, ...block);
2573
2740
  return lines.join("\n");
2574
2741
  }
2575
- function applyIfChanged(filePath, content, nextContent) {
2576
- if (nextContent === null || nextContent === content) return;
2577
- writeFileAtomic(filePath, nextContent);
2578
- 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 : []);
2579
2747
  }
2580
2748
  function patchBuildGradle(appRoot, entries) {
2581
- const gradlePath = path.join(appRoot, "android", "app", "build.gradle");
2582
- if (!fs.existsSync(gradlePath)) {
2749
+ const gradlePath = buildGradlePath(appRoot);
2750
+ if (!fs$4.existsSync(gradlePath)) {
2583
2751
  dlog("android/app/build.gradle not found, skipping");
2584
2752
  return;
2585
2753
  }
2586
2754
  const primaryProjectNames = entries.filter((entry) => entry.manifest.android && entry.manifest.android.gradleProjectName).map((entry) => entry.manifest.android.gradleProjectName);
2587
2755
  const serviceProjectNames = androidServicesOf(entries).map((service) => service.gradleProjectName);
2588
2756
  const deps = [.../* @__PURE__ */ new Set([...primaryProjectNames, ...serviceProjectNames])].sort().map((name) => `implementation project(':${name}')`);
2589
- const content = fs.readFileSync(gradlePath, "utf8");
2590
- applyIfChanged(gradlePath, content, writeRegion({
2757
+ const content = fs$4.readFileSync(gradlePath, "utf8");
2758
+ const next = writeRegion({
2591
2759
  filePath: gradlePath,
2592
2760
  content,
2593
2761
  name: "DEPENDENCIES",
@@ -2595,41 +2763,102 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2595
2763
  bodyLines: deps,
2596
2764
  findAnchorIndex: (lines) => lines.findIndex((line) => line.includes(REACT_ANDROID_ANCHOR)),
2597
2765
  anchorLabel: `no \`${REACT_ANDROID_ANCHOR}\` line to anchor below`
2598
- }));
2766
+ });
2767
+ applyIfChanged(gradlePath, content, next);
2599
2768
  }
2600
- function androidModulesOf(entries) {
2601
- 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");
2602
2776
  }
2603
- function androidServicesOf(entries) {
2604
- 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();
2605
2843
  }
2606
2844
  function patchMainApplication(appRoot, entries) {
2607
2845
  const filePath = findMainApplicationFile(appRoot);
2608
2846
  if (!filePath) {
2609
- dlog("MainApplication.kt not found, skipping");
2847
+ dlog(`${MAIN_APPLICATION_FILE} not found, skipping`);
2610
2848
  return;
2611
2849
  }
2612
2850
  const modules = androidModulesOf(entries);
2613
2851
  const services = androidServicesOf(entries);
2614
- const imports = [.../* @__PURE__ */ new Set([
2615
- ...modules.map((module$1) => `import ${module$1.importPath}`),
2616
- ...services.map((service) => `import ${service.importPath}`),
2617
- "import expo.modules.kotlin.services.Service"
2618
- ])].sort();
2619
2852
  const mapEntries = [...new Set(modules.map((module$2) => `${module$2.className}::class.java to "${module$2.nativeName}",`))].sort();
2620
2853
  const serviceEntries = [...new Set(services.map((service) => ` ${service.className}::class.java,`))].sort();
2621
- const content = fs.readFileSync(filePath, "utf8");
2854
+ const content = fs$4.readFileSync(filePath, "utf8");
2622
2855
  const withImports = writeRegion({
2623
2856
  filePath,
2624
2857
  content,
2625
2858
  name: "IMPORTS",
2626
2859
  indent: "",
2627
- bodyLines: imports,
2628
- findAnchorIndex: (lines) => {
2629
- let lastImport = -1;
2630
- for (let i = 0; i < lines.length; i += 1) if (lines[i].trim().startsWith("import ")) lastImport = i;
2631
- return lastImport;
2632
- },
2860
+ bodyLines: mainApplicationImports(modules, services),
2861
+ findAnchorIndex: lastImportIndex,
2633
2862
  anchorLabel: "no import statements to anchor below"
2634
2863
  });
2635
2864
  if (withImports === null) return;
@@ -2643,7 +2872,7 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2643
2872
  anchorLabel: "no `getModulesMap()` declaration to anchor below"
2644
2873
  });
2645
2874
  if (withModules === null) return;
2646
- applyIfChanged(filePath, content, writeRegion({
2875
+ const next = writeRegion({
2647
2876
  filePath,
2648
2877
  content: withModules,
2649
2878
  name: "SERVICES",
@@ -2655,31 +2884,45 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2655
2884
  ],
2656
2885
  findAnchorIndex: (lines) => lines.findIndex((line) => line.includes(MODULES_PROVIDER_CLASS_ANCHOR)),
2657
2886
  anchorLabel: "no `ExpoModulesProvider : ModulesProvider` class declaration to anchor below"
2658
- }));
2659
- }
2660
- function escapeXml(value) {
2661
- return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2887
+ });
2888
+ applyIfChanged(filePath, content, next);
2662
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";
2663
2904
  function escapeXmlAttribute(value) {
2664
2905
  return escapeXml(value).replace(/"/g, "&quot;");
2665
2906
  }
2666
- function findTag(content, tagName) {
2667
- const match = new RegExp(`<${tagName}(?=[\\s>/])`).exec(content);
2668
- if (!match) return null;
2907
+ function scanTagEnd(content, from) {
2669
2908
  let quote = null;
2670
- for (let i = match.index; i < content.length; i += 1) {
2909
+ for (let i = from; i < content.length; i += 1) {
2671
2910
  const char = content[i];
2672
2911
  if (quote !== null) {
2673
2912
  if (char === quote) quote = null;
2674
- continue;
2675
- }
2676
- if (char === "\"" || char === "'") quote = char;
2677
- else if (char === ">") return {
2678
- start: match.index,
2679
- end: i
2680
- };
2913
+ } else if (char === "\"" || char === "'") quote = char;
2914
+ else if (char === ">") return i;
2681
2915
  }
2682
- 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
+ };
2683
2926
  }
2684
2927
  function findApplicationTag(content) {
2685
2928
  return findTag(content, "application");
@@ -2689,27 +2932,22 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2689
2932
  const re = new RegExp(`<${tagName}(?=[\\s>/])`, "g");
2690
2933
  let match = re.exec(content);
2691
2934
  while (match) {
2692
- let quote = null;
2693
- for (let i = match.index; i < content.length; i += 1) {
2694
- const char = content[i];
2695
- if (quote !== null) {
2696
- if (char === quote) quote = null;
2697
- continue;
2698
- }
2699
- if (char === "\"" || char === "'") quote = char;
2700
- else if (char === ">") {
2701
- tags.push({
2702
- start: match.index,
2703
- end: i
2704
- });
2705
- re.lastIndex = i + 1;
2706
- break;
2707
- }
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;
2708
2942
  }
2709
2943
  match = re.exec(content);
2710
2944
  }
2711
2945
  return tags;
2712
2946
  }
2947
+ function readXmlAttribute(tagText, name) {
2948
+ const match = new RegExp(`\\s${name}\\s*=\\s*"([^"]*)"`).exec(tagText);
2949
+ return match ? match[1] : null;
2950
+ }
2713
2951
  function findMainActivityTag(content) {
2714
2952
  for (const tag of findAllTags(content, "activity")) {
2715
2953
  const name = readXmlAttribute(content.slice(tag.start, tag.end), "android:name");
@@ -2717,26 +2955,48 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2717
2955
  }
2718
2956
  return null;
2719
2957
  }
2720
- function readXmlAttribute(tagText, name) {
2721
- const match = new RegExp(`\\s${name}\\s*=\\s*"([^"]*)"`).exec(tagText);
2722
- 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]}`;
2723
2961
  }
2724
- 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) {
2725
2982
  const wanted = /* @__PURE__ */ new Map();
2726
2983
  for (const entry of entries) {
2727
2984
  const attributes = entry.manifest.android && entry.manifest.android.manifestApplicationAttributes;
2728
- if (!attributes) continue;
2729
- for (const [name, value] of Object.entries(attributes)) if (!wanted.has(name)) wanted.set(name, value);
2730
- }
2731
- if (wanted.size === 0) return;
2732
- const manifestPath = path.join(appRoot, "android", "app", "src", "main", "AndroidManifest.xml");
2733
- if (!fs.existsSync(manifestPath)) {
2734
- dlog("AndroidManifest.xml not found, skipping application attributes");
2735
- return;
2985
+ for (const [name, value] of Object.entries(attributes || {})) if (!wanted.has(name)) wanted.set(name, value);
2736
2986
  }
2737
- 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;
2738
2998
  let next = content;
2739
- for (const [name, value] of [...wanted].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
2999
+ for (const [name, value] of wanted) {
2740
3000
  if (!/^[\w.:-]+$/.test(name)) {
2741
3001
  warn(`skipping the manifest attribute "${name}": not a plain XML attribute name`);
2742
3002
  continue;
@@ -2746,79 +3006,55 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2746
3006
  warn(`${manifestPath} has no <application> element, skipping ${name}`);
2747
3007
  return;
2748
3008
  }
2749
- const tagText = next.slice(tag.start, tag.end);
2750
3009
  const escaped = escapeXmlAttribute(value);
2751
- const existing = readXmlAttribute(tagText, name);
2752
- if (existing !== null) {
2753
- 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.`);
2754
- continue;
2755
- }
2756
- const lastNewline = tagText.lastIndexOf("\n");
2757
- const separator = lastNewline === -1 ? " " : `\n${/^[ \t]*/.exec(tagText.slice(lastNewline + 1))[0]}`;
2758
- const insertAt = next[tag.end - 1] === "/" ? tag.end - 1 : tag.end;
2759
- 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);
2760
3013
  }
2761
3014
  applyIfChanged(manifestPath, content, next);
2762
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
+ }
2763
3030
  function patchMainActivityConfigChanges(appRoot, entries) {
2764
- const wanted = /* @__PURE__ */ new Set();
2765
- for (const entry of entries) {
2766
- const values = entry.manifest.android && entry.manifest.android.mainActivityConfigChanges;
2767
- if (!values) continue;
2768
- for (const value of values) wanted.add(value);
2769
- }
3031
+ const wanted = new Set(androidListOf(entries, "mainActivityConfigChanges"));
2770
3032
  if (wanted.size === 0) return;
2771
- const manifestPath = path.join(appRoot, "android", "app", "src", "main", "AndroidManifest.xml");
2772
- if (!fs.existsSync(manifestPath)) {
2773
- dlog("AndroidManifest.xml not found, skipping MainActivity configChanges");
2774
- return;
2775
- }
2776
- const content = fs.readFileSync(manifestPath, "utf8");
3033
+ const loaded = loadAndroidManifest(appRoot, "MainActivity configChanges");
3034
+ if (!loaded) return;
3035
+ const { manifestPath, content } = loaded;
2777
3036
  const tag = findMainActivityTag(content);
2778
3037
  if (!tag) {
2779
3038
  warn(`${manifestPath} has no MainActivity <activity> element, skipping configChanges`);
2780
3039
  return;
2781
3040
  }
2782
- const tagText = content.slice(tag.start, tag.end);
2783
- const existingValue = readXmlAttribute(tagText, "android:configChanges");
3041
+ const existingValue = readXmlAttribute(content.slice(tag.start, tag.end), CONFIG_CHANGES_ATTRIBUTE);
2784
3042
  const existingTokens = existingValue ? existingValue.split("|").map((token) => token.trim()).filter(Boolean) : [];
2785
- const existingSet = new Set(existingTokens);
2786
- const missing = [...wanted].filter((token) => !existingSet.has(token)).sort();
3043
+ const missing = [...wanted].filter((token) => !existingTokens.includes(token)).sort();
2787
3044
  if (missing.length === 0) return;
2788
3045
  const merged = [...existingTokens, ...missing].join("|");
2789
- let next;
2790
- if (existingValue !== null) {
2791
- const valueMatch = /android:configChanges\s*=\s*"/.exec(tagText);
2792
- const valueStart = valueMatch.index + valueMatch[0].length;
2793
- const valueEnd = tagText.indexOf("\"", valueStart);
2794
- const nextTagText = tagText.slice(0, valueStart) + merged + tagText.slice(valueEnd);
2795
- next = content.slice(0, tag.start) + nextTagText + content.slice(tag.end);
2796
- } else {
2797
- const lastNewline = tagText.lastIndexOf("\n");
2798
- const separator = lastNewline === -1 ? " " : `\n${/^[ \t]*/.exec(tagText.slice(lastNewline + 1))[0]}`;
2799
- const selfClosing = content[tag.end - 1] === "/";
2800
- const insertAt = selfClosing ? tag.end - 1 : tag.end;
2801
- next = `${content.slice(0, insertAt).replace(/[ \t]+$/, "")}${separator}android:configChanges="${merged}"${selfClosing ? " " : ""}${content.slice(insertAt)}`;
2802
- }
3046
+ const next = existingValue !== null ? replaceConfigChangesValue(content, tag, merged) : addConfigChangesAttribute(content, tag, merged);
2803
3047
  applyIfChanged(manifestPath, content, next);
2804
3048
  }
2805
3049
  function readManifestPermissions(content) {
2806
3050
  return new Set([...content.matchAll(/<uses-permission\s[^>]*android:name="([^"]+)"/g)].map((match) => match[1]));
2807
3051
  }
2808
3052
  function patchAndroidManifestPermissions(appRoot, entries) {
2809
- const wanted = /* @__PURE__ */ new Set();
2810
- for (const entry of entries) {
2811
- const permissions = entry.manifest.android && entry.manifest.android.manifestPermissions;
2812
- if (!permissions) continue;
2813
- for (const permission of permissions) wanted.add(permission);
2814
- }
3053
+ const wanted = new Set(androidListOf(entries, "manifestPermissions"));
2815
3054
  if (wanted.size === 0) return;
2816
- const manifestPath = path.join(appRoot, "android", "app", "src", "main", "AndroidManifest.xml");
2817
- if (!fs.existsSync(manifestPath)) {
2818
- dlog("AndroidManifest.xml not found, skipping permissions");
2819
- return;
2820
- }
2821
- const content = fs.readFileSync(manifestPath, "utf8");
3055
+ const loaded = loadAndroidManifest(appRoot, "permissions");
3056
+ if (!loaded) return;
3057
+ const { manifestPath, content } = loaded;
2822
3058
  const existing = readManifestPermissions(content);
2823
3059
  const missing = [...wanted].filter((permission) => !existing.has(permission)).sort();
2824
3060
  if (missing.length === 0) return;
@@ -2841,21 +3077,13 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2841
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>`;
2842
3078
  }
2843
3079
  function patchAndroidManifestServices(appRoot, entries) {
2844
- const wanted = /* @__PURE__ */ new Map();
2845
- for (const entry of entries) {
2846
- const services = entry.manifest.android && entry.manifest.android.manifestServices;
2847
- if (!services) continue;
2848
- for (const service of services) if (!wanted.has(service.name)) wanted.set(service.name, service);
2849
- }
2850
- if (wanted.size === 0) return;
2851
- const manifestPath = path.join(appRoot, "android", "app", "src", "main", "AndroidManifest.xml");
2852
- if (!fs.existsSync(manifestPath)) {
2853
- dlog("AndroidManifest.xml not found, skipping services");
2854
- return;
2855
- }
2856
- 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;
2857
3085
  const existing = readManifestServiceNames(content);
2858
- 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);
2859
3087
  if (missing.length === 0) return;
2860
3088
  const tag = findApplicationTag(content);
2861
3089
  if (!tag) {
@@ -2867,50 +3095,130 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2867
3095
  return;
2868
3096
  }
2869
3097
  const lastNewline = content.lastIndexOf("\n", tag.start);
2870
- const indent = `${/^[ \t]*/.exec(content.slice(lastNewline + 1, tag.start))[0]} `;
2871
- 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");
2872
3100
  const insertAt = tag.end + 1;
2873
3101
  applyIfChanged(manifestPath, content, `${content.slice(0, insertAt)}\n${blocks}${content.slice(insertAt)}`);
2874
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
+ }
2875
3216
  function readPlistString(content, key) {
2876
3217
  const keyIndex = content.indexOf(`<key>${key}</key>`);
2877
3218
  if (keyIndex === -1) return null;
2878
3219
  const match = /<string>([\s\S]*?)<\/string>/.exec(content.slice(keyIndex));
2879
3220
  return match ? match[1] : null;
2880
3221
  }
2881
- function patchInfoPlist(appRoot, entries) {
2882
- const wanted = /* @__PURE__ */ new Map();
2883
- for (const entry of entries) {
2884
- const keys = entry.manifest.ios && entry.manifest.ios.infoPlistKeys;
2885
- if (!keys) continue;
2886
- for (const [key, description] of Object.entries(keys)) if (!wanted.has(key)) wanted.set(key, description);
2887
- }
2888
- if (wanted.size === 0) return;
2889
- const plistPath = findInfoPlistFile(appRoot);
2890
- if (!plistPath) {
2891
- dlog("Info.plist not found, skipping iOS permission strings");
2892
- return;
2893
- }
2894
- const content = fs.readFileSync(plistPath, "utf8");
2895
- let next = content;
2896
- for (const [key, description] of [...wanted].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
2897
- const escaped = escapeXml(description);
2898
- const existing = readPlistString(next, key);
2899
- if (existing !== null) {
2900
- 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.`);
2901
- continue;
2902
- }
2903
- const plistCloseIndex = next.lastIndexOf("</plist>");
2904
- const dictCloseIndex = next.lastIndexOf("</dict>", plistCloseIndex === -1 ? void 0 : plistCloseIndex);
2905
- if (dictCloseIndex === -1) {
2906
- warn(`${plistPath} has no closing </dict>, skipping ${key}`);
2907
- continue;
2908
- }
2909
- const block = `\t<key>${key}</key>\n\t<string>${escaped}</string>\n`;
2910
- next = next.slice(0, dictCloseIndex) + block + next.slice(dictCloseIndex);
2911
- }
2912
- applyIfChanged(plistPath, content, next);
2913
- }
2914
3222
  function readPlistArray(content, key) {
2915
3223
  const keyTag = `<key>${key}</key>`;
2916
3224
  const keyIndex = content.indexOf(keyTag);
@@ -2923,44 +3231,151 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2923
3231
  items: [...content.slice(arrayOpenIndex + 7, arrayCloseIndex).matchAll(/<string>([\s\S]*?)<\/string>/g)].map((match) => match[1])
2924
3232
  };
2925
3233
  }
2926
- 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) {
2927
3277
  const wanted = /* @__PURE__ */ new Map();
2928
- for (const entry of entries) {
2929
- const arrayKeys = entry.manifest.ios && entry.manifest.ios.infoPlistArrayKeys;
2930
- if (!arrayKeys) continue;
2931
- for (const [key, items] of Object.entries(arrayKeys)) {
2932
- if (!wanted.has(key)) wanted.set(key, /* @__PURE__ */ new Set());
2933
- for (const item of items) wanted.get(key).add(item);
2934
- }
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;
2935
3294
  }
2936
- 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;
2937
3309
  const plistPath = findInfoPlistFile(appRoot);
2938
3310
  if (!plistPath) {
2939
- dlog("Info.plist not found, skipping iOS array keys");
3311
+ dlog(`${INFO_PLIST} not found, skipping iOS entitlements`);
2940
3312
  return;
2941
3313
  }
2942
- const content = fs.readFileSync(plistPath, "utf8");
2943
- let next = content;
2944
- for (const [key, itemsSet] of [...wanted].sort((a, b) => a[0] < b[0] ? -1 : 1)) {
2945
- const wantedItems = [...itemsSet];
2946
- const existing = readPlistArray(next, key);
2947
- if (existing === null) {
2948
- const plistCloseIndex = next.lastIndexOf("</plist>");
2949
- const dictCloseIndex = next.lastIndexOf("</dict>", plistCloseIndex === -1 ? void 0 : plistCloseIndex);
2950
- if (dictCloseIndex === -1) {
2951
- warn(`${plistPath} has no closing </dict>, skipping ${key}`);
2952
- continue;
2953
- }
2954
- 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`;
2955
- next = next.slice(0, dictCloseIndex) + block + next.slice(dictCloseIndex);
2956
- continue;
2957
- }
2958
- const missing = wantedItems.filter((item) => !existing.items.includes(item));
2959
- if (missing.length === 0) continue;
2960
- const missingLines = missing.map((item) => `\t\t<string>${escapeXml(item)}</string>\n`).join("");
2961
- 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;
3345
+ }
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;
2962
3359
  }
2963
- applyIfChanged(plistPath, content, next);
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;
2964
3379
  }
2965
3380
  function linkApp(explicitAppRoot) {
2966
3381
  const appRoot = explicitAppRoot || findAppRoot();
@@ -2970,6 +3385,7 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2970
3385
  }
2971
3386
  const entries = collectManifests(appRoot);
2972
3387
  patchBuildGradle(appRoot, entries);
3388
+ patchRootBuildGradle(appRoot, entries);
2973
3389
  patchMainApplication(appRoot, entries);
2974
3390
  patchAndroidManifest(appRoot, entries);
2975
3391
  patchAndroidManifestPermissions(appRoot, entries);
@@ -2977,6 +3393,8 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2977
3393
  patchMainActivityConfigChanges(appRoot, entries);
2978
3394
  patchInfoPlist(appRoot, entries);
2979
3395
  patchInfoPlistArrays(appRoot, entries);
3396
+ patchInfoPlistBooleans(appRoot, entries);
3397
+ patchEntitlements(appRoot, entries);
2980
3398
  }
2981
3399
  module.exports = {
2982
3400
  linkApp,
@@ -2985,13 +3403,16 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
2985
3403
  findInfoPlistFile,
2986
3404
  findMainApplicationFile,
2987
3405
  patchBuildGradle,
3406
+ patchRootBuildGradle,
2988
3407
  patchMainApplication,
2989
3408
  patchAndroidManifest,
2990
3409
  patchAndroidManifestPermissions,
2991
3410
  patchAndroidManifestServices,
2992
3411
  patchMainActivityConfigChanges,
2993
3412
  patchInfoPlist,
2994
- patchInfoPlistArrays
3413
+ patchInfoPlistArrays,
3414
+ patchInfoPlistBooleans,
3415
+ patchEntitlements
2995
3416
  };
2996
3417
  })))();
2997
3418
  function discoveredBundlesFromLayers(layers) {
@@ -3482,13 +3903,13 @@ function tryGitInit(root) {
3482
3903
  async function runNew(parsed) {
3483
3904
  intro("@symbiote-native/cli new");
3484
3905
  const appName = await resolveAppName(parsed.appName);
3485
- const targetDir = path$1.resolve(process.cwd(), appName);
3906
+ const targetDir = path$5.resolve(process.cwd(), appName);
3486
3907
  if (!isEmptyDir(targetDir)) {
3487
3908
  if (!await resolveOverwrite(targetDir, parsed.hasForce)) {
3488
3909
  cancel("Cancelled.");
3489
3910
  process.exit(1);
3490
3911
  }
3491
- fs$1.rmSync(targetDir, {
3912
+ fs$6.rmSync(targetDir, {
3492
3913
  recursive: true,
3493
3914
  force: true
3494
3915
  });
@@ -3514,7 +3935,7 @@ async function runNew(parsed) {
3514
3935
  styling,
3515
3936
  packageManager
3516
3937
  });
3517
- const hasGitDir = fs$1.existsSync(path$1.join(root, ".git"));
3938
+ const hasGitDir = fs$6.existsSync(path$5.join(root, ".git"));
3518
3939
  const lines = [
3519
3940
  `Scaffolded "${appName}" at ${root}.`,
3520
3941
  "",
@@ -3545,7 +3966,7 @@ async function runNew(parsed) {
3545
3966
  }
3546
3967
  //#endregion
3547
3968
  //#region package.json
3548
- var version = "0.2.1";
3969
+ var version = "0.4.0";
3549
3970
  //#endregion
3550
3971
  //#region src/index.ts
3551
3972
  function printHelp() {