rnxsim 0.1.403 → 0.1.404

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 (53) hide show
  1. package/README.md +17 -4
  2. package/dist-lib/agent-daemon-client.cjs +1 -1
  3. package/dist-lib/agent-events.cjs +1 -1
  4. package/dist-lib/agent-identity.cjs +1 -1
  5. package/dist-lib/agent-sessions.cjs +1 -1
  6. package/dist-lib/attached-projects.cjs +1 -1
  7. package/dist-lib/auth/shared-session.cjs +1 -1
  8. package/dist-lib/backend-origin.cjs +1 -1
  9. package/dist-lib/beta.cjs +1 -1
  10. package/dist-lib/beta.mjs +1 -1
  11. package/dist-lib/bridge-constants.cjs +1 -1
  12. package/dist-lib/bridge-contract-input.cjs +1 -1
  13. package/dist-lib/bridge-contract-input.mjs +1 -1
  14. package/dist-lib/bridge-contract.cjs +1 -1
  15. package/dist-lib/bridge-contract.mjs +1 -1
  16. package/dist-lib/capture-contract.cjs +1 -1
  17. package/dist-lib/capture-contract.mjs +1 -1
  18. package/dist-lib/cli-constants.cjs +1 -1
  19. package/dist-lib/cloud-contract.cjs +1 -1
  20. package/dist-lib/cloud-contract.mjs +1 -1
  21. package/dist-lib/config.cjs +1 -1
  22. package/dist-lib/detox/index.cjs +1 -1
  23. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  24. package/dist-lib/home-paths.cjs +1 -1
  25. package/dist-lib/host/bridge-host.cjs +1 -1
  26. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  27. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  28. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  29. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  30. package/dist-lib/host/websocket-proxy.cjs +1 -1
  31. package/dist-lib/index.cjs +30 -6
  32. package/dist-lib/jump-to-source-babel.cjs +1 -1
  33. package/dist-lib/menu.cjs +1 -1
  34. package/dist-lib/menu.mjs +1 -1
  35. package/dist-lib/metro-fingerprint-registry.cjs +186 -0
  36. package/dist-lib/metro-fingerprint-registry.mjs +156 -0
  37. package/dist-lib/metro-production-bundle.cjs +66 -17
  38. package/dist-lib/metro-production-bundle.mjs +62 -15
  39. package/dist-lib/metro.cjs +30 -6
  40. package/dist-lib/profiles.cjs +1 -1
  41. package/dist-lib/public-brand.cjs +1 -1
  42. package/dist-lib/react-native-host-modules.cjs +1 -1
  43. package/dist-lib/react-native-host-modules.mjs +1 -1
  44. package/dist-lib/render-mode.cjs +1 -1
  45. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  46. package/dist-lib/sdk.cjs +1 -1
  47. package/dist-lib/sdk.mjs +1 -1
  48. package/dist-lib/skills.cjs +56 -24
  49. package/dist-lib/vite.cjs +1 -1
  50. package/package.json +8 -1
  51. package/src/metro-fingerprint-registry.ts +302 -0
  52. package/src/metro-plugin.ts +47 -4
  53. package/src/metro-production-bundle.ts +94 -16
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.404 | (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;
@@ -1347,23 +1347,35 @@ var init_supported_native_packages = __esm({
1347
1347
  "expo-apple-authentication",
1348
1348
  // preset
1349
1349
  "expo-application",
1350
- // coverage
1350
+ // visual-proof+coverage
1351
1351
  "expo-asset",
1352
1352
  // preset+coverage
1353
1353
  "expo-audio",
1354
1354
  // coverage
1355
1355
  "expo-av",
1356
1356
  // coverage
1357
+ "expo-background-fetch",
1358
+ // visual-proof
1359
+ "expo-background-task",
1360
+ // visual-proof
1357
1361
  "expo-battery",
1358
- // coverage
1362
+ // visual-proof+coverage
1359
1363
  "expo-blur",
1360
1364
  // preset+coverage
1365
+ "expo-brightness",
1366
+ // visual-proof
1367
+ "expo-calendar",
1368
+ // visual-proof
1361
1369
  "expo-camera",
1362
1370
  // coverage
1371
+ "expo-cellular",
1372
+ // visual-proof
1363
1373
  "expo-clipboard",
1364
1374
  // preset+coverage
1365
1375
  "expo-constants",
1366
- // preset+coverage
1376
+ // preset+visual-proof+coverage
1377
+ "expo-contacts",
1378
+ // visual-proof
1367
1379
  "expo-crypto",
1368
1380
  // preset+coverage
1369
1381
  "expo-dev-client",
@@ -1385,23 +1397,27 @@ var init_supported_native_packages = __esm({
1385
1397
  "expo-ignore-battery-optimizations",
1386
1398
  // coverage
1387
1399
  "expo-image",
1388
- // preset+coverage
1400
+ // preset+visual-proof+coverage
1389
1401
  "expo-image-manipulator",
1390
1402
  // coverage
1391
1403
  "expo-image-picker",
1392
1404
  // preset+coverage
1393
1405
  "expo-insights",
1394
1406
  // coverage
1407
+ "expo-intent-launcher",
1408
+ // visual-proof
1395
1409
  "expo-keep-awake",
1396
1410
  // coverage
1397
1411
  "expo-linear-gradient",
1398
- // preset+coverage
1412
+ // preset+visual-proof+coverage
1399
1413
  "expo-linking",
1400
1414
  // preset+coverage
1401
1415
  "expo-local-authentication",
1402
1416
  // coverage
1403
1417
  "expo-localization",
1404
1418
  // coverage
1419
+ "expo-mail-composer",
1420
+ // visual-proof
1405
1421
  "expo-media-library",
1406
1422
  // coverage
1407
1423
  "expo-mesh-gradient",
@@ -1409,35 +1425,51 @@ var init_supported_native_packages = __esm({
1409
1425
  "expo-modules-core",
1410
1426
  // preset
1411
1427
  "expo-network",
1412
- // preset+coverage
1428
+ // preset+visual-proof+coverage
1413
1429
  "expo-notifications",
1414
1430
  // preset+coverage
1431
+ "expo-print",
1432
+ // visual-proof
1433
+ "expo-screen-capture",
1434
+ // visual-proof
1415
1435
  "expo-screen-corner-radius",
1416
1436
  // coverage
1417
1437
  "expo-screen-orientation",
1418
- // coverage
1438
+ // visual-proof+coverage
1419
1439
  "expo-secure-store",
1420
- // coverage
1440
+ // visual-proof+coverage
1441
+ "expo-sensors",
1442
+ // visual-proof
1443
+ "expo-sharing",
1444
+ // visual-proof
1445
+ "expo-sms",
1446
+ // visual-proof
1447
+ "expo-speech",
1448
+ // visual-proof
1421
1449
  "expo-speech-recognition",
1422
1450
  // preset
1423
1451
  "expo-splash-screen",
1424
- // coverage
1452
+ // visual-proof+coverage
1425
1453
  "expo-sqlite",
1426
1454
  // preset+coverage
1427
1455
  "expo-store-review",
1428
- // coverage
1456
+ // visual-proof+coverage
1429
1457
  "expo-symbols",
1430
1458
  // coverage
1431
1459
  "expo-system-ui",
1432
1460
  // coverage
1433
1461
  "expo-task-manager",
1434
- // coverage
1462
+ // visual-proof+coverage
1435
1463
  "expo-testflight",
1436
1464
  // coverage
1437
1465
  "expo-tracking-transparency",
1438
- // coverage
1466
+ // visual-proof+coverage
1439
1467
  "expo-updates",
1440
1468
  // preset+coverage
1469
+ "expo-video",
1470
+ // visual-proof
1471
+ "expo-video-thumbnails",
1472
+ // visual-proof
1441
1473
  "expo-web-browser",
1442
1474
  // preset
1443
1475
  "expo-winter-fetch",
@@ -1461,7 +1493,7 @@ var init_supported_native_packages = __esm({
1461
1493
  "posthog-react-native-session-replay",
1462
1494
  // preset+coverage
1463
1495
  "react-native",
1464
- // preset+coverage
1496
+ // preset+visual-proof+coverage
1465
1497
  "react-native-actions-shortcuts",
1466
1498
  // coverage
1467
1499
  "react-native-adjust",
@@ -1485,7 +1517,7 @@ var init_supported_native_packages = __esm({
1485
1517
  "react-native-bootsplash",
1486
1518
  // preset+coverage
1487
1519
  "react-native-bottom-tabs",
1488
- // preset
1520
+ // preset+visual-proof
1489
1521
  "react-native-branch",
1490
1522
  // coverage
1491
1523
  "react-native-config",
@@ -1507,7 +1539,7 @@ var init_supported_native_packages = __esm({
1507
1539
  "react-native-fs",
1508
1540
  // coverage
1509
1541
  "react-native-gesture-handler",
1510
- // preset+coverage
1542
+ // preset+visual-proof+coverage
1511
1543
  "react-native-get-device-locale",
1512
1544
  // coverage
1513
1545
  "react-native-get-location",
@@ -1535,7 +1567,7 @@ var init_supported_native_packages = __esm({
1535
1567
  "react-native-key-command",
1536
1568
  // coverage
1537
1569
  "react-native-keyboard-controller",
1538
- // preset
1570
+ // preset+visual-proof
1539
1571
  "react-native-keychain",
1540
1572
  // coverage
1541
1573
  "react-native-keys",
@@ -1565,7 +1597,7 @@ var init_supported_native_packages = __esm({
1565
1597
  "react-native-notification-badge",
1566
1598
  // coverage
1567
1599
  "react-native-pager-view",
1568
- // preset
1600
+ // preset+visual-proof
1569
1601
  "react-native-pdf",
1570
1602
  // coverage
1571
1603
  "react-native-permissions",
@@ -1585,7 +1617,7 @@ var init_supported_native_packages = __esm({
1585
1617
  "react-native-randombytes",
1586
1618
  // coverage
1587
1619
  "react-native-reanimated",
1588
- // preset+coverage
1620
+ // preset+visual-proof+coverage
1589
1621
  "react-native-restart",
1590
1622
  // coverage
1591
1623
  "react-native-restart-newarch",
@@ -1593,11 +1625,11 @@ var init_supported_native_packages = __esm({
1593
1625
  "react-native-rsa-signer",
1594
1626
  // coverage
1595
1627
  "react-native-safe-area-context",
1596
- // preset+coverage
1628
+ // preset+visual-proof+coverage
1597
1629
  "react-native-screen-corner-radius",
1598
1630
  // coverage
1599
1631
  "react-native-screens",
1600
- // preset+coverage
1632
+ // preset+visual-proof+coverage
1601
1633
  "react-native-securerandom",
1602
1634
  // coverage
1603
1635
  "react-native-settings",
@@ -1613,7 +1645,7 @@ var init_supported_native_packages = __esm({
1613
1645
  "react-native-splash-screen",
1614
1646
  // coverage
1615
1647
  "react-native-svg",
1616
- // preset
1648
+ // preset+visual-proof
1617
1649
  "react-native-system-bars",
1618
1650
  // coverage
1619
1651
  "react-native-teleport",
@@ -1627,7 +1659,7 @@ var init_supported_native_packages = __esm({
1627
1659
  "react-native-turbo-haptics",
1628
1660
  // coverage
1629
1661
  "react-native-uitextview",
1630
- // coverage
1662
+ // visual-proof+coverage
1631
1663
  "react-native-ulid-jsi",
1632
1664
  // coverage
1633
1665
  "react-native-ux-cam",
@@ -1647,7 +1679,7 @@ var init_supported_native_packages = __esm({
1647
1679
  "react-native-webp-format",
1648
1680
  // coverage
1649
1681
  "react-native-webview",
1650
- // preset
1682
+ // preset+visual-proof
1651
1683
  "react-native-worklets",
1652
1684
  // preset+coverage
1653
1685
  "react-native-xxhash",
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.404 | (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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnxsim",
3
- "version": "0.1.403",
3
+ "version": "0.1.404",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for rnx.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -45,6 +45,13 @@
45
45
  "import": "./dist-lib/metro-production-bundle.mjs",
46
46
  "default": "./dist-lib/metro-production-bundle.cjs"
47
47
  },
48
+ "./metro-fingerprint-registry": {
49
+ "types": "./src/metro-fingerprint-registry.ts",
50
+ "source": "./src/metro-fingerprint-registry.ts",
51
+ "bun": "./src/metro-fingerprint-registry.ts",
52
+ "import": "./dist-lib/metro-fingerprint-registry.mjs",
53
+ "default": "./dist-lib/metro-fingerprint-registry.cjs"
54
+ },
48
55
  "./react-native-host-modules": {
49
56
  "types": "./src/react-native-host-modules.ts",
50
57
  "source": "./src/react-native-host-modules.ts",
@@ -0,0 +1,302 @@
1
+ export const RNX_METRO_FINGERPRINT_SCHEMA_VERSION = 2
2
+ export const RNX_METRO_FINGERPRINT_POINTER_PATH = '/compile/metro-fingerprints.json'
3
+ export const RNX_METRO_FINGERPRINT_MAX_POINTER_BYTES = 16 * 1024
4
+ export const RNX_METRO_FINGERPRINT_MAX_PAYLOAD_BYTES = 8 * 1024 * 1024
5
+
6
+ export type RNXMetroFingerprintPlatform = 'ios' | 'android'
7
+
8
+ export interface RNXMetroFingerprintAtom {
9
+ key: string
10
+ exports: string[]
11
+ literals: string[]
12
+ dependencyArity: number
13
+ factoryDigest: string
14
+ }
15
+
16
+ export interface RNXMetroFingerprintNode {
17
+ atom: number
18
+ internalDependencies: Array<{ position: number; node: number }>
19
+ landmarkPath?: string
20
+ }
21
+
22
+ export interface RNXMetroFingerprintRoot {
23
+ node: number
24
+ }
25
+
26
+ export interface RNXMetroFingerprintTemplate {
27
+ packageName: string
28
+ packageVersion: string
29
+ sourceIntegrity: string
30
+ profile: string
31
+ platform: RNXMetroFingerprintPlatform
32
+ nodes: RNXMetroFingerprintNode[]
33
+ roots: RNXMetroFingerprintRoot[]
34
+ }
35
+
36
+ export interface RNXMetroFingerprintCandidate {
37
+ template: number
38
+ root: number
39
+ }
40
+
41
+ export interface RNXMetroFingerprintRegistry {
42
+ schemaVersion: typeof RNX_METRO_FINGERPRINT_SCHEMA_VERSION
43
+ revision: string
44
+ atoms: RNXMetroFingerprintAtom[]
45
+ templates: RNXMetroFingerprintTemplate[]
46
+ candidatesByFactoryDigest: Record<string, RNXMetroFingerprintCandidate[]>
47
+ candidatesByDiscriminator: Record<string, RNXMetroFingerprintCandidate[]>
48
+ }
49
+
50
+ export interface RNXMetroFingerprintPointer {
51
+ schemaVersion: typeof RNX_METRO_FINGERPRINT_SCHEMA_VERSION
52
+ revision: string
53
+ integrity: string
54
+ bytes: number
55
+ payloadUrl: string
56
+ }
57
+
58
+ function isRecord(value: unknown): value is Record<string, unknown> {
59
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
60
+ }
61
+
62
+ function isSha256(value: unknown): value is string {
63
+ return typeof value === 'string' && /^[a-f0-9]{64}$/.test(value)
64
+ }
65
+
66
+ function readStringArray(value: unknown): string[] | null {
67
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === 'string')) {
68
+ return null
69
+ }
70
+ const entries = [...value]
71
+ if (
72
+ new Set(entries).size !== entries.length ||
73
+ entries.some((entry, index) => index > 0 && entries[index - 1] >= entry)
74
+ ) {
75
+ return null
76
+ }
77
+ return entries
78
+ }
79
+
80
+ export function parseRNXMetroFingerprintPointer(
81
+ value: unknown,
82
+ ): RNXMetroFingerprintPointer | null {
83
+ if (!isRecord(value)) return null
84
+ const { schemaVersion, revision, integrity, bytes, payloadUrl } = value
85
+ if (
86
+ schemaVersion !== RNX_METRO_FINGERPRINT_SCHEMA_VERSION ||
87
+ !isSha256(revision) ||
88
+ !isSha256(integrity) ||
89
+ typeof bytes !== 'number' ||
90
+ !Number.isSafeInteger(bytes) ||
91
+ bytes < 1 ||
92
+ typeof payloadUrl !== 'string' ||
93
+ payloadUrl.length === 0
94
+ ) {
95
+ return null
96
+ }
97
+ return { schemaVersion, revision, integrity, bytes, payloadUrl }
98
+ }
99
+
100
+ export function parseRNXMetroFingerprintRegistry(
101
+ value: unknown,
102
+ ): RNXMetroFingerprintRegistry | null {
103
+ if (!isRecord(value)) return null
104
+ const { schemaVersion, revision } = value
105
+ if (
106
+ schemaVersion !== RNX_METRO_FINGERPRINT_SCHEMA_VERSION ||
107
+ !isSha256(revision) ||
108
+ !Array.isArray(value.atoms) ||
109
+ !Array.isArray(value.templates) ||
110
+ !isRecord(value.candidatesByFactoryDigest) ||
111
+ !isRecord(value.candidatesByDiscriminator)
112
+ ) {
113
+ return null
114
+ }
115
+
116
+ const atoms: RNXMetroFingerprintAtom[] = []
117
+ for (const atomValue of value.atoms) {
118
+ if (!isRecord(atomValue)) return null
119
+ const exports = readStringArray(atomValue.exports)
120
+ const literals = readStringArray(atomValue.literals)
121
+ if (
122
+ typeof atomValue.key !== 'string' ||
123
+ atomValue.key.length === 0 ||
124
+ !exports ||
125
+ !literals ||
126
+ typeof atomValue.dependencyArity !== 'number' ||
127
+ !Number.isSafeInteger(atomValue.dependencyArity) ||
128
+ atomValue.dependencyArity < 0 ||
129
+ !isSha256(atomValue.factoryDigest)
130
+ ) {
131
+ return null
132
+ }
133
+ atoms.push({
134
+ key: atomValue.key,
135
+ exports,
136
+ literals,
137
+ dependencyArity: atomValue.dependencyArity,
138
+ factoryDigest: atomValue.factoryDigest,
139
+ })
140
+ }
141
+
142
+ const templates: RNXMetroFingerprintTemplate[] = []
143
+ for (const templateValue of value.templates) {
144
+ if (
145
+ !isRecord(templateValue) ||
146
+ typeof templateValue.packageName !== 'string' ||
147
+ templateValue.packageName.length === 0 ||
148
+ typeof templateValue.packageVersion !== 'string' ||
149
+ templateValue.packageVersion.length === 0 ||
150
+ !isSha256(templateValue.sourceIntegrity) ||
151
+ typeof templateValue.profile !== 'string' ||
152
+ templateValue.profile.length === 0 ||
153
+ (templateValue.platform !== 'ios' && templateValue.platform !== 'android') ||
154
+ !Array.isArray(templateValue.nodes) ||
155
+ templateValue.nodes.length === 0 ||
156
+ !Array.isArray(templateValue.roots) ||
157
+ templateValue.roots.length === 0
158
+ ) {
159
+ return null
160
+ }
161
+ const nodes: RNXMetroFingerprintNode[] = []
162
+ for (const nodeValue of templateValue.nodes) {
163
+ if (
164
+ !isRecord(nodeValue) ||
165
+ typeof nodeValue.atom !== 'number' ||
166
+ !Number.isSafeInteger(nodeValue.atom) ||
167
+ nodeValue.atom < 0 ||
168
+ nodeValue.atom >= atoms.length ||
169
+ !Array.isArray(nodeValue.internalDependencies) ||
170
+ (nodeValue.landmarkPath !== undefined &&
171
+ (typeof nodeValue.landmarkPath !== 'string' ||
172
+ nodeValue.landmarkPath.length === 0))
173
+ ) {
174
+ return null
175
+ }
176
+ const internalDependencies: Array<{ position: number; node: number }> = []
177
+ const positions = new Set<number>()
178
+ for (const edgeValue of nodeValue.internalDependencies) {
179
+ if (
180
+ !isRecord(edgeValue) ||
181
+ typeof edgeValue.position !== 'number' ||
182
+ !Number.isSafeInteger(edgeValue.position) ||
183
+ edgeValue.position < 0 ||
184
+ positions.has(edgeValue.position) ||
185
+ typeof edgeValue.node !== 'number' ||
186
+ !Number.isSafeInteger(edgeValue.node) ||
187
+ edgeValue.node < 0 ||
188
+ edgeValue.node >= templateValue.nodes.length
189
+ ) {
190
+ return null
191
+ }
192
+ positions.add(edgeValue.position)
193
+ internalDependencies.push({
194
+ position: edgeValue.position,
195
+ node: edgeValue.node,
196
+ })
197
+ }
198
+ internalDependencies.sort((left, right) => left.position - right.position)
199
+ nodes.push({
200
+ atom: nodeValue.atom,
201
+ internalDependencies,
202
+ ...(typeof nodeValue.landmarkPath === 'string'
203
+ ? { landmarkPath: nodeValue.landmarkPath }
204
+ : {}),
205
+ })
206
+ }
207
+ const roots: RNXMetroFingerprintRoot[] = []
208
+ for (const rootValue of templateValue.roots) {
209
+ if (
210
+ !isRecord(rootValue) ||
211
+ typeof rootValue.node !== 'number' ||
212
+ !Number.isSafeInteger(rootValue.node) ||
213
+ rootValue.node < 0 ||
214
+ rootValue.node >= nodes.length ||
215
+ !nodes[rootValue.node].landmarkPath
216
+ ) {
217
+ return null
218
+ }
219
+ roots.push({ node: rootValue.node })
220
+ }
221
+ templates.push({
222
+ packageName: templateValue.packageName,
223
+ packageVersion: templateValue.packageVersion,
224
+ sourceIntegrity: templateValue.sourceIntegrity,
225
+ profile: templateValue.profile,
226
+ platform: templateValue.platform,
227
+ nodes,
228
+ roots,
229
+ })
230
+ }
231
+
232
+ const candidatesByFactoryDigest: Record<string, RNXMetroFingerprintCandidate[]> = {}
233
+ for (const [factoryDigest, candidateValues] of Object.entries(
234
+ value.candidatesByFactoryDigest,
235
+ )) {
236
+ if (!isSha256(factoryDigest) || !Array.isArray(candidateValues)) return null
237
+ const candidates: RNXMetroFingerprintCandidate[] = []
238
+ for (const candidateValue of candidateValues) {
239
+ if (
240
+ !isRecord(candidateValue) ||
241
+ typeof candidateValue.template !== 'number' ||
242
+ !Number.isSafeInteger(candidateValue.template) ||
243
+ candidateValue.template < 0 ||
244
+ candidateValue.template >= templates.length ||
245
+ typeof candidateValue.root !== 'number' ||
246
+ !Number.isSafeInteger(candidateValue.root) ||
247
+ candidateValue.root < 0 ||
248
+ candidateValue.root >= templates[candidateValue.template].roots.length
249
+ ) {
250
+ return null
251
+ }
252
+ const template = templates[candidateValue.template]
253
+ const root = template.roots[candidateValue.root]
254
+ if (atoms[template.nodes[root.node].atom].factoryDigest !== factoryDigest) {
255
+ return null
256
+ }
257
+ candidates.push({
258
+ template: candidateValue.template,
259
+ root: candidateValue.root,
260
+ })
261
+ }
262
+ candidatesByFactoryDigest[factoryDigest] = candidates
263
+ }
264
+
265
+ const candidatesByDiscriminator: Record<string, RNXMetroFingerprintCandidate[]> = {}
266
+ for (const [discriminator, candidateValues] of Object.entries(
267
+ value.candidatesByDiscriminator,
268
+ )) {
269
+ if (!/^(?:e|l|ea|la|el|ela):[a-f0-9]{16}$/.test(discriminator)) return null
270
+ if (!Array.isArray(candidateValues) || candidateValues.length === 0) return null
271
+ const candidates: RNXMetroFingerprintCandidate[] = []
272
+ for (const candidateValue of candidateValues) {
273
+ if (
274
+ !isRecord(candidateValue) ||
275
+ typeof candidateValue.template !== 'number' ||
276
+ !Number.isSafeInteger(candidateValue.template) ||
277
+ candidateValue.template < 0 ||
278
+ candidateValue.template >= templates.length ||
279
+ typeof candidateValue.root !== 'number' ||
280
+ !Number.isSafeInteger(candidateValue.root) ||
281
+ candidateValue.root < 0 ||
282
+ candidateValue.root >= templates[candidateValue.template].roots.length
283
+ ) {
284
+ return null
285
+ }
286
+ candidates.push({
287
+ template: candidateValue.template,
288
+ root: candidateValue.root,
289
+ })
290
+ }
291
+ candidatesByDiscriminator[discriminator] = candidates
292
+ }
293
+
294
+ return {
295
+ schemaVersion,
296
+ revision,
297
+ atoms,
298
+ templates,
299
+ candidatesByFactoryDigest,
300
+ candidatesByDiscriminator,
301
+ }
302
+ }
@@ -24,9 +24,11 @@ import {
24
24
  isReplacementModuleRequestUrl,
25
25
  } from './host/replacement-module-handler'
26
26
  import {
27
- appendRNXMetroModulePathsFooter,
27
+ appendRNXMetroModuleIdentityFooter,
28
+ RNX_METRO_MODULE_IDENTITY_VERSION,
28
29
  toRNXDevelopmentBundleUrl,
29
30
  toRNXProductionBundleUrl,
31
+ type RNXMetroModuleIdentityMetadata,
30
32
  } from './metro-production-bundle'
31
33
  import {
32
34
  isRootRuntimeAssetPath,
@@ -39,8 +41,19 @@ import {
39
41
  const prefix = '/__soot'
40
42
  export { toRNXDevelopmentBundleUrl, toRNXProductionBundleUrl }
41
43
 
44
+ type MetroSerializerModule = {
45
+ path: string
46
+ dependencies?: ReadonlyMap<
47
+ string,
48
+ {
49
+ absolutePath?: string
50
+ data?: { name?: string }
51
+ }
52
+ >
53
+ }
54
+
42
55
  type MetroSerializerGraph = {
43
- dependencies?: ReadonlyMap<unknown, { path: string }>
56
+ dependencies?: ReadonlyMap<unknown, MetroSerializerModule>
44
57
  }
45
58
 
46
59
  type MetroSerializerOptions = {
@@ -160,9 +173,39 @@ function appendRNXModulePaths(
160
173
  throw new Error('rnxsim: bundle module IDs were not unique')
161
174
  }
162
175
 
176
+ const specifiersByModuleId = new Map<string, Set<string>>()
177
+ for (const module of dependencies.values()) {
178
+ for (const [dependencyKey, dependency] of module.dependencies ?? []) {
179
+ const specifier = dependency.data?.name ?? dependencyKey
180
+ if (
181
+ specifier.length === 0 ||
182
+ specifier.startsWith('.') ||
183
+ specifier.startsWith('/') ||
184
+ typeof dependency.absolutePath !== 'string'
185
+ ) {
186
+ continue
187
+ }
188
+ const moduleId = String(createModuleId(dependency.absolutePath))
189
+ if (!Object.prototype.hasOwnProperty.call(modulePaths, moduleId)) continue
190
+ const found = specifiersByModuleId.get(moduleId)
191
+ if (found) found.add(specifier)
192
+ else specifiersByModuleId.set(moduleId, new Set([specifier]))
193
+ }
194
+ }
195
+ const logicalSpecifiers = Object.fromEntries(
196
+ [...specifiersByModuleId]
197
+ .sort(([left], [right]) => Number(left) - Number(right))
198
+ .map(([moduleId, specifiers]) => [moduleId, [...specifiers].sort()]),
199
+ )
200
+ const identity: RNXMetroModuleIdentityMetadata = {
201
+ version: RNX_METRO_MODULE_IDENTITY_VERSION,
202
+ identitySource: 'rnx-plugin',
203
+ modulePaths,
204
+ logicalSpecifiers,
205
+ }
163
206
  return typeof result === 'string'
164
- ? appendRNXMetroModulePathsFooter(result, modulePaths)
165
- : { ...result, code: appendRNXMetroModulePathsFooter(result.code, modulePaths) }
207
+ ? appendRNXMetroModuleIdentityFooter(result, identity)
208
+ : { ...result, code: appendRNXMetroModuleIdentityFooter(result.code, identity) }
166
209
  }
167
210
 
168
211
  export function withRNX<T extends Record<string, unknown>>(