peachsim 0.1.1619 → 0.1.1625

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 (140) hide show
  1. package/README.md +35 -5
  2. package/cli/app-fonts.ts +2 -1
  3. package/cli/bridge-flow-runner.ts +222 -175
  4. package/cli/cloud-client.ts +81 -48
  5. package/cli/commands/box/search-command.ts +3 -2
  6. package/cli/commands/box.ts +10 -29
  7. package/cli/commands/control.ts +108 -43
  8. package/cli/commands/inspect/settling.ts +57 -20
  9. package/cli/commands/maestro-compare.ts +11 -32
  10. package/cli/commands/platform.ts +74 -17
  11. package/cli/commands/record.ts +20 -15
  12. package/cli/commands/upload.ts +5 -1
  13. package/cli/dev-build-freshness.ts +37 -2
  14. package/cli/drivers/playwright-sim-host.ts +18 -6
  15. package/cli/maestro-accessibility.ts +53 -0
  16. package/cli/maestro-compare.ts +24 -0
  17. package/cli/outbound-endpoints.ts +27 -8
  18. package/cli/parent-pid.ts +0 -9
  19. package/cli/remote-simulators.ts +112 -0
  20. package/cli/remoteSimulatorContract.ts +21 -0
  21. package/detox/expectations.ts +5 -1
  22. package/detox/index.ts +133 -98
  23. package/detox/motion-change.cjs +37 -0
  24. package/detox/motion-change.d.cts +16 -0
  25. package/detox/navigation-lifecycle.ts +22 -4
  26. package/dist-lib/agent-daemon-client.cjs +3 -2
  27. package/dist-lib/agent-events.cjs +1 -1
  28. package/dist-lib/attached-projects.cjs +1 -1
  29. package/dist-lib/auth/shared-session.cjs +3 -2
  30. package/dist-lib/backend-origin.cjs +1 -1
  31. package/dist-lib/beta.cjs +1 -1
  32. package/dist-lib/beta.mjs +1 -1
  33. package/dist-lib/bridge-constants.cjs +1 -1
  34. package/dist-lib/bridge-contract-input.cjs +1 -1
  35. package/dist-lib/bridge-contract-input.mjs +1 -1
  36. package/dist-lib/bridge-contract.cjs +1 -1
  37. package/dist-lib/bridge-contract.mjs +1 -1
  38. package/dist-lib/bundle-channel.cjs +1528 -937
  39. package/dist-lib/capture-contract.cjs +1 -1
  40. package/dist-lib/capture-contract.mjs +1 -1
  41. package/dist-lib/cloud-contract.cjs +1 -1
  42. package/dist-lib/cloud-contract.mjs +1 -1
  43. package/dist-lib/cloud-test-args.cjs +1 -1
  44. package/dist-lib/cloud-test-args.mjs +1 -1
  45. package/dist-lib/cloud.cjs +4 -3
  46. package/dist-lib/cloud.mjs +4 -3
  47. package/dist-lib/config.cjs +5 -4
  48. package/dist-lib/config.mjs +5 -4
  49. package/dist-lib/detox/index.cjs +1021 -364
  50. package/dist-lib/dev-bundle-resolution.cjs +51 -23
  51. package/dist-lib/expo-swift-bundle.cjs +1351 -804
  52. package/dist-lib/expo-swift-metadata.cjs +5 -4
  53. package/dist-lib/expo-swift-metadata.mjs +5 -4
  54. package/dist-lib/home-paths.cjs +1 -1
  55. package/dist-lib/host/bridge-host.cjs +2475 -1343
  56. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  57. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  58. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  59. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  60. package/dist-lib/host/websocket-proxy.cjs +1 -1
  61. package/dist-lib/index.cjs +1269 -300
  62. package/dist-lib/jump-to-source-babel.cjs +1 -1
  63. package/dist-lib/jump-to-source-native.cjs +1 -1
  64. package/dist-lib/kotlin-host.cjs +1 -1
  65. package/dist-lib/menu.cjs +3 -2
  66. package/dist-lib/menu.mjs +3 -2
  67. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  68. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  69. package/dist-lib/metro-production-bundle.cjs +5 -4
  70. package/dist-lib/metro-production-bundle.mjs +5 -4
  71. package/dist-lib/metro.cjs +806 -398
  72. package/dist-lib/native-app-identity-project.cjs +729 -21
  73. package/dist-lib/native-app-identity-project.mjs +728 -21
  74. package/dist-lib/native-app-identity.cjs +232 -9
  75. package/dist-lib/native-app-identity.mjs +230 -8
  76. package/dist-lib/native-firebase-metadata.cjs +5 -4
  77. package/dist-lib/native-firebase-metadata.mjs +5 -4
  78. package/dist-lib/plist.cjs +1 -1
  79. package/dist-lib/plist.mjs +1 -1
  80. package/dist-lib/profiles.cjs +1 -1
  81. package/dist-lib/public-brand.cjs +3 -2
  82. package/dist-lib/react-native-host-modules.cjs +4 -1
  83. package/dist-lib/react-native-host-modules.mjs +4 -1
  84. package/dist-lib/scripts/dev-server-scanner.cjs +94 -28
  85. package/dist-lib/sdk.cjs +89 -24
  86. package/dist-lib/sdk.mjs +89 -24
  87. package/dist-lib/skills.cjs +4471 -2394
  88. package/dist-lib/swift-compile.cjs +336 -145
  89. package/dist-lib/swift-compile.mjs +350 -150
  90. package/dist-lib/swift-package.cjs +1 -1
  91. package/dist-lib/swift-package.mjs +1 -1
  92. package/dist-lib/swift-submit.cjs +1 -1
  93. package/dist-lib/swift-submit.mjs +1 -1
  94. package/dist-lib/swift-toolchain.cjs +1 -1
  95. package/dist-lib/swift.cjs +1 -1
  96. package/dist-lib/vite-swift.cjs +891 -557
  97. package/dist-lib/vite.cjs +68 -14
  98. package/dist-lib/worklet-executable.cjs +25 -2
  99. package/dist-lib/worklet-executable.mjs +25 -2
  100. package/package.json +2 -2
  101. package/scripts/dev-server-scanner.ts +33 -13
  102. package/src/admitted-mac-build.ts +34 -8
  103. package/src/android-app-identity.ts +146 -0
  104. package/src/android-autolinking.ts +75 -0
  105. package/src/android-string-value.ts +36 -0
  106. package/src/android-strings.ts +5 -36
  107. package/src/androidAutolinkingContract.ts +40 -0
  108. package/src/bundleGlobalFooter.ts +26 -7
  109. package/src/cloud.ts +2 -2
  110. package/src/codex-client.ts +3 -2
  111. package/src/dev-bundle-resolution.ts +58 -25
  112. package/src/expo-kotlin-host.ts +6 -36
  113. package/src/expo-swift-bundle.ts +17 -12
  114. package/src/expo-swift-host.ts +477 -173
  115. package/src/expoSwiftContract.ts +6 -0
  116. package/src/host/bridge-host.ts +7 -55
  117. package/src/host/staged-host-files.ts +29 -2
  118. package/src/installed-package-versions.ts +54 -14
  119. package/src/launchMetadataContract.ts +24 -0
  120. package/src/metro-plugin.ts +37 -42
  121. package/src/native-app-identity-project.ts +118 -3
  122. package/src/native-app-identity.ts +103 -11
  123. package/src/nitro-compile.ts +2 -5
  124. package/src/nitro-source.ts +147 -63
  125. package/src/process-group.ts +90 -60
  126. package/src/process-output.ts +0 -9
  127. package/src/public-brand.ts +1 -0
  128. package/src/react-native-host-modules.ts +3 -0
  129. package/src/sim-client.ts +6 -4
  130. package/src/swift-compile.ts +185 -31
  131. package/src/swift-distribution.lock.json +37 -11
  132. package/src/swift-distribution.ts +61 -9
  133. package/src/swift-host.ts +58 -27
  134. package/src/swift-objc-standin.ts +13 -0
  135. package/src/swift-standin-emit.ts +1 -1
  136. package/src/swift-standins.ts +17 -5
  137. package/src/vite-plugin.ts +8 -19
  138. package/src/worklet-executable.ts +30 -3
  139. package/src/worklets-babel.ts +35 -14
  140. package/src/xcode-project.ts +10 -0
