rnxsim 0.1.519 → 0.1.520
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 +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 +1 -1
- package/dist-lib/cloud.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 +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 +43 -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 +674 -12
- package/dist-lib/vite.cjs +26 -3
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +72 -5
- package/src/cloud-contract.ts +7 -1
- package/src/cloud.ts +11 -4
- package/src/connect.ts +1 -1
- package/src/menu.ts +44 -0
- package/src/vite-plugin-one.ts +12 -0
- package/src/vite-plugin-swift.ts +17 -5
package/cli/cloud-dispatch.ts
CHANGED
|
@@ -157,13 +157,9 @@ export async function dispatchCloudBoundary({
|
|
|
157
157
|
}
|
|
158
158
|
try {
|
|
159
159
|
const descriptor = cloudSession.requireCloudSessionShellUpdate()
|
|
160
|
-
await cloudClient.
|
|
160
|
+
await cloudClient.closeCloudSession(session)
|
|
161
161
|
cloudSession.publishCloudSessionToShell(null, descriptor)
|
|
162
|
-
console.log(
|
|
163
|
-
session.service === 'box'
|
|
164
|
-
? ` closed: remote box ${session.boxId}`
|
|
165
|
-
: ` closed: remote simulator ${session.simId}`,
|
|
166
|
-
)
|
|
162
|
+
console.log(` closed: remote simulator ${session.simId}`)
|
|
167
163
|
return { handled: true, code: 0 }
|
|
168
164
|
} catch (error) {
|
|
169
165
|
console.error(
|
|
@@ -173,21 +169,6 @@ export async function dispatchCloudBoundary({
|
|
|
173
169
|
}
|
|
174
170
|
}
|
|
175
171
|
|
|
176
|
-
// a box relays the whole bridge vocabulary to the page hosting its
|
|
177
|
-
// simulator, which runs the same shell the local CLI drives. so there is no
|
|
178
|
-
// subset to police: every command below reaches it through the ordinary
|
|
179
|
-
// bridge, which createBridgeFromParsed points at the box. claim is specific
|
|
180
|
-
// to nano.
|
|
181
|
-
if (session.service === 'box') {
|
|
182
|
-
if (command === 'claim') {
|
|
183
|
-
console.error(
|
|
184
|
-
` rnx ${command} is not available for a box; its simulator is held by the Box page. use rnx box stop to end the box.`,
|
|
185
|
-
)
|
|
186
|
-
return { handled: true, code: 1 }
|
|
187
|
-
}
|
|
188
|
-
return { handled: false }
|
|
189
|
-
}
|
|
190
|
-
|
|
191
172
|
if (command === 'claim') {
|
|
192
173
|
try {
|
|
193
174
|
const descriptor = cloudSession.requireCloudSessionShellUpdate()
|
package/cli/cloud-session.ts
CHANGED
|
@@ -10,10 +10,9 @@ export const RNX_SHELL_UPDATE_PREFIX = '__RNX_SHELL_UPDATE_V1__'
|
|
|
10
10
|
export const RNX_SHELL_UPDATE_SET_PREFIX = `${RNX_SHELL_UPDATE_PREFIX}set:`
|
|
11
11
|
export const RNX_SHELL_UPDATE_UNSET = `${RNX_SHELL_UPDATE_PREFIX}unset`
|
|
12
12
|
|
|
13
|
-
// the shell's remote target
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
export type CloudSession = CloudSimSession | CloudBoxSession
|
|
13
|
+
// the shell's remote target: one simulator on the instance API, addressed by
|
|
14
|
+
// its own id and token.
|
|
15
|
+
export type CloudSession = CloudSimSession
|
|
17
16
|
|
|
18
17
|
export type RemotePlatform = 'ios' | 'android'
|
|
19
18
|
|
|
@@ -23,39 +22,25 @@ export interface CloudSessionArtifactIdentity {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
|
-
* one remote
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* one remote simulator and the claim ordinary commands drive it with. the
|
|
26
|
+
* watch URL the create returned rides along so a reattach reopens the same
|
|
27
|
+
* simulator. a nano-box simulator keeps the box that hosts it; an instance
|
|
28
|
+
* API simulator has no box.
|
|
29
29
|
*/
|
|
30
30
|
export interface CloudSimSession {
|
|
31
31
|
version: 3
|
|
32
32
|
service: 'sim'
|
|
33
|
-
boxId: string
|
|
34
|
-
boxToken: string
|
|
35
33
|
simId: string
|
|
36
34
|
apiOrigin: string
|
|
37
35
|
claimId: string
|
|
38
36
|
token: string
|
|
37
|
+
streamUrl?: string | null
|
|
39
38
|
platform?: RemotePlatform
|
|
40
39
|
device?: string
|
|
41
40
|
artifact?: CloudSessionArtifactIdentity
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* a micro box, whose simulator lives in its own Box page rather than in the
|
|
46
|
-
* service. there is nothing to claim and no separate simulator token: the box
|
|
47
|
-
* token addresses the box, and the box relays each command to the page that
|
|
48
|
-
* is hosting it.
|
|
49
|
-
*/
|
|
50
|
-
export interface CloudBoxSession {
|
|
51
|
-
version: 3
|
|
52
|
-
service: 'box'
|
|
53
|
-
boxId: string
|
|
54
|
-
boxToken: string
|
|
55
|
-
apiOrigin: string
|
|
56
|
-
platform?: RemotePlatform
|
|
57
|
-
device?: string
|
|
58
|
-
artifact?: CloudSessionArtifactIdentity
|
|
41
|
+
/** nano-box simulators only: the box that hosts them. absent on the instance API. */
|
|
42
|
+
boxId?: string
|
|
43
|
+
boxToken?: string
|
|
59
44
|
}
|
|
60
45
|
|
|
61
46
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -86,70 +71,43 @@ export function normalizeCloudOrigin(source: string): string {
|
|
|
86
71
|
}
|
|
87
72
|
|
|
88
73
|
export function createCloudSession(input: {
|
|
89
|
-
boxId: string
|
|
90
|
-
boxToken: string
|
|
91
74
|
simId: string
|
|
92
75
|
apiOrigin: string
|
|
93
76
|
claimId: string
|
|
94
77
|
token: string
|
|
78
|
+
streamUrl?: string | null
|
|
95
79
|
platform?: RemotePlatform
|
|
96
80
|
device?: string
|
|
97
81
|
artifact?: CloudSessionArtifactIdentity
|
|
82
|
+
boxId?: string
|
|
83
|
+
boxToken?: string
|
|
98
84
|
}): CloudSimSession {
|
|
85
|
+
const boxId = input.boxId?.trim() ?? ''
|
|
86
|
+
const boxToken = input.boxToken ?? ''
|
|
87
|
+
if ((boxId === '') !== (boxToken === '')) {
|
|
88
|
+
throw new Error('cloud session names a box with both its id and its token')
|
|
89
|
+
}
|
|
99
90
|
const session: CloudSimSession = {
|
|
100
91
|
version: CLOUD_SESSION_VERSION,
|
|
101
92
|
service: 'sim',
|
|
102
|
-
boxId: input.boxId.trim(),
|
|
103
|
-
boxToken: input.boxToken,
|
|
104
93
|
simId: input.simId.trim(),
|
|
105
94
|
apiOrigin: normalizeCloudOrigin(input.apiOrigin.trim()),
|
|
106
95
|
claimId: input.claimId.trim(),
|
|
107
96
|
token: input.token,
|
|
97
|
+
...(input.streamUrl ? { streamUrl: input.streamUrl } : {}),
|
|
108
98
|
...(input.platform ? { platform: input.platform } : {}),
|
|
109
99
|
...(input.device?.trim() ? { device: input.device.trim() } : {}),
|
|
110
100
|
...(input.artifact ? { artifact: input.artifact } : {}),
|
|
101
|
+
...(boxId && boxToken ? { boxId, boxToken } : {}),
|
|
111
102
|
}
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
!session.boxToken ||
|
|
115
|
-
!session.simId ||
|
|
116
|
-
!session.claimId ||
|
|
117
|
-
!session.token
|
|
118
|
-
) {
|
|
119
|
-
throw new Error('cloud session requires box, simulator, origin, claim, and tokens')
|
|
120
|
-
}
|
|
121
|
-
return session
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function createCloudBoxSession(input: {
|
|
125
|
-
boxId: string
|
|
126
|
-
boxToken: string
|
|
127
|
-
apiOrigin: string
|
|
128
|
-
platform?: RemotePlatform
|
|
129
|
-
device?: string
|
|
130
|
-
artifact?: CloudSessionArtifactIdentity
|
|
131
|
-
}): CloudBoxSession {
|
|
132
|
-
const session: CloudBoxSession = {
|
|
133
|
-
version: CLOUD_SESSION_VERSION,
|
|
134
|
-
service: 'box',
|
|
135
|
-
boxId: input.boxId.trim(),
|
|
136
|
-
boxToken: input.boxToken,
|
|
137
|
-
apiOrigin: normalizeCloudOrigin(input.apiOrigin.trim()),
|
|
138
|
-
...(input.platform ? { platform: input.platform } : {}),
|
|
139
|
-
...(input.device?.trim() ? { device: input.device.trim() } : {}),
|
|
140
|
-
...(input.artifact ? { artifact: input.artifact } : {}),
|
|
141
|
-
}
|
|
142
|
-
if (!session.boxId || !session.boxToken) {
|
|
143
|
-
throw new Error('cloud box session requires a box, an origin, and a box token')
|
|
103
|
+
if (!session.simId || !session.claimId || !session.token) {
|
|
104
|
+
throw new Error('cloud session requires a simulator, an origin, a claim, and a token')
|
|
144
105
|
}
|
|
145
106
|
return session
|
|
146
107
|
}
|
|
147
108
|
|
|
148
109
|
export function serializeCloudSession(session: CloudSession): string {
|
|
149
|
-
const validated =
|
|
150
|
-
session.service === 'sim'
|
|
151
|
-
? createCloudSession(session)
|
|
152
|
-
: createCloudBoxSession(session)
|
|
110
|
+
const validated = createCloudSession(session)
|
|
153
111
|
return Buffer.from(JSON.stringify(validated), 'utf8').toString('base64url')
|
|
154
112
|
}
|
|
155
113
|
|
|
@@ -175,14 +133,8 @@ export function parseCloudSession(encoded: string): CloudSession {
|
|
|
175
133
|
if (!isRecord(parsed) || parsed.version !== CLOUD_SESSION_VERSION) {
|
|
176
134
|
throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`)
|
|
177
135
|
}
|
|
178
|
-
const boxId = parsed.boxId
|
|
179
|
-
const boxToken = parsed.boxToken
|
|
180
136
|
const apiOrigin = parsed.apiOrigin
|
|
181
|
-
if (
|
|
182
|
-
typeof boxId !== 'string' ||
|
|
183
|
-
typeof boxToken !== 'string' ||
|
|
184
|
-
typeof apiOrigin !== 'string'
|
|
185
|
-
) {
|
|
137
|
+
if (typeof apiOrigin !== 'string') {
|
|
186
138
|
throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`)
|
|
187
139
|
}
|
|
188
140
|
const platform =
|
|
@@ -198,17 +150,10 @@ export function parseCloudSession(encoded: string): CloudSession {
|
|
|
198
150
|
typeof parsed.artifact.bytes === 'number' ? parsed.artifact.bytes : undefined,
|
|
199
151
|
}
|
|
200
152
|
: undefined
|
|
153
|
+
const streamUrl = typeof parsed.streamUrl === 'string' ? parsed.streamUrl : undefined
|
|
154
|
+
const boxId = typeof parsed.boxId === 'string' ? parsed.boxId : undefined
|
|
155
|
+
const boxToken = typeof parsed.boxToken === 'string' ? parsed.boxToken : undefined
|
|
201
156
|
try {
|
|
202
|
-
if (parsed.service === 'box') {
|
|
203
|
-
return createCloudBoxSession({
|
|
204
|
-
boxId,
|
|
205
|
-
boxToken,
|
|
206
|
-
apiOrigin,
|
|
207
|
-
platform,
|
|
208
|
-
device,
|
|
209
|
-
artifact,
|
|
210
|
-
})
|
|
211
|
-
}
|
|
212
157
|
const simId = parsed.simId
|
|
213
158
|
const claimId = parsed.claimId
|
|
214
159
|
const token = parsed.token
|
|
@@ -221,15 +166,16 @@ export function parseCloudSession(encoded: string): CloudSession {
|
|
|
221
166
|
throw new Error('unreadable cloud session')
|
|
222
167
|
}
|
|
223
168
|
return createCloudSession({
|
|
224
|
-
boxId,
|
|
225
|
-
boxToken,
|
|
226
169
|
simId,
|
|
227
170
|
apiOrigin,
|
|
228
171
|
claimId,
|
|
229
172
|
token,
|
|
173
|
+
streamUrl,
|
|
230
174
|
platform,
|
|
231
175
|
device,
|
|
232
176
|
artifact,
|
|
177
|
+
boxId,
|
|
178
|
+
boxToken,
|
|
233
179
|
})
|
|
234
180
|
} catch {
|
|
235
181
|
throw new Error(`${RNX_CLOUD_SESSION_ENV} is invalid`)
|
package/cli/commands/box.ts
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
// boxTarget below.
|
|
12
12
|
|
|
13
13
|
import { spawn } from 'node:child_process'
|
|
14
|
-
import fs from 'node:fs/promises'
|
|
15
14
|
import path from 'node:path'
|
|
16
15
|
import { createInterface } from 'node:readline'
|
|
17
16
|
import { BoxClientError, createBoxClient } from '@rnx/box/client'
|
|
@@ -34,6 +33,7 @@ import {
|
|
|
34
33
|
import { readDefaultBox, writeDefaultBox } from '../../src/home-paths'
|
|
35
34
|
import { rnxPublicBrand } from '../../src/public-brand'
|
|
36
35
|
import { authHeaderOrExit, cloudAccountIdOrExit, type CliAuth } from '../auth'
|
|
36
|
+
import { produceCloudBoxArtifact, type CloudBoxArtifactProduction } from '../cloud-client'
|
|
37
37
|
import { findRepositoryPackageRoot } from '../setup-repository'
|
|
38
38
|
import { resolvePlacement } from '../ws-bridge'
|
|
39
39
|
import { BundleFilePlane } from './box/bundle-plane'
|
|
@@ -214,7 +214,7 @@ function requireEndpoint(args: string[], label: string): string | null {
|
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
216
|
* the cloud box service this command talks to, or null with the reason
|
|
217
|
-
* printed. exported because `rnx
|
|
217
|
+
* printed. exported because `rnx box send` creates a box too, and a second
|
|
218
218
|
* spelling of where boxes live is a second place to configure.
|
|
219
219
|
*/
|
|
220
220
|
export function cloudBoxEndpoint(args: string[], command: string): string | null {
|
|
@@ -520,10 +520,10 @@ function cloudBoxClient(options: {
|
|
|
520
520
|
/**
|
|
521
521
|
* create a Box that runs an app somebody already built.
|
|
522
522
|
*
|
|
523
|
-
* `box create --bundle` reaches this with a file from disk and `rnx
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
523
|
+
* `box create --bundle` reaches this with a file from disk and `rnx box send`
|
|
524
|
+
* with the artifact it just produced. both are the same box: the bundle is
|
|
525
|
+
* the whole project, the provenance says so, and the page that opens it boots
|
|
526
|
+
* that bundle instead of building anything.
|
|
527
527
|
*/
|
|
528
528
|
export async function createPrebuiltBundleBox(options: {
|
|
529
529
|
bundle: Uint8Array
|
|
@@ -589,22 +589,26 @@ async function runCloudBoxCreate(
|
|
|
589
589
|
// a prebuilt box carries no sources, so there is no checkout to read and
|
|
590
590
|
// this can run anywhere, including a directory that is not a repository.
|
|
591
591
|
const resolved = path.resolve(bundlePath)
|
|
592
|
-
|
|
592
|
+
// the page boots these bytes with no asset server behind it, so the Metro
|
|
593
|
+
// assets beside the bundle are embedded the way every other remote path
|
|
594
|
+
// embeds them. the box service owns the size ceiling, not this producer.
|
|
595
|
+
let artifact: CloudBoxArtifactProduction
|
|
593
596
|
try {
|
|
594
|
-
|
|
597
|
+
artifact = await produceCloudBoxArtifact(resolved)
|
|
595
598
|
} catch (error) {
|
|
596
599
|
console.error(
|
|
597
|
-
` ${rnx} box create: could not
|
|
600
|
+
` ${rnx} box create: could not prepare the bundle at ${resolved}: ${
|
|
598
601
|
error instanceof Error ? error.message : String(error)
|
|
599
602
|
}`,
|
|
600
603
|
)
|
|
601
604
|
return 1
|
|
602
605
|
}
|
|
603
|
-
if (
|
|
606
|
+
if (artifact.bytes.byteLength === 0) {
|
|
604
607
|
console.error(` ${rnx} box create: the bundle at ${resolved} is empty.`)
|
|
605
608
|
return 1
|
|
606
609
|
}
|
|
607
|
-
|
|
610
|
+
for (const warning of artifact.warnings) console.error(` ${warning}`)
|
|
611
|
+
source = { bundle: new Uint8Array(artifact.bytes) }
|
|
608
612
|
defaultName = path.basename(resolved).replace(/\.[^.]+$/, '')
|
|
609
613
|
} else {
|
|
610
614
|
const root = findRepositoryPackageRoot(process.cwd())
|
package/cli/commands/inspect.ts
CHANGED
|
@@ -648,6 +648,35 @@ export function printShellPerfReport(result: Record<string, any>) {
|
|
|
648
648
|
)
|
|
649
649
|
}
|
|
650
650
|
}
|
|
651
|
+
const liveFrames = result.liveFrames
|
|
652
|
+
if (liveFrames && typeof liveFrames === 'object') {
|
|
653
|
+
const tenant = liveFrames.tenant
|
|
654
|
+
const compositorLive =
|
|
655
|
+
liveFrames.compositor && typeof liveFrames.compositor === 'object'
|
|
656
|
+
? liveFrames.compositor
|
|
657
|
+
: null
|
|
658
|
+
console.log(``)
|
|
659
|
+
console.log(` live frames (GL / camera / video / WebGPU / maps):`)
|
|
660
|
+
if (tenant && typeof tenant === 'object') {
|
|
661
|
+
const presentation = tenant.presentation ?? {}
|
|
662
|
+
console.log(
|
|
663
|
+
` tenant published ${presentation.publishes ?? 0} · skipped ${presentation.backpressureSkips ?? 0} (in-flight) · stale-healed ${presentation.staleHeals ?? 0} · density ${tenant.renderDensity ?? '?'}`,
|
|
664
|
+
)
|
|
665
|
+
} else {
|
|
666
|
+
console.log(` tenant stats unavailable (tenant did not answer)`)
|
|
667
|
+
}
|
|
668
|
+
if (compositorLive) {
|
|
669
|
+
console.log(
|
|
670
|
+
` compositor received ${compositorLive.publishes ?? 0} on ${compositorLive.activeChannels ?? 0} channel(s) · ${compositorLive.uploads ?? 0} texture uploads · ${compositorLive.uploadMsTotal ?? 0}ms total / ${compositorLive.uploadMsMax ?? 0}ms max`,
|
|
671
|
+
)
|
|
672
|
+
const shared = compositorLive.sharedDeviceImages
|
|
673
|
+
if (shared && typeof shared === 'object') {
|
|
674
|
+
console.log(
|
|
675
|
+
` shared device: ${shared.bitmapUploadRequests ?? 0} upload requests · ${shared.bitmapUploadsHeld ?? 0} held (motion) · ${shared.bitmapUploadBytes ?? 0} bytes · ${shared.bitmapCacheHits ?? 0} cache hits`,
|
|
676
|
+
)
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
651
680
|
if (surfaces.length > 0) {
|
|
652
681
|
console.log(``)
|
|
653
682
|
console.log(` surfaces (last 100 frames):`)
|
|
@@ -3749,9 +3778,13 @@ export async function runInspect(args: string[], opts: InspectOptions) {
|
|
|
3749
3778
|
// wrap in async to support await in worker mode. no identifier
|
|
3750
3779
|
// rewriting — the snippet runs verbatim so tokens like `root`,
|
|
3751
3780
|
// `debug`, `test` refer to whatever the user actually typed.
|
|
3781
|
+
// multi-statement snippets need a block body: the bare expression
|
|
3782
|
+
// form terminates at the first `;` (Unexpected token).
|
|
3752
3783
|
let expandedCode = code
|
|
3753
3784
|
if (!expandedCode.startsWith('(async')) {
|
|
3754
|
-
expandedCode =
|
|
3785
|
+
expandedCode = /[;\n]/.test(expandedCode)
|
|
3786
|
+
? `(async () => { ${expandedCode} })()`
|
|
3787
|
+
: `(async () => ${expandedCode})()`
|
|
3755
3788
|
}
|
|
3756
3789
|
const result = await bridge.send({ type: 'evaluate', code: expandedCode })
|
|
3757
3790
|
console.log(JSON.stringify(result, null, 2))
|