peachsim 0.1.657 → 0.1.658

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 (105) hide show
  1. package/README.md +21 -2
  2. package/cli/app-config.ts +25 -9
  3. package/cli/bridge-flow-runner.ts +10 -23
  4. package/cli/commands/compat.ts +234 -10
  5. package/cli/commands/do-chain.ts +5 -1
  6. package/cli/commands/inspect/actions.ts +33 -76
  7. package/cli/commands/inspect/resolve-target.ts +67 -2
  8. package/cli/commands/inspect.ts +16 -1
  9. package/cli/drivers/playwright-provisioning.ts +95 -0
  10. package/cli/ws-bridge.ts +7 -7
  11. package/dist-lib/agent-daemon-client.cjs +1 -1
  12. package/dist-lib/agent-events.cjs +1 -1
  13. package/dist-lib/agent-identity.cjs +1 -1
  14. package/dist-lib/agent-sessions.cjs +1 -1
  15. package/dist-lib/attached-projects.cjs +1 -1
  16. package/dist-lib/auth/shared-session.cjs +1 -1
  17. package/dist-lib/backend-origin.cjs +1 -1
  18. package/dist-lib/beta.cjs +1 -1
  19. package/dist-lib/beta.mjs +1 -1
  20. package/dist-lib/bridge-constants.cjs +1 -1
  21. package/dist-lib/bridge-contract-input.cjs +9 -1
  22. package/dist-lib/bridge-contract-input.mjs +9 -1
  23. package/dist-lib/bridge-contract.cjs +1 -1
  24. package/dist-lib/bridge-contract.mjs +1 -1
  25. package/dist-lib/capture-contract.cjs +1 -1
  26. package/dist-lib/capture-contract.mjs +1 -1
  27. package/dist-lib/cli-constants.cjs +1 -1
  28. package/dist-lib/cloud-contract.cjs +1 -1
  29. package/dist-lib/cloud-contract.mjs +1 -1
  30. package/dist-lib/cloud-test-args.cjs +1 -1
  31. package/dist-lib/cloud-test-args.mjs +1 -1
  32. package/dist-lib/cloud.cjs +1 -1
  33. package/dist-lib/cloud.mjs +1 -1
  34. package/dist-lib/config.cjs +3 -2
  35. package/dist-lib/detox/index.cjs +1 -1
  36. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  37. package/dist-lib/home-paths.cjs +1 -1
  38. package/dist-lib/host/bridge-host.cjs +78 -35
  39. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  40. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  41. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  42. package/dist-lib/host/replacement-module-handler.cjs +51 -8
  43. package/dist-lib/host/websocket-proxy.cjs +1 -1
  44. package/dist-lib/index.cjs +317 -70
  45. package/dist-lib/jump-to-source-babel.cjs +1 -1
  46. package/dist-lib/jump-to-source-native.cjs +1 -1
  47. package/dist-lib/kotlin-host.cjs +8 -4
  48. package/dist-lib/menu.cjs +1 -1
  49. package/dist-lib/menu.mjs +1 -1
  50. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  51. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  52. package/dist-lib/metro-production-bundle.cjs +1 -1
  53. package/dist-lib/metro-production-bundle.mjs +1 -1
  54. package/dist-lib/metro.cjs +300 -29
  55. package/dist-lib/native-app-identity.cjs +58 -25
  56. package/dist-lib/native-app-identity.mjs +57 -25
  57. package/dist-lib/plist.cjs +64 -0
  58. package/dist-lib/plist.mjs +29 -0
  59. package/dist-lib/profiles.cjs +1 -1
  60. package/dist-lib/public-brand.cjs +1 -1
  61. package/dist-lib/react-native-host-modules.cjs +1 -1
  62. package/dist-lib/react-native-host-modules.mjs +1 -1
  63. package/dist-lib/render-mode.cjs +1 -1
  64. package/dist-lib/scripts/dev-server-scanner.cjs +2 -2
  65. package/dist-lib/sdk.cjs +36 -49
  66. package/dist-lib/sdk.mjs +36 -49
  67. package/dist-lib/skills.cjs +250 -131
  68. package/dist-lib/swift-compile.cjs +1 -1
  69. package/dist-lib/swift-compile.mjs +1 -1
  70. package/dist-lib/swift-package.cjs +1 -1
  71. package/dist-lib/swift-package.mjs +1 -1
  72. package/dist-lib/swift-submit.cjs +1 -1
  73. package/dist-lib/swift-submit.mjs +1 -1
  74. package/dist-lib/swift.cjs +1 -1
  75. package/dist-lib/vite-swift.cjs +1 -1
  76. package/dist-lib/vite.cjs +51 -8
  77. package/kotlin/AppSources/android/app/UiModeManager.kt +9 -0
  78. package/kotlin/AppSources/android/content/Context.kt +9 -2
  79. package/kotlin/AppSources/android/content/res/Configuration.kt +16 -0
  80. package/kotlin/AppSources/android/content/res/Resources.kt +7 -0
  81. package/kotlin/AppSources/android/graphics/Color.kt +13 -0
  82. package/kotlin/AppSources/android/view/View.kt +2 -0
  83. package/kotlin/AppSources/android/view/Window.kt +99 -8
  84. package/kotlin/AppSources/android/view/WindowInsetsController.kt +12 -0
  85. package/kotlin/AppSources/android/view/WindowManager.kt +16 -0
  86. package/kotlin/AppSources/androidx/activity/EdgeToEdge.kt +71 -0
  87. package/kotlin/AppSources/androidx/core/view/WindowCompat.kt +32 -0
  88. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +16 -11
  89. package/kotlin/AppSources/dev/peach/app/PeachWindowHost.kt +2 -1
  90. package/kotlin/ComposeSources/PeachComposeHost.kt +11 -1
  91. package/package.json +8 -1
  92. package/skills/peach-setup/SKILL.md +27 -0
  93. package/src/android-theme.ts +167 -0
  94. package/src/android-xml.ts +70 -0
  95. package/src/bridge-contract-input.ts +10 -1
  96. package/src/bridge-contract.ts +7 -1
  97. package/src/config.ts +8 -0
  98. package/src/generated/android-framework-themes.json +6303 -0
  99. package/src/host/replacement-module-handler.ts +33 -0
  100. package/src/index.ts +1 -0
  101. package/src/kotlin-app.ts +75 -61
  102. package/src/kotlin-host.ts +7 -3
  103. package/src/metro-plugin.ts +46 -3
  104. package/src/native-app-identity.ts +75 -26
  105. package/src/sim-client.ts +2 -4
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.657 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.658 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -62,6 +62,7 @@ function tagRnxBundleForPlugin(bundleUrl, replacementOrigin, replacementToken, p
62
62
  }
