rnxsim 0.1.393 → 0.1.394

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 (55) hide show
  1. package/cli/cloud-client.ts +74 -52
  2. package/cli/cloud-dispatch.ts +2 -1
  3. package/cli/commands/inspect/core.ts +33 -2
  4. package/cli/commands/platform.ts +34 -9
  5. package/dist-lib/agent-daemon-client.cjs +1 -1
  6. package/dist-lib/agent-events.cjs +1 -1
  7. package/dist-lib/agent-identity.cjs +1 -1
  8. package/dist-lib/agent-sessions.cjs +1 -1
  9. package/dist-lib/attached-projects.cjs +1 -1
  10. package/dist-lib/auth/shared-session.cjs +1 -1
  11. package/dist-lib/backend-origin.cjs +1 -1
  12. package/dist-lib/beta.cjs +1 -1
  13. package/dist-lib/beta.mjs +1 -1
  14. package/dist-lib/bridge-constants.cjs +1 -1
  15. package/dist-lib/bridge-contract-input.cjs +12 -1
  16. package/dist-lib/bridge-contract-input.mjs +10 -1
  17. package/dist-lib/bridge-contract.cjs +7 -1
  18. package/dist-lib/bridge-contract.mjs +5 -1
  19. package/dist-lib/capture-contract.cjs +1 -1
  20. package/dist-lib/capture-contract.mjs +1 -1
  21. package/dist-lib/cli-constants.cjs +1 -1
  22. package/dist-lib/cloud-contract.cjs +29 -3
  23. package/dist-lib/cloud-contract.mjs +26 -2
  24. package/dist-lib/config.cjs +1 -1
  25. package/dist-lib/detox/index.cjs +1 -1
  26. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  27. package/dist-lib/home-paths.cjs +1 -1
  28. package/dist-lib/host/bridge-host.cjs +1 -1
  29. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  30. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  31. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  32. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  33. package/dist-lib/host/websocket-proxy.cjs +1 -1
  34. package/dist-lib/index.cjs +23 -3
  35. package/dist-lib/jump-to-source-babel.cjs +1 -1
  36. package/dist-lib/menu.cjs +1 -1
  37. package/dist-lib/menu.mjs +1 -1
  38. package/dist-lib/metro-production-bundle.cjs +132 -3
  39. package/dist-lib/metro-production-bundle.mjs +129 -2
  40. package/dist-lib/metro.cjs +1 -1
  41. package/dist-lib/profiles.cjs +1 -1
  42. package/dist-lib/public-brand.cjs +1 -1
  43. package/dist-lib/react-native-host-modules.cjs +1 -1
  44. package/dist-lib/react-native-host-modules.mjs +1 -1
  45. package/dist-lib/render-mode.cjs +1 -1
  46. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  47. package/dist-lib/sdk.cjs +24 -3
  48. package/dist-lib/sdk.mjs +24 -3
  49. package/dist-lib/skills.cjs +28 -25
  50. package/dist-lib/vite.cjs +1 -1
  51. package/package.json +1 -1
  52. package/src/bridge-contract-input.ts +12 -0
  53. package/src/bridge-contract.ts +7 -0
  54. package/src/cloud-contract.ts +29 -0
  55. package/src/metro-production-bundle.ts +200 -0
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.394 | (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.393",
3
+ "version": "0.1.394",
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",
@@ -3,6 +3,7 @@
3
3
  // WebSocket routes) validates its request bodies here rather than growing a
4
4
  // second reader for the same shapes.
5
5
 
6
+ import { SIM_LONG_PRESS_MAX_MS } from './bridge-contract'
6
7
  import type {
7
8
  ResetOptions,
8
9
  SemanticWaitOptions,
@@ -130,6 +131,17 @@ export function parsePerformStep(value: unknown, index: number): SimPerformStep
130
131
  }
131
132
  case 'longPress': {
132
133
  const durationMs = optionalNumber(step, 'durationMs', path)
134
+ if (
135
+ durationMs !== undefined &&
136
+ (!Number.isInteger(durationMs) ||
137
+ durationMs < 1 ||
138
+ durationMs > SIM_LONG_PRESS_MAX_MS)
139
+ ) {
140
+ validationFailure(
141
+ `${path}.durationMs`,
142
+ `must be an integer between 1 and ${SIM_LONG_PRESS_MAX_MS}`,
143
+ )
144
+ }
133
145
  return {
134
146
  type,
135
147
  x: numberValue(step.x, `${path}.x`),
@@ -22,6 +22,7 @@ export type BridgeCommandType =
22
22
  | 'settle'
23
23
  | 'setAppearance'
24
24
  | 'openUrl'
25
+ | 'memory'
25
26
  | 'tap'
26
27
  | 'longPress'
27
28
  | 'keyboard'
@@ -216,6 +217,12 @@ export interface SemanticReadyProbe {
216
217
  suppressedEntryError: string
217
218
  }
218
219
 
220
+ // react native's own Pressable/Touchable delayLongPress default, and the
221
+ // ceiling any transport accepts for a press-and-hold. a driver that holds
222
+ // longer than this is describing a drag, not a long press.
223
+ export const SIM_LONG_PRESS_DEFAULT_MS = 500
224
+ export const SIM_LONG_PRESS_MAX_MS = 5_000
225
+
219
226
  export const SEMANTIC_READY_CONTENT_NODE_FLOOR = 100
220
227
  export const SEMANTIC_READY_NODE_STABLE_MS = 750
221
228
  export const SEMANTIC_READY_POLL_MS = 150
@@ -1,8 +1,37 @@
1
+ import type { BridgeCommandType } from './bridge-contract'
2
+
1
3
  export const RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024
2
4
  export const RNX_CLOUD_DESIGN_PARALLEL_JOB_LIMIT = 16
3
5
  export const RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES = 256
4
6
  export const RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024
5
7
 
8
+ export const RNX_CLOUD_COMMAND_TYPES = Object.freeze([
9
+ 'state',
10
+ 'query',
11
+ 'resolve',
12
+ 'perform',
13
+ 'waitFor',
14
+ 'reset',
15
+ 'screenshot',
16
+ 'capture',
17
+ 'captureRegions',
18
+ 'settle',
19
+ 'setAppearance',
20
+ 'openUrl',
21
+ 'tree',
22
+ 'tap',
23
+ 'longPress',
24
+ 'memory',
25
+ ]) satisfies readonly BridgeCommandType[]
26
+
27
+ const RNX_CLOUD_COMMAND_TYPE_SET = new Set<string>(RNX_CLOUD_COMMAND_TYPES)
28
+
29
+ export function isRnxCloudCommandType(
30
+ value: string,
31
+ ): value is (typeof RNX_CLOUD_COMMAND_TYPES)[number] {
32
+ return RNX_CLOUD_COMMAND_TYPE_SET.has(value)
33
+ }
34
+
6
35
  export interface RnxCloudClaim {
7
36
  id: string
8
37
  expiresAt: number
@@ -49,3 +49,203 @@ export function readRNXMetroModuleIdentity(
49
49
  }
50
50
  return { source: bundleText.slice(0, footerStart), modulePaths }
51
51
  }
52
+
53
+ // the exact layout Metro's baseJSBundle emits and the rnx runtime contract
54
+ // depends on: a prelude, then one `__d(` or `__r(` statement per line, each
55
+ // running until the next statement starts its own line. one reader owns this
56
+ // recognition so the loader's identity validation, the source-map attribution
57
+ // pass, and the cloud artifact producer cannot drift apart.
58
+ export interface RNXMetroDefine {
59
+ kind: 'define'
60
+ moduleId: number
61
+ start: number
62
+ end: number
63
+ lineIndex: number
64
+ endLineIndex: number
65
+ /** the factory expression, between `__d(` and the module ID. */
66
+ factory: { start: number; end: number }
67
+ /** the dependency array, brackets included. */
68
+ dependencies: { start: number; end: number }
69
+ }
70
+
71
+ export interface RNXMetroRun {
72
+ kind: 'run'
73
+ start: number
74
+ end: number
75
+ lineIndex: number
76
+ endLineIndex: number
77
+ }
78
+
79
+ export type RNXMetroStatement = RNXMetroDefine | RNXMetroRun
80
+
81
+ export interface RNXMetroBundleLayout {
82
+ /** everything before the first `__d(`/`__r(` line: prelude vars and polyfills. */
83
+ prelude: string
84
+ statements: RNXMetroStatement[]
85
+ /** `__d(` statements whose module ID could not be read. */
86
+ moduleIdFailures: number
87
+ }
88
+
89
+ interface DefineTail {
90
+ moduleId: number
91
+ factoryEnd: number
92
+ dependenciesStart: number
93
+ dependenciesEnd: number
94
+ }
95
+
96
+ // each `__d(...)` ends with `),<id>,[dep,dep,...]);?`. the dependency array can
97
+ // be hundreds of entries long, so walk back from `end` instead of searching a
98
+ // fixed tail window: skip trailing `;` and whitespace, consume the closing `)`,
99
+ // balance `[...]` to find its opening bracket, then read the integer before it.
100
+ function readDefineTail(source: string, start: number, end: number): DefineTail | null {
101
+ let cursor = end
102
+ while (
103
+ cursor > start &&
104
+ (source[cursor] === ' ' ||
105
+ source[cursor] === '\n' ||
106
+ source[cursor] === '\t' ||
107
+ source[cursor] === ';')
108
+ ) {
109
+ cursor--
110
+ }
111
+ if (source[cursor] !== ')') return null
112
+ cursor--
113
+ while (cursor > start && /\s/.test(source[cursor])) cursor--
114
+ if (source[cursor] !== ']') return null
115
+ const dependenciesEnd = cursor + 1
116
+ let depth = 1
117
+ cursor--
118
+ while (cursor > start && depth > 0) {
119
+ const character = source[cursor]
120
+ if (character === ']') depth++
121
+ else if (character === '[') depth--
122
+ if (depth === 0) break
123
+ cursor--
124
+ }
125
+ if (depth !== 0) return null
126
+ const dependenciesStart = cursor
127
+ let idEnd = cursor - 1
128
+ while (idEnd > start && /\s/.test(source[idEnd])) idEnd--
129
+ if (source[idEnd] !== ',') return null
130
+ // a minifier is free to write the module ID as `1e3` or `0x3e8`, so read the
131
+ // whole numeric literal rather than a run of decimal digits. the factory
132
+ // before it always ends in `}` or `)`, which stops this walk immediately.
133
+ let idStart = idEnd
134
+ while (idStart > start && /[0-9a-fA-FxX.+\-]/.test(source[idStart - 1])) idStart--
135
+ const literal = source.slice(idStart, idEnd)
136
+ if (!/^-?(?:0[xXbBoO][0-9a-fA-F]+|\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)$/.test(literal)) {
137
+ return null
138
+ }
139
+ const moduleId = Number(literal)
140
+ if (!Number.isSafeInteger(moduleId)) return null
141
+ let factoryEnd = idStart - 1
142
+ while (factoryEnd > start && /\s/.test(source[factoryEnd])) factoryEnd--
143
+ if (source[factoryEnd] !== ',') return null
144
+ return {
145
+ moduleId,
146
+ factoryEnd,
147
+ dependenciesStart,
148
+ dependenciesEnd,
149
+ }
150
+ }
151
+
152
+ /** reads the canonical Metro production layout from footer-stripped source. */
153
+ export function readRNXMetroBundleLayout(source: string): RNXMetroBundleLayout {
154
+ const lineOffsets = [0]
155
+ for (let index = 0; index < source.length; index++) {
156
+ if (source.charCodeAt(index) === 10) lineOffsets.push(index + 1)
157
+ }
158
+
159
+ const boundaries: number[] = []
160
+ for (let lineIndex = 0; lineIndex < lineOffsets.length; lineIndex++) {
161
+ const start = lineOffsets[lineIndex]
162
+ if (source.startsWith('__d(', start) || source.startsWith('__r(', start)) {
163
+ boundaries.push(lineIndex)
164
+ }
165
+ }
166
+
167
+ const statements: RNXMetroStatement[] = []
168
+ let moduleIdFailures = 0
169
+ for (let index = 0; index < boundaries.length; index++) {
170
+ const lineIndex = boundaries[index]
171
+ const start = lineOffsets[lineIndex]
172
+ const nextLineIndex = boundaries[index + 1]
173
+ const endLineIndex =
174
+ nextLineIndex !== undefined ? nextLineIndex - 1 : lineOffsets.length - 1
175
+ const end = nextLineIndex !== undefined ? lineOffsets[nextLineIndex] : source.length
176
+ if (!source.startsWith('__d(', start)) {
177
+ statements.push({ kind: 'run', start, end, lineIndex, endLineIndex })
178
+ continue
179
+ }
180
+ const tail = readDefineTail(source, start, end - 1)
181
+ if (!tail) {
182
+ moduleIdFailures++
183
+ continue
184
+ }
185
+ statements.push({
186
+ kind: 'define',
187
+ moduleId: tail.moduleId,
188
+ start,
189
+ end,
190
+ lineIndex,
191
+ endLineIndex,
192
+ factory: { start: start + '__d('.length, end: tail.factoryEnd },
193
+ dependencies: { start: tail.dependenciesStart, end: tail.dependenciesEnd },
194
+ })
195
+ }
196
+
197
+ return {
198
+ prelude: source.slice(
199
+ 0,
200
+ boundaries.length > 0 ? lineOffsets[boundaries[0]] : source.length,
201
+ ),
202
+ statements,
203
+ moduleIdFailures,
204
+ }
205
+ }
206
+
207
+ /**
208
+ * validates the exact identity contract before a loader installs a Metro
209
+ * factory trap or a producer ships an artifact: every define carries a unique,
210
+ * readable module ID, and the identity map names those IDs and no others.
211
+ * serializers that emit another layout need an ingestion adapter that
212
+ * normalizes them to this format.
213
+ */
214
+ export function validateRNXMetroModulePaths(
215
+ source: string,
216
+ modulePaths: RNXMetroModulePathMap,
217
+ ): void {
218
+ const layout = readRNXMetroBundleLayout(source)
219
+ if (layout.moduleIdFailures > 0) {
220
+ throw new Error(
221
+ `Metro module identity could not read ${layout.moduleIdFailures} module IDs`,
222
+ )
223
+ }
224
+ const definedIds = new Set<number>()
225
+ for (const statement of layout.statements) {
226
+ if (statement.kind !== 'define') continue
227
+ if (definedIds.has(statement.moduleId)) {
228
+ throw new Error(`Metro module identity has duplicate module ${statement.moduleId}`)
229
+ }
230
+ definedIds.add(statement.moduleId)
231
+ if (!Object.prototype.hasOwnProperty.call(modulePaths, String(statement.moduleId))) {
232
+ throw new Error(`Metro module identity is missing ${statement.moduleId}`)
233
+ }
234
+ }
235
+ if (definedIds.size === 0) {
236
+ throw new Error('Metro module identity found no canonical JavaScript modules')
237
+ }
238
+
239
+ for (const [key, modulePath] of Object.entries(modulePaths)) {
240
+ const moduleId = Number(key)
241
+ if (!Number.isSafeInteger(moduleId) || String(moduleId) !== key) {
242
+ throw new Error(`Metro module identity has invalid module ID ${key}`)
243
+ }
244
+ if (typeof modulePath !== 'string' || modulePath.length === 0) {
245
+ throw new Error(`Metro module identity has no path for ${key}`)
246
+ }
247
+ if (!definedIds.has(moduleId)) {
248
+ throw new Error(`Metro module identity has no module ${key}`)
249
+ }
250
+ }
251
+ }