rnxsim 0.1.416 → 0.1.418
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.
- package/cli/cloud-client.ts +104 -32
- package/cli/cloud-dispatch.ts +38 -4
- package/cli/commands/inspect/actions.ts +313 -385
- package/cli/commands/inspect/core.ts +6 -0
- package/cli/commands/inspect/resolve-target.ts +2 -3
- package/cli/commands/inspect/settle.ts +10 -0
- package/cli/commands/inspect.ts +31 -48
- package/cli/commands/platform.ts +41 -10
- package/cli/outbound-endpoints.ts +1 -1
- package/cli/shell-init.ts +1 -1
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +1 -1
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +626 -749
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +1206 -1342
- package/dist-lib/sdk.mjs +1206 -1342
- package/dist-lib/skills.cjs +57 -1
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
package/cli/cloud-client.ts
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto'
|
|
2
|
-
import { closeSync, fstatSync, openSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { closeSync, fstatSync, openSync, readdirSync, readFileSync } from 'node:fs'
|
|
3
3
|
import { dirname, extname, join, resolve } from 'node:path'
|
|
4
4
|
import {
|
|
5
5
|
embedMetroAssetFiles,
|
|
6
|
-
|
|
6
|
+
metroAssetScaleFilename,
|
|
7
|
+
metroIosAssetDestRelatives,
|
|
8
|
+
resolveMetroIosAssetDestFile,
|
|
7
9
|
} from '../../contrast-bundler/src/metroAssetArtifact'
|
|
8
10
|
import {
|
|
9
11
|
buildRnxCloudArtifact,
|
|
10
12
|
type RnxCloudArtifactReceipt,
|
|
11
13
|
} from '../../contrast-bundler/src/metroCloudArtifact'
|
|
14
|
+
import { inferMetroModuleIdentity } from '../../sootsim-engine/src/metro-fingerprint'
|
|
15
|
+
import { loadMetroFingerprintRegistry } from '../../sootsim-engine/src/metro-fingerprint-registry-client'
|
|
12
16
|
import {
|
|
13
17
|
isRnxCloudCommandType,
|
|
14
18
|
RNX_CLOUD_MAX_ARTIFACT_BYTES,
|
|
15
19
|
type RnxCloudBoxCreateReceipt,
|
|
16
20
|
} from '../src/cloud-contract'
|
|
21
|
+
import {
|
|
22
|
+
RNX_METRO_MODULE_IDENTITY_VERSION,
|
|
23
|
+
type RNXMetroModuleIdentityMetadata,
|
|
24
|
+
} from '../src/metro-production-bundle'
|
|
17
25
|
import { rnxPublicBrand } from '../src/public-brand'
|
|
18
26
|
import {
|
|
19
27
|
createCloudSession,
|
|
@@ -74,16 +82,16 @@ function resolveCloudOrigin(value?: string): string {
|
|
|
74
82
|
function readImmutableBundleSource(bundlePath: string): string {
|
|
75
83
|
const filepath = resolve(bundlePath)
|
|
76
84
|
if (extname(filepath) !== '.js') {
|
|
77
|
-
throw new Error('rnx ios --
|
|
85
|
+
throw new Error('rnx ios --nano requires one local Metro .js bundle')
|
|
78
86
|
}
|
|
79
87
|
const descriptor = openSync(filepath, 'r')
|
|
80
88
|
try {
|
|
81
89
|
const before = fstatSync(descriptor, { bigint: true })
|
|
82
90
|
if (!before.isFile()) {
|
|
83
|
-
throw new Error('rnx ios --
|
|
91
|
+
throw new Error('rnx ios --nano bundle must be a regular local file')
|
|
84
92
|
}
|
|
85
93
|
if (before.size <= 0n) {
|
|
86
|
-
throw new Error('rnx ios --
|
|
94
|
+
throw new Error('rnx ios --nano bundle is empty')
|
|
87
95
|
}
|
|
88
96
|
const bytes = readFileSync(descriptor)
|
|
89
97
|
const after = fstatSync(descriptor, { bigint: true })
|
|
@@ -94,16 +102,16 @@ function readImmutableBundleSource(bundlePath: string): string {
|
|
|
94
102
|
before.mtimeNs !== after.mtimeNs ||
|
|
95
103
|
bytes.length !== Number(before.size)
|
|
96
104
|
) {
|
|
97
|
-
throw new Error('rnx ios --
|
|
105
|
+
throw new Error('rnx ios --nano bundle changed while it was being read')
|
|
98
106
|
}
|
|
99
107
|
let source: string
|
|
100
108
|
try {
|
|
101
109
|
source = new TextDecoder('utf-8', { fatal: true }).decode(bytes)
|
|
102
110
|
} catch {
|
|
103
|
-
throw new Error('rnx ios --
|
|
111
|
+
throw new Error('rnx ios --nano bundle must be valid UTF-8 JavaScript')
|
|
104
112
|
}
|
|
105
113
|
if (source.includes('\0') || !source.includes('__d(') || !source.includes('__r(')) {
|
|
106
|
-
throw new Error('rnx ios --
|
|
114
|
+
throw new Error('rnx ios --nano requires a Metro JavaScript bundle')
|
|
107
115
|
}
|
|
108
116
|
return source
|
|
109
117
|
} finally {
|
|
@@ -111,46 +119,110 @@ function readImmutableBundleSource(bundlePath: string): string {
|
|
|
111
119
|
}
|
|
112
120
|
}
|
|
113
121
|
|
|
114
|
-
// `react-native bundle --assets-dest
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
// `react-native bundle --assets-dest` dest paths are not always the JS
|
|
123
|
+
// descriptor's httpServerLocation with `../` replaced by `_`. Metro 0.83 does
|
|
124
|
+
// that (`__node_modules`); Metro 0.84 often dests `node_modules` and keeps the
|
|
125
|
+
// project-root-relative app path. resolveMetroIosAssetDestFile matches both.
|
|
126
|
+
function listMetroAssetDestFiles(assetsPath: string): string[] {
|
|
127
|
+
const files: string[] = []
|
|
128
|
+
const walk = (dir: string, prefix: string) => {
|
|
129
|
+
let entries
|
|
130
|
+
try {
|
|
131
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
132
|
+
} catch {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name
|
|
137
|
+
if (entry.isDirectory()) walk(join(dir, entry.name), rel)
|
|
138
|
+
else if (entry.isFile()) files.push(rel)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
walk(assetsPath, '')
|
|
142
|
+
return files
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function inferCloudModuleIdentity(
|
|
146
|
+
source: string,
|
|
147
|
+
): Promise<RNXMetroModuleIdentityMetadata> {
|
|
148
|
+
let receipt
|
|
149
|
+
try {
|
|
150
|
+
receipt = await loadMetroFingerprintRegistry()
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`rnx Cloud could not load the Metro fingerprint registry needed to identify this bundle: ${
|
|
154
|
+
error instanceof Error ? error.message : String(error)
|
|
155
|
+
}. Build it through the published Metro plugin: withRNX(config, { productionBundles: true }) for the production graph, or withRNX(config, { developmentBundles: true }) for the development graph.`,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const inferred = await inferMetroModuleIdentity({
|
|
160
|
+
source,
|
|
161
|
+
registry: receipt.registry,
|
|
162
|
+
registryIntegrity: receipt.integrity,
|
|
163
|
+
})
|
|
164
|
+
return {
|
|
165
|
+
version: RNX_METRO_MODULE_IDENTITY_VERSION,
|
|
166
|
+
identitySource: 'contrast-bundler',
|
|
167
|
+
modulePaths: inferred.modulePaths,
|
|
168
|
+
logicalSpecifiers: {},
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`rnx Cloud could not infer module identity for this Metro bundle: ${
|
|
173
|
+
error instanceof Error ? error.message : String(error)
|
|
174
|
+
}. Build it through the published Metro plugin: withRNX(config, { productionBundles: true }) for the production graph, or withRNX(config, { developmentBundles: true }) for the development graph.`,
|
|
175
|
+
)
|
|
176
|
+
}
|
|
127
177
|
}
|
|
128
178
|
|
|
129
179
|
/**
|
|
130
180
|
* turns the Metro production bundle a caller built into the immutable artifact
|
|
131
181
|
* the runner executes: every asset scale is read from the asset graph and
|
|
132
182
|
* embedded, module identity is normalized, and the JavaScript is minified.
|
|
183
|
+
* an ordinary unannotated Metro production bundle is identified through the
|
|
184
|
+
* published fingerprint registry; a withRNX footer remains the fast path.
|
|
133
185
|
*/
|
|
134
186
|
async function produceCloudArtifact(
|
|
135
187
|
bundlePath: string,
|
|
136
188
|
assetsPath: string,
|
|
137
189
|
): Promise<{ bytes: Buffer; sha256: string; receipt: RnxCloudArtifactReceipt }> {
|
|
138
190
|
const source = readImmutableBundleSource(bundlePath)
|
|
191
|
+
const destFiles = listMetroAssetDestFiles(assetsPath)
|
|
139
192
|
const embedded = await embedMetroAssetFiles(source, async (variant) => {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
193
|
+
const relative = resolveMetroIosAssetDestFile(
|
|
194
|
+
destFiles,
|
|
195
|
+
variant.descriptor,
|
|
196
|
+
variant.scale,
|
|
197
|
+
)
|
|
198
|
+
if (relative) return readFileSync(join(assetsPath, relative))
|
|
199
|
+
// iOS copies only @1x..@3x, so a declared scale with no file is ordinary
|
|
200
|
+
// and the producer narrows the descriptor. an asset with no file at any
|
|
201
|
+
// scale is the real failure, and this is where the dest path a customer
|
|
202
|
+
// should look for is still known.
|
|
203
|
+
const written = variant.descriptor.scales.some((scale) =>
|
|
204
|
+
resolveMetroIosAssetDestFile(destFiles, variant.descriptor, scale),
|
|
205
|
+
)
|
|
206
|
+
if (written) return null
|
|
207
|
+
const filename = metroAssetScaleFilename(
|
|
208
|
+
variant.descriptor.name,
|
|
209
|
+
variant.descriptor.type,
|
|
210
|
+
variant.scale,
|
|
211
|
+
)
|
|
212
|
+
const classic =
|
|
213
|
+
metroIosAssetDestRelatives(variant.descriptor.httpServerLocation, filename)[0] ??
|
|
214
|
+
filename
|
|
215
|
+
throw new Error(
|
|
216
|
+
`rnx ios --nano could not read ${join(assetsPath, classic)} for ${variant.descriptor.name}.${variant.descriptor.type} at scale ${variant.scale}. Build the bundle with --assets-dest, or pass --assets <dir>.`,
|
|
217
|
+
)
|
|
218
|
+
})
|
|
219
|
+
const artifact = await buildRnxCloudArtifact(embedded.code, {
|
|
220
|
+
inferModuleIdentity: inferCloudModuleIdentity,
|
|
148
221
|
})
|
|
149
|
-
const artifact = await buildRnxCloudArtifact(embedded.code)
|
|
150
222
|
const bytes = Buffer.from(artifact.code, 'utf8')
|
|
151
223
|
if (bytes.length > MAX_RNX_CLOUD_BUNDLE_BYTES) {
|
|
152
224
|
throw new Error(
|
|
153
|
-
`rnx ios --
|
|
225
|
+
`rnx ios --nano artifact is ${bytes.length} bytes and exceeds the ${MAX_RNX_CLOUD_BUNDLE_BYTES}-byte limit`,
|
|
154
226
|
)
|
|
155
227
|
}
|
|
156
228
|
return {
|
|
@@ -219,7 +291,7 @@ function readCreateResponse(
|
|
|
219
291
|
}
|
|
220
292
|
}
|
|
221
293
|
|
|
222
|
-
// `rnx ios --
|
|
294
|
+
// `rnx ios --nano <bundle>` creates a nano box around the artifact with its
|
|
223
295
|
// first simulator in one request, and the shell then targets that box
|
|
224
296
|
export async function createCloudBox(options: CreateCloudBoxOptions): Promise<{
|
|
225
297
|
receipt: RnxCloudBoxCreateReceipt
|
package/cli/cloud-dispatch.ts
CHANGED
|
@@ -38,6 +38,39 @@ function firstVerb(positional: string[]): string | null {
|
|
|
38
38
|
return positional.find((value) => !value.startsWith('-')) ?? null
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
const CLOUD_DO_VERBS = new Set([
|
|
42
|
+
'tap',
|
|
43
|
+
'tap-id',
|
|
44
|
+
'tap-text',
|
|
45
|
+
'tap-best',
|
|
46
|
+
'double-tap',
|
|
47
|
+
'long-press',
|
|
48
|
+
'sleep',
|
|
49
|
+
'settle',
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
function doChainVerbs(positional: string[]): string[] {
|
|
53
|
+
const verbs: string[] = []
|
|
54
|
+
let expectingVerb = true
|
|
55
|
+
for (const argument of positional) {
|
|
56
|
+
if (argument === '--then') {
|
|
57
|
+
expectingVerb = true
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
if (expectingVerb && !argument.startsWith('-')) {
|
|
61
|
+
verbs.push(argument)
|
|
62
|
+
expectingVerb = false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return verbs
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function supportsCloudDo(positional: string[]): boolean {
|
|
69
|
+
const verbs = doChainVerbs(positional)
|
|
70
|
+
if (verbs.length === 0) return true
|
|
71
|
+
return verbs.every((verb) => CLOUD_DO_VERBS.has(verb))
|
|
72
|
+
}
|
|
73
|
+
|
|
41
74
|
function supportsCloudRead(
|
|
42
75
|
command: string,
|
|
43
76
|
args: string[],
|
|
@@ -77,7 +110,8 @@ function supportsCloudRead(
|
|
|
77
110
|
].includes(arg),
|
|
78
111
|
)
|
|
79
112
|
}
|
|
80
|
-
if (command === 'do'
|
|
113
|
+
if (command === 'do') return supportsCloudDo(positional)
|
|
114
|
+
if (command === 'reset') return true
|
|
81
115
|
return false
|
|
82
116
|
}
|
|
83
117
|
|
|
@@ -95,10 +129,10 @@ export async function dispatchCloudBoundary({
|
|
|
95
129
|
command,
|
|
96
130
|
args,
|
|
97
131
|
}: CloudBoundaryInvocation): Promise<CloudBoundaryResult> {
|
|
98
|
-
if (command === 'ios' && args.includes('--
|
|
99
|
-
if (command === 'android' && args.includes('--
|
|
132
|
+
if (command === 'ios' && args.includes('--nano')) return { handled: false }
|
|
133
|
+
if (command === 'android' && args.includes('--nano')) {
|
|
100
134
|
console.error(
|
|
101
|
-
' rnx android --
|
|
135
|
+
' rnx android --nano is not available; RNX Cloud currently supports iOS',
|
|
102
136
|
)
|
|
103
137
|
return { handled: true, code: 1 }
|
|
104
138
|
}
|