rnxsim 0.1.519 → 0.1.521
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 +419 -287
- package/cli/cloud-dispatch.ts +2 -21
- package/cli/cloud-session.ts +31 -85
- package/cli/commands/box.ts +15 -11
- package/cli/commands/inspect.ts +34 -1
- package/cli/commands/platform.ts +184 -232
- package/cli/main.ts +10 -2
- package/cli/outbound-endpoints.ts +21 -6
- 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 +7 -2
- package/dist-lib/bridge-contract-input.mjs +7 -2
- 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/cloud.cjs +18 -2
- package/dist-lib/cloud.mjs +18 -2
- 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 +112 -39
- 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 +44 -117
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/menu.cjs +30 -1
- package/dist-lib/menu.mjs +27 -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 +41 -4
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +138 -164
- package/dist-lib/swift.cjs +766 -24
- package/dist-lib/vite.cjs +103 -30
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +72 -5
- package/src/bridge-contract-input.ts +10 -0
- package/src/bridge-contract.ts +5 -0
- package/src/cloud-contract.ts +7 -1
- package/src/cloud.ts +45 -6
- package/src/connect.ts +1 -1
- package/src/menu.ts +44 -0
- package/src/swift.ts +2 -0
- package/src/vite-plugin-one.ts +12 -0
- package/src/vite-plugin-swift.ts +141 -39
package/cli/commands/platform.ts
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
} from 'sootsim-engine/settings'
|
|
15
15
|
import { settingsStore } from 'sootsim-engine/settings/store'
|
|
16
16
|
import { rnxPublicBrand } from '../../src/public-brand'
|
|
17
|
-
import { UnsupportedRemoteHostError } from '../cloud-client'
|
|
18
17
|
import { rethrowIfExit } from '../run-rnx'
|
|
18
|
+
import type { CloudSession } from '../cloud-session'
|
|
19
19
|
|
|
20
20
|
const PLATFORM_LABEL: Record<RuntimePlatform, string> = {
|
|
21
21
|
ios: 'iOS',
|
|
@@ -27,24 +27,23 @@ function isDeviceModel(value: string): value is DeviceModel {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// flags this command reads for itself rather than treating as the bundle.
|
|
30
|
-
const REMOTE_VALUE_FLAGS = new Set(['--device'
|
|
30
|
+
const REMOTE_VALUE_FLAGS = new Set(['--device'])
|
|
31
31
|
|
|
32
32
|
function remoteInputFromArgs(
|
|
33
33
|
args: string[],
|
|
34
34
|
platform: RuntimePlatform,
|
|
35
35
|
): {
|
|
36
36
|
bundlePath: string
|
|
37
|
-
|
|
37
|
+
reuse: boolean
|
|
38
|
+
fresh: boolean
|
|
38
39
|
} {
|
|
39
40
|
const positional: string[] = []
|
|
40
|
-
let name: string | null = null
|
|
41
41
|
for (let index = 0; index < args.length; index++) {
|
|
42
42
|
const arg = args[index]
|
|
43
|
-
if (arg === '--remote' || arg === '--
|
|
43
|
+
if (arg === '--remote' || arg === '--reuse' || arg === '--new') continue
|
|
44
44
|
if (REMOTE_VALUE_FLAGS.has(arg)) {
|
|
45
45
|
const value = args[++index] ?? null
|
|
46
46
|
if (!value) throw new Error(`rnx remote ${platform} ${arg} requires a value`)
|
|
47
|
-
if (arg === '--name') name = value
|
|
48
47
|
continue
|
|
49
48
|
}
|
|
50
49
|
const assigned = arg.match(/^(--[a-z-]+)=(.*)$/)
|
|
@@ -52,7 +51,6 @@ function remoteInputFromArgs(
|
|
|
52
51
|
const value = assigned[2] ?? ''
|
|
53
52
|
if (!value)
|
|
54
53
|
throw new Error(`rnx remote ${platform} ${assigned[1]} requires a value`)
|
|
55
|
-
if (assigned[1] === '--name') name = value
|
|
56
54
|
continue
|
|
57
55
|
}
|
|
58
56
|
if (
|
|
@@ -74,28 +72,15 @@ function remoteInputFromArgs(
|
|
|
74
72
|
}
|
|
75
73
|
if (positional.length !== 1 || !positional[0]) {
|
|
76
74
|
throw new Error(
|
|
77
|
-
`usage: rnx remote ${platform} <local bundle.js|https://bundle-url> [--device <model>]`,
|
|
75
|
+
`usage: rnx remote ${platform} <local bundle.js|https://bundle-url> [--device <model>] [--reuse|--new]`,
|
|
78
76
|
)
|
|
79
77
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* a bundle arrives as a file or a URL and neither carries a name, so the last
|
|
87
|
-
* path segment without its extension is the one thing both have. box names are
|
|
88
|
-
* lowercase words joined by dashes, so anything else in it becomes a dash.
|
|
89
|
-
*/
|
|
90
|
-
function boxNameForBundle(bundlePath: string): string {
|
|
91
|
-
const segment = bundlePath.split(/[?#]/)[0]?.split('/').filter(Boolean).at(-1) ?? ''
|
|
92
|
-
const stem = segment.replace(/\.[^.]+$/, '')
|
|
93
|
-
const name = stem
|
|
94
|
-
.toLowerCase()
|
|
95
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
96
|
-
.replace(/^-+|-+$/g, '')
|
|
97
|
-
if (!name) throw new Error(`rnx remote could not name a box after ${bundlePath}`)
|
|
98
|
-
return name
|
|
78
|
+
const reuse = args.includes('--reuse')
|
|
79
|
+
const fresh = args.includes('--new')
|
|
80
|
+
if (reuse && fresh) {
|
|
81
|
+
throw new Error(`rnx remote ${platform} cannot combine --reuse and --new`)
|
|
82
|
+
}
|
|
83
|
+
return { bundlePath: positional[0], reuse, fresh }
|
|
99
84
|
}
|
|
100
85
|
|
|
101
86
|
export async function runPlatformCommand(
|
|
@@ -144,112 +129,8 @@ export async function runPlatformCommand(
|
|
|
144
129
|
}
|
|
145
130
|
|
|
146
131
|
if (args.includes('--remote')) {
|
|
147
|
-
if (platform === 'android' && args.includes('--nano')) {
|
|
148
|
-
const error = new UnsupportedRemoteHostError('android', 'nano')
|
|
149
|
-
console.error(` ${error.message}`)
|
|
150
|
-
return 1
|
|
151
|
-
}
|
|
152
132
|
try {
|
|
153
|
-
|
|
154
|
-
if (!args.includes('--nano')) {
|
|
155
|
-
return await runRemoteBox(input, args, platform, selectedDevice)
|
|
156
|
-
}
|
|
157
|
-
const {
|
|
158
|
-
publishCloudSessionToShell,
|
|
159
|
-
readCloudSession,
|
|
160
|
-
requireCloudSessionShellUpdate,
|
|
161
|
-
} = await import('../cloud-session')
|
|
162
|
-
const descriptor = requireCloudSessionShellUpdate()
|
|
163
|
-
const { authHeaderOrExit, cloudAccountIdOrExit } = await import('../auth')
|
|
164
|
-
const { auth, header } = authHeaderOrExit(`${platform} --remote`)
|
|
165
|
-
const accountId = cloudAccountIdOrExit(auth)
|
|
166
|
-
const { closeCloudBox, createCloudBox, claimCloudSimulator, addCloudSimulator } =
|
|
167
|
-
await import('../cloud-client')
|
|
168
|
-
const artifact = await (
|
|
169
|
-
await import('../cloud-client')
|
|
170
|
-
).produceCloudArtifact(input.bundlePath)
|
|
171
|
-
const existingSession = readCloudSession()
|
|
172
|
-
if (
|
|
173
|
-
existingSession &&
|
|
174
|
-
existingSession.service === 'sim' &&
|
|
175
|
-
existingSession.platform === platform &&
|
|
176
|
-
existingSession.artifact?.sha256 &&
|
|
177
|
-
existingSession.artifact.sha256 === artifact.sha256 &&
|
|
178
|
-
args.includes('--reconnect')
|
|
179
|
-
) {
|
|
180
|
-
const claimed = await claimCloudSimulator(existingSession)
|
|
181
|
-
publishCloudSessionToShell(claimed.session, descriptor)
|
|
182
|
-
console.log(
|
|
183
|
-
` reconnected to remote simulator: ${existingSession.simId} (${platform})`,
|
|
184
|
-
)
|
|
185
|
-
console.log(` claim expires: ${claimed.claim.expiresAt}`)
|
|
186
|
-
console.log(
|
|
187
|
-
' commands: describe, find, get tree|node|count|memory, wait ready|selector, do, reset, logs, state, screenshot',
|
|
188
|
-
)
|
|
189
|
-
return 0
|
|
190
|
-
}
|
|
191
|
-
if (existingSession) {
|
|
192
|
-
if (
|
|
193
|
-
existingSession.service === 'sim' &&
|
|
194
|
-
(!existingSession.platform || existingSession.platform === platform) &&
|
|
195
|
-
(!existingSession.artifact ||
|
|
196
|
-
existingSession.artifact.sha256 === artifact.sha256 ||
|
|
197
|
-
args.includes('--new'))
|
|
198
|
-
) {
|
|
199
|
-
const added = await addCloudSimulator(existingSession, {
|
|
200
|
-
device: selectedDevice,
|
|
201
|
-
authorization: header,
|
|
202
|
-
accountId,
|
|
203
|
-
})
|
|
204
|
-
publishCloudSessionToShell(added.session, descriptor)
|
|
205
|
-
console.log(` remote simulator: ${added.receipt.simId}`)
|
|
206
|
-
console.log(` claim expires: ${added.receipt.claim.expiresAt}`)
|
|
207
|
-
console.log(
|
|
208
|
-
' commands: describe, find, get tree|node|count|memory, wait ready|selector, do, reset, logs, state, screenshot',
|
|
209
|
-
)
|
|
210
|
-
return 0
|
|
211
|
-
}
|
|
212
|
-
await closeCloudBox(existingSession)
|
|
213
|
-
publishCloudSessionToShell(null, descriptor)
|
|
214
|
-
}
|
|
215
|
-
const created = await createCloudBox({
|
|
216
|
-
bundlePath: input.bundlePath,
|
|
217
|
-
device: selectedDevice,
|
|
218
|
-
authorization: header,
|
|
219
|
-
accountId,
|
|
220
|
-
platform,
|
|
221
|
-
})
|
|
222
|
-
try {
|
|
223
|
-
publishCloudSessionToShell(created.session, descriptor)
|
|
224
|
-
} catch (error) {
|
|
225
|
-
await closeCloudBox(created.session)
|
|
226
|
-
throw error
|
|
227
|
-
}
|
|
228
|
-
const receipt = created.receipt
|
|
229
|
-
const produced = created.artifact
|
|
230
|
-
console.log(` remote simulator: ${receipt.simulator.simId}`)
|
|
231
|
-
console.log(` platform: ${platform}`)
|
|
232
|
-
console.log(` artifact: ${receipt.artifact.id} (${receipt.artifact.bytes} bytes)`)
|
|
233
|
-
console.log(
|
|
234
|
-
` source bundle: sha256:${produced.source.sha256} (${produced.source.bytes} bytes)`,
|
|
235
|
-
)
|
|
236
|
-
console.log(
|
|
237
|
-
` normalized modules: ${produced.modules}; javascript ${produced.javascript.sourceBytes} -> ${produced.javascript.minifiedBytes} bytes`,
|
|
238
|
-
)
|
|
239
|
-
console.log(
|
|
240
|
-
` embedded assets: ${produced.assets.descriptors} descriptors, ${produced.assets.variants} scales, ${produced.assets.bytes} bytes (${produced.assets.encodedBytes} encoded)`,
|
|
241
|
-
)
|
|
242
|
-
// the dest directory was chosen by resolving this bundle's descriptors
|
|
243
|
-
// against it, so say which one won rather than leaving it invisible.
|
|
244
|
-
if (created.assetsDirectory) {
|
|
245
|
-
console.log(` asset dest: ${created.assetsDirectory}`)
|
|
246
|
-
}
|
|
247
|
-
console.log(` claim expires: ${receipt.simulator.claim.expiresAt}`)
|
|
248
|
-
console.log(
|
|
249
|
-
' commands: describe, find, get tree|node|count|memory, wait ready|selector, do, reset, logs, state, screenshot',
|
|
250
|
-
)
|
|
251
|
-
for (const warning of created.warnings) console.warn(` warning: ${warning}`)
|
|
252
|
-
return 0
|
|
133
|
+
return await runRemoteCreate(platform, args, selectedDevice)
|
|
253
134
|
} catch (error) {
|
|
254
135
|
rethrowIfExit(error)
|
|
255
136
|
console.error(
|
|
@@ -304,130 +185,107 @@ export function runRemotePlatformCommand(
|
|
|
304
185
|
args: string[],
|
|
305
186
|
opts: { port?: number } = {},
|
|
306
187
|
): Promise<number | undefined> {
|
|
307
|
-
if (platform === 'android' && args.includes('--nano')) {
|
|
308
|
-
const error = new UnsupportedRemoteHostError('android', 'nano')
|
|
309
|
-
console.error(` ${error.message}`)
|
|
310
|
-
return Promise.resolve(1)
|
|
311
|
-
}
|
|
312
188
|
return runPlatformCommand(platform, ['--remote', ...args], opts)
|
|
313
189
|
}
|
|
314
190
|
|
|
191
|
+
const REMOTE_COMMANDS_HELP =
|
|
192
|
+
' commands: describe, find, get tree|node|count|memory, wait ready|selector, do, reset, logs, state, screenshot'
|
|
193
|
+
|
|
315
194
|
/**
|
|
316
|
-
* `rnx
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
* on this machine. it is then a Box created from an already-built app, which
|
|
321
|
-
* is what its page reads to boot the bundle instead of building one.
|
|
195
|
+
* `rnx remote ios|android <bundle>` on the one customer plane: one typed
|
|
196
|
+
* create through the instance API for both platforms, and a reattach to the
|
|
197
|
+
* shell's own simulator when it already runs this bundle. the box stays the
|
|
198
|
+
* host behind the API and is never created or addressed here.
|
|
322
199
|
*/
|
|
323
|
-
async function
|
|
324
|
-
|
|
200
|
+
async function runRemoteCreate(
|
|
201
|
+
platform: RuntimePlatform,
|
|
325
202
|
args: string[],
|
|
326
|
-
|
|
327
|
-
selectedDevice?: string,
|
|
203
|
+
selectedDevice: string,
|
|
328
204
|
): Promise<number> {
|
|
329
|
-
const
|
|
205
|
+
const input = remoteInputFromArgs(args, platform)
|
|
330
206
|
const [
|
|
331
|
-
{ produceCloudArtifact, closeCloudBox },
|
|
332
|
-
box,
|
|
333
|
-
{ cloudBoxConnectUrl, ensureCloudBoxBrowser, readCloudBoxSession },
|
|
334
207
|
{ authHeaderOrExit, cloudAccountIdOrExit },
|
|
335
208
|
{
|
|
336
|
-
|
|
209
|
+
claimCloudSimulator,
|
|
210
|
+
closeCloudSession,
|
|
211
|
+
createRemoteSim,
|
|
212
|
+
getRemoteSim,
|
|
213
|
+
produceCloudArtifact,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
createCloudSession,
|
|
337
217
|
publishCloudSessionToShell,
|
|
338
218
|
readCloudSession,
|
|
339
|
-
|
|
340
|
-
RNX_CLOUD_SESSION_ENV,
|
|
219
|
+
requireCloudSessionShellUpdate,
|
|
341
220
|
},
|
|
221
|
+
{ openInBrowser },
|
|
342
222
|
] = await Promise.all([
|
|
343
|
-
import('../cloud-client'),
|
|
344
|
-
import('./box'),
|
|
345
|
-
import('rnx-cloud-box/client'),
|
|
346
223
|
import('../auth'),
|
|
224
|
+
import('../cloud-client'),
|
|
347
225
|
import('../cloud-session'),
|
|
226
|
+
import('./box'),
|
|
348
227
|
])
|
|
349
|
-
const artifact = await produceCloudArtifact(input.bundlePath
|
|
350
|
-
const
|
|
351
|
-
|
|
228
|
+
const artifact = await produceCloudArtifact(input.bundlePath)
|
|
229
|
+
const descriptor = requireCloudSessionShellUpdate()
|
|
230
|
+
const { auth, header } = authHeaderOrExit(`${platform} --remote`)
|
|
231
|
+
const accountId = cloudAccountIdOrExit(auth)
|
|
352
232
|
|
|
353
233
|
const existingSession = readCloudSession()
|
|
354
234
|
if (
|
|
355
235
|
existingSession &&
|
|
356
|
-
|
|
357
|
-
existingSession.apiOrigin === endpoint &&
|
|
236
|
+
!input.fresh &&
|
|
358
237
|
existingSession.platform === platform &&
|
|
359
|
-
existingSession.device === selectedDevice &&
|
|
360
238
|
existingSession.artifact?.sha256 === artifact.sha256
|
|
361
239
|
) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
240
|
+
// an api key reads the simulator back through the account, which mints a
|
|
241
|
+
// fresh watch url with the read. any other credential rereads with the
|
|
242
|
+
// simulator's own token and keeps the stored watch url.
|
|
243
|
+
const live = await getRemoteSim({
|
|
244
|
+
apiOrigin: existingSession.apiOrigin,
|
|
245
|
+
simId: existingSession.simId,
|
|
246
|
+
authorization: auth.kind === 'api-key' ? header : `Bearer ${existingSession.token}`,
|
|
366
247
|
})
|
|
367
|
-
if (live.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
248
|
+
if (live && (live.status === 'open' || live.status === 'hibernated')) {
|
|
249
|
+
const claimed = await claimCloudSimulator(existingSession)
|
|
250
|
+
const streamUrl = live.streamUrl ?? existingSession.streamUrl ?? null
|
|
251
|
+
const session = createCloudSession({
|
|
252
|
+
...existingSession,
|
|
253
|
+
claimId: claimed.claim.id,
|
|
254
|
+
...(streamUrl ? { streamUrl } : {}),
|
|
373
255
|
})
|
|
256
|
+
publishCloudSessionToShell(session, descriptor)
|
|
374
257
|
console.log(
|
|
375
|
-
` reconnected to remote simulator: ${existingSession.
|
|
376
|
-
)
|
|
377
|
-
console.log(
|
|
378
|
-
' commands: describe, find, get tree|node|count|memory, wait ready|selector, do, reset, logs, state, screenshot',
|
|
258
|
+
` reconnected to remote simulator: ${existingSession.simId} (${platform})`,
|
|
379
259
|
)
|
|
260
|
+
console.log(` claim expires: ${claimed.claim.expiresAt}`)
|
|
261
|
+
console.log(REMOTE_COMMANDS_HELP)
|
|
380
262
|
return 0
|
|
381
263
|
}
|
|
382
264
|
}
|
|
383
265
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
} catch {
|
|
392
|
-
// no shell update channel is required outside an interactive rnx shell
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
const created = await box.createPrebuiltBundleBox({
|
|
396
|
-
bundle: new Uint8Array(artifact.bytes),
|
|
397
|
-
name: input.name ?? boxNameForBundle(input.bundlePath),
|
|
398
|
-
endpoint,
|
|
399
|
-
apiOrigin: box.cloudBoxApiOrigin(args, auth),
|
|
400
|
-
apiKey: header.replace(/^Bearer /, ''),
|
|
266
|
+
// the new session supersedes the old one, so the shell update channel
|
|
267
|
+
// carries exactly one update: the shell accepts nothing else.
|
|
268
|
+
if (existingSession) await closeCloudSession(existingSession)
|
|
269
|
+
const created = await createRemoteSim({
|
|
270
|
+
bundlePath: input.bundlePath,
|
|
271
|
+
device: selectedDevice,
|
|
272
|
+
authorization: header,
|
|
401
273
|
accountId,
|
|
402
|
-
})
|
|
403
|
-
// creating a box starts no browser, and only its Browser Run tab hosts the
|
|
404
|
-
// simulator, so without this the page opens onto a box nobody is running.
|
|
405
|
-
await ensureCloudBoxBrowser({
|
|
406
|
-
connectUrl: cloudBoxConnectUrl(endpoint, created.id),
|
|
407
|
-
accessToken: created.accessToken ?? '',
|
|
408
|
-
})
|
|
409
|
-
const produced = artifact.receipt
|
|
410
|
-
const session = createCloudBoxSession({
|
|
411
|
-
boxId: created.id,
|
|
412
|
-
boxToken: created.accessToken ?? '',
|
|
413
|
-
apiOrigin: endpoint,
|
|
414
274
|
platform,
|
|
415
|
-
|
|
416
|
-
artifact: {
|
|
417
|
-
sha256: artifact.sha256,
|
|
418
|
-
bytes: artifact.bytes.length,
|
|
419
|
-
},
|
|
275
|
+
reuse: input.reuse,
|
|
420
276
|
})
|
|
421
277
|
try {
|
|
422
|
-
publishCloudSessionToShell(session)
|
|
423
|
-
} catch {
|
|
424
|
-
|
|
278
|
+
publishCloudSessionToShell(created.session, descriptor)
|
|
279
|
+
} catch (error) {
|
|
280
|
+
await closeCloudSession(created.session)
|
|
281
|
+
throw error
|
|
425
282
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
console.log(`
|
|
283
|
+
const receipt = created.receipt
|
|
284
|
+
const produced = created.artifact
|
|
285
|
+
console.log(` remote simulator: ${receipt.simId}${created.reused ? ' (reused)' : ''}`)
|
|
429
286
|
console.log(` platform: ${platform}`)
|
|
430
|
-
|
|
287
|
+
console.log(` device: ${selectedDevice}`)
|
|
288
|
+
console.log(` artifact: ${receipt.artifact.id} (${receipt.artifact.bytes} bytes)`)
|
|
431
289
|
console.log(
|
|
432
290
|
` source bundle: sha256:${produced.source.sha256} (${produced.source.bytes} bytes)`,
|
|
433
291
|
)
|
|
@@ -439,20 +297,114 @@ async function runRemoteBox(
|
|
|
439
297
|
)
|
|
440
298
|
// the dest directory was chosen by resolving this bundle's descriptors
|
|
441
299
|
// against it, so say which one won rather than leaving it invisible.
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
|
|
300
|
+
if (created.assetsDirectory) {
|
|
301
|
+
console.log(` asset dest: ${created.assetsDirectory}`)
|
|
302
|
+
}
|
|
303
|
+
console.log(` claim expires: ${receipt.claim.expiresAt}`)
|
|
304
|
+
console.log(REMOTE_COMMANDS_HELP)
|
|
305
|
+
for (const warning of created.warnings) console.warn(` warning: ${warning}`)
|
|
306
|
+
if (!created.session.streamUrl) {
|
|
307
|
+
console.log(' the service minted no watch url for this simulator')
|
|
308
|
+
return 0
|
|
309
|
+
}
|
|
310
|
+
return openInBrowser(created.session.streamUrl)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** `rnx remote list`: the simulators this account runs that `rnx remote` made. */
|
|
314
|
+
export async function runRemoteList(args: string[]): Promise<number> {
|
|
315
|
+
try {
|
|
316
|
+
let json = false
|
|
317
|
+
for (const arg of args) {
|
|
318
|
+
if (arg === '--json') {
|
|
319
|
+
json = true
|
|
320
|
+
continue
|
|
321
|
+
}
|
|
322
|
+
throw new Error(`usage: rnx remote list [--json]`)
|
|
323
|
+
}
|
|
324
|
+
const [{ authHeaderOrExit, cloudAccountIdOrExit }, { listRemoteSims }] =
|
|
325
|
+
await Promise.all([import('../auth'), import('../cloud-client')])
|
|
326
|
+
const { auth, header } = authHeaderOrExit('remote list')
|
|
327
|
+
const accountId = cloudAccountIdOrExit(auth)
|
|
328
|
+
const sims = await listRemoteSims({ authorization: header, accountId })
|
|
329
|
+
if (json) {
|
|
330
|
+
console.log(JSON.stringify({ sims }))
|
|
331
|
+
return 0
|
|
332
|
+
}
|
|
333
|
+
if (sims.length === 0) {
|
|
334
|
+
console.log(' no remote simulators are running')
|
|
335
|
+
return 0
|
|
336
|
+
}
|
|
337
|
+
for (const sim of sims) {
|
|
338
|
+
const platformLabel = sim.labels['rnx.platform'] ?? 'unknown'
|
|
339
|
+
console.log(
|
|
340
|
+
` ${sim.simId} ${sim.status} ${platformLabel} sha256:${sim.artifact.sha256.slice(0, 12)}`,
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
return 0
|
|
344
|
+
} catch (error) {
|
|
345
|
+
rethrowIfExit(error)
|
|
346
|
+
console.error(
|
|
347
|
+
` ${rnxPublicBrand.commandName} remote list failed: ${
|
|
348
|
+
error instanceof Error ? error.message : String(error)
|
|
349
|
+
}`,
|
|
350
|
+
)
|
|
351
|
+
return 1
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** `rnx remote stop <id>`: delete one remote simulator by id. */
|
|
356
|
+
export async function runRemoteStop(args: string[]): Promise<number> {
|
|
357
|
+
try {
|
|
358
|
+
const positional = args.filter((arg) => !arg.startsWith('-'))
|
|
359
|
+
if (
|
|
360
|
+
args.some((arg) => arg.startsWith('-')) ||
|
|
361
|
+
positional.length !== 1 ||
|
|
362
|
+
!positional[0]
|
|
363
|
+
) {
|
|
364
|
+
throw new Error(`usage: rnx remote stop <sim-id>`)
|
|
365
|
+
}
|
|
366
|
+
const simId = positional[0]
|
|
367
|
+
const [
|
|
368
|
+
{ authHeaderOrExit, cloudAccountIdOrExit },
|
|
369
|
+
{ deleteRemoteSim },
|
|
370
|
+
{ publishCloudSessionToShell, readCloudSession, requireCloudSessionShellUpdate },
|
|
371
|
+
] = await Promise.all([
|
|
372
|
+
import('../auth'),
|
|
373
|
+
import('../cloud-client'),
|
|
374
|
+
import('../cloud-session'),
|
|
375
|
+
])
|
|
376
|
+
let current: CloudSession | null = null
|
|
377
|
+
try {
|
|
378
|
+
current = readCloudSession()
|
|
379
|
+
} catch {
|
|
380
|
+
current = null
|
|
381
|
+
}
|
|
382
|
+
// stopping the shell's own simulator clears the shell with it, so the
|
|
383
|
+
// update channel is required before anything is deleted.
|
|
384
|
+
const descriptor = current?.simId === simId ? requireCloudSessionShellUpdate() : null
|
|
385
|
+
const { auth, header } = authHeaderOrExit('remote stop')
|
|
386
|
+
const accountId = cloudAccountIdOrExit(auth)
|
|
387
|
+
const deleted = await deleteRemoteSim({
|
|
388
|
+
simId,
|
|
389
|
+
authorization: header,
|
|
390
|
+
accountId,
|
|
391
|
+
})
|
|
392
|
+
if (!deleted) {
|
|
393
|
+
console.error(
|
|
394
|
+
` ${rnxPublicBrand.commandName} remote stop: ${simId} does not exist`,
|
|
395
|
+
)
|
|
396
|
+
return 1
|
|
397
|
+
}
|
|
398
|
+
if (descriptor !== null) publishCloudSessionToShell(null, descriptor)
|
|
399
|
+
console.log(` stopped remote simulator: ${deleted.simId}`)
|
|
400
|
+
return 0
|
|
401
|
+
} catch (error) {
|
|
402
|
+
rethrowIfExit(error)
|
|
403
|
+
console.error(
|
|
404
|
+
` ${rnxPublicBrand.commandName} remote stop failed: ${
|
|
405
|
+
error instanceof Error ? error.message : String(error)
|
|
406
|
+
}`,
|
|
407
|
+
)
|
|
449
408
|
return 1
|
|
450
409
|
}
|
|
451
|
-
const pageUrl = new URL(created.description.pageUrl)
|
|
452
|
-
pageUrl.searchParams.set(
|
|
453
|
-
'device',
|
|
454
|
-
selectedDevice ?? DEFAULT_DEVICE_BY_PLATFORM[platform],
|
|
455
|
-
)
|
|
456
|
-
console.log(` open ${pageUrl}`)
|
|
457
|
-
return box.openInBrowser(pageUrl.toString())
|
|
458
410
|
}
|
package/cli/main.ts
CHANGED
|
@@ -242,6 +242,7 @@ const port = parsed.globalFlags['port'] as number | undefined
|
|
|
242
242
|
const verbose = parsed.verbose
|
|
243
243
|
const device = parsed.globalFlags['device'] as string | undefined
|
|
244
244
|
const theme = parsed.globalFlags['theme'] as string | undefined
|
|
245
|
+
const homeScreen = parsed.globalFlags['home-screen'] as string | undefined
|
|
245
246
|
const driver = parsed.globalFlags['driver'] as string | undefined
|
|
246
247
|
const headless = (parsed.globalFlags['headless'] as boolean | undefined) === true
|
|
247
248
|
const globalSimTarget = (parsed.globalFlags['sim'] ??
|
|
@@ -251,12 +252,13 @@ const commandArgsWithSim = globalSimTarget
|
|
|
251
252
|
? ['--sim', globalSimTarget, ...parsed.commandArgs]
|
|
252
253
|
: parsed.commandArgs
|
|
253
254
|
|
|
254
|
-
// apply device/theme to settings store if provided
|
|
255
|
-
if (device || theme) {
|
|
255
|
+
// apply device/theme/home-screen to settings store if provided
|
|
256
|
+
if (device || theme || homeScreen) {
|
|
256
257
|
const { settingsStore } = await import('sootsim-engine/settings/store')
|
|
257
258
|
const overrides: Record<string, any> = {}
|
|
258
259
|
if (device) overrides.deviceModel = device
|
|
259
260
|
if (theme) overrides.colorScheme = theme
|
|
261
|
+
if (homeScreen) overrides.homeScreenVersion = homeScreen
|
|
260
262
|
settingsStore.apply(overrides)
|
|
261
263
|
}
|
|
262
264
|
|
|
@@ -515,6 +517,12 @@ try {
|
|
|
515
517
|
{ port },
|
|
516
518
|
)
|
|
517
519
|
await exitWithFlush(typeof code === 'number' ? code : 0)
|
|
520
|
+
} else if (platform === 'list') {
|
|
521
|
+
const { runRemoteList } = await import('./commands/platform')
|
|
522
|
+
await exitWithFlush(await runRemoteList(parsed.commandArgs.slice(1)))
|
|
523
|
+
} else if (platform === 'stop') {
|
|
524
|
+
const { runRemoteStop } = await import('./commands/platform')
|
|
525
|
+
await exitWithFlush(await runRemoteStop(parsed.commandArgs.slice(1)))
|
|
518
526
|
} else {
|
|
519
527
|
console.error(
|
|
520
528
|
` usage: ${rnxPublicBrand.commandName} remote ios|android <bundle.js|https://bundle-url>`,
|
|
@@ -140,18 +140,33 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
140
140
|
category: 'rnx_cloud_sim',
|
|
141
141
|
count: 1,
|
|
142
142
|
},
|
|
143
|
-
'packages/sootsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/boxes/${encodeURIComponent(session.boxId)}/simulators`':
|
|
144
|
-
{ category: 'rnx_cloud_sim', count: 1 },
|
|
145
143
|
'packages/sootsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/sims/${encodeURIComponent(session.simId)}/claim`':
|
|
146
144
|
{ category: 'rnx_cloud_sim', count: 1 },
|
|
147
|
-
|
|
145
|
+
// closing a nano-box simulator deletes its box; the local names changed
|
|
146
|
+
// with the close but the destination did not.
|
|
147
|
+
'packages/sootsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/boxes/${encodeURIComponent(boxId)}`':
|
|
148
148
|
{ category: 'rnx_cloud_sim', count: 1 },
|
|
149
|
-
|
|
149
|
+
// closing an instance simulator deletes it by its own id with its own token.
|
|
150
|
+
'packages/sootsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/sims/${encodeURIComponent(session.simId)}`':
|
|
150
151
|
{ category: 'rnx_cloud_sim', count: 1 },
|
|
152
|
+
// `rnx remote` creates through the instance api and lists what it made.
|
|
153
|
+
// the create carries labels and wait=true in the query; the list filters
|
|
154
|
+
// by the remote label.
|
|
155
|
+
'packages/sootsim/cli/cloud-client.ts :: fetch :: `${apiOrigin}/v1/sims?${query}`': {
|
|
156
|
+
category: 'rnx_cloud_sim',
|
|
157
|
+
count: 1,
|
|
158
|
+
},
|
|
159
|
+
'packages/sootsim/cli/cloud-client.ts :: fetch :: `${apiOrigin}/v1/sims?${selector}`': {
|
|
160
|
+
category: 'rnx_cloud_sim',
|
|
161
|
+
count: 1,
|
|
162
|
+
},
|
|
163
|
+
// rereading one simulator and deleting one by id share the route: the
|
|
164
|
+
// reattach probe reads with either credential and `rnx remote stop`
|
|
165
|
+
// deletes with the account credential.
|
|
166
|
+
'packages/sootsim/cli/cloud-client.ts :: fetch :: `${apiOrigin}/v1/sims/${encodeURIComponent(options.simId)}`':
|
|
167
|
+
{ category: 'rnx_cloud_sim', count: 2 },
|
|
151
168
|
'packages/sootsim/cli/cloud-client.ts :: fetch :: `${this.session.apiOrigin}/v1/sims/${encodeURIComponent(this.session.simId)}/commands`':
|
|
152
169
|
{ category: 'rnx_cloud_sim', count: 1 },
|
|
153
|
-
'packages/sootsim/cli/cloud-client.ts :: fetch :: `${this.session.apiOrigin}/v1/boxes/${encodeURIComponent(this.session.boxId)}/commands`':
|
|
154
|
-
{ category: 'rnx_cloud_sim', count: 1 },
|
|
155
170
|
'packages/sootsim/cli/cloud-client.ts :: fetch :: bundleUrl': {
|
|
156
171
|
category: 'guest_app_network',
|
|
157
172
|
count: 1,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.521 | (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.
|
|
1
|
+
/*! rnx v0.1.521 | (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.
|
|
1
|
+
/*! rnx v0.1.521 | (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.
|
|
1
|
+
/*! rnx v0.1.521 | (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.
|
|
1
|
+
/*! rnx v0.1.521 | (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.
|
|
1
|
+
/*! rnx v0.1.521 | (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.
|
|
1
|
+
/*! rnx v0.1.521 | (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/beta.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.521 | (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/beta.mjs
CHANGED