@@ -1,13 +1,14 @@
1
1
  // generic native app identity: the values an installed iOS/Android binary
2
2
  // carries, read from the app's own native project (`Info.plist` resolved
3
- // against the Xcode Release build settings, or `android/app/build.gradle`), so
4
- // a simulator tenant reports the same identity the native Release build does.
3
+ // against Xcode Release build settings, or a completed Android app manifest
4
+ // merger output), so a simulator tenant reports the selected native build.
5
5
  //
6
6
  // identity travels in a bundle footer or response header. the one updates
7
7
  // receipt travels only in the response header so publisher JavaScript stays
8
8
  // unchanged. tenant boot validates both before evaluation and clears the realm
9
9
  // store when neither carries identity.
10
10
 
11
+ import { nativeAppIdentityFromAndroidManifest } from './android-app-identity'
11
12
  import { findBundleFooter } from './bundleGlobalFooter'
12
13
  import {
13
14
  resolveNativeAppIconArtwork,
@@ -27,8 +28,14 @@ export interface NativeOneUpdatesReceipt {
27
28
  embeddedManifestJson: string | null
28
29
  }
29
30
 
31
+ export interface AndroidManifestPermission {
32
+ name: string
33
+ minSdkVersion: number
34
+ maxSdkVersion?: number
35
+ }
36
+
30
37
  export interface NativeAppIdentity {
31
- // the user-facing name (CFBundleDisplayName, else CFBundleName).
38
+ // the user-facing name (iOS bundle name or a resolved Android app label).
32
39
  applicationName: string | null
33
40
  // the literal CFBundleDisplayName, without a bundle-name fallback.
34
41
  applicationDisplayName?: string
@@ -51,6 +58,9 @@ export interface NativeAppIdentity {
51
58
  buildVersion: string
52
59
  bundleIdentifier: string
53
60
  platform: NativeAppIdentityPlatform
61
+ // declarations from the selected app manifest merger output; install-time
62
+ // SDK filtering belongs to the evaluation's device, not the bundle builder.
63
+ androidManifestPermissions?: readonly AndroidManifestPermission[]
54
64
  permissionUsageDescriptions?: Readonly<Record<string, unknown>>
55
65
  urlQuerySchemes?: readonly unknown[]
56
66
  appIntentIdentifiers?: readonly string[]
@@ -60,6 +70,8 @@ export interface NativeAppIdentity {
60
70
  ipadSupportedInterfaceOrientations?: readonly string[]
61
71
  defaultScreenOrientationMask?: string
62
72
  shareIntoAppGroupId?: string
73
+ rtcAppGroupIdentifier?: string
74
+ screenShareExtensionBundleId?: string
63
75
  alternateIconNames?: readonly string[]
64
76
  appIconArtwork?: NativeAppIconArtwork
65
77
  // resolved Reanimated compile flags from the native build inputs.
@@ -113,6 +125,24 @@ export function isNativeAppIdentity(value: unknown): value is NativeAppIdentity
113
125
  typeof value.dtPlatformVersion !== 'string'
114
126
  )
115
127
  return false
128
+ if (
129
+ value.androidManifestPermissions !== undefined &&
130
+ (!Array.isArray(value.androidManifestPermissions) ||
131
+ !value.androidManifestPermissions.every(
132
+ (permission: unknown) =>
133
+ isRecord(permission) &&
134
+ typeof permission.name === 'string' &&
135
+ permission.name.length > 0 &&
136
+ Number.isInteger(permission.minSdkVersion) &&
137
+ typeof permission.minSdkVersion === 'number' &&
138
+ permission.minSdkVersion > 0 &&
139
+ (permission.maxSdkVersion === undefined ||
140
+ (typeof permission.maxSdkVersion === 'number' &&
141
+ Number.isInteger(permission.maxSdkVersion) &&
142
+ permission.maxSdkVersion >= 0)),
143
+ ))
144
+ )
145
+ return false
116
146
  const descriptions = value.permissionUsageDescriptions
117
147
  if (
118
148
  value.reanimatedStaticFeatureFlags !== undefined &&
@@ -150,6 +180,16 @@ export function isNativeAppIdentity(value: unknown): value is NativeAppIdentity
150
180
  typeof value.shareIntoAppGroupId !== 'string'
151
181
  )
152
182
  return false
183
+ if (
184
+ value.rtcAppGroupIdentifier !== undefined &&
185
+ typeof value.rtcAppGroupIdentifier !== 'string'
186
+ )
187
+ return false
188
+ if (
189
+ value.screenShareExtensionBundleId !== undefined &&
190
+ typeof value.screenShareExtensionBundleId !== 'string'
191
+ )
192
+ return false
153
193
  if (
154
194
  value.applicationDisplayName !== undefined &&
155
195
  typeof value.applicationDisplayName !== 'string'
@@ -244,6 +284,13 @@ export function readNativeAppIdentity(value: unknown): NativeAppIdentity | null
244
284
  ...(value.alternateIconNames === undefined
245
285
  ? {}
246
286
  : { alternateIconNames: [...value.alternateIconNames] }),
287
+ ...(value.androidManifestPermissions === undefined
288
+ ? {}
289
+ : {
290
+ androidManifestPermissions: value.androidManifestPermissions.map(
291
+ (permission) => ({ ...permission }),
292
+ ),
293
+ }),
247
294
  ...(value.permissionUsageDescriptions === undefined
248
295
  ? {}
249
296
  : {
@@ -276,6 +323,12 @@ export function readNativeAppIdentity(value: unknown): NativeAppIdentity | null
276
323
  ...(value.shareIntoAppGroupId === undefined
277
324
  ? {}
278
325
  : { shareIntoAppGroupId: value.shareIntoAppGroupId }),
326
+ ...(value.rtcAppGroupIdentifier === undefined
327
+ ? {}
328
+ : { rtcAppGroupIdentifier: value.rtcAppGroupIdentifier }),
329
+ ...(value.screenShareExtensionBundleId === undefined
330
+ ? {}
331
+ : { screenShareExtensionBundleId: value.screenShareExtensionBundleId }),
279
332
  ...(value.urlQuerySchemes === undefined
280
333
  ? {}
281
334
  : { urlQuerySchemes: [...value.urlQuerySchemes] }),
@@ -857,6 +910,20 @@ function nativeAppIdentityFromIos(
857
910
  : undefined
858
911
  if (shareIntoAppGroupId === null)
859
912
  throw new Error('native app Info.plist ExpoShareIntoAppGroupId is unresolved')
913
+ const declaredRtcGroup = dictionary.RTCAppGroupIdentifier
914
+ const rtcAppGroupIdentifier =
915
+ typeof declaredRtcGroup === 'string'
916
+ ? resolveBuildSetting(declaredRtcGroup, settings)
917
+ : undefined
918
+ if (rtcAppGroupIdentifier === null)
919
+ throw new Error('native app Info.plist RTCAppGroupIdentifier is unresolved')
920
+ const declaredScreenShareExtension = dictionary.ScreenShareExtensionBundleId
921
+ const screenShareExtensionBundleId =
922
+ typeof declaredScreenShareExtension === 'string'
923
+ ? resolveBuildSetting(declaredScreenShareExtension, settings)
924
+ : undefined
925
+ if (screenShareExtensionBundleId === null)
926
+ throw new Error('native app Info.plist ScreenShareExtensionBundleId is unresolved')
860
927
  const querySchemes = dictionary.LSApplicationQueriesSchemes
861
928
  if (querySchemes !== undefined && !Array.isArray(querySchemes)) {
862
929
  throw new Error('native app Info.plist LSApplicationQueriesSchemes is not an array')
@@ -991,6 +1058,10 @@ function nativeAppIdentityFromIos(
991
1058
  permissionUsageDescriptions,
992
1059
  urlQuerySchemes,
993
1060
  ...(shareIntoAppGroupId === undefined ? {} : { shareIntoAppGroupId }),
1061
+ ...(rtcAppGroupIdentifier === undefined ? {} : { rtcAppGroupIdentifier }),
1062
+ ...(screenShareExtensionBundleId === undefined
1063
+ ? {}
1064
+ : { screenShareExtensionBundleId }),
994
1065
  backgroundTaskIdentifiers: backgroundTaskIdentifiers.every((value) => value !== null)
995
1066
  ? backgroundTaskIdentifiers
996
1067
  : [],
@@ -1003,11 +1074,6 @@ function nativeAppIdentityFromIos(
1003
1074
  }
1004
1075
  }
1005
1076
 
1006
- // android's shipped identity requires resolving the selected flavor and
1007
- // buildType overrides (`productFlavors.prod`, `buildTypes.release`), which a
1008
- // text reader of `defaultConfig` cannot distinguish from the defaults it
1009
- // overrides. an Android project therefore ships no footer until that
1010
- // resolution exists. unsupported or absent bundle platforms ship no footer.
1011
1077
  // the module an app's own sources start react native with: the template
1012
1078
  // `startReactNative(withModuleName: "X")`, RCTAppDelegate's
1013
1079
  // `self.moduleName = @"X"` and RCTRootView's `moduleName:@"X"`. a target that
@@ -1022,8 +1088,19 @@ export function readRootModuleName(sources: readonly string[]): string | undefin
1022
1088
  return names.size === 1 ? [...names][0] : undefined
1023
1089
  }
1024
1090
 
1025
- export function canExtractNativeAppIdentity(platform: unknown): platform is 'ios' {
1026
- return platform === 'ios'
1091
+ export function canExtractNativeAppIdentity(
1092
+ platform: unknown,
1093
+ ): platform is NativeAppIdentityPlatform {
1094
+ return platform === 'ios' || platform === 'android'
1095
+ }
1096
+
1097
+ // the bundle platform's identity reader. selecting it here keeps every native
1098
+ // project reader free of platform branches.
1099
+ export function selectNativeAppIdentityReader<Reader>(
1100
+ platform: unknown,
1101
+ readers: Readonly<Record<NativeAppIdentityPlatform, Reader>>,
1102
+ ): Reader | null {
1103
+ return canExtractNativeAppIdentity(platform) ? readers[platform] : null
1027
1104
  }
1028
1105
 
1029
1106
  // read the identity a bundle builder transports. files are project-relative
@@ -1036,8 +1113,23 @@ export function extractNativeAppIdentityFromFiles(
1036
1113
  parseInfoPlist: (source: string) => unknown,
1037
1114
  iosTarget?: string,
1038
1115
  resourceUri?: (file: string) => string | undefined,
1116
+ androidMergedManifest?: string,
1039
1117
  ): NativeAppIdentity | null {
1040
- return canExtractNativeAppIdentity(platform)
1118
+ if (platform === 'android') {
1119
+ const candidates = androidMergedManifest
1120
+ ? [androidMergedManifest]
1121
+ : Object.keys(files).filter((file) =>
1122
+ /^android\/[^/]+\/build\/intermediates\/merged_manifests\/.+\/AndroidManifest\.xml$/.test(
1123
+ file,
1124
+ ),
1125
+ )
1126
+ if (candidates.length !== 1) return null
1127
+ const source = files[candidates[0]!]
1128
+ return source === undefined
1129
+ ? null
1130
+ : nativeAppIdentityFromAndroidManifest(source, candidates[0]!, files)
1131
+ }
1132
+ return platform === 'ios'
1041
1133
  ? nativeAppIdentityFromIos(files, parseInfoPlist, iosTarget, resourceUri)
1042
1134
  : null
1043
1135
  }
@@ -20,10 +20,7 @@ export function collectNitroCompileInput(
20
20
  ): { input: SwiftCompileInput; collection: NitroSourceCollection } {
21
21
  const collection = collectNitroSources(options)
22
22
  const files = { ...collection.files }
23
- const platform = path.join(
24
- options.moduleRoot,
25
- 'platform/react-native-nitro-modules',
26
- )
23
+ const platform = path.join(options.moduleRoot, 'platform/react-native-nitro-modules')
27
24
  const read = (file: string) =>
28
25
  readPackageFile(
29
26
  platform,
@@ -71,7 +68,7 @@ export function collectNitroCompileInput(
71
68
  'let package = Package(name: "PeachNativeModule", targets: [',
72
69
  ...collection.targets.map(
73
70
  (target) =>
74
- `.target(name: ${JSON.stringify(target.name)}, dependencies: ${JSON.stringify(target.dependencies?.map((dependency) => dependency.name) ?? [])}, path: ${JSON.stringify(target.path)}, cxxSettings: [.define("DEBUG")]),`,
71
+ `.target(name: ${JSON.stringify(target.name)}, dependencies: ${JSON.stringify(target.dependencies?.map((dependency) => dependency.name) ?? [])}, path: ${JSON.stringify(target.path)}, ${target.cxxInterop ? 'swiftSettings: [.interoperabilityMode(.Cxx)]' : 'cxxSettings: [.define("DEBUG")]'}),`,
75
72
  ),
76
73
  `.target(name: "${runtime}", dependencies: ["JsiBridge", "NitroCore"], cxxSettings: [.define("DEBUG")]),`,
77
74
  `.executableTarget(name: "PeachNitroApp", dependencies: ["${runtime}", "${implementation.name}"], swiftSettings: [.interoperabilityMode(.Cxx)]),`,
@@ -55,61 +55,72 @@ function packageInfo(root: string): NitroSourceCollection['packages'][number] {
55
55
  return { name: value.name, version: value.version, root }
56
56
  }
57
57
 
58
- function registrations(root: string): {
59
- namespace: string
60
- classes: { name: string; implementation: string }[]
61
- } {
62
- const config = readJson(root, 'nitro.json')
63
- if (
64
- !object(config) ||
65
- !Array.isArray(config.cxxNamespace) ||
66
- !config.cxxNamespace.every(
67
- (part): part is string =>
68
- typeof part === 'string' && /^[A-Za-z_][A-Za-z0-9_]*$/.test(part),
69
- ) ||
70
- !object(config.autolinking)
71
- )
72
- throw new Error(`${root}: invalid Nitro C++ namespace or autolinking config`)
73
- const classes: { name: string; implementation: string }[] = []
74
- for (const [name, entry] of Object.entries(config.autolinking).sort(([a], [b]) =>
75
- a.localeCompare(b),
58
+ function registrations(root: string) {
59
+ // cocoapods compiles this generated initializer through +autolinking.rb.
60
+ // nitro.json is a generator input and need not ship in the package.
61
+ const directory = path.join(root, 'nitrogen/generated/ios')
62
+ const initializers = fs
63
+ .readdirSync(directory)
64
+ .filter((file) => file.endsWith('Autolinking.mm'))
65
+ if (initializers.length !== 1)
66
+ throw new Error(`${root}: expected one generated Nitro iOS initializer`)
67
+ const source = readPackageFile(
68
+ root,
69
+ path.join(directory, initializers[0]!),
70
+ MAX_SWIFT_COMPILE_SOURCE_BYTES,
71
+ ).toString('utf8')
72
+ const namespaces = [
73
+ ...source.matchAll(/\busing namespace (margelo::nitro(?:::[A-Za-z_]\w*)*)\s*;/g),
74
+ ]
75
+ const namespace = namespaces[1]?.[1]
76
+ if (namespaces.length !== 2 || namespaces[0]?.[1] !== 'margelo::nitro' || !namespace)
77
+ throw new Error(`${root}: invalid generated Nitro C++ namespace`)
78
+
79
+ const classes: (
80
+ | { name: string; implementation: string; language: 'c++' }
81
+ | { name: string; language: 'swift' }
82
+ )[] = []
83
+ let swiftModule: string | null = null
84
+ for (const registration of source.matchAll(
85
+ /\bHybridObjectRegistry::registerHybridObjectConstructor\(\s*("(?:[^"\\]|\\.)*")\s*,\s*\[\]\(\)\s*->\s*std::shared_ptr<HybridObject>\s*\{([\s\S]*?)\}\s*\);/g,
76
86
  )) {
77
- if (!object(entry)) throw new Error(`${root}: invalid Nitro autolinking ${name}`)
78
- // platform-specific registrations cannot be substituted for shared C++.
79
- // report their actual language instead of silently dropping Swift or JNI.
80
- if (
81
- !object(entry.all) ||
82
- entry.all.language !== 'c++' ||
83
- Object.keys(entry).some((platform) => platform !== 'all')
84
- ) {
85
- const languages = Object.entries(entry).map(
86
- ([platform, value]) =>
87
- `${platform}:${object(value) ? String(value.language) : 'invalid'}`,
88
- )
89
- throw new Error(
90
- `${root}: unsupported Nitro module ${name} (${languages.join(', ')}); ` +
91
- 'this collector supports shared all.language=c++ only, not Swift, Kotlin, Java or JNI',
87
+ const name: unknown = JSON.parse(registration[1]!)
88
+ const body = registration[2]!
89
+ if (typeof name !== 'string' || name.length === 0)
90
+ throw new Error(`${root}: invalid generated Nitro registration name`)
91
+ const cpp = /return\s+std::make_shared<([A-Za-z_]\w*)>\(\);/.exec(body)
92
+ const swift =
93
+ /=\s*([A-Za-z_]\w*)::([A-Za-z_]\w*)::create([A-Za-z_]\w*)\(\);\s*return hybridObject;/.exec(
94
+ body,
92
95
  )
96
+ if (cpp && !swift) {
97
+ classes.push({ name, implementation: cpp[1]!, language: 'c++' })
98
+ } else if (
99
+ swift &&
100
+ !cpp &&
101
+ swift[2] === `${swift[1]}Autolinking` &&
102
+ swift[3] === name &&
103
+ (swiftModule === null || swiftModule === swift[1])
104
+ ) {
105
+ swiftModule = swift[1]!
106
+ classes.push({ name, language: 'swift' })
107
+ } else {
108
+ throw new Error(`${root}: unsupported generated Nitro iOS registration ${name}`)
93
109
  }
94
- const implementation = entry.all.implementationClassName
95
- if (
96
- name.length === 0 ||
97
- typeof implementation !== 'string' ||
98
- !/^[A-Za-z_][A-Za-z0-9_]*$/.test(implementation)
99
- )
100
- throw new Error(`${root}: invalid Nitro C++ implementation for ${name}`)
101
- classes.push({ name, implementation })
102
- }
103
- if (classes.length === 0) throw new Error(`${root}: no Nitro C++ registrations`)
104
- return {
105
- namespace: ['margelo', 'nitro', ...config.cxxNamespace].join('::'),
106
- classes,
107
110
  }
111
+ const count = [
112
+ ...source.matchAll(/\bHybridObjectRegistry::registerHybridObjectConstructor\s*\(/g),
113
+ ].length
114
+ if (classes.length === 0 || classes.length !== count)
115
+ throw new Error(`${root}: incomplete generated Nitro iOS registrations`)
116
+ if (new Set(classes.map(({ name }) => name)).size !== classes.length)
117
+ throw new Error(`${root}: duplicate generated Nitro iOS registration`)
118
+ classes.sort((a, b) => a.name.localeCompare(b.name))
119
+ return { namespace, swiftModule, classes }
108
120
  }
109
121
 
110
- /** collect installed C++ bodies, Nitro core and RN's JSI, without reading or
111
- * rewriting package JavaScript. views and platform implementations are outside
112
- * this C++ module collector; the caller must supply real platform bindings. */
122
+ /** collect installed native bodies, generated bridges, Nitro core and RN's
123
+ * JSI without reading or rewriting package JavaScript. */
113
124
  export function collectNitroSources(options: NitroSourceOptions): NitroSourceCollection {
114
125
  const module = packageInfo(options.packageRoot)
115
126
  const config = registrations(module.root)
@@ -189,6 +200,7 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
189
200
  includeNamespace: string | null,
190
201
  skip: ReadonlySet<string> = new Set(),
191
202
  select: (file: string) => boolean = () => true,
203
+ extensions: RegExp = /\.(?:c|cc|cpp|cxx|h|hpp|hxx|inl)$/,
192
204
  ) => {
193
205
  const headers = new Map<string, string>()
194
206
  const walk = (relative: string) => {
@@ -204,7 +216,7 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
204
216
  walk(sourceRelative)
205
217
  continue
206
218
  }
207
- if (!/\.(?:c|cc|cpp|cxx|h|hpp|hxx|inl)$/.test(entry.name)) continue
219
+ if (!extensions.test(entry.name)) continue
208
220
  if (!select(entry.name)) continue
209
221
  if (/[\p{Cc}"\\]/u.test(sourceRelative))
210
222
  throw new Error(`invalid Nitro source path: ${sourceRelative}`)
@@ -259,13 +271,72 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
259
271
  (file) => (file.endsWith('.h') && file !== 'JSIDynamic.h') || file === 'jsi.cpp',
260
272
  )
261
273
  collect(nitro.root, 'cpp', coreTarget, 'NitroModules', new Set(['views']))
262
- const implementationHeaders = collect(module.root, 'cpp', targetName, null)
274
+ const implementationHeaders = fs.existsSync(path.join(module.root, 'cpp'))
275
+ ? collect(module.root, 'cpp', targetName, null)
276
+ : new Map<string, string>()
263
277
  collect(module.root, 'nitrogen/generated/shared/c++', targetName, null)
264
- for (const { implementation } of config.classes)
265
- if (!implementationHeaders.has(`${implementation}.hpp`))
278
+ for (const entry of config.classes)
279
+ if (
280
+ entry.language === 'c++' &&
281
+ !implementationHeaders.has(`${entry.implementation}.hpp`)
282
+ )
266
283
  throw new Error(
267
- `${module.root}: missing C++ implementation header ${implementation}.hpp`,
284
+ `${module.root}: missing C++ implementation header ${entry.implementation}.hpp`,
285
+ )
286
+
287
+ if (config.swiftModule) {
288
+ for (const directory of ['ios/core', 'ios/utils'])
289
+ collect(
290
+ nitro.root,
291
+ directory,
292
+ coreTarget,
293
+ 'NitroModules',
294
+ new Set(),
295
+ (file) => !file.endsWith('.swift'),
268
296
  )
297
+ const nitroSwift = target('NitroModules', [coreTarget, jsiTarget])
298
+ nitroSwift.cxxInterop = true
299
+ const packageSwift = target(config.swiftModule, [targetName, 'NitroModules'])
300
+ packageSwift.cxxInterop = true
301
+ result.targets.push(nitroSwift, packageSwift)
302
+ for (const directory of ['ios/core', 'ios/utils', 'ios/views'])
303
+ collect(
304
+ nitro.root,
305
+ directory,
306
+ 'NitroModules',
307
+ null,
308
+ new Set(['HybridView.swift']),
309
+ () => true,
310
+ /\.swift$/,
311
+ )
312
+ add(
313
+ `${targetPath('NitroModules')}/PeachNitroExports.swift`,
314
+ `@_exported import ${coreTarget}\n`,
315
+ )
316
+ collect(
317
+ module.root,
318
+ 'ios',
319
+ config.swiftModule,
320
+ null,
321
+ new Set(['Tests']),
322
+ () => true,
323
+ /\.swift$/,
324
+ )
325
+ collect(
326
+ module.root,
327
+ 'nitrogen/generated/ios',
328
+ config.swiftModule,
329
+ null,
330
+ new Set(),
331
+ () => true,
332
+ /\.swift$/,
333
+ )
334
+ collect(module.root, 'nitrogen/generated/ios', targetName, null)
335
+ add(
336
+ `${targetPath(config.swiftModule)}/PeachNitroExports.swift`,
337
+ `@_exported import ${targetName}\n`,
338
+ )
339
+ }
269
340
 
270
341
  // explicit module maps avoid importing every RN view or private JSI header.
271
342
  add(
@@ -274,7 +345,7 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
274
345
  )
275
346
  add(
276
347
  `${targetPath(coreTarget)}/include/module.modulemap`,
277
- `module ${coreTarget} { header "NitroModules/InstallNitro.hpp" export * }\n`,
348
+ `module ${coreTarget} { header "NitroModules/InstallNitro.hpp"${config.swiftModule ? ['ArrayBufferHolder.hpp', 'AnyMapUtils.hpp', 'PromiseHolder.hpp', 'Result.hpp', 'RuntimeError.hpp', 'DateToChronoDate.hpp'].map((header) => ` header "NitroModules/${header}"`).join('') : ''} export * }\n`,
278
349
  )
279
350
  add(
280
351
  result.registration.header,
@@ -282,7 +353,7 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
282
353
  )
283
354
  add(
284
355
  `${targetPath(targetName)}/include/module.modulemap`,
285
- `module ${targetName} { header "PeachNitroRegistration.hpp" export * }\n`,
356
+ `module ${targetName} { header "PeachNitroRegistration.hpp"${config.swiftModule ? ` header "${config.swiftModule}-Swift-Cxx-Bridge.hpp"` : ''} export * }\n`,
286
357
  )
287
358
  add(
288
359
  `${targetPath(targetName)}/PeachNitroRegistration.cpp`,
@@ -291,15 +362,28 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
291
362
  '#include <NitroModules/HybridObjectRegistry.hpp>',
292
363
  '#include <memory>',
293
364
  '#include <type_traits>',
294
- ...[...new Set(config.classes.map(({ implementation }) => implementation))].map(
295
- (implementation) => `#include "${implementation}.hpp"`,
296
- ),
365
+ ...(config.swiftModule
366
+ ? [`#include "${config.swiftModule}-Swift-Cxx-Umbrella.hpp"`]
367
+ : []),
368
+ ...[
369
+ ...new Set(
370
+ config.classes
371
+ .filter((entry) => entry.language === 'c++')
372
+ .map(({ implementation }) => implementation),
373
+ ),
374
+ ].map((implementation) => `#include "${implementation}.hpp"`),
297
375
  `extern "C" void ${result.registration.symbol}() {`,
298
- ...config.classes.flatMap(({ name, implementation }) => [
299
- ` static_assert(std::is_default_constructible_v<${config.namespace}::${implementation}>);`,
376
+ ...config.classes.flatMap((entry) => [
377
+ ...(entry.language === 'c++'
378
+ ? [
379
+ ` static_assert(std::is_default_constructible_v<${config.namespace}::${entry.implementation}>);`,
380
+ ]
381
+ : []),
300
382
  ' margelo::nitro::HybridObjectRegistry::registerHybridObjectConstructor(',
301
- ` ${JSON.stringify(name)}, []() -> std::shared_ptr<margelo::nitro::HybridObject> {`,
302
- ` return std::make_shared<${config.namespace}::${implementation}>();`,
383
+ ` ${JSON.stringify(entry.name)}, []() -> std::shared_ptr<margelo::nitro::HybridObject> {`,
384
+ entry.language === 'swift'
385
+ ? ` return ${config.swiftModule}::${config.swiftModule}Autolinking::create${entry.name}();`
386
+ : ` return std::make_shared<${config.namespace}::${entry.implementation}>();`,
303
387
  ' });',
304
388
  ]),
305
389
  '}',