rnxsim 0.1.431 → 0.1.433
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/README.md +20 -50
- package/cli/auth.ts +33 -3
- package/cli/cloud-client.ts +524 -88
- package/cli/cloud-dispatch.ts +5 -10
- package/cli/cloud-session.ts +5 -5
- package/cli/commands/auth.ts +13 -2
- package/cli/commands/box.ts +189 -20
- package/cli/commands/control.ts +40 -13
- package/cli/commands/device.ts +1 -1
- package/cli/commands/flow.ts +189 -43
- package/cli/commands/inspect/get-layout.ts +1 -1
- package/cli/commands/inspect.ts +8 -1
- package/cli/commands/login.ts +79 -14
- package/cli/commands/maestro-generate.ts +70 -54
- package/cli/commands/maestro.ts +69 -20
- package/cli/commands/platform.ts +30 -32
- package/cli/commands/screenshot.ts +3 -2
- package/cli/commands/upload.ts +28 -10
- package/cli/help-core.ts +11 -1
- package/cli/outbound-endpoints.ts +27 -5
- package/cli/parse-args.ts +17 -0
- package/cli/shell-init.ts +4 -4
- package/cli/ws-bridge.ts +109 -62
- 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 +17 -4
- 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/cloud.cjs +108 -93
- package/dist-lib/cloud.mjs +106 -91
- 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 +58 -139
- 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 +33 -212
- 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 +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +436 -351
- package/dist-lib/vite.cjs +1 -1
- package/package.json +2 -1
- package/src/auth/shared-session.ts +31 -0
- package/src/cloud.ts +83 -113
- package/src/metro-plugin.ts +30 -113
- package/src/vite-plugin.ts +11 -2
package/dist-lib/vite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.433 | (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.
|
|
3
|
+
"version": "0.1.433",
|
|
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",
|
|
@@ -234,6 +234,7 @@
|
|
|
234
234
|
"esbuild": "^0.25.0",
|
|
235
235
|
"jiti": "2.7.0",
|
|
236
236
|
"playwright-core": "1.61.0",
|
|
237
|
+
"terser": "^5.49.0",
|
|
237
238
|
"ws": "^8.18.0"
|
|
238
239
|
},
|
|
239
240
|
"peerDependencies": {
|
|
@@ -22,6 +22,8 @@ export type SharedDesktopAuthUser = {
|
|
|
22
22
|
email?: string
|
|
23
23
|
image?: string
|
|
24
24
|
githubUsername?: string
|
|
25
|
+
/** the person's own account; every team they belong to is in `teams` */
|
|
26
|
+
personalAccountId?: string
|
|
25
27
|
teams?: SharedDesktopAuthTeamSummary[]
|
|
26
28
|
isAdmin?: boolean
|
|
27
29
|
}
|
|
@@ -34,6 +36,24 @@ export type SharedDesktopAuthSession = {
|
|
|
34
36
|
source: 'cli' | 'electron' | 'browser' | 'unknown'
|
|
35
37
|
updatedAt: string
|
|
36
38
|
validatedAt?: string
|
|
39
|
+
/**
|
|
40
|
+
* the account this sign-in bills cloud work to. a session token names the
|
|
41
|
+
* person, not an account, so the CLI chooses one at `rnx login` and names it
|
|
42
|
+
* on every request that opens a paid session.
|
|
43
|
+
*/
|
|
44
|
+
accountId?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** the accounts a signed-in person can bill: their own, then their teams. */
|
|
48
|
+
export function sharedDesktopAuthAccounts(
|
|
49
|
+
user: SharedDesktopAuthUser | null | undefined,
|
|
50
|
+
): { id: string; name: string }[] {
|
|
51
|
+
return [
|
|
52
|
+
...(user?.personalAccountId
|
|
53
|
+
? [{ id: user.personalAccountId, name: 'Personal' }]
|
|
54
|
+
: []),
|
|
55
|
+
...(user?.teams ?? []).map((team) => ({ id: team.id, name: team.name })),
|
|
56
|
+
]
|
|
37
57
|
}
|
|
38
58
|
|
|
39
59
|
const SESSION_VERSION = 1 as const
|
|
@@ -65,6 +85,10 @@ function normalizeUser(input: unknown): SharedDesktopAuthUser | null {
|
|
|
65
85
|
image: typeof value.image === 'string' ? value.image : undefined,
|
|
66
86
|
githubUsername:
|
|
67
87
|
typeof value.githubUsername === 'string' ? value.githubUsername : undefined,
|
|
88
|
+
personalAccountId:
|
|
89
|
+
typeof value.personalAccountId === 'string' && value.personalAccountId.trim()
|
|
90
|
+
? value.personalAccountId.trim()
|
|
91
|
+
: undefined,
|
|
68
92
|
isAdmin: value.isAdmin === true,
|
|
69
93
|
teams: Array.isArray(value.teams)
|
|
70
94
|
? value.teams
|
|
@@ -108,6 +132,10 @@ function normalizeSession(input: unknown): SharedDesktopAuthSession | null {
|
|
|
108
132
|
typeof value.validatedAt === 'string' && value.validatedAt
|
|
109
133
|
? value.validatedAt
|
|
110
134
|
: undefined
|
|
135
|
+
const accountId =
|
|
136
|
+
typeof value.accountId === 'string' && value.accountId.trim()
|
|
137
|
+
? value.accountId.trim()
|
|
138
|
+
: undefined
|
|
111
139
|
|
|
112
140
|
return {
|
|
113
141
|
version: SESSION_VERSION,
|
|
@@ -117,6 +145,7 @@ function normalizeSession(input: unknown): SharedDesktopAuthSession | null {
|
|
|
117
145
|
source,
|
|
118
146
|
updatedAt,
|
|
119
147
|
validatedAt,
|
|
148
|
+
accountId,
|
|
120
149
|
}
|
|
121
150
|
}
|
|
122
151
|
|
|
@@ -153,6 +182,7 @@ export function writeSharedDesktopAuthSession(
|
|
|
153
182
|
source: session.source,
|
|
154
183
|
updatedAt: session.updatedAt || new Date().toISOString(),
|
|
155
184
|
validatedAt: session.validatedAt,
|
|
185
|
+
accountId: session.accountId?.trim() || undefined,
|
|
156
186
|
}
|
|
157
187
|
writeFileSync(filepath, JSON.stringify(normalized, null, 2) + '\n')
|
|
158
188
|
try {
|
|
@@ -197,6 +227,7 @@ export async function refreshSharedDesktopAuthSession(
|
|
|
197
227
|
origin,
|
|
198
228
|
source: current.source,
|
|
199
229
|
validatedAt: new Date().toISOString(),
|
|
230
|
+
accountId: current.accountId,
|
|
200
231
|
})
|
|
201
232
|
} catch {
|
|
202
233
|
return current
|
package/src/cloud.ts
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
// typed
|
|
1
|
+
// typed Remote Simulators client for agents. the CLI produces artifact bytes
|
|
2
2
|
// (local file or a fetched URL); this module talks to the service in those
|
|
3
3
|
// bytes only. command types and receipts are the same ones the CLI already
|
|
4
4
|
// sends. do not invent a second vocabulary.
|
|
5
|
+
//
|
|
6
|
+
// one call gives you one remote simulator. it runs on a nano box today, which
|
|
7
|
+
// is why the session below carries a box id and the create request asks for a
|
|
8
|
+
// size, but that is the service's business: nothing box-shaped reaches the
|
|
9
|
+
// public surface.
|
|
5
10
|
|
|
6
11
|
import { isLoopbackHost } from './backend-origin'
|
|
12
|
+
import { isRnxScreenCapture, type RnxScreenCapture } from './capture-contract'
|
|
7
13
|
import {
|
|
8
14
|
isRnxCloudCommandType,
|
|
9
15
|
RNX_CLOUD_COMMAND_TYPES,
|
|
10
16
|
RNX_CLOUD_MAX_ARTIFACT_BYTES,
|
|
11
17
|
type RnxCloudBoxCreateReceipt,
|
|
12
18
|
type RnxCloudClaim,
|
|
19
|
+
type RnxCloudCreateReceipt,
|
|
13
20
|
} from './cloud-contract'
|
|
14
21
|
import { rnxPublicBrand } from './public-brand'
|
|
15
22
|
|
|
16
23
|
export { isRnxCloudCommandType, RNX_CLOUD_COMMAND_TYPES, RNX_CLOUD_MAX_ARTIFACT_BYTES }
|
|
17
|
-
export type {
|
|
24
|
+
export type { RnxCloudClaim, RnxCloudCreateReceipt, RnxScreenCapture }
|
|
18
25
|
|
|
19
26
|
const DEFAULT_DEVICE = 'iphone-16'
|
|
20
27
|
const DEFAULT_WAIT_READY_MS = 20_000
|
|
21
28
|
|
|
22
|
-
export type
|
|
29
|
+
export type RemoteCommand = {
|
|
23
30
|
type: (typeof RNX_CLOUD_COMMAND_TYPES)[number]
|
|
24
31
|
[key: string]: unknown
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
export interface
|
|
34
|
+
export interface CreateRemoteSimulatorInput {
|
|
28
35
|
artifact: Uint8Array
|
|
29
36
|
authorization: string
|
|
30
37
|
origin?: string
|
|
@@ -32,20 +39,33 @@ export interface CreateNanoBoxInput {
|
|
|
32
39
|
sha256?: string
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
// the simulator-driving verbs. the same vocabulary the CLI drives, one for
|
|
43
|
+
// one: `rnx describe`, `rnx do tap`, `rnx wait selector`.
|
|
44
|
+
export interface RnxCommands {
|
|
45
|
+
command(
|
|
46
|
+
command: { type: string; [key: string]: unknown },
|
|
47
|
+
options?: { timeoutMs?: number },
|
|
48
|
+
): Promise<unknown>
|
|
49
|
+
describe(): Promise<unknown>
|
|
50
|
+
do(command: { type: string; [key: string]: unknown }): Promise<unknown>
|
|
51
|
+
wait(
|
|
52
|
+
condition: 'ready' | { type: 'selector'; selector: string },
|
|
53
|
+
options?: { maxMs?: number },
|
|
54
|
+
): Promise<unknown>
|
|
55
|
+
// a `data:image/png;base64,` url, assembled by the service from the frame
|
|
56
|
+
screenshot(options?: { layers?: 'full' | 'tenant' | 'shell' }): Promise<string>
|
|
57
|
+
capture(): Promise<RnxScreenCapture>
|
|
58
|
+
reset(): Promise<unknown>
|
|
38
59
|
}
|
|
39
60
|
|
|
40
|
-
interface
|
|
61
|
+
interface RemoteSimulatorSession {
|
|
41
62
|
origin: string
|
|
42
|
-
authorization: string
|
|
43
63
|
boxId: string
|
|
44
64
|
boxToken: string
|
|
45
65
|
simId: string
|
|
46
66
|
simulatorToken: string
|
|
47
67
|
claim: RnxCloudClaim
|
|
48
|
-
artifact:
|
|
68
|
+
artifact: RnxCloudCreateReceipt['artifact']
|
|
49
69
|
}
|
|
50
70
|
|
|
51
71
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -221,7 +241,7 @@ function readBoxCreate(
|
|
|
221
241
|
}
|
|
222
242
|
}
|
|
223
243
|
|
|
224
|
-
function cloudCommand(command: { type: string; [key: string]: unknown }):
|
|
244
|
+
function cloudCommand(command: { type: string; [key: string]: unknown }): RemoteCommand {
|
|
225
245
|
const clean: Record<string, unknown> = {}
|
|
226
246
|
for (const [key, value] of Object.entries(command)) {
|
|
227
247
|
if (key !== 'id' && key !== 'simId') clean[key] = value
|
|
@@ -232,15 +252,50 @@ function cloudCommand(command: { type: string; [key: string]: unknown }): NanoCo
|
|
|
232
252
|
return { ...clean, type: command.type }
|
|
233
253
|
}
|
|
234
254
|
|
|
235
|
-
export class
|
|
236
|
-
private session:
|
|
255
|
+
export class RemoteSimulator {
|
|
256
|
+
private session: RemoteSimulatorSession
|
|
237
257
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
// driving verbs live here so the client reads the way the CLI does. bound to
|
|
259
|
+
// this simulator at construction, and every verb reads the session live, so a
|
|
260
|
+
// confirmed claim reaches commands issued after it.
|
|
261
|
+
readonly rnx: RnxCommands
|
|
241
262
|
|
|
242
|
-
|
|
243
|
-
|
|
263
|
+
constructor(session: RemoteSimulatorSession) {
|
|
264
|
+
this.session = session
|
|
265
|
+
this.rnx = {
|
|
266
|
+
command: (command, options) => this.sendCommand(command, options),
|
|
267
|
+
describe: () => this.sendCommand({ type: 'tree' }),
|
|
268
|
+
do: (command) => this.sendCommand(command),
|
|
269
|
+
wait: (condition, options) => {
|
|
270
|
+
const timeoutMs = options?.maxMs ?? DEFAULT_WAIT_READY_MS
|
|
271
|
+
const waitCondition = condition === 'ready' ? { type: 'ready' } : condition
|
|
272
|
+
return this.sendCommand(
|
|
273
|
+
{
|
|
274
|
+
type: 'waitFor',
|
|
275
|
+
waitForOptions: { condition: waitCondition, timeoutMs },
|
|
276
|
+
},
|
|
277
|
+
{ timeoutMs: timeoutMs + 1_000 },
|
|
278
|
+
)
|
|
279
|
+
},
|
|
280
|
+
screenshot: async (options) => {
|
|
281
|
+
const result = await this.sendCommand({
|
|
282
|
+
type: 'screenshot',
|
|
283
|
+
layers: options?.layers ?? 'tenant',
|
|
284
|
+
})
|
|
285
|
+
if (typeof result !== 'string' || !result.startsWith('data:image/')) {
|
|
286
|
+
throw new Error('RNX Cloud screenshot did not return an image data url')
|
|
287
|
+
}
|
|
288
|
+
return result
|
|
289
|
+
},
|
|
290
|
+
capture: async () => {
|
|
291
|
+
const result = await this.sendCommand({ type: 'capture' })
|
|
292
|
+
if (!isRnxScreenCapture(result)) {
|
|
293
|
+
throw new Error('RNX Cloud capture returned an invalid screen capture')
|
|
294
|
+
}
|
|
295
|
+
return result
|
|
296
|
+
},
|
|
297
|
+
reset: () => this.sendCommand({ type: 'reset' }),
|
|
298
|
+
}
|
|
244
299
|
}
|
|
245
300
|
|
|
246
301
|
get simId(): string {
|
|
@@ -251,20 +306,15 @@ export class NanoBox {
|
|
|
251
306
|
return this.session.claim
|
|
252
307
|
}
|
|
253
308
|
|
|
254
|
-
get receipt():
|
|
309
|
+
get receipt(): RnxCloudCreateReceipt {
|
|
255
310
|
return {
|
|
256
|
-
|
|
257
|
-
size: 'nano',
|
|
311
|
+
simId: this.session.simId,
|
|
258
312
|
artifact: this.session.artifact,
|
|
259
|
-
|
|
260
|
-
simId: this.session.simId,
|
|
261
|
-
artifact: this.session.artifact,
|
|
262
|
-
claim: this.session.claim,
|
|
263
|
-
},
|
|
313
|
+
claim: this.session.claim,
|
|
264
314
|
}
|
|
265
315
|
}
|
|
266
316
|
|
|
267
|
-
async
|
|
317
|
+
private async sendCommand(
|
|
268
318
|
command: { type: string; [key: string]: unknown },
|
|
269
319
|
options?: { timeoutMs?: number },
|
|
270
320
|
): Promise<unknown> {
|
|
@@ -314,44 +364,6 @@ export class NanoBox {
|
|
|
314
364
|
return parsed.result
|
|
315
365
|
}
|
|
316
366
|
|
|
317
|
-
describe(): Promise<unknown> {
|
|
318
|
-
return this.command({ type: 'tree' })
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
do(command: { type: string; [key: string]: unknown }): Promise<unknown> {
|
|
322
|
-
return this.command(command)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
wait(
|
|
326
|
-
condition: 'ready' | { type: 'selector'; selector: string },
|
|
327
|
-
options?: { maxMs?: number },
|
|
328
|
-
): Promise<unknown> {
|
|
329
|
-
const timeoutMs = options?.maxMs ?? DEFAULT_WAIT_READY_MS
|
|
330
|
-
const waitCondition = condition === 'ready' ? { type: 'ready' } : condition
|
|
331
|
-
return this.command(
|
|
332
|
-
{
|
|
333
|
-
type: 'waitFor',
|
|
334
|
-
waitForOptions: { condition: waitCondition, timeoutMs },
|
|
335
|
-
},
|
|
336
|
-
{ timeoutMs: timeoutMs + 1_000 },
|
|
337
|
-
)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
screenshot(options?: { layers?: 'full' | 'tenant' | 'shell' }): Promise<unknown> {
|
|
341
|
-
return this.command({
|
|
342
|
-
type: 'screenshot',
|
|
343
|
-
layers: options?.layers ?? 'tenant',
|
|
344
|
-
})
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
capture(): Promise<unknown> {
|
|
348
|
-
return this.command({ type: 'capture' })
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
reset(): Promise<unknown> {
|
|
352
|
-
return this.command({ type: 'reset' })
|
|
353
|
-
}
|
|
354
|
-
|
|
355
367
|
async confirm(): Promise<RnxCloudClaim> {
|
|
356
368
|
const response = await fetch(
|
|
357
369
|
`${this.session.origin}/v1/sims/${encodeURIComponent(this.session.simId)}/claim`,
|
|
@@ -380,49 +392,6 @@ export class NanoBox {
|
|
|
380
392
|
return claim
|
|
381
393
|
}
|
|
382
394
|
|
|
383
|
-
async addSimulator(input: AddNanoSimulatorInput = {}): Promise<NanoBox> {
|
|
384
|
-
const device = input.device?.trim() || DEFAULT_DEVICE
|
|
385
|
-
const response = await fetch(
|
|
386
|
-
`${this.session.origin}/v1/boxes/${encodeURIComponent(this.session.boxId)}/simulators`,
|
|
387
|
-
{
|
|
388
|
-
method: 'POST',
|
|
389
|
-
headers: {
|
|
390
|
-
authorization: `Bearer ${this.session.boxToken}`,
|
|
391
|
-
'x-rnx-account-authorization': this.session.authorization,
|
|
392
|
-
'content-type': 'application/json',
|
|
393
|
-
},
|
|
394
|
-
body: JSON.stringify({
|
|
395
|
-
device,
|
|
396
|
-
...(input.storageFrom === undefined ? {} : { storageFrom: input.storageFrom }),
|
|
397
|
-
}),
|
|
398
|
-
},
|
|
399
|
-
)
|
|
400
|
-
const text = await response.text()
|
|
401
|
-
if (!response.ok) throw responseError(response.status, text)
|
|
402
|
-
let parsed: unknown
|
|
403
|
-
try {
|
|
404
|
-
parsed = JSON.parse(text)
|
|
405
|
-
} catch {
|
|
406
|
-
throw new Error('RNX Cloud simulator create returned unreadable JSON')
|
|
407
|
-
}
|
|
408
|
-
if (!isRecord(parsed) || parsed.boxId !== this.session.boxId) {
|
|
409
|
-
throw new Error('RNX Cloud simulator create returned an invalid box id')
|
|
410
|
-
}
|
|
411
|
-
const simulator = readSimulatorCreate(
|
|
412
|
-
parsed.simulator,
|
|
413
|
-
this.session.artifact.id,
|
|
414
|
-
this.session.artifact.bytes,
|
|
415
|
-
)
|
|
416
|
-
const pooled = new NanoBox({
|
|
417
|
-
...this.session,
|
|
418
|
-
simId: simulator.simId,
|
|
419
|
-
simulatorToken: simulator.token,
|
|
420
|
-
claim: simulator.claim,
|
|
421
|
-
})
|
|
422
|
-
await pooled.confirm()
|
|
423
|
-
return pooled
|
|
424
|
-
}
|
|
425
|
-
|
|
426
395
|
async close(): Promise<void> {
|
|
427
396
|
const response = await fetch(
|
|
428
397
|
`${this.session.origin}/v1/boxes/${encodeURIComponent(this.session.boxId)}`,
|
|
@@ -438,7 +407,9 @@ export class NanoBox {
|
|
|
438
407
|
}
|
|
439
408
|
}
|
|
440
409
|
|
|
441
|
-
export async function
|
|
410
|
+
export async function createRemoteSimulator(
|
|
411
|
+
input: CreateRemoteSimulatorInput,
|
|
412
|
+
): Promise<RemoteSimulator> {
|
|
442
413
|
if (input.artifact.byteLength > RNX_CLOUD_MAX_ARTIFACT_BYTES) {
|
|
443
414
|
throw new Error(
|
|
444
415
|
`rnx Cloud artifact is ${input.artifact.byteLength} bytes and exceeds the ${RNX_CLOUD_MAX_ARTIFACT_BYTES}-byte limit`,
|
|
@@ -484,9 +455,8 @@ export async function createNanoBox(input: CreateNanoBoxInput): Promise<NanoBox>
|
|
|
484
455
|
throw new Error('RNX Cloud create returned unreadable JSON')
|
|
485
456
|
}
|
|
486
457
|
const created = readBoxCreate(parsed, input.artifact.byteLength, sha256)
|
|
487
|
-
const
|
|
458
|
+
const simulator = new RemoteSimulator({
|
|
488
459
|
origin,
|
|
489
|
-
authorization: input.authorization,
|
|
490
460
|
boxId: created.receipt.boxId,
|
|
491
461
|
boxToken: created.boxToken,
|
|
492
462
|
simId: created.receipt.simulator.simId,
|
|
@@ -494,6 +464,6 @@ export async function createNanoBox(input: CreateNanoBoxInput): Promise<NanoBox>
|
|
|
494
464
|
claim: created.receipt.simulator.claim,
|
|
495
465
|
artifact: created.receipt.artifact,
|
|
496
466
|
})
|
|
497
|
-
await
|
|
498
|
-
return
|
|
467
|
+
await simulator.confirm()
|
|
468
|
+
return simulator
|
|
499
469
|
}
|
package/src/metro-plugin.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// rnx metro plugin for
|
|
1
|
+
// rnx metro plugin for dev shell and bundle module identity annotation.
|
|
2
2
|
// shell assets come from an engine runtime under ~/.rnx/runtimes/, fetched
|
|
3
3
|
// by `rnx runtime install`. ordinary localhost requests
|
|
4
4
|
// follow active; a version-owned localhost origin stays on its exact directory
|
|
@@ -6,19 +6,13 @@
|
|
|
6
6
|
//
|
|
7
7
|
// usage in metro.config.js:
|
|
8
8
|
// const { withRNX } = require('rnxsim/metro')
|
|
9
|
-
// module.exports = withRNX(getDefaultConfig(__dirname)
|
|
9
|
+
// module.exports = withRNX(getDefaultConfig(__dirname))
|
|
10
10
|
|
|
11
11
|
import fs from 'fs'
|
|
12
12
|
import { createRequire } from 'module'
|
|
13
13
|
import { randomUUID } from 'node:crypto'
|
|
14
14
|
import path from 'path'
|
|
15
|
-
import {
|
|
16
|
-
openRnxDesktopWhenMetroIsReady,
|
|
17
|
-
resolveRnxDesktopAppName,
|
|
18
|
-
resolveRnxDesktopLaunchTarget,
|
|
19
|
-
tagRnxBundleForPlugin,
|
|
20
|
-
type RnxDesktopOpenOption,
|
|
21
|
-
} from './dev-server-open'
|
|
15
|
+
import { tagRnxBundleForPlugin } from './dev-server-open'
|
|
22
16
|
import {
|
|
23
17
|
handleReplacementModuleRequest,
|
|
24
18
|
isReplacementModuleRequestUrl,
|
|
@@ -71,26 +65,6 @@ type MetroSerializer = (
|
|
|
71
65
|
options: MetroSerializerOptions,
|
|
72
66
|
) => MetroSerializerResult | Promise<MetroSerializerResult>
|
|
73
67
|
|
|
74
|
-
export interface RNXMetroOptions {
|
|
75
|
-
bundleUrl?: string
|
|
76
|
-
devServer?: boolean
|
|
77
|
-
open?: RnxDesktopOpenOption
|
|
78
|
-
/**
|
|
79
|
-
* append the `moduleId -> sourcePath` identity footer to every production
|
|
80
|
-
* bundle. resolution is untouched, so the annotated artifact is the same
|
|
81
|
-
* bundle the app ships and rnx can load it directly.
|
|
82
|
-
*/
|
|
83
|
-
productionBundles?: boolean
|
|
84
|
-
/**
|
|
85
|
-
* append the `moduleId -> sourcePath` identity footer to every development
|
|
86
|
-
* bundle. resolution and transforms stay ordinary, while Metro keeps verbose
|
|
87
|
-
* module names and its source map.
|
|
88
|
-
*/
|
|
89
|
-
developmentBundles?: boolean
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// the slice of metro's config we touch — keeps `T` open for whatever
|
|
93
|
-
// shape consumers pass while letting us extend `server.enhanceMiddleware`.
|
|
94
68
|
type MetroConfigWithServer = {
|
|
95
69
|
projectRoot?: string
|
|
96
70
|
serializer?: {
|
|
@@ -160,7 +134,7 @@ function appendRNXModulePaths(
|
|
|
160
134
|
const dependencies = graph.dependencies
|
|
161
135
|
const createModuleId = options.createModuleId
|
|
162
136
|
if (!dependencies || !createModuleId) {
|
|
163
|
-
|
|
137
|
+
return result
|
|
164
138
|
}
|
|
165
139
|
|
|
166
140
|
const modulePaths: Record<string, string> = {}
|
|
@@ -208,23 +182,9 @@ function appendRNXModulePaths(
|
|
|
208
182
|
: { ...result, code: appendRNXMetroModuleIdentityFooter(result.code, identity) }
|
|
209
183
|
}
|
|
210
184
|
|
|
211
|
-
export function withRNX<T extends Record<string, unknown>>(
|
|
212
|
-
config: T,
|
|
213
|
-
options: RNXMetroOptions = {},
|
|
214
|
-
): T {
|
|
215
|
-
if (
|
|
216
|
-
!options.devServer &&
|
|
217
|
-
!options.open &&
|
|
218
|
-
!options.productionBundles &&
|
|
219
|
-
!options.developmentBundles
|
|
220
|
-
) {
|
|
221
|
-
return config
|
|
222
|
-
}
|
|
223
|
-
|
|
185
|
+
export function withRNX<T extends Record<string, unknown>>(config: T): T {
|
|
224
186
|
const cfgProjectRoot = (config as MetroConfigWithServer).projectRoot || process.cwd()
|
|
225
|
-
const
|
|
226
|
-
const configuredBundleUrl =
|
|
227
|
-
options.bundleUrl || '/index.bundle?platform=ios&dev=true&hot=true&minify=false'
|
|
187
|
+
const configuredBundleUrl = '/index.bundle?platform=ios&dev=true&hot=true&minify=false'
|
|
228
188
|
|
|
229
189
|
const cfgServer = (config as MetroConfigWithServer).server
|
|
230
190
|
const port = resolveMetroPort(cfgServer)
|
|
@@ -234,42 +194,29 @@ export function withRNX<T extends Record<string, unknown>>(
|
|
|
234
194
|
`http://localhost:${port}`,
|
|
235
195
|
metroServerId,
|
|
236
196
|
cfgProjectRoot,
|
|
237
|
-
appName,
|
|
238
197
|
)
|
|
239
198
|
const cfgSerializer = (config as MetroConfigWithServer).serializer
|
|
240
199
|
const existingEnhance = cfgServer?.enhanceMiddleware
|
|
241
200
|
const existingSerializer = cfgSerializer?.customSerializer
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const result = await serializer(
|
|
260
|
-
entryPoint,
|
|
261
|
-
preModules,
|
|
262
|
-
graph,
|
|
263
|
-
serializerOptions,
|
|
264
|
-
)
|
|
265
|
-
const dev = serializerOptions.dev
|
|
266
|
-
return (dev === false && options.productionBundles === true) ||
|
|
267
|
-
(dev === true && options.developmentBundles === true)
|
|
268
|
-
? appendRNXModulePaths(result, graph, serializerOptions, cfgProjectRoot)
|
|
269
|
-
: result
|
|
270
|
-
},
|
|
201
|
+
|
|
202
|
+
let defaultSerializer: MetroSerializer | undefined
|
|
203
|
+
|
|
204
|
+
const serializerConfig = {
|
|
205
|
+
serializer: {
|
|
206
|
+
...cfgSerializer,
|
|
207
|
+
customSerializer: async (
|
|
208
|
+
entryPoint: string,
|
|
209
|
+
preModules: readonly unknown[],
|
|
210
|
+
graph: MetroSerializerGraph,
|
|
211
|
+
serializerOptions: MetroSerializerOptions,
|
|
212
|
+
) => {
|
|
213
|
+
const serializer =
|
|
214
|
+
existingSerializer ||
|
|
215
|
+
(defaultSerializer ||= createMetroDefaultSerializer(cfgProjectRoot))
|
|
216
|
+
const result = await serializer(entryPoint, preModules, graph, serializerOptions)
|
|
217
|
+
return appendRNXModulePaths(result, graph, serializerOptions, cfgProjectRoot)
|
|
271
218
|
},
|
|
272
|
-
}
|
|
219
|
+
},
|
|
273
220
|
}
|
|
274
221
|
|
|
275
222
|
const serverConfig = {
|
|
@@ -305,26 +252,6 @@ export function withRNX<T extends Record<string, unknown>>(
|
|
|
305
252
|
const url = req.url || ''
|
|
306
253
|
const pathname = url.split('?')[0]
|
|
307
254
|
|
|
308
|
-
if (pathname === '/__server-scan') {
|
|
309
|
-
const absoluteBundleUrl = bundleUrl.startsWith('http')
|
|
310
|
-
? bundleUrl
|
|
311
|
-
: `http://localhost:${port}${bundleUrl}`
|
|
312
|
-
res.setHeader('content-type', 'application/json')
|
|
313
|
-
res.end(
|
|
314
|
-
JSON.stringify([
|
|
315
|
-
{
|
|
316
|
-
port,
|
|
317
|
-
framework: 'metro',
|
|
318
|
-
rnxServerId: metroServerId,
|
|
319
|
-
bundleUrl: absoluteBundleUrl,
|
|
320
|
-
...(appName ? { projectName: appName } : {}),
|
|
321
|
-
lastSeen: Date.now(),
|
|
322
|
-
},
|
|
323
|
-
]),
|
|
324
|
-
)
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
|
|
328
255
|
const runtimeRoot = resolveRuntimeRootForRequestHost(req.headers?.host)
|
|
329
256
|
|
|
330
257
|
if (!runtimeRoot) {
|
|
@@ -371,10 +298,10 @@ export function withRNX<T extends Record<string, unknown>>(
|
|
|
371
298
|
if (!ext) {
|
|
372
299
|
const htmlPath = path.join(runtimeRoot, 'index.html')
|
|
373
300
|
let html = fs.readFileSync(htmlPath, 'utf8')
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
301
|
+
const script = `<script>(function(){var u=new URL(location.href);if(!u.searchParams.has('bundle'))u.searchParams.set('bundle',${JSON.stringify(bundleUrl)});history.replaceState(null,'','${prefix}/'+u.search)})()</script>`
|
|
302
|
+
html = html.includes('</head>')
|
|
303
|
+
? html.replace('</head>', `${script}</head>`)
|
|
304
|
+
: `${html}${script}`
|
|
378
305
|
res.setHeader('content-type', 'text/html')
|
|
379
306
|
res.end(html)
|
|
380
307
|
return
|
|
@@ -391,20 +318,10 @@ export function withRNX<T extends Record<string, unknown>>(
|
|
|
391
318
|
},
|
|
392
319
|
}
|
|
393
320
|
|
|
394
|
-
if (appName && !process.env.CI && !process.env.RNX_NO_OPEN) {
|
|
395
|
-
void openRnxDesktopWhenMetroIsReady({
|
|
396
|
-
port,
|
|
397
|
-
bundleUrl: resolveRnxDesktopLaunchTarget(port, bundleUrl),
|
|
398
|
-
appName,
|
|
399
|
-
projectRoot: cfgProjectRoot,
|
|
400
|
-
serverId: metroServerId,
|
|
401
|
-
})
|
|
402
|
-
}
|
|
403
|
-
|
|
404
321
|
return {
|
|
405
322
|
...config,
|
|
406
|
-
...
|
|
407
|
-
|
|
323
|
+
...serializerConfig,
|
|
324
|
+
server: serverConfig,
|
|
408
325
|
} as T
|
|
409
326
|
}
|
|
410
327
|
|
package/src/vite-plugin.ts
CHANGED
|
@@ -493,8 +493,17 @@ export type WorkerBootClosure = {
|
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
const ENGINE_WORKER_BOOT_CLOSURES: WorkerBootClosure[] = [
|
|
496
|
-
|
|
497
|
-
|
|
496
|
+
// both caps went up by about half a megabyte for fontkit, which replaced
|
|
497
|
+
// opentype.js as the app-font parser so woff2 faces (what expo-font ships to
|
|
498
|
+
// iOS) get real vertical metrics instead of measuring at CanvasKit's rounded
|
|
499
|
+
// paragraph height. these workers inline their dynamic imports, so all of it
|
|
500
|
+
// lands in the boot file even though only an app that ships a font evaluates
|
|
501
|
+
// it. removing it again means the shell and compositor taking the parsed
|
|
502
|
+
// names and metrics off the wire from the tenant instead of re-parsing the
|
|
503
|
+
// same bytes in three contexts, behind a static import boundary so the parser
|
|
504
|
+
// is not reachable from these two entries at all.
|
|
505
|
+
{ entry: 'src/render-worker/shell-worker-entry.ts', maxBytes: 4_020_000 },
|
|
506
|
+
{ entry: 'src/render-worker/compositor-worker-entry.ts', maxBytes: 1_835_000 },
|
|
498
507
|
]
|
|
499
508
|
|
|
500
509
|
export function workerBootChunkPlugin(closures: WorkerBootClosure[]): Plugin {
|