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
@@ -0,0 +1,156 @@
1
+ /*! rnx v0.1.405 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
+
3
+ // src/metro-fingerprint-registry.ts
4
+ var RNX_METRO_FINGERPRINT_SCHEMA_VERSION = 2;
5
+ var RNX_METRO_FINGERPRINT_POINTER_PATH = "/compile/metro-fingerprints.json";
6
+ var RNX_METRO_FINGERPRINT_MAX_POINTER_BYTES = 16 * 1024;
7
+ var RNX_METRO_FINGERPRINT_MAX_PAYLOAD_BYTES = 8 * 1024 * 1024;
8
+ function isRecord(value) {
9
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10
+ }
11
+ function isSha256(value) {
12
+ return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
13
+ }
14
+ function readStringArray(value) {
15
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
16
+ return null;
17
+ }
18
+ const entries = [...value];
19
+ if (new Set(entries).size !== entries.length || entries.some((entry, index) => index > 0 && entries[index - 1] >= entry)) {
20
+ return null;
21
+ }
22
+ return entries;
23
+ }
24
+ function parseRNXMetroFingerprintPointer(value) {
25
+ if (!isRecord(value)) return null;
26
+ const { schemaVersion, revision, integrity, bytes, payloadUrl } = value;
27
+ if (schemaVersion !== RNX_METRO_FINGERPRINT_SCHEMA_VERSION || !isSha256(revision) || !isSha256(integrity) || typeof bytes !== "number" || !Number.isSafeInteger(bytes) || bytes < 1 || typeof payloadUrl !== "string" || payloadUrl.length === 0) {
28
+ return null;
29
+ }
30
+ return { schemaVersion, revision, integrity, bytes, payloadUrl };
31
+ }
32
+ function parseRNXMetroFingerprintRegistry(value) {
33
+ if (!isRecord(value)) return null;
34
+ const { schemaVersion, revision } = value;
35
+ if (schemaVersion !== RNX_METRO_FINGERPRINT_SCHEMA_VERSION || !isSha256(revision) || !Array.isArray(value.atoms) || !Array.isArray(value.templates) || !isRecord(value.candidatesByFactoryDigest) || !isRecord(value.candidatesByDiscriminator)) {
36
+ return null;
37
+ }
38
+ const atoms = [];
39
+ for (const atomValue of value.atoms) {
40
+ if (!isRecord(atomValue)) return null;
41
+ const exports = readStringArray(atomValue.exports);
42
+ const literals = readStringArray(atomValue.literals);
43
+ if (typeof atomValue.key !== "string" || atomValue.key.length === 0 || !exports || !literals || typeof atomValue.dependencyArity !== "number" || !Number.isSafeInteger(atomValue.dependencyArity) || atomValue.dependencyArity < 0 || !isSha256(atomValue.factoryDigest)) {
44
+ return null;
45
+ }
46
+ atoms.push({
47
+ key: atomValue.key,
48
+ exports,
49
+ literals,
50
+ dependencyArity: atomValue.dependencyArity,
51
+ factoryDigest: atomValue.factoryDigest
52
+ });
53
+ }
54
+ const templates = [];
55
+ for (const templateValue of value.templates) {
56
+ if (!isRecord(templateValue) || typeof templateValue.packageName !== "string" || templateValue.packageName.length === 0 || typeof templateValue.packageVersion !== "string" || templateValue.packageVersion.length === 0 || !isSha256(templateValue.sourceIntegrity) || typeof templateValue.profile !== "string" || templateValue.profile.length === 0 || templateValue.platform !== "ios" && templateValue.platform !== "android" || !Array.isArray(templateValue.nodes) || templateValue.nodes.length === 0 || !Array.isArray(templateValue.roots) || templateValue.roots.length === 0) {
57
+ return null;
58
+ }
59
+ const nodes = [];
60
+ for (const nodeValue of templateValue.nodes) {
61
+ if (!isRecord(nodeValue) || typeof nodeValue.atom !== "number" || !Number.isSafeInteger(nodeValue.atom) || nodeValue.atom < 0 || nodeValue.atom >= atoms.length || !Array.isArray(nodeValue.internalDependencies) || nodeValue.landmarkPath !== void 0 && (typeof nodeValue.landmarkPath !== "string" || nodeValue.landmarkPath.length === 0)) {
62
+ return null;
63
+ }
64
+ const internalDependencies = [];
65
+ const positions = /* @__PURE__ */ new Set();
66
+ for (const edgeValue of nodeValue.internalDependencies) {
67
+ if (!isRecord(edgeValue) || typeof edgeValue.position !== "number" || !Number.isSafeInteger(edgeValue.position) || edgeValue.position < 0 || positions.has(edgeValue.position) || typeof edgeValue.node !== "number" || !Number.isSafeInteger(edgeValue.node) || edgeValue.node < 0 || edgeValue.node >= templateValue.nodes.length) {
68
+ return null;
69
+ }
70
+ positions.add(edgeValue.position);
71
+ internalDependencies.push({
72
+ position: edgeValue.position,
73
+ node: edgeValue.node
74
+ });
75
+ }
76
+ internalDependencies.sort((left, right) => left.position - right.position);
77
+ nodes.push({
78
+ atom: nodeValue.atom,
79
+ internalDependencies,
80
+ ...typeof nodeValue.landmarkPath === "string" ? { landmarkPath: nodeValue.landmarkPath } : {}
81
+ });
82
+ }
83
+ const roots = [];
84
+ for (const rootValue of templateValue.roots) {
85
+ if (!isRecord(rootValue) || typeof rootValue.node !== "number" || !Number.isSafeInteger(rootValue.node) || rootValue.node < 0 || rootValue.node >= nodes.length || !nodes[rootValue.node].landmarkPath) {
86
+ return null;
87
+ }
88
+ roots.push({ node: rootValue.node });
89
+ }
90
+ templates.push({
91
+ packageName: templateValue.packageName,
92
+ packageVersion: templateValue.packageVersion,
93
+ sourceIntegrity: templateValue.sourceIntegrity,
94
+ profile: templateValue.profile,
95
+ platform: templateValue.platform,
96
+ nodes,
97
+ roots
98
+ });
99
+ }
100
+ const candidatesByFactoryDigest = {};
101
+ for (const [factoryDigest, candidateValues] of Object.entries(
102
+ value.candidatesByFactoryDigest
103
+ )) {
104
+ if (!isSha256(factoryDigest) || !Array.isArray(candidateValues)) return null;
105
+ const candidates = [];
106
+ for (const candidateValue of candidateValues) {
107
+ if (!isRecord(candidateValue) || typeof candidateValue.template !== "number" || !Number.isSafeInteger(candidateValue.template) || candidateValue.template < 0 || candidateValue.template >= templates.length || typeof candidateValue.root !== "number" || !Number.isSafeInteger(candidateValue.root) || candidateValue.root < 0 || candidateValue.root >= templates[candidateValue.template].roots.length) {
108
+ return null;
109
+ }
110
+ const template = templates[candidateValue.template];
111
+ const root = template.roots[candidateValue.root];
112
+ if (atoms[template.nodes[root.node].atom].factoryDigest !== factoryDigest) {
113
+ return null;
114
+ }
115
+ candidates.push({
116
+ template: candidateValue.template,
117
+ root: candidateValue.root
118
+ });
119
+ }
120
+ candidatesByFactoryDigest[factoryDigest] = candidates;
121
+ }
122
+ const candidatesByDiscriminator = {};
123
+ for (const [discriminator, candidateValues] of Object.entries(
124
+ value.candidatesByDiscriminator
125
+ )) {
126
+ if (!/^(?:e|l|ea|la|el|ela):[a-f0-9]{16}$/.test(discriminator)) return null;
127
+ if (!Array.isArray(candidateValues) || candidateValues.length === 0) return null;
128
+ const candidates = [];
129
+ for (const candidateValue of candidateValues) {
130
+ if (!isRecord(candidateValue) || typeof candidateValue.template !== "number" || !Number.isSafeInteger(candidateValue.template) || candidateValue.template < 0 || candidateValue.template >= templates.length || typeof candidateValue.root !== "number" || !Number.isSafeInteger(candidateValue.root) || candidateValue.root < 0 || candidateValue.root >= templates[candidateValue.template].roots.length) {
131
+ return null;
132
+ }
133
+ candidates.push({
134
+ template: candidateValue.template,
135
+ root: candidateValue.root
136
+ });
137
+ }
138
+ candidatesByDiscriminator[discriminator] = candidates;
139
+ }
140
+ return {
141
+ schemaVersion,
142
+ revision,
143
+ atoms,
144
+ templates,
145
+ candidatesByFactoryDigest,
146
+ candidatesByDiscriminator
147
+ };
148
+ }
149
+ export {
150
+ RNX_METRO_FINGERPRINT_MAX_PAYLOAD_BYTES,
151
+ RNX_METRO_FINGERPRINT_MAX_POINTER_BYTES,
152
+ RNX_METRO_FINGERPRINT_POINTER_PATH,
153
+ RNX_METRO_FINGERPRINT_SCHEMA_VERSION,
154
+ parseRNXMetroFingerprintPointer,
155
+ parseRNXMetroFingerprintRegistry
156
+ };
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (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;
@@ -22,17 +22,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  // src/metro-production-bundle.ts
23
23
  var metro_production_bundle_exports = {};
24
24
  __export(metro_production_bundle_exports, {
25
- appendRNXMetroModulePathsFooter: () => appendRNXMetroModulePathsFooter,
26
- createRNXMetroModulePathsFooter: () => createRNXMetroModulePathsFooter,
25
+ RNX_METRO_MODULE_IDENTITY_VERSION: () => RNX_METRO_MODULE_IDENTITY_VERSION,
26
+ appendRNXMetroModuleIdentityFooter: () => appendRNXMetroModuleIdentityFooter,
27
+ createRNXMetroModuleIdentityFooter: () => createRNXMetroModuleIdentityFooter,
27
28
  detachRNXMetroSourceMapUrl: () => detachRNXMetroSourceMapUrl,
28
29
  readRNXMetroBundleLayout: () => readRNXMetroBundleLayout,
29
30
  readRNXMetroModuleIdentity: () => readRNXMetroModuleIdentity,
30
31
  toRNXDevelopmentBundleUrl: () => toRNXDevelopmentBundleUrl,
31
32
  toRNXProductionBundleUrl: () => toRNXProductionBundleUrl,
33
+ validateRNXMetroModuleIdentity: () => validateRNXMetroModuleIdentity,
32
34
  validateRNXMetroModulePaths: () => validateRNXMetroModulePaths
33
35
  });
34
36
  module.exports = __toCommonJS(metro_production_bundle_exports);
35
37
  var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
38
+ var RNX_METRO_MODULE_IDENTITY_VERSION = 2;
36
39
  function toRNXProductionBundleUrl(bundleUrl) {
37
40
  const relative = bundleUrl.startsWith("/");
38
41
  const url = new URL(bundleUrl, "http://rnxsim.local");
@@ -59,13 +62,13 @@ ${RNXSIM_METRO_SOURCE_MAP_COMMENT}`);
59
62
  if (url.includes("\n")) return { source: bundleText, sourceMappingUrl: null };
60
63
  return { source: trimmedEnd.slice(0, commentStart), sourceMappingUrl: url };
61
64
  }
62
- function createRNXMetroModulePathsFooter(modulePaths) {
63
- const payload = JSON.stringify(modulePaths).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/<\//g, "<\\/");
65
+ function createRNXMetroModuleIdentityFooter(identity) {
66
+ const payload = JSON.stringify(identity).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/<\//g, "<\\/");
64
67
  return `
65
68
  ${RNXSIM_METRO_MODULE_PATHS_PREFIX}${payload};`;
66
69
  }
67
- function appendRNXMetroModulePathsFooter(bundleText, modulePaths) {
68
- const footer = createRNXMetroModulePathsFooter(modulePaths);
70
+ function appendRNXMetroModuleIdentityFooter(bundleText, identity) {
71
+ const footer = createRNXMetroModuleIdentityFooter(identity);
69
72
  const detached = detachRNXMetroSourceMapUrl(bundleText);
70
73
  return detached.sourceMappingUrl === null ? bundleText + footer : `${detached.source}${footer}
71
74
  ${RNXSIM_METRO_SOURCE_MAP_COMMENT}${detached.sourceMappingUrl}`;
@@ -76,21 +79,55 @@ function readRNXMetroModuleIdentity(bundleText) {
76
79
  const footerPrefix = `
77
80
  ${RNXSIM_METRO_MODULE_PATHS_PREFIX}`;
78
81
  const footerStart = withoutMap.lastIndexOf(footerPrefix);
79
- if (footerStart < 0 || !withoutMap.endsWith(";")) return null;
80
- const parsed = JSON.parse(
81
- withoutMap.slice(footerStart + footerPrefix.length, -1)
82
- );
82
+ if (footerStart < 0) return null;
83
+ if (!withoutMap.endsWith(";")) {
84
+ throw new Error("Metro module identity footer is truncated");
85
+ }
86
+ let parsed;
87
+ try {
88
+ parsed = JSON.parse(withoutMap.slice(footerStart + footerPrefix.length, -1));
89
+ } catch {
90
+ throw new Error("Metro module identity footer is not valid JSON");
91
+ }
83
92
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
84
- return null;
93
+ throw new Error("Metro module identity footer is malformed");
94
+ }
95
+ const version = Reflect.get(parsed, "version");
96
+ if (version === void 0) return null;
97
+ const identitySource = Reflect.get(parsed, "identitySource");
98
+ const parsedModulePaths = Reflect.get(parsed, "modulePaths");
99
+ const parsedLogicalSpecifiers = Reflect.get(parsed, "logicalSpecifiers");
100
+ if (version !== RNX_METRO_MODULE_IDENTITY_VERSION || identitySource !== "rnx-plugin" && identitySource !== "contrast-bundler" && identitySource !== "metro-source-map" || typeof parsedModulePaths !== "object" || parsedModulePaths === null || Array.isArray(parsedModulePaths) || typeof parsedLogicalSpecifiers !== "object" || parsedLogicalSpecifiers === null || Array.isArray(parsedLogicalSpecifiers)) {
101
+ throw new Error("Metro module identity footer is malformed");
85
102
  }
86
103
  const modulePaths = {};
87
- for (const [moduleId, modulePath] of Object.entries(parsed)) {
88
- if (typeof modulePath !== "string") return null;
104
+ for (const [moduleId, modulePath] of Object.entries(parsedModulePaths)) {
105
+ if (typeof modulePath !== "string") {
106
+ throw new Error(`Metro module identity has an invalid path for ${moduleId}`);
107
+ }
89
108
  modulePaths[moduleId] = modulePath;
90
109
  }
110
+ const logicalSpecifiers = {};
111
+ for (const [moduleId, specifiers] of Object.entries(parsedLogicalSpecifiers)) {
112
+ if (!Array.isArray(specifiers) || specifiers.length === 0 || !specifiers.every(
113
+ (specifier) => typeof specifier === "string" && specifier.length > 0
114
+ )) {
115
+ throw new Error(`Metro module identity has invalid specifiers for ${moduleId}`);
116
+ }
117
+ const unique = [...new Set(specifiers)].sort(
118
+ (left, right) => left.localeCompare(right)
119
+ );
120
+ if (unique.length !== specifiers.length) {
121
+ throw new Error(`Metro module identity repeats a specifier for ${moduleId}`);
122
+ }
123
+ logicalSpecifiers[moduleId] = unique;
124
+ }
91
125
  return {
92
126
  source: withoutMap.slice(0, footerStart),
127
+ version,
128
+ identitySource,
93
129
  modulePaths,
130
+ logicalSpecifiers,
94
131
  sourceMappingUrl: detached.sourceMappingUrl
95
132
  };
96
133
  }
@@ -145,7 +182,7 @@ function readDefineTail(source, start, end) {
145
182
  while (idEnd > start && /\s/.test(source[idEnd])) idEnd--;
146
183
  if (source[idEnd] !== ",") return null;
147
184
  let idStart = idEnd;
148
- while (idStart > start && /[0-9a-fA-FxX.+\-]/.test(source[idStart - 1])) idStart--;
185
+ while (idStart > start && /[0-9a-fA-FxX.+-]/.test(source[idStart - 1])) idStart--;
149
186
  const literal = source.slice(idStart, idEnd);
150
187
  if (!/^-?(?:0[xXbBoO][0-9a-fA-F]+|\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)$/.test(literal)) {
151
188
  return null;
@@ -248,14 +285,26 @@ function validateRNXMetroModulePaths(source, modulePaths) {
248
285
  }
249
286
  }
250
287
  }
288
+ function validateRNXMetroModuleIdentity(identity) {
289
+ validateRNXMetroModulePaths(identity.source, identity.modulePaths);
290
+ for (const moduleId of Object.keys(identity.logicalSpecifiers)) {
291
+ if (!Object.prototype.hasOwnProperty.call(identity.modulePaths, moduleId)) {
292
+ throw new Error(
293
+ `Metro module identity has logical specifiers for unknown module ${moduleId}`
294
+ );
295
+ }
296
+ }
297
+ }
251
298
  // Annotate the CommonJS export names for ESM import in node:
252
299
  0 && (module.exports = {
253
- appendRNXMetroModulePathsFooter,
254
- createRNXMetroModulePathsFooter,
300
+ RNX_METRO_MODULE_IDENTITY_VERSION,
301
+ appendRNXMetroModuleIdentityFooter,
302
+ createRNXMetroModuleIdentityFooter,
255
303
  detachRNXMetroSourceMapUrl,
256
304
  readRNXMetroBundleLayout,
257
305
  readRNXMetroModuleIdentity,
258
306
  toRNXDevelopmentBundleUrl,
259
307
  toRNXProductionBundleUrl,
308
+ validateRNXMetroModuleIdentity,
260
309
  validateRNXMetroModulePaths
261
310
  });
@@ -1,7 +1,8 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/metro-production-bundle.ts
4
4
  var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
5
+ var RNX_METRO_MODULE_IDENTITY_VERSION = 2;
5
6
  function toRNXProductionBundleUrl(bundleUrl) {
6
7
  const relative = bundleUrl.startsWith("/");
7
8
  const url = new URL(bundleUrl, "http://rnxsim.local");
@@ -28,13 +29,13 @@ ${RNXSIM_METRO_SOURCE_MAP_COMMENT}`);
28
29
  if (url.includes("\n")) return { source: bundleText, sourceMappingUrl: null };
29
30
  return { source: trimmedEnd.slice(0, commentStart), sourceMappingUrl: url };
30
31
  }
