rnxsim 0.1.403 → 0.1.405

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,9 +1,22 @@
1
1
  const RNXSIM_METRO_MODULE_PATHS_PREFIX = 'globalThis.__sootsimModulePaths='
2
+ export const RNX_METRO_MODULE_IDENTITY_VERSION = 2
2
3
 
3
4
  export type RNXMetroModulePathMap = Record<string, string>
4
- export interface RNXMetroModuleIdentity {
5
- source: string
5
+ export type RNXMetroLogicalSpecifierMap = Record<string, string[]>
6
+ export type RNXMetroModuleIdentitySource =
7
+ | 'rnx-plugin'
8
+ | 'contrast-bundler'
9
+ | 'metro-source-map'
10
+
11
+ export interface RNXMetroModuleIdentityMetadata {
12
+ version: typeof RNX_METRO_MODULE_IDENTITY_VERSION
13
+ identitySource: RNXMetroModuleIdentitySource
6
14
  modulePaths: RNXMetroModulePathMap
15
+ logicalSpecifiers: RNXMetroLogicalSpecifierMap
16
+ }
17
+
18
+ export interface RNXMetroModuleIdentity extends RNXMetroModuleIdentityMetadata {
19
+ source: string
7
20
  /**
8
21
  * Metro's trailing `//# sourceMappingURL=` comment when the graph carried
9
22
  * one, already split off `source`. A production graph has none.
@@ -67,10 +80,10 @@ export function detachRNXMetroSourceMapUrl(
67
80
  return { source: trimmedEnd.slice(0, commentStart), sourceMappingUrl: url }
68
81
  }
69
82
 
70
- export function createRNXMetroModulePathsFooter(
71
- modulePaths: RNXMetroModulePathMap,
83
+ export function createRNXMetroModuleIdentityFooter(
84
+ identity: RNXMetroModuleIdentityMetadata,
72
85
  ): string {
73
- const payload = JSON.stringify(modulePaths)
86
+ const payload = JSON.stringify(identity)
74
87
  .replace(/\u2028/g, '\\u2028')
75
88
  .replace(/\u2029/g, '\\u2029')
76
89
  .replace(/<\//g, '<\\/')
@@ -83,11 +96,11 @@ export function createRNXMetroModulePathsFooter(
83
96
  * production graph has no such comment and gets the footer appended, exactly
84
97
  * as before.
85
98
  */
86
- export function appendRNXMetroModulePathsFooter(
99
+ export function appendRNXMetroModuleIdentityFooter(
87
100
  bundleText: string,
88
- modulePaths: RNXMetroModulePathMap,
101
+ identity: RNXMetroModuleIdentityMetadata,
89
102
  ): string {
90
- const footer = createRNXMetroModulePathsFooter(modulePaths)
103
+ const footer = createRNXMetroModuleIdentityFooter(identity)
91
104
  const detached = detachRNXMetroSourceMapUrl(bundleText)
92
105
  return detached.sourceMappingUrl === null
93
106
  ? bundleText + footer
@@ -101,23 +114,77 @@ export function readRNXMetroModuleIdentity(
101
114
  const withoutMap = detached.source
102
115
  const footerPrefix = `\n${RNXSIM_METRO_MODULE_PATHS_PREFIX}`
103
116
  const footerStart = withoutMap.lastIndexOf(footerPrefix)
104
- if (footerStart < 0 || !withoutMap.endsWith(';')) return null
117
+ if (footerStart < 0) return null
118
+ if (!withoutMap.endsWith(';')) {
119
+ throw new Error('Metro module identity footer is truncated')
120
+ }
105
121
 
106
- const parsed: unknown = JSON.parse(
107
- withoutMap.slice(footerStart + footerPrefix.length, -1),
108
- )
122
+ let parsed: unknown
123
+ try {
124
+ parsed = JSON.parse(withoutMap.slice(footerStart + footerPrefix.length, -1))
125
+ } catch {
126
+ throw new Error('Metro module identity footer is not valid JSON')
127
+ }
109
128
  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
110
- return null
129
+ throw new Error('Metro module identity footer is malformed')
130
+ }
131
+
132
+ const version = Reflect.get(parsed, 'version')
133
+ // stored artifacts from before the versioned footer carry a bare module-path
134
+ // map with no version field. they are development bundles whose identity
135
+ // comes from Metro's verbose names, so treat their footer as absent rather
136
+ // than failing every stored replay.
137
+ if (version === undefined) return null
138
+ const identitySource = Reflect.get(parsed, 'identitySource')
139
+ const parsedModulePaths = Reflect.get(parsed, 'modulePaths')
140
+ const parsedLogicalSpecifiers = Reflect.get(parsed, 'logicalSpecifiers')
141
+ if (
142
+ version !== RNX_METRO_MODULE_IDENTITY_VERSION ||
143
+ (identitySource !== 'rnx-plugin' &&
144
+ identitySource !== 'contrast-bundler' &&
145
+ identitySource !== 'metro-source-map') ||
146
+ typeof parsedModulePaths !== 'object' ||
147
+ parsedModulePaths === null ||
148
+ Array.isArray(parsedModulePaths) ||
149
+ typeof parsedLogicalSpecifiers !== 'object' ||
150
+ parsedLogicalSpecifiers === null ||
151
+ Array.isArray(parsedLogicalSpecifiers)
152
+ ) {
153
+ throw new Error('Metro module identity footer is malformed')
111
154
  }
112
155
 
113
156
  const modulePaths: RNXMetroModulePathMap = {}
114
- for (const [moduleId, modulePath] of Object.entries(parsed)) {
115
- if (typeof modulePath !== 'string') return null
157
+ for (const [moduleId, modulePath] of Object.entries(parsedModulePaths)) {
158
+ if (typeof modulePath !== 'string') {
159
+ throw new Error(`Metro module identity has an invalid path for ${moduleId}`)
160
+ }
116
161
  modulePaths[moduleId] = modulePath
117
162
  }
163
+ const logicalSpecifiers: RNXMetroLogicalSpecifierMap = {}
164
+ for (const [moduleId, specifiers] of Object.entries(parsedLogicalSpecifiers)) {
165
+ if (
166
+ !Array.isArray(specifiers) ||
167
+ specifiers.length === 0 ||
168
+ !specifiers.every(
169
+ (specifier) => typeof specifier === 'string' && specifier.length > 0,
170
+ )
171
+ ) {
172
+ throw new Error(`Metro module identity has invalid specifiers for ${moduleId}`)
173
+ }
174
+ const unique = [...new Set(specifiers)].sort((left, right) =>
175
+ left.localeCompare(right),
176
+ )
177
+ if (unique.length !== specifiers.length) {
178
+ throw new Error(`Metro module identity repeats a specifier for ${moduleId}`)
179
+ }
180
+ logicalSpecifiers[moduleId] = unique
181
+ }
118
182
  return {
119
183
  source: withoutMap.slice(0, footerStart),
184
+ version,
185
+ identitySource,
120
186
  modulePaths,
187
+ logicalSpecifiers,
121
188
  sourceMappingUrl: detached.sourceMappingUrl,
122
189
  }
123
190
  }
@@ -243,7 +310,7 @@ function readDefineTail(source: string, start: number, end: number): DefineTail
243
310
  // whole numeric literal rather than a run of decimal digits. the factory
244
311
  // before it always ends in `}` or `)`, which stops this walk immediately.
245
312
  let idStart = idEnd
246
- while (idStart > start && /[0-9a-fA-FxX.+\-]/.test(source[idStart - 1])) idStart--
313
+ while (idStart > start && /[0-9a-fA-FxX.+-]/.test(source[idStart - 1])) idStart--
247
314
  const literal = source.slice(idStart, idEnd)
248
315
  if (!/^-?(?:0[xXbBoO][0-9a-fA-F]+|\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)$/.test(literal)) {
249
316
  return null
@@ -366,3 +433,14 @@ export function validateRNXMetroModulePaths(
366
433
  }
367
434
  }
368
435
  }
436
+
437
+ export function validateRNXMetroModuleIdentity(identity: RNXMetroModuleIdentity): void {
438
+ validateRNXMetroModulePaths(identity.source, identity.modulePaths)
439
+ for (const moduleId of Object.keys(identity.logicalSpecifiers)) {
440
+ if (!Object.prototype.hasOwnProperty.call(identity.modulePaths, moduleId)) {
441
+ throw new Error(
442
+ `Metro module identity has logical specifiers for unknown module ${moduleId}`,
443
+ )
444
+ }
445
+ }
446
+ }