63
63
 
64
64
  // src/host/replacement-module-handler.ts
65
+ var import_node_fs2 = require("node:fs");
65
66
  var import_promises = __toESM(require("node:fs/promises"), 1);
66
67
  var import_node_path = __toESM(require("node:path"), 1);
67
68
 
@@ -191,12 +192,15 @@ function transformsFor(filePath) {
191
192
  );
192
193
  }
193
194
  }
195
+ function isLocalSpecifier(specifier) {
196
+ return specifier.startsWith("./") || specifier.startsWith("../");
197
+ }
194
198
  function isContrastKitSpecifier(specifier) {
195
199
  return specifier === "contrast-kit" || specifier.startsWith("contrast-kit/");
196
200
  }
197
201
  function unsupportedSpecifier(specifier) {
198
202
  return new Error(
199
- `replacement modules may only import react, react-native, contrast-kit, and the packages rnx stubs for this app; '${specifier}' is none of those. vendor the code into the replacement file, or add the package to peach.config so the simulator bridges it.`
203
+ `replacement modules may only import react, react-native, contrast-kit, the packages rnx stubs for this app, and other project files by relative path; '${specifier}' is none of those. vendor the code into a project file, or add the package to peach.config so the simulator bridges it.`
200
204
  );
201
205
  }