31
- function createRNXMetroModulePathsFooter(modulePaths) {
32
- const payload = JSON.stringify(modulePaths).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/<\//g, "<\\/");
32
+ function createRNXMetroModuleIdentityFooter(identity) {
33
+ const payload = JSON.stringify(identity).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/<\//g, "<\\/");
33
34
  return `
34
35
  ${RNXSIM_METRO_MODULE_PATHS_PREFIX}${payload};`;
35
36
  }
36
- function appendRNXMetroModulePathsFooter(bundleText, modulePaths) {
37
- const footer = createRNXMetroModulePathsFooter(modulePaths);
37
+ function appendRNXMetroModuleIdentityFooter(bundleText, identity) {
38
+ const footer = createRNXMetroModuleIdentityFooter(identity);
38
39
  const detached = detachRNXMetroSourceMapUrl(bundleText);
39
40
  return detached.sourceMappingUrl === null ? bundleText + footer : `${detached.source}${footer}
40
41
  ${RNXSIM_METRO_SOURCE_MAP_COMMENT}${detached.sourceMappingUrl}`;
@@ -45,21 +46,55 @@ function readRNXMetroModuleIdentity(bundleText) {
45
46
  const footerPrefix = `
46
47
  ${RNXSIM_METRO_MODULE_PATHS_PREFIX}`;
47
48
  const footerStart = withoutMap.lastIndexOf(footerPrefix);
48
- if (footerStart < 0 || !withoutMap.endsWith(";")) return null;
49
- const parsed = JSON.parse(
50
- withoutMap.slice(footerStart + footerPrefix.length, -1)
51
- );
49
+ if (footerStart < 0) return null;
50
+ if (!withoutMap.endsWith(";")) {
51
+ throw new Error("Metro module identity footer is truncated");
52
+ }
53
+ let parsed;
54
+ try {
55
+ parsed = JSON.parse(withoutMap.slice(footerStart + footerPrefix.length, -1));
56
+ } catch {
57
+ throw new Error("Metro module identity footer is not valid JSON");
58
+ }
52
59
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
53
- return null;
60
+ throw new Error("Metro module identity footer is malformed");
61
+ }
62
+ const version = Reflect.get(parsed, "version");
63
+ if (version === void 0) return null;
64
+ const identitySource = Reflect.get(parsed, "identitySource");
65
+ const parsedModulePaths = Reflect.get(parsed, "modulePaths");
66
+ const parsedLogicalSpecifiers = Reflect.get(parsed, "logicalSpecifiers");
67
+ if (version !== RNX_METRO_MODULE_IDENTITY_VERSION || identitySource !== "rnx-plugin" && identitySource !== "contrast-bundler" && identitySource !== "metro-source-map" || typeof parsedModulePaths !== "object" || parsedModulePaths === null || Array.isArray(parsedModulePaths) || typeof parsedLogicalSpecifiers !== "object" || parsedLogicalSpecifiers === null || Array.isArray(parsedLogicalSpecifiers)) {
68
+ throw new Error("Metro module identity footer is malformed");
54
69
  }
55
70
  const modulePaths = {};
56
- for (const [moduleId, modulePath] of Object.entries(parsed)) {
57
- if (typeof modulePath !== "string") return null;
71
+ for (const [moduleId, modulePath] of Object.entries(parsedModulePaths)) {
72
+ if (typeof modulePath !== "string") {
73
+ throw new Error(`Metro module identity has an invalid path for ${moduleId}`);
74
+ }
58
75
  modulePaths[moduleId] = modulePath;
59
76
  }
77
+ const logicalSpecifiers = {};
78
+ for (const [moduleId, specifiers] of Object.entries(parsedLogicalSpecifiers)) {
79
+ if (!Array.isArray(specifiers) || specifiers.length === 0 || !specifiers.every(
80
+ (specifier) => typeof specifier === "string" && specifier.length > 0
81
+ )) {
82
+ throw new Error(`Metro module identity has invalid specifiers for ${moduleId}`);
83
+ }
84
+ const unique = [...new Set(specifiers)].sort(
85
+ (left, right) => left.localeCompare(right)
86
+ );
87
+ if (unique.length !== specifiers.length) {
88
+ throw new Error(`Metro module identity repeats a specifier for ${moduleId}`);
89
+ }
90
+ logicalSpecifiers[moduleId] = unique;
91
+ }
60
92
  return {
61
93
  source: withoutMap.slice(0, footerStart),
94
+ version,
95
+ identitySource,
62
96
  modulePaths,
97
+ logicalSpecifiers,
63
98
  sourceMappingUrl: detached.sourceMappingUrl
64
99
  };
65
100
  }
@@ -114,7 +149,7 @@ function readDefineTail(source, start, end) {
114
149
  while (idEnd > start && /\s/.test(source[idEnd])) idEnd--;
115
150
  if (source[idEnd] !== ",") return null;
116
151
  let idStart = idEnd;
117
- while (idStart > start && /[0-9a-fA-FxX.+\-]/.test(source[idStart - 1])) idStart--;
152
+ while (idStart > start && /[0-9a-fA-FxX.+-]/.test(source[idStart - 1])) idStart--;
118
153
  const literal = source.slice(idStart, idEnd);
119
154
  if (!/^-?(?:0[xXbBoO][0-9a-fA-F]+|\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)$/.test(literal)) {
120
155
  return null;
@@ -217,13 +252,25 @@ function validateRNXMetroModulePaths(source, modulePaths) {
217
252
  }
218
253
  }
219
254
  }
255
+ function validateRNXMetroModuleIdentity(identity) {
256
+ validateRNXMetroModulePaths(identity.source, identity.modulePaths);
257
+ for (const moduleId of Object.keys(identity.logicalSpecifiers)) {
258
+ if (!Object.prototype.hasOwnProperty.call(identity.modulePaths, moduleId)) {
259
+ throw new Error(
260
+ `Metro module identity has logical specifiers for unknown module ${moduleId}`
261
+ );
262
+ }
263
+ }
264
+ }
220
265
  export {
221
- appendRNXMetroModulePathsFooter,
222
- createRNXMetroModulePathsFooter,
266
+ RNX_METRO_MODULE_IDENTITY_VERSION,
267
+ appendRNXMetroModuleIdentityFooter,
268
+ createRNXMetroModuleIdentityFooter,
223
269
  detachRNXMetroSourceMapUrl,
224
270
  readRNXMetroBundleLayout,
225
271
  readRNXMetroModuleIdentity,
226
272
  toRNXDevelopmentBundleUrl,
227
273
  toRNXProductionBundleUrl,
274
+ validateRNXMetroModuleIdentity,
228
275
  validateRNXMetroModulePaths
229
276
  };
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (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;
@@ -412,6 +412,7 @@ exports.jsxDEV = exports.jsx`,
412
412
 
413
413
  // src/metro-production-bundle.ts
414
414
  var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
415
+ var RNX_METRO_MODULE_IDENTITY_VERSION = 2;
415
416
  function toRNXProductionBundleUrl(bundleUrl) {
416
417
  const relative = bundleUrl.startsWith("/");
417
418
  const url = new URL(bundleUrl, "http://rnxsim.local");
@@ -438,13 +439,13 @@ ${RNXSIM_METRO_SOURCE_MAP_COMMENT}`);
438
439
  if (url.includes("\n")) return { source: bundleText, sourceMappingUrl: null };
439
440
  return { source: trimmedEnd.slice(0, commentStart), sourceMappingUrl: url };
440
441
  }
441
- function createRNXMetroModulePathsFooter(modulePaths) {
442
- const payload = JSON.stringify(modulePaths).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/<\//g, "<\\/");
442
+ function createRNXMetroModuleIdentityFooter(identity) {
443
+ const payload = JSON.stringify(identity).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/<\//g, "<\\/");
443
444
  return `
444
445
  ${RNXSIM_METRO_MODULE_PATHS_PREFIX}${payload};`;
445
446
  }
446
- function appendRNXMetroModulePathsFooter(bundleText, modulePaths) {
447
- const footer = createRNXMetroModulePathsFooter(modulePaths);
447
+ function appendRNXMetroModuleIdentityFooter(bundleText, identity) {
448
+ const footer = createRNXMetroModuleIdentityFooter(identity);
448
449
  const detached = detachRNXMetroSourceMapUrl(bundleText);
449
450
  return detached.sourceMappingUrl === null ? bundleText + footer : `${detached.source}${footer}
450
451
  ${RNXSIM_METRO_SOURCE_MAP_COMMENT}${detached.sourceMappingUrl}`;
@@ -675,7 +676,30 @@ function appendRNXModulePaths(result, graph, options, projectRoot) {
675
676
  if (Object.keys(modulePaths).length !== dependencies.size) {
676
677
  throw new Error("rnxsim: bundle module IDs were not unique");
677
678
  }
678
- return typeof result === "string" ? appendRNXMetroModulePathsFooter(result, modulePaths) : { ...result, code: appendRNXMetroModulePathsFooter(result.code, modulePaths) };
679
+ const specifiersByModuleId = /* @__PURE__ */ new Map();
680
+ for (const module2 of dependencies.values()) {
681
+ for (const [dependencyKey, dependency] of module2.dependencies ?? []) {
682
+ const specifier = dependency.data?.name ?? dependencyKey;
683
+ if (specifier.length === 0 || specifier.startsWith(".") || specifier.startsWith("/") || typeof dependency.absolutePath !== "string") {
684
+ continue;
685
+ }
686
+ const moduleId = String(createModuleId(dependency.absolutePath));
687
+ if (!Object.prototype.hasOwnProperty.call(modulePaths, moduleId)) continue;
688
+ const found = specifiersByModuleId.get(moduleId);
689
+ if (found) found.add(specifier);
690
+ else specifiersByModuleId.set(moduleId, /* @__PURE__ */ new Set([specifier]));
691
+ }
692
+ }
693
+ const logicalSpecifiers = Object.fromEntries(
694
+ [...specifiersByModuleId].sort(([left], [right]) => Number(left) - Number(right)).map(([moduleId, specifiers]) => [moduleId, [...specifiers].sort()])
695
+ );
696
+ const identity = {
697
+ version: RNX_METRO_MODULE_IDENTITY_VERSION,
698
+ identitySource: "rnx-plugin",
699
+ modulePaths,
700
+ logicalSpecifiers
701
+ };
702
+ return typeof result === "string" ? appendRNXMetroModuleIdentityFooter(result, identity) : { ...result, code: appendRNXMetroModuleIdentityFooter(result.code, identity) };
679
703
  }
680
704
  function withRNX(config, options = {}) {
681
705
  if (!options.devServer && !options.open && !options.productionBundles && !options.developmentBundles) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/react-native-host-modules.ts
4
4
  var REACT_NATIVE_PASSTHROUGH_SPECIFIERS = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (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;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (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/dist-lib/sdk.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (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;
package/dist-lib/sdk.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.403 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.405 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);