202
206
  function bridgeDeclaration(name, specifier) {
@@ -221,11 +225,9 @@ function compileReplacementModule(input) {
221
225
  const transformed = (0, import_dist.transform)(input.source, {
222
226
  transforms: transformsFor(input.filePath),
223
227
  filePath: input.filePath,
224
- // classic runtime, matching what the app's own Metro build does for a file
225
- // that has no automatic-runtime pragma: the file imports React itself.
226
- jsxRuntime: "classic",
227
- jsxPragma: "React.createElement",
228
- jsxFragmentPragma: "React.Fragment",
228
+ // the automatic runtime, as @react-native/babel-preset and babel-preset-expo
229
+ // compile the app's own files: jsx needs no React import.
230
+ jsxRuntime: "automatic",
229
231
  production: true,
230
232
  sourceMapOptions: { compiledFilename: input.filePath }
231
233
  });
@@ -252,6 +254,13 @@ function compileReplacementModule(input) {
252
254
  return name;
253
255
  };
254
256
  const edits = [];
257
+ const pointAtLocalModule = (specifierToken, specifier) => {
258
+ edits.push({
259
+ start: specifierToken.start,
260
+ end: specifierToken.end,
261
+ text: JSON.stringify(input.localModuleUrl(specifier))
262
+ });
263
+ };
255
264
  for (let index = 0; index < tokens.length; index++) {
256
265
  const token = tokens[index];
257
266
  if (token.type === EXPORT_KEYWORD) {
@@ -271,6 +280,10 @@ function compileReplacementModule(input) {
271
280
  }
272
281
  if (fromAt > 0 && tokens[fromAt] && read(tokens[fromAt]) === "from" && tokens[fromAt + 1]?.type === import_types.TokenType.string) {
273
282
  const specifier2 = readSpecifier(tokens[fromAt + 1]);
283
+ if (isLocalSpecifier(specifier2)) {
284
+ pointAtLocalModule(tokens[fromAt + 1], specifier2);
285
+ continue;
286
+ }
274
287
  throw new Error(
275
288
  `a replacement module cannot re-export from another module, and this one re-exports from '${specifier2}'. import what you need and export the binding.`
276
289
  );
@@ -323,6 +336,10 @@ function compileReplacementModule(input) {
323
336
  const specifierToken = tokens[cursor];
324
337
  if (!specifierToken) throw new Error("replacement module import is unterminated");
325
338
  const specifier = readSpecifier(specifierToken);
339
+ if (isLocalSpecifier(specifier)) {
340
+ pointAtLocalModule(specifierToken, specifier);
341
+ continue;
342
+ }
326
343
  if (specifier !== "react" && specifier !== "react-native" && !JSX_RUNTIME_SPECIFIERS.has(specifier) && !isContrastKitSpecifier(specifier) && !input.runtimePackages.has(specifier)) {
327
344
  throw unsupportedSpecifier(specifier);
328
345
  }
@@ -379,6 +396,25 @@ function sendJavaScript(req, res, source) {
379
396
  });
380
397
  res.end(req.method === "HEAD" ? void 0 : source);
381
398
  }
399
+ function resolveLocalImport(fromFile, specifier) {
400
+ const base = import_node_path.default.resolve(import_node_path.default.dirname(fromFile), specifier);
401
+ const candidates = [
402
+ ...REPLACEMENT_MODULE_EXTENSIONS.some(
403
+ (ext) => ext === import_node_path.default.extname(base).toLowerCase()
404
+ ) ? [base] : [],
405
+ ...REPLACEMENT_MODULE_EXTENSIONS.map((ext) => base + ext),
406
+ ...REPLACEMENT_MODULE_EXTENSIONS.map((ext) => import_node_path.default.join(base, `index${ext}`))
407
+ ];
408
+ const found = candidates.find(
409
+ (candidate) => (0, import_node_fs2.statSync)(candidate, { throwIfNoEntry: false })?.isFile()
410
+ );
411
+ if (!found) {
412
+ throw new Error(
413
+ `'${specifier}' imported by ${fromFile} matches no ${REPLACEMENT_MODULE_EXTENSIONS.join(", ")} file`
414
+ );
415
+ }
416
+ return found;
417
+ }
382
418
  function isReplacementModuleRequestUrl(rawUrl) {
383
419
  if (!rawUrl) return false;
384
420
  try {
@@ -469,7 +505,14 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
469
505
  const compiled = compileReplacementModule({
470
506
  filePath: resolvedTargetPath,
471
507
  source: await import_promises.default.readFile(resolvedTargetPath, "utf8"),
472
- runtimePackages
508
+ runtimePackages,
509
+ // the imported file's url is this request's with only its path changed,
510
+ // so the token, cache key and runtime packages carry over.
511
+ localModuleUrl: (specifier) => {
512
+ const local = new URL(url.href);
513
+ local.searchParams.set("path", resolveLocalImport(targetPath, specifier));
514
+ return `${local.pathname}${local.search}`;
515
+ }
473
516
  });
474
517
  const map = Buffer.from(JSON.stringify(compiled.sourceMap), "utf8").toString("base64");
475
518
  sendJavaScript(
@@ -528,26 +571,223 @@ function appendRNXMetroModuleIdentityFooter(bundleText, identity) {
528
571
  ${RNXSIM_METRO_SOURCE_MAP_COMMENT}${detached.sourceMappingUrl}`;
529
572
  }
530
573
 
574
+ // src/native-app-identity.ts
575
+ var NATIVE_APP_IDENTITY_PREFIX = "globalThis.__sootsimNativeAppIdentity=";
576
+ var METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
577
+ function isRecord(value) {
578
+ return typeof value === "object" && value !== null && !Array.isArray(value);
579
+ }
580
+ function nonEmptyString(value) {
581
+ return typeof value === "string" && value.length > 0 ? value : null;
582
+ }
583
+ function createNativeAppIdentityFooter(identity) {
584
+ const payload = JSON.stringify(identity).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/<\//g, "<\\/");
585
+ return `
586
+ ${NATIVE_APP_IDENTITY_PREFIX}${payload};`;
587
+ }
588
+ function nativeAppIdentityFooterStart(source) {
589
+ return source.lastIndexOf(`
590
+ ${NATIVE_APP_IDENTITY_PREFIX}`);
591
+ }
592
+ function insertNativeAppIdentityFooter(source, identity) {
593
+ const existing = nativeAppIdentityFooterStart(source);
594
+ const existingEnd = existing < 0 ? -1 : source.includes("\n", existing + 1) ? source.indexOf("\n", existing + 1) : -1;
595
+ const withoutExisting = existing < 0 ? source : source.slice(0, existing) + source.slice(existingEnd < 0 ? source.length : existingEnd);
596
+ const moduleFooter = withoutExisting.lastIndexOf(`
597
+ ${METRO_MODULE_PATHS_PREFIX}`);
598
+ const footer = createNativeAppIdentityFooter(identity);
599
+ if (moduleFooter < 0) return withoutExisting + footer;
600
+ return withoutExisting.slice(0, moduleFooter) + footer + withoutExisting.slice(moduleFooter);
601
+ }
602
+ function plistString(plist, key) {
603
+ const match = plist.match(new RegExp(`<key>${key}</key>\\s*<string>([^<]*)</string>`));
604
+ return match ? match[1].trim() : null;
605
+ }
606
+ function resolveBuildSetting(value, settings, depth = 0) {
607
+ if (depth > 8) return null;
608
+ let unresolved = false;
609
+ const resolved = value.replace(/\$\(([^()]+)\)/g, (_match, reference) => {
610
+ const separator = reference.indexOf(":");
611
+ if (separator >= 0) {
612
+ unresolved = true;
613
+ return "";
614
+ }
615
+ const replacement = settings[reference];
616
+ if (replacement === void 0) {
617
+ unresolved = true;
618
+ return "";
619
+ }
620
+ return replacement;
621
+ });
622
+ if (unresolved) return null;
623
+ if (resolved.includes("$(")) return resolveBuildSetting(resolved, settings, depth + 1);
624
+ return resolved;
625
+ }
626
+ function resolvedIdentityField(value) {
627
+ const trimmed = value?.trim() ?? "";
628
+ if (!trimmed || trimmed.includes("$(")) return null;
629
+ return trimmed;
630
+ }
631
+ function identityFieldFrom(settings, raw, fallbackSetting) {
632
+ if (raw !== null) return resolvedIdentityField(resolveBuildSetting(raw, settings));
633
+ return resolvedIdentityField(nonEmptyString(fallbackSetting ?? null));
634
+ }
635
+ function readBuildSettingsBlock(source, start) {
636
+ let depth = 0;
637
+ for (let index = start; index < source.length; index++) {
638
+ const character = source[index];
639
+ if (character === "{") depth++;
640
+ else if (character === "}") {
641
+ depth--;
642
+ if (depth === 0) return source.slice(start, index + 1);
643
+ }
644
+ }
645
+ return source.slice(start);
646
+ }
647
+ function parseBuildSettings(block) {
648
+ const settings = {};
649
+ const matchPattern = /([A-Za-z0-9_]+) = ("(?:[^"\\]|\\.)*"|[^;{}]*);/g;
650
+ let match;
651
+ while ((match = matchPattern.exec(block)) !== null) {
652
+ const raw = match[2].trim();
653
+ settings[match[1]] = raw.startsWith('"') ? raw.slice(1, -1) : raw.trim();
654
+ }
655
+ return settings;
656
+ }
657
+ function appTargetReleaseSettings(pbxproj, targetName, infoPlistRelativePath) {
658
+ const configurationPattern = /\/\* (Release|Debug) \*\/ = \{/g;
659
+ const candidates = [];
660
+ let match;
661
+ while ((match = configurationPattern.exec(pbxproj)) !== null) {
662
+ const blockStart = pbxproj.indexOf("{", match.index);
663
+ if (blockStart < 0) continue;
664
+ const block = readBuildSettingsBlock(pbxproj, blockStart);
665
+ const buildSettingsStart = block.indexOf("buildSettings = {");
666
+ if (buildSettingsStart < 0) continue;
667
+ const settings = parseBuildSettings(
668
+ readBuildSettingsBlock(block, block.indexOf("{", buildSettingsStart))
669
+ );
670
+ candidates.push({ name: match[1], settings });
671
+ }
672
+ const plistMatch = (settings) => {
673
+ const plist = settings.INFOPLIST_FILE;
674
+ if (!plist) return false;
675
+ const normalized = plist.replace(/^"|"$/g, "");
676
+ return normalized === infoPlistRelativePath || infoPlistRelativePath.endsWith(`/${normalized}`) || normalized.endsWith(`/${infoPlistRelativePath}`);
677
+ };
678
+ const named = (settings) => resolveBuildSetting(settings.PRODUCT_NAME ?? "", settings) === targetName;
679
+ return candidates.find(
680
+ (candidate) => candidate.name === "Release" && plistMatch(candidate.settings)
681
+ )?.settings ?? candidates.find(
682
+ (candidate) => candidate.name === "Release" && named(candidate.settings)
683
+ )?.settings ?? null;
684
+ }
685
+ function nativeAppIdentityFromIos(files, parseInfoPlist) {
686
+ const pbxprojPath = Object.keys(files).find(
687
+ (path6) => /(?:^|\/)ios\/[^/]+\.xcodeproj\/project\.pbxproj$/.test(path6)
688
+ );
689
+ if (!pbxprojPath) return null;
690
+ const pbxproj = files[pbxprojPath];
691
+ const applicationTargets = new Set(
692
+ [
693
+ ...pbxproj.matchAll(
694
+ /isa = PBXNativeTarget;[^}]*?name = "?([^";]+)"?;[^}]*?productType = "com\.apple\.product-type\.application";/g
695
+ )
696
+ ].map((match) => match[1])
697
+ );
698
+ const appDelegatePattern = /(?:^|\/)ios\/([^/]+)\/AppDelegate\.(?:swift|mm|m)$/;
699
+ const targetName = Object.keys(files).filter((path6) => !path6.includes("/Pods/")).map((path6) => path6.match(appDelegatePattern)?.[1]).find((name) => name !== void 0 && applicationTargets.has(name)) ?? null;
700
+ if (!targetName) return null;
701
+ const plist = files[`ios/${targetName}/Info.plist`];
702
+ if (!plist) return null;
703
+ const settings = appTargetReleaseSettings(
704
+ pbxproj,
705
+ targetName,
706
+ `${targetName}/Info.plist`
707
+ );
708
+ if (!settings) return null;
709
+ const bundleIdentifier = identityFieldFrom(
710
+ settings,
711
+ plistString(plist, "CFBundleIdentifier"),
712
+ settings.PRODUCT_BUNDLE_IDENTIFIER
713
+ );
714
+ if (!bundleIdentifier) return null;
715
+ const appVersion = identityFieldFrom(
716
+ settings,
717
+ plistString(plist, "CFBundleShortVersionString"),
718
+ settings.MARKETING_VERSION
719
+ );
720
+ const buildVersion = identityFieldFrom(
721
+ settings,
722
+ plistString(plist, "CFBundleVersion"),
723
+ settings.CURRENT_PROJECT_VERSION
724
+ );
725
+ if (!appVersion || !buildVersion) return null;
726
+ const applicationName = identityFieldFrom(settings, plistString(plist, "CFBundleDisplayName"), void 0) ?? identityFieldFrom(
727
+ settings,
728
+ plistString(plist, "CFBundleName"),
729
+ settings.PRODUCT_NAME
730
+ ) ?? resolvedIdentityField(resolveBuildSetting(settings.PRODUCT_NAME ?? "", settings)) ?? resolvedIdentityField(targetName);
731
+ if (!applicationName) return null;
732
+ const dictionary = parseInfoPlist(plist);
733
+ if (!isRecord(dictionary)) throw new Error("native app Info.plist is not a dictionary");
734
+ const permissionUsageDescriptions = {};
735
+ for (const [key, value] of Object.entries(dictionary)) {
736
+ if (!/^NS.*UsageDescription$/.test(key)) continue;
737
+ if (typeof value !== "string") {
738
+ permissionUsageDescriptions[key] = value;
739
+ continue;
740
+ }
741
+ const resolved = resolveBuildSetting(value, settings);
742
+ if (resolved === null) throw new Error(`native app Info.plist ${key} is unresolved`);
743
+ permissionUsageDescriptions[key] = resolved;
744
+ }
745
+ if (settings.GENERATE_INFOPLIST_FILE === "YES") {
746
+ for (const [setting, value] of Object.entries(settings)) {
747
+ if (!/^INFOPLIST_KEY_NS.*UsageDescription$/.test(setting)) continue;
748
+ const key = setting.slice("INFOPLIST_KEY_".length);
749
+ const resolved = resolveBuildSetting(value, settings);
750
+ if (resolved === null)
751
+ throw new Error(`native app build setting ${setting} is unresolved`);
752
+ permissionUsageDescriptions[key] = resolved;
753
+ }
754
+ }
755
+ return {
756
+ applicationName,
757
+ appVersion,
758
+ buildVersion,
759
+ bundleIdentifier,
760
+ platform: "ios",
761
+ permissionUsageDescriptions
762
+ };
763
+ }
764
+ function canExtractNativeAppIdentity(platform) {
765
+ return platform === "ios";
766
+ }
767
+ function extractNativeAppIdentityFromFiles(files, platform, parseInfoPlist) {
768
+ return canExtractNativeAppIdentity(platform) ? nativeAppIdentityFromIos(files, parseInfoPlist) : null;
769
+ }
770
+
531
771
  // src/native-firebase-config.ts
532
- var import_node_fs2 = __toESM(require("node:fs"), 1);
772
+ var import_node_fs3 = __toESM(require("node:fs"), 1);
533
773
  var import_node_module = require("node:module");
534
774
  var import_node_path2 = __toESM(require("node:path"), 1);
535
775
 
536
776
  // src/is-record.ts
537
- function isRecord(value) {
777
+ function isRecord2(value) {
538
778
  return typeof value === "object" && value !== null && !Array.isArray(value);
539
779
  }
540
780
 
541
781
  // src/plist.ts
542
782
  var import_plist = __toESM(require("@expo/plist"), 1);
543
783
  function resolvePlistParser(value) {
544
- if (!isRecord(value)) return null;
784
+ if (!isRecord2(value)) return null;
545
785
  const direct = value.parse;
546
786
  if (typeof direct === "function") {
547
787
  return (source) => Reflect.apply(direct, value, [source]);
548
788
  }
549
789
  const nested = value.default;
550
- if (!isRecord(nested)) return null;
790
+ if (!isRecord2(nested)) return null;
551
791
  const nestedParse = nested.parse;
552
792
  return typeof nestedParse === "function" ? (source) => Reflect.apply(nestedParse, nested, [source]) : null;
553
793
  }
@@ -565,28 +805,28 @@ function readNativeFirebaseConfig(projectRoot) {
565
805
  "app.config.ts",
566
806
  "app.config.cjs",
567
807
  "app.config.mjs"
568
- ].some((name) => import_node_fs2.default.existsSync(import_node_path2.default.join(projectRoot, name))))
808
+ ].some((name) => import_node_fs3.default.existsSync(import_node_path2.default.join(projectRoot, name))))
569
809
  return result;
570
810
  const require2 = (0, import_node_module.createRequire)(import_node_path2.default.join(projectRoot, "package.json"));
571
811
  const configModule = require2("@expo/config");
572
- if (!isRecord(configModule) || typeof configModule.getConfig !== "function") {
812
+ if (!isRecord2(configModule) || typeof configModule.getConfig !== "function") {
573
813
  throw new Error("Expo native configuration is unavailable.");
574
814
  }
575
815
  const resolved = Reflect.apply(configModule.getConfig, configModule, [
576
816
  projectRoot,
577
817
  { skipSDKVersionRequirement: true }
578
818
  ]);
579
- const config = isRecord(resolved) && isRecord(resolved.exp) ? resolved.exp : {};
819
+ const config = isRecord2(resolved) && isRecord2(resolved.exp) ? resolved.exp : {};
580
820
  for (const platform of ["ios", "android"]) {
581
821
  const native = config[platform];
582
- if (!isRecord(native) || typeof native.googleServicesFile !== "string") continue;
822
+ if (!isRecord2(native) || typeof native.googleServicesFile !== "string") continue;
583
823
  try {
584
- const source = import_node_fs2.default.readFileSync(
824
+ const source = import_node_fs3.default.readFileSync(
585
825
  import_node_path2.default.resolve(projectRoot, native.googleServicesFile),
586
826
  "utf8"
587
827
  );
588
828
  const parsed = platform === "ios" ? parsePlistSource(source) : JSON.parse(source);
589
- if (!isRecord(parsed)) throw new Error("Invalid Google services file.");
829
+ if (!isRecord2(parsed)) throw new Error("Invalid Google services file.");
590
830
  if (platform === "ios") {
591
831
  result[platform] = {
592
832
  apiKey: parsed.API_KEY,
@@ -601,31 +841,31 @@ function readNativeFirebaseConfig(projectRoot) {
601
841
  deepLinkUrlScheme: parsed.REVERSED_CLIENT_ID
602
842
  };
603
843
  } else {
604
- const project = isRecord(parsed.project_info) ? parsed.project_info : {};
844
+ const project = isRecord2(parsed.project_info) ? parsed.project_info : {};
605
845
  const clients = Array.isArray(parsed.client) ? parsed.client : [];
606
846
  const client = clients.find((value) => {
607
- if (!isRecord(value) || !isRecord(value.client_info)) return false;
847
+ if (!isRecord2(value) || !isRecord2(value.client_info)) return false;
608
848
  const android = value.client_info.android_client_info;
609
- return isRecord(android) && android.package_name === native.package;
849
+ return isRecord2(android) && android.package_name === native.package;
610
850
  });
611
- if (!isRecord(client) || !isRecord(client.client_info))
851
+ if (!isRecord2(client) || !isRecord2(client.client_info))
612
852
  throw new Error("No Google services client matches the Android package.");
613
853
  const apiKeys = Array.isArray(client.api_key) ? client.api_key : [];
614
854
  const apiKey = apiKeys.find(
615
- (value) => isRecord(value) && typeof value.current_key === "string"
855
+ (value) => isRecord2(value) && typeof value.current_key === "string"
616
856
  );
617
857
  const oauthClients = Array.isArray(client.oauth_client) ? client.oauth_client : [];
618
858
  const oauth = oauthClients.find(
619
- (value) => isRecord(value) && value.client_type === 3
859
+ (value) => isRecord2(value) && value.client_type === 3
620
860
  );
621
861
  result[platform] = {
622
- apiKey: isRecord(apiKey) ? apiKey.current_key : void 0,
862
+ apiKey: isRecord2(apiKey) ? apiKey.current_key : void 0,
623
863
  appId: client.client_info.mobilesdk_app_id,
624
864
  projectId: project.project_id,
625
865
  databaseURL: project.firebase_url,
626
866
  storageBucket: project.storage_bucket,
627
867
  messagingSenderId: project.project_number,
628
- clientId: isRecord(oauth) ? oauth.client_id : void 0
868
+ clientId: isRecord2(oauth) ? oauth.client_id : void 0
629
869
  };
630
870
  }
631
871
  } catch (error) {
@@ -646,7 +886,7 @@ function isValidRuntimeVersion(value) {
646
886
  }
647
887
 
648
888
  // src/home-paths.ts
649
- var import_node_fs3 = __toESM(require("node:fs"), 1);
889
+ var import_node_fs4 = __toESM(require("node:fs"), 1);
650
890
  var import_node_os = require("node:os");
651
891
  var import_node_path3 = __toESM(require("node:path"), 1);
652
892
  var PEACH_HOME_ENV = "PEACH_HOME";
@@ -667,7 +907,7 @@ function activeRuntimeFile() {
667
907
  }
668
908
  function readActiveRuntime() {
669
909
  try {
670
- const value = import_node_fs3.default.readFileSync(activeRuntimeFile(), "utf8").trim();
910
+ const value = import_node_fs4.default.readFileSync(activeRuntimeFile(), "utf8").trim();
671
911
  return value.length > 0 ? value : null;
672
912
  } catch {
673
913
  return null;
@@ -950,7 +1190,38 @@ function withRNX(config) {
950
1190
  customSerializer: async (entryPoint, preModules, graph, serializerOptions) => {
951
1191
  const serializer = existingSerializer || (defaultSerializer ||= createMetroDefaultSerializer(cfgProjectRoot));
952
1192
  const result = await serializer(entryPoint, preModules, graph, serializerOptions);
953
- return appendRNXModulePaths(result, graph, serializerOptions, cfgProjectRoot);
1193
+ let annotated = appendRNXModulePaths(
1194
+ result,
1195
+ graph,
1196
+ serializerOptions,
1197
+ cfgProjectRoot
1198
+ );
1199
+ const iosRoot = import_path2.default.join(cfgProjectRoot, "ios");
1200
+ const bundlePlatform = graph.transformOptions?.platform;
1201
+ if (canExtractNativeAppIdentity(bundlePlatform) && import_fs2.default.existsSync(iosRoot)) {
1202
+ const files = {};
1203
+ for (const entry of import_fs2.default.readdirSync(iosRoot, { withFileTypes: true })) {
1204
+ if (!entry.isDirectory() || entry.name === "Pods") continue;
1205
+ const names = entry.name.endsWith(".xcodeproj") ? ["project.pbxproj"] : ["Info.plist", "AppDelegate.swift", "AppDelegate.mm", "AppDelegate.m"];
1206
+ for (const name of names) {
1207
+ const relative2 = `ios/${entry.name}/${name}`;
1208
+ const file = import_path2.default.join(cfgProjectRoot, relative2);
1209
+ if (import_fs2.default.existsSync(file)) files[relative2] = import_fs2.default.readFileSync(file, "utf8");
1210
+ }
1211
+ }
1212
+ const identity = extractNativeAppIdentityFromFiles(
1213
+ files,
1214
+ bundlePlatform,
1215
+ parsePlistSource
1216
+ );
1217
+ if (identity) {
1218
+ annotated = typeof annotated === "string" ? insertNativeAppIdentityFooter(annotated, identity) : {
1219
+ ...annotated,
1220
+ code: insertNativeAppIdentityFooter(annotated.code, identity)
1221
+ };
1222
+ }
1223
+ }
1224
+ return annotated;
954
1225
  }
955
1226
  }
956
1227
  };
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.657 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.658 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -23,6 +23,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
23
23
  var native_app_identity_exports = {};
24
24
  __export(native_app_identity_exports, {
25
25
  applyNativeAppIdentity: () => applyNativeAppIdentity,
26
+ canExtractNativeAppIdentity: () => canExtractNativeAppIdentity,
26
27
  createNativeAppIdentityFooter: () => createNativeAppIdentityFooter,
27
28
  detachNativeAppIdentityFooter: () => detachNativeAppIdentityFooter,
28
29
  extractNativeAppIdentityFromFiles: () => extractNativeAppIdentityFromFiles,
@@ -43,6 +44,8 @@ function nonEmptyString(value) {
43
44
  }
44
45
  function isNativeAppIdentity(value) {
45
46
  if (!isRecord(value)) return false;
47
+ const descriptions = value.permissionUsageDescriptions;
48
+ if (descriptions !== void 0 && !isRecord(descriptions)) return false;
46
49
  const platform = value.platform;
47
50
  if (platform !== "ios" && platform !== "android") return false;
48
51
  const applicationName = value.applicationName;
@@ -64,7 +67,10 @@ function readNativeAppIdentity(value) {
64
67
  appVersion: value.appVersion,
65
68
  buildVersion: value.buildVersion,
66
69
  bundleIdentifier: value.bundleIdentifier,
67
- platform: value.platform
70
+ platform: value.platform,
71
+ ...value.permissionUsageDescriptions === void 0 ? {} : {
72
+ permissionUsageDescriptions: { ...value.permissionUsageDescriptions }
73
+ }
68
74
  };
69
75
  }
70
76
  function applyNativeAppIdentity(identity) {
@@ -169,7 +175,7 @@ function readBuildSettingsBlock(source, start) {
169
175
  }
170
176
  function parseBuildSettings(block) {
171
177
  const settings = {};
172
- const matchPattern = /([A-Z0-9_]+) = ("(?:[^"\\]|\\.)*"|[^;{}]*);/g;
178
+ const matchPattern = /([A-Za-z0-9_]+) = ("(?:[^"\\]|\\.)*"|[^;{}]*);/g;
173
179
  let match;
174
180
  while ((match = matchPattern.exec(block)) !== null) {
175
181
  const raw = match[2].trim();
@@ -205,27 +211,29 @@ function appTargetReleaseSettings(pbxproj, targetName, infoPlistRelativePath) {
205
211
  (candidate) => candidate.name === "Release" && named(candidate.settings)
206
212
  )?.settings ?? null;
207
213
  }
208
- function nativeAppIdentityFromIos(files) {
214
+ function nativeAppIdentityFromIos(files, parseInfoPlist) {
215
+ const pbxprojPath = Object.keys(files).find(
216
+ (path) => /(?:^|\/)ios\/[^/]+\.xcodeproj\/project\.pbxproj$/.test(path)
217
+ );
218
+ if (!pbxprojPath) return null;
219
+ const pbxproj = files[pbxprojPath];
220
+ const applicationTargets = new Set(
221
+ [
222
+ ...pbxproj.matchAll(
223
+ /isa = PBXNativeTarget;[^}]*?name = "?([^";]+)"?;[^}]*?productType = "com\.apple\.product-type\.application";/g
224
+ )
225
+ ].map((match) => match[1])
226
+ );
209
227
  const appDelegatePattern = /(?:^|\/)ios\/([^/]+)\/AppDelegate\.(?:swift|mm|m)$/;
210
- let targetName = null;
211
- for (const path of Object.keys(files)) {
212
- if (path.includes("/Pods/")) continue;
213
- const match = path.match(appDelegatePattern);
214
- if (match) {
215
- targetName = match[1];
216
- break;
217
- }
218
- }
228
+ const targetName = Object.keys(files).filter((path) => !path.includes("/Pods/")).map((path) => path.match(appDelegatePattern)?.[1]).find((name) => name !== void 0 && applicationTargets.has(name)) ?? null;
219
229
  if (!targetName) return null;
220
- const plistPath = Object.keys(files).find(
221
- (path) => path === `ios/${targetName}/Info.plist`
222
- );
223
- const plist = plistPath ? files[plistPath] : null;
230
+ const plist = files[`ios/${targetName}/Info.plist`];
224
231
  if (!plist) return null;
225
- const pbxprojPath = Object.keys(files).find(
226
- (path) => /(?:^|\/)ios\/[^/]+\.xcodeproj\/project\.pbxproj$/.test(path)
232
+ const settings = appTargetReleaseSettings(
233
+ pbxproj,
234
+ targetName,
235
+ `${targetName}/Info.plist`
227
236
  );
228
- const settings = pbxprojPath ? appTargetReleaseSettings(files[pbxprojPath], targetName, `${targetName}/Info.plist`) : null;
229
237
  if (!settings) return null;
230
238
  const bundleIdentifier = identityFieldFrom(
231
239
  settings,
@@ -250,23 +258,48 @@ function nativeAppIdentityFromIos(files) {
250
258
  settings.PRODUCT_NAME
251
259
  ) ?? resolvedIdentityField(resolveBuildSetting(settings.PRODUCT_NAME ?? "", settings)) ?? resolvedIdentityField(targetName);
252
260
  if (!applicationName) return null;
261
+ const dictionary = parseInfoPlist(plist);
262
+ if (!isRecord(dictionary)) throw new Error("native app Info.plist is not a dictionary");
263
+ const permissionUsageDescriptions = {};
264
+ for (const [key, value] of Object.entries(dictionary)) {
265
+ if (!/^NS.*UsageDescription$/.test(key)) continue;
266
+ if (typeof value !== "string") {
267
+ permissionUsageDescriptions[key] = value;
268
+ continue;
269
+ }
270
+ const resolved = resolveBuildSetting(value, settings);
271
+ if (resolved === null) throw new Error(`native app Info.plist ${key} is unresolved`);
272
+ permissionUsageDescriptions[key] = resolved;
273
+ }
274
+ if (settings.GENERATE_INFOPLIST_FILE === "YES") {
275
+ for (const [setting, value] of Object.entries(settings)) {
276
+ if (!/^INFOPLIST_KEY_NS.*UsageDescription$/.test(setting)) continue;
277
+ const key = setting.slice("INFOPLIST_KEY_".length);
278
+ const resolved = resolveBuildSetting(value, settings);
279
+ if (resolved === null)
280
+ throw new Error(`native app build setting ${setting} is unresolved`);
281
+ permissionUsageDescriptions[key] = resolved;
282
+ }
283
+ }
253
284
  return {
254
285
  applicationName,
255
286
  appVersion,
256
287
  buildVersion,
257
288
  bundleIdentifier,
258
- platform: "ios"
289
+ platform: "ios",
290
+ permissionUsageDescriptions
259
291
  };
260
292
  }
261
- function nativeAppIdentityFromAndroid() {
262
- return null;
293
+ function canExtractNativeAppIdentity(platform) {
294
+ return platform === "ios";
263
295
  }
264
- function extractNativeAppIdentityFromFiles(files, platform) {
265
- return platform === "android" ? nativeAppIdentityFromAndroid() : nativeAppIdentityFromIos(files);
296
+ function extractNativeAppIdentityFromFiles(files, platform, parseInfoPlist) {
297
+ return canExtractNativeAppIdentity(platform) ? nativeAppIdentityFromIos(files, parseInfoPlist) : null;
266
298
  }
267
299
  // Annotate the CommonJS export names for ESM import in node:
268
300
  0 && (module.exports = {
269
301
  applyNativeAppIdentity,
302
+ canExtractNativeAppIdentity,
270
303
  createNativeAppIdentityFooter,
271
304
  detachNativeAppIdentityFooter,
272
305
  extractNativeAppIdentityFromFiles,