peachsim 0.1.570 → 0.1.572
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/LICENSE +1 -1
- package/README.md +108 -108
- package/cli/app-config.ts +1 -1
- package/cli/app-fonts.ts +4 -4
- package/cli/auth.ts +11 -11
- package/cli/bin.ts +4 -4
- package/cli/bridge-flow-runner.ts +28 -20
- package/cli/browser-evals.ts +2 -2
- package/cli/cli-update.ts +6 -6
- package/cli/cloud-client.ts +16 -16
- package/cli/cloud-dispatch.ts +3 -2
- package/cli/cloud-session.ts +12 -12
- package/cli/cloud-test-result-files.ts +97 -0
- package/cli/cloud-test-tunnel.ts +4 -4
- package/cli/command-registry.ts +7 -7
- package/cli/commands/agent-wrapper.ts +7 -7
- package/cli/commands/agent.ts +16 -16
- package/cli/commands/app-fonts.ts +3 -3
- package/cli/commands/assert.ts +10 -10
- package/cli/commands/auth.ts +5 -5
- package/cli/commands/box/checkout-plane.ts +1 -1
- package/cli/commands/box/plane-share.ts +2 -2
- package/cli/commands/box/rnx-command.ts +5 -5
- package/cli/commands/box.ts +38 -38
- package/cli/commands/camera.ts +13 -13
- package/cli/commands/cleanup.ts +13 -13
- package/cli/commands/compat.ts +7 -7
- package/cli/commands/config.ts +5 -5
- package/cli/commands/control.ts +63 -63
- package/cli/commands/cpu-profile.ts +11 -11
- package/cli/commands/daemon-mac-app.ts +10 -10
- package/cli/commands/daemon.ts +29 -27
- package/cli/commands/debug.ts +35 -35
- package/cli/commands/desktop.ts +3 -3
- package/cli/commands/detox.ts +33 -33
- package/cli/commands/device.ts +9 -9
- package/cli/commands/diagnose.ts +17 -17
- package/cli/commands/do-chain.ts +4 -4
- package/cli/commands/electron.ts +9 -9
- package/cli/commands/film.ts +9 -9
- package/cli/commands/flow.ts +49 -49
- package/cli/commands/inspect/core.ts +12 -12
- package/cli/commands/inspect/count.ts +1 -1
- package/cli/commands/inspect/describe.ts +1 -1
- package/cli/commands/inspect/env.ts +2 -2
- package/cli/commands/inspect/find.ts +2 -2
- package/cli/commands/inspect/get-layout.ts +2 -2
- package/cli/commands/inspect/keyboard.ts +1 -1
- package/cli/commands/inspect/list.ts +4 -4
- package/cli/commands/inspect/memory.ts +1 -1
- package/cli/commands/inspect/resolve-target.ts +1 -1
- package/cli/commands/inspect/screens.ts +1 -1
- package/cli/commands/inspect/shared.ts +1 -1
- package/cli/commands/inspect/tree.ts +1 -1
- package/cli/commands/inspect/url.ts +1 -1
- package/cli/commands/inspect/wait-event.ts +5 -5
- package/cli/commands/inspect.ts +82 -82
- package/cli/commands/install-cli.ts +16 -16
- package/cli/commands/install-desktop.ts +26 -26
- package/cli/commands/login.ts +11 -11
- package/cli/commands/maestro-generate.ts +10 -10
- package/cli/commands/maestro.ts +173 -71
- package/cli/commands/mode.ts +4 -4
- package/cli/commands/no-bridge-hint.ts +12 -12
- package/cli/commands/perf.ts +11 -11
- package/cli/commands/permissions.ts +5 -5
- package/cli/commands/platform.ts +21 -21
- package/cli/commands/preview.ts +5 -5
- package/cli/commands/profile.ts +12 -12
- package/cli/commands/react.ts +15 -15
- package/cli/commands/record.ts +31 -31
- package/cli/commands/report-issue.ts +4 -4
- package/cli/commands/reset.ts +2 -2
- package/cli/commands/runtime.ts +23 -23
- package/cli/commands/screenshot-command.ts +2 -2
- package/cli/commands/screenshot-layers.ts +5 -5
- package/cli/commands/screenshot-mode.ts +7 -7
- package/cli/commands/screenshot.ts +24 -24
- package/cli/commands/screenshots-capture.ts +13 -13
- package/cli/commands/screenshots.ts +6 -6
- package/cli/commands/serve.ts +12 -12
- package/cli/commands/setup.ts +25 -25
- package/cli/commands/skills.ts +30 -23
- package/cli/commands/slides.ts +20 -20
- package/cli/commands/state.ts +2 -2
- package/cli/commands/storage.ts +3 -3
- package/cli/commands/telemetry.ts +2 -2
- package/cli/commands/test.ts +176 -18
- package/cli/commands/three-mode.ts +17 -17
- package/cli/commands/timeline.ts +12 -12
- package/cli/commands/upgrade.ts +11 -11
- package/cli/commands/upload.ts +26 -26
- package/cli/commands/version.ts +7 -7
- package/cli/commands/what-happened.ts +20 -20
- package/cli/current-sim.ts +3 -3
- package/cli/desktop-companion.ts +16 -16
- package/cli/drivers/electron.ts +4 -4
- package/cli/drivers/playwright-sim-host.ts +7 -7
- package/cli/drivers/playwright.ts +4 -4
- package/cli/drivers/registry.ts +1 -1
- package/cli/drivers/types.ts +5 -5
- package/cli/flow-export.ts +1 -1
- package/cli/flow-file.ts +5 -5
- package/cli/flow-session.ts +1 -1
- package/cli/help-core.ts +13 -13
- package/cli/hints.ts +14 -14
- package/cli/inspect-notice-state.ts +1 -1
- package/cli/internal-child.ts +2 -2
- package/cli/maestro-js.ts +4 -4
- package/cli/maestro-report.ts +4 -2
- package/cli/main.ts +14 -14
- package/cli/outbound-endpoints.ts +32 -30
- package/cli/parse-args.ts +7 -7
- package/cli/privacy.ts +1 -1
- package/cli/recording-access.ts +10 -10
- package/cli/resolve-assets.ts +2 -2
- package/cli/rnx-in-process.ts +5 -5
- package/cli/run-registry.ts +3 -2
- package/cli/run-rnx-core.ts +3 -3
- package/cli/run-rnx.ts +1 -1
- package/cli/runtime-notes.ts +1 -1
- package/cli/self-invocation.ts +1 -1
- package/cli/send-to-box.ts +1 -1
- package/cli/shell-init.ts +40 -40
- package/cli/ui.ts +1 -1
- package/cli/ws-bridge.ts +22 -22
- package/detox/config-loader.ts +1 -1
- package/detox/index.ts +28 -28
- package/detox/jest-preset.cjs +2 -2
- package/detox/navigation.ts +2 -2
- package/dist-lib/agent-daemon-client.cjs +12 -12
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +2 -2
- package/dist-lib/agent-sessions.cjs +12 -12
- package/dist-lib/attached-projects.cjs +4 -4
- package/dist-lib/auth/shared-session.cjs +4 -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 +4 -4
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +2 -2
- package/dist-lib/bridge-contract.mjs +2 -2
- 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-test-args.cjs +116 -0
- package/dist-lib/cloud-test-args.mjs +86 -0
- package/dist-lib/cloud.cjs +42 -42
- package/dist-lib/cloud.mjs +42 -42
- package/dist-lib/config.cjs +2 -2
- package/dist-lib/detox/index.cjs +29 -29
- package/dist-lib/detox/jest-preset.cjs +2 -2
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +12 -12
- package/dist-lib/host/bridge-host.cjs +87 -77
- 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 +6 -6
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +63 -73
- 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 +5 -5
- package/dist-lib/menu.mjs +5 -5
- 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 +15 -15
- package/dist-lib/profiles.cjs +4 -4
- package/dist-lib/public-brand.cjs +3 -3
- 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 +2 -1
- package/dist-lib/sdk.cjs +9 -9
- package/dist-lib/sdk.mjs +9 -9
- package/dist-lib/skills.cjs +534 -520
- package/dist-lib/swift-compile.cjs +372 -0
- package/dist-lib/swift-compile.mjs +339 -0
- package/dist-lib/swift-package.cjs +78 -0
- package/dist-lib/swift-package.mjs +52 -0
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite.cjs +479 -161
- package/package.json +22 -1
- package/scripts/dev-server-scanner.ts +4 -3
- package/skills/contrast/SKILL.md +75 -75
- package/skills/{rnx-debug → peach-debug}/SKILL.md +90 -90
- package/skills/{rnx-perf → peach-perf}/SKILL.md +13 -13
- package/skills/{rnx-setup → peach-setup}/SKILL.md +60 -60
- package/skills/{rnx-test → peach-test}/SKILL.md +47 -47
- package/skills/{rnx-visual → peach-visual}/SKILL.md +26 -26
- package/src/agent-daemon-client.ts +5 -5
- package/src/agent-events.ts +1 -1
- package/src/agent-identity.ts +1 -1
- package/src/agent-sessions.ts +13 -13
- package/src/attached-projects.ts +1 -1
- package/src/auth/shared-session.ts +1 -1
- package/src/bridge-constants.ts +3 -3
- package/src/bridge-contract-input.ts +1 -1
- package/src/bridge-contract.ts +3 -3
- package/src/cli-version.ts +6 -6
- package/src/cloud-test-args.ts +100 -0
- package/src/cloud.ts +40 -40
- package/src/config.ts +1 -1
- package/src/connect.ts +6 -6
- package/src/dev-server-open.ts +8 -8
- package/src/disk-cleanup.ts +3 -3
- package/src/home-paths.ts +26 -26
- package/src/host/agent-host.ts +3 -3
- package/src/host/bridge-host.ts +33 -33
- package/src/host/fetch-proxy-handler.ts +1 -1
- package/src/host/plane-host.ts +2 -2
- package/src/host/replacement-module-handler.ts +2 -2
- package/src/menu.ts +2 -2
- package/src/metro-plugin.ts +7 -7
- package/src/metro-production-bundle.ts +2 -2
- package/src/native-dev-bundle-url.ts +2 -2
- package/src/playwright-host-log.ts +1 -1
- package/src/public-brand.ts +2 -2
- package/src/react-native-host-modules.ts +1 -1
- package/src/rolldown-plugin-swift.ts +11 -10
- package/src/runtime-assets.ts +11 -11
- package/src/runtime-delivery.ts +6 -6
- package/src/screenshots/orchestrate.ts +1 -1
- package/src/screenshots/org-deck.ts +1 -1
- package/src/screenshots/schema.ts +2 -2
- package/src/sim-client.ts +1 -1
- package/src/skills/builtin/compat-check.ts +2 -2
- package/src/swift-compile.ts +359 -0
- package/src/swift-package.ts +69 -0
- package/src/swift-submit.ts +3 -2
- package/src/vite-plugin-one.ts +8 -8
- package/src/vite-plugin-swift.ts +106 -110
- package/src/vite-plugin.ts +6 -19
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// node-side client for the daemon's agent:* ws protocol.
|
|
2
2
|
//
|
|
3
3
|
// used by electron main (translates IPC → daemon) and by the CLI
|
|
4
|
-
// `
|
|
4
|
+
// `peach agent …` subcommands. the browser shell has its own shim that
|
|
5
5
|
// uses window.WebSocket; this file is node-only.
|
|
6
6
|
//
|
|
7
7
|
// single long-lived connection per client. if the daemon goes away, the
|
|
@@ -105,7 +105,7 @@ export class AgentDaemonClient {
|
|
|
105
105
|
this.ws.off('open', onOpen)
|
|
106
106
|
reject(
|
|
107
107
|
new AgentDaemonError(
|
|
108
|
-
`could not connect to
|
|
108
|
+
`could not connect to peach daemon on port ${this.port}: ${err.message}`,
|
|
109
109
|
'NO_DAEMON',
|
|
110
110
|
),
|
|
111
111
|
)
|
|
@@ -361,7 +361,7 @@ export async function ensureDaemonRunning(
|
|
|
361
361
|
}
|
|
362
362
|
if (shouldSkipPersistentDaemon()) {
|
|
363
363
|
throw new AgentDaemonError(
|
|
364
|
-
`no
|
|
364
|
+
`no peach bridge on port ${port}. run \`peach daemon install\` to enable the faster background service, or \`peach serve\` in another shell.`,
|
|
365
365
|
'DEV_HOST_REFUSED',
|
|
366
366
|
)
|
|
367
367
|
}
|
|
@@ -385,8 +385,8 @@ export async function ensureDaemonRunning(
|
|
|
385
385
|
await new Promise((r) => setTimeout(r, 100))
|
|
386
386
|
}
|
|
387
387
|
throw new AgentDaemonError(
|
|
388
|
-
`spawned
|
|
389
|
-
`run \`
|
|
388
|
+
`spawned peach daemon on port ${port} but it did not come up in time. ` +
|
|
389
|
+
`run \`peach serve\` manually to diagnose.`,
|
|
390
390
|
'SPAWN_TIMEOUT',
|
|
391
391
|
)
|
|
392
392
|
}
|
package/src/agent-events.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// wire format emitted by the agent-wrapper CLI subcommand on the events.out
|
|
2
2
|
// FIFO. every line on the FIFO is exactly one JSON object — parse line-by-line.
|
|
3
3
|
//
|
|
4
|
-
// this file is the source of truth for the
|
|
4
|
+
// this file is the source of truth for the peach ↔ agent channel. it's
|
|
5
5
|
// imported by electron main (cockpit bridge), the CLI watch/transcript tools,
|
|
6
6
|
// and the wrapper implementation itself, so it must stay runtime-safe (no
|
|
7
7
|
// node-specific or electron-specific imports).
|
package/src/agent-identity.ts
CHANGED
|
@@ -6,7 +6,7 @@ export function agentSessionIdFromCliIdentity(identity: string | null): string |
|
|
|
6
6
|
if (colon < 0) return identity.trim() || null
|
|
7
7
|
const source = identity.slice(0, colon)
|
|
8
8
|
const value = identity.slice(colon + 1).trim()
|
|
9
|
-
return source === '
|
|
9
|
+
return source === 'PEACH_CLI_IDENTITY' && value ? value : null
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function teamMachineSessionIdFromCliIdentity(
|
package/src/agent-sessions.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// session lifecycle shared between the CLI (`
|
|
2
|
-
// main (IPC handlers). both contexts spawn the same `
|
|
1
|
+
// session lifecycle shared between the CLI (`peach agent ...`) and electron
|
|
2
|
+
// main (IPC handlers). both contexts spawn the same `peach agent-wrapper`
|
|
3
3
|
// subcommand, mutate the same AttachedProjects store, and subscribe to the
|
|
4
4
|
// same events.out FIFO.
|
|
5
5
|
//
|
|
@@ -67,10 +67,10 @@ export function pidIsAlive(pid: number | undefined, sessionId?: string): boolean
|
|
|
67
67
|
|
|
68
68
|
// --- invocation resolution ---
|
|
69
69
|
|
|
70
|
-
/** locate the
|
|
70
|
+
/** locate the peach CLI so this process can run a subcommand of it. callers
|
|
71
71
|
* range from the CLI itself to electron main and the vite-plugin daemon.
|
|
72
72
|
* priority:
|
|
73
|
-
* 1.
|
|
73
|
+
* 1. PEACH_BIN env var (explicit override)
|
|
74
74
|
* 2. this process already IS the CLI — `rnxSelfInvocation` owns that answer
|
|
75
75
|
* 3. electron prod: bundled binary under process.resourcesPath/bin/
|
|
76
76
|
* 4. workspace build artifacts (dist-bin/<binary> or dist-cli/bin.js)
|
|
@@ -80,8 +80,8 @@ export function pidIsAlive(pid: number | undefined, sessionId?: string): boolean
|
|
|
80
80
|
* 5. CLI dev (argv[1] points at the entry script): re-use argv[0] + argv[1]
|
|
81
81
|
*/
|
|
82
82
|
export function resolveSootsimInvocation(): RnxSelfInvocation {
|
|
83
|
-
if (process.env.
|
|
84
|
-
return { executable: process.env.
|
|
83
|
+
if (process.env.PEACH_BIN) {
|
|
84
|
+
return { executable: process.env.PEACH_BIN, prefixArgs: [] }
|
|
85
85
|
}
|
|
86
86
|
// the compiled standalone binary cannot be found through argv: bun reports
|
|
87
87
|
// argv[0] as the literal string "bun" and argv[1] as a path inside its
|
|
@@ -121,11 +121,11 @@ export function resolveSootsimInvocation(): RnxSelfInvocation {
|
|
|
121
121
|
return { executable: process.argv[0], prefixArgs: [argv1] }
|
|
122
122
|
}
|
|
123
123
|
// anything else — a bin shim such as `…/node_modules/.bin/vite`, or no
|
|
124
|
-
// argv[1] at all — names no
|
|
124
|
+
// argv[1] at all — names no peach entry script, so say so instead of
|
|
125
125
|
// spawning the host runtime with no arguments.
|
|
126
126
|
throw new Error(
|
|
127
|
-
'
|
|
128
|
-
'or build the workspace CLI via `bun run --cwd packages/
|
|
127
|
+
'peach CLI not found. set PEACH_BIN to the path of the peach binary, ' +
|
|
128
|
+
'or build the workspace CLI via `bun run --cwd packages/peachsim build:cli`.',
|
|
129
129
|
)
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -133,7 +133,7 @@ function tryWorkspaceSootsim(): RnxSelfInvocation | null {
|
|
|
133
133
|
try {
|
|
134
134
|
const sootsimDir = resolveSootsimPackageDir()
|
|
135
135
|
if (!sootsimDir) return null
|
|
136
|
-
const binaryName = `
|
|
136
|
+
const binaryName = `peach-${
|
|
137
137
|
process.platform === 'win32' ? 'windows' : process.platform
|
|
138
138
|
}-${process.arch}${process.platform === 'win32' ? '.exe' : ''}`
|
|
139
139
|
const distBinary = path.join(sootsimDir, 'dist-bin', binaryName)
|
|
@@ -147,8 +147,8 @@ function tryWorkspaceSootsim(): RnxSelfInvocation | null {
|
|
|
147
147
|
const buildMtime = fs.statSync(distBin).mtimeMs
|
|
148
148
|
if (buildMtime < srcMtime) {
|
|
149
149
|
console.warn(
|
|
150
|
-
`[
|
|
151
|
-
`rebuild with \`bun run --cwd packages/
|
|
150
|
+
`[peach] dist-cli/bin.js is older than agent-wrapper.ts — ` +
|
|
151
|
+
`rebuild with \`bun run --cwd packages/peachsim build:cli\`.`,
|
|
152
152
|
)
|
|
153
153
|
}
|
|
154
154
|
}
|
|
@@ -342,7 +342,7 @@ export async function startSession(opts: StartSessionOpts): Promise<StartSession
|
|
|
342
342
|
'ALREADY_RUNNING',
|
|
343
343
|
`session already running for project=${project.id} provider=${provider} ` +
|
|
344
344
|
`(session ${existingLive.id}, pid ${existingLive.wrapperPid}). ` +
|
|
345
|
-
'end it first with `
|
|
345
|
+
'end it first with `peach agent end <sessionId>`.',
|
|
346
346
|
)
|
|
347
347
|
}
|
|
348
348
|
|
package/src/attached-projects.ts
CHANGED
|
@@ -114,7 +114,7 @@ function userDataDir(): string {
|
|
|
114
114
|
return electronUserDataDir()
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/** exposed for CLI status printing so `
|
|
117
|
+
/** exposed for CLI status printing so `peach agent projects` can show the
|
|
118
118
|
* user which file it's reading from. */
|
|
119
119
|
export function getUserDataDir(): string {
|
|
120
120
|
return userDataDir()
|
|
@@ -38,7 +38,7 @@ export type SharedDesktopAuthSession = {
|
|
|
38
38
|
validatedAt?: string
|
|
39
39
|
/**
|
|
40
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 `
|
|
41
|
+
* person, not an account, so the CLI chooses one at `peach login` and names it
|
|
42
42
|
* on every request that opens a paid session.
|
|
43
43
|
*/
|
|
44
44
|
accountId?: string
|
package/src/bridge-constants.ts
CHANGED
|
@@ -127,16 +127,16 @@ export function resolveSootsimWebSocketProxyUrl(input: {
|
|
|
127
127
|
}): string {
|
|
128
128
|
const pageUrl = parseHrefUrl(input.href)
|
|
129
129
|
if (!pageUrl || (pageUrl.protocol !== 'http:' && pageUrl.protocol !== 'https:')) {
|
|
130
|
-
throw new Error(`invalid
|
|
130
|
+
throw new Error(`invalid peach websocket proxy page URL: ${input.href}`)
|
|
131
131
|
}
|
|
132
132
|
let targetUrl: URL
|
|
133
133
|
try {
|
|
134
134
|
targetUrl = new URL(input.targetUrl)
|
|
135
135
|
} catch {
|
|
136
|
-
throw new Error(`invalid
|
|
136
|
+
throw new Error(`invalid peach websocket proxy target URL: ${input.targetUrl}`)
|
|
137
137
|
}
|
|
138
138
|
if (targetUrl.protocol !== 'ws:' && targetUrl.protocol !== 'wss:') {
|
|
139
|
-
throw new Error(`invalid
|
|
139
|
+
throw new Error(`invalid peach websocket proxy target URL: ${input.targetUrl}`)
|
|
140
140
|
}
|
|
141
141
|
const proxyUrl = new URL('/__websocket-proxy', pageUrl)
|
|
142
142
|
proxyUrl.protocol = pageUrl.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SIM_LONG_PRESS_MAX_MS } from './bridge-contract'
|
|
2
2
|
// untrusted JSON -> bridge-contract argument. one owner for that conversion, so
|
|
3
|
-
// every transport in front of a sim (the
|
|
3
|
+
// every transport in front of a sim (the Peach Cloud sim service's HTTP and
|
|
4
4
|
// WebSocket routes) validates its request bodies here rather than growing a
|
|
5
5
|
// second reader for the same shapes.
|
|
6
6
|
import type {
|
package/src/bridge-contract.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// the wire contract for the
|
|
1
|
+
// the wire contract for the peach ws bridge. the engine implements it in
|
|
2
2
|
// sootsim-engine/src/mcp/ws-bridge.ts; the peachsim cli client, the SimClient
|
|
3
3
|
// sdk, and the flow runner speak it. it is the single owner of these shapes,
|
|
4
4
|
// and neither side redeclares them.
|
|
@@ -367,7 +367,7 @@ export const SEMANTIC_READY_CONTENT_NODE_FLOOR = 100
|
|
|
367
367
|
export const SEMANTIC_READY_NODE_STABLE_MS = 750
|
|
368
368
|
export const SEMANTIC_READY_POLL_MS = 150
|
|
369
369
|
|
|
370
|
-
export const PUBLIC_NO_NETWORK_MARKER = 'public
|
|
370
|
+
export const PUBLIC_NO_NETWORK_MARKER = 'public Peach Cloud simulators have no network'
|
|
371
371
|
export const HEADLESS_TENANT_FETCH_HUNG_MARKER = 'headless tenant fetch hung'
|
|
372
372
|
|
|
373
373
|
export function readyProbeHasContent(
|
|
@@ -424,7 +424,7 @@ export interface SimStateOptions {
|
|
|
424
424
|
route?: boolean
|
|
425
425
|
errors?: boolean
|
|
426
426
|
// include the bounded guest console tail. omitted by the ordinary state
|
|
427
|
-
// command so one-shot state remains compact; `
|
|
427
|
+
// command so one-shot state remains compact; `peach logs` requests it.
|
|
428
428
|
logs?: boolean
|
|
429
429
|
// tree depth, matching the standalone `tree` command. default 5.
|
|
430
430
|
depth?: number
|
package/src/cli-version.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
//
|
|
1
|
+
// peach cli version, resolved once and cached. standalone builds embed it so
|
|
2
2
|
// lazy command chunks do not depend on an npm package layout at runtime.
|
|
3
3
|
|
|
4
4
|
import { readFileSync } from 'node:fs'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
import { rnxPublicBrand } from './public-brand.ts'
|
|
7
7
|
|
|
8
|
-
declare const
|
|
8
|
+
declare const __PEACH_CLI_VERSION__: string
|
|
9
9
|
|
|
10
10
|
let cached: string | null = null
|
|
11
11
|
|
|
12
12
|
export function getCliVersion(): string {
|
|
13
13
|
if (cached != null) return cached
|
|
14
14
|
if (
|
|
15
|
-
typeof
|
|
16
|
-
typeof
|
|
17
|
-
|
|
15
|
+
typeof __PEACH_CLI_VERSION__ !== 'undefined' &&
|
|
16
|
+
typeof __PEACH_CLI_VERSION__ === 'string' &&
|
|
17
|
+
__PEACH_CLI_VERSION__
|
|
18
18
|
) {
|
|
19
|
-
cached =
|
|
19
|
+
cached = __PEACH_CLI_VERSION__
|
|
20
20
|
return cached
|
|
21
21
|
}
|
|
22
22
|
// prefer package resolution (works for npm/global installs); fall back to
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// hosted `rnx test` selection args: the argv slice a cloud submitter
|
|
2
|
+
// forwards and the recorder appends verbatim to its executor invocation.
|
|
3
|
+
//
|
|
4
|
+
// selection only. the flags below choose WHICH tests run; everything
|
|
5
|
+
// about HOW they run (workers, timeouts, shards, devices, environment)
|
|
6
|
+
// stays with the recorder that owns the execution environment, and
|
|
7
|
+
// everything about WHERE they run (app, origin, tunnel) is consumed by
|
|
8
|
+
// the submitter. a flag outside this list never reaches the executor:
|
|
9
|
+
// the submitter warns and the server refuses, so a filter is never
|
|
10
|
+
// silently dropped. pure: the gpu recorder daemon imports this too, so
|
|
11
|
+
// no ~/ aliases, no server-only, no node builtins.
|
|
12
|
+
export const CLOUD_TEST_ARGS_MAX_COUNT = 32
|
|
13
|
+
export const CLOUD_TEST_ARG_MAX_LENGTH = 1000
|
|
14
|
+
export const CLOUD_TEST_ARGS_MAX_BYTES = 16 * 1024
|
|
15
|
+
|
|
16
|
+
// every flag a hosted job honors from the submitting command line. each
|
|
17
|
+
// takes exactly one value, as a separate token or --flag=value.
|
|
18
|
+
export const CLOUD_TEST_VALUE_FLAGS: readonly string[] = [
|
|
19
|
+
'-t',
|
|
20
|
+
'--testNamePattern',
|
|
21
|
+
'--testPathPattern',
|
|
22
|
+
'--include-tags',
|
|
23
|
+
'--exclude-tags',
|
|
24
|
+
'--grep',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const CLOUD_TEST_VALUE_FLAG_SET = new Set<string>(CLOUD_TEST_VALUE_FLAGS)
|
|
28
|
+
|
|
29
|
+
export function isCloudTestValueFlag(name: string): boolean {
|
|
30
|
+
return CLOUD_TEST_VALUE_FLAG_SET.has(name)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type CloudTestArgsVerdict =
|
|
34
|
+
| { ok: true; args: string[] }
|
|
35
|
+
| { ok: false; message: string }
|
|
36
|
+
|
|
37
|
+
// the submit payload's testArgs field, or null when the submission
|
|
38
|
+
// carries no selection. every violation names the offending arg.
|
|
39
|
+
export function parseCloudTestArgs(value: unknown): CloudTestArgsVerdict {
|
|
40
|
+
if (value === undefined || value === null) return { ok: true, args: [] }
|
|
41
|
+
if (!Array.isArray(value)) {
|
|
42
|
+
return { ok: false, message: 'testArgs must be an array of strings' }
|
|
43
|
+
}
|
|
44
|
+
if (value.length > CLOUD_TEST_ARGS_MAX_COUNT) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
message: `testArgs may contain at most ${CLOUD_TEST_ARGS_MAX_COUNT} entries`,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// utf-8 bytes, not utf-16 code units: the bound names bytes, and a
|
|
51
|
+
// multibyte pattern must not slip twice its size past the gate.
|
|
52
|
+
const encoder = new TextEncoder()
|
|
53
|
+
let bytes = 0
|
|
54
|
+
const entries: string[] = []
|
|
55
|
+
for (const entry of value) {
|
|
56
|
+
if (typeof entry !== 'string' || entry.length === 0) {
|
|
57
|
+
return { ok: false, message: 'testArgs entries must be non-empty strings' }
|
|
58
|
+
}
|
|
59
|
+
if (entry.length > CLOUD_TEST_ARG_MAX_LENGTH || entry.includes('\0')) {
|
|
60
|
+
return { ok: false, message: `testArgs entry is too long or carries a NUL byte: ${entry.slice(0, 60)}` }
|
|
61
|
+
}
|
|
62
|
+
bytes += encoder.encode(entry).length
|
|
63
|
+
if (bytes > CLOUD_TEST_ARGS_MAX_BYTES) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
message: `testArgs may contain at most ${CLOUD_TEST_ARGS_MAX_BYTES} bytes`,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
entries.push(entry)
|
|
70
|
+
}
|
|
71
|
+
const args: string[] = []
|
|
72
|
+
for (let i = 0; i < entries.length; i++) {
|
|
73
|
+
const entry = entries[i]
|
|
74
|
+
if (!entry.startsWith('-')) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
message: `testArgs carries a positional argument (selection flags only): ${entry.slice(0, 60)}`,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const eq = entry.indexOf('=')
|
|
81
|
+
const head = eq > 0 ? entry.slice(0, eq) : entry
|
|
82
|
+
if (!isCloudTestValueFlag(head)) {
|
|
83
|
+
return { ok: false, message: `testArgs carries an unknown flag: ${head.slice(0, 60)}` }
|
|
84
|
+
}
|
|
85
|
+
if (eq > 0) {
|
|
86
|
+
if (!entry.slice(eq + 1)) {
|
|
87
|
+
return { ok: false, message: `${head} expects a value` }
|
|
88
|
+
}
|
|
89
|
+
args.push(entry)
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
const val = entries[i + 1]
|
|
93
|
+
if (val === undefined || val.startsWith('-')) {
|
|
94
|
+
return { ok: false, message: `${head} expects a value` }
|
|
95
|
+
}
|
|
96
|
+
args.push(entry, val)
|
|
97
|
+
i += 1
|
|
98
|
+
}
|
|
99
|
+
return { ok: true, args }
|
|
100
|
+
}
|
package/src/cloud.ts
CHANGED
|
@@ -78,7 +78,7 @@ export interface ConnectRemoteSimulatorInput {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// the simulator-driving verbs. the same vocabulary the CLI drives, one for
|
|
81
|
-
// one: `
|
|
81
|
+
// one: `peach describe`, `peach do tap`, `peach wait selector`.
|
|
82
82
|
export interface RnxCommands {
|
|
83
83
|
command(
|
|
84
84
|
command: { type: string; [key: string]: unknown },
|
|
@@ -130,7 +130,7 @@ function normalizeCloudOrigin(source: string): string {
|
|
|
130
130
|
try {
|
|
131
131
|
parsed = new URL(source)
|
|
132
132
|
} catch {
|
|
133
|
-
throw new Error(`invalid
|
|
133
|
+
throw new Error(`invalid Peach Cloud origin: ${source}`)
|
|
134
134
|
}
|
|
135
135
|
if (
|
|
136
136
|
(parsed.protocol !== 'https:' && parsed.protocol !== 'http:') ||
|
|
@@ -140,16 +140,16 @@ function normalizeCloudOrigin(source: string): string {
|
|
|
140
140
|
parsed.search ||
|
|
141
141
|
parsed.hash
|
|
142
142
|
) {
|
|
143
|
-
throw new Error(`
|
|
143
|
+
throw new Error(`Peach Cloud origin must be an HTTP(S) origin: ${source}`)
|
|
144
144
|
}
|
|
145
145
|
if (parsed.protocol === 'http:' && !isLoopbackHost(parsed.hostname)) {
|
|
146
|
-
throw new Error(`
|
|
146
|
+
throw new Error(`Peach Cloud origin requires HTTPS outside loopback: ${source}`)
|
|
147
147
|
}
|
|
148
148
|
return parsed.origin
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
function resolveOrigin(value?: string): string {
|
|
152
|
-
const configured = value?.trim() || process.env.
|
|
152
|
+
const configured = value?.trim() || process.env.PEACH_CLOUD_ORIGIN?.trim()
|
|
153
153
|
return normalizeCloudOrigin(configured || rnxPublicBrand.origin)
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -159,14 +159,14 @@ function responseError(status: number, text: string): Error {
|
|
|
159
159
|
const parsed: unknown = JSON.parse(text)
|
|
160
160
|
const error = isRecord(parsed) ? parsed.error : null
|
|
161
161
|
if (isRecord(error) && typeof error.message === 'string') {
|
|
162
|
-
return new Error(`
|
|
162
|
+
return new Error(`Peach Cloud request failed (${status}): ${error.message}`)
|
|
163
163
|
}
|
|
164
164
|
} catch {
|
|
165
165
|
// body was not json; fall through to the raw text
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
return new Error(
|
|
169
|
-
`
|
|
169
|
+
`Peach Cloud request failed (${status})${text.trim() ? `: ${text.trim()}` : ''}`,
|
|
170
170
|
)
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -269,10 +269,10 @@ function readJsArtifact(bytes: Uint8Array): string {
|
|
|
269
269
|
try {
|
|
270
270
|
source = new TextDecoder('utf-8', { fatal: true }).decode(bytes)
|
|
271
271
|
} catch {
|
|
272
|
-
throw new Error('
|
|
272
|
+
throw new Error('peach Cloud artifact must be valid UTF-8 JavaScript, not bytecode')
|
|
273
273
|
}
|
|
274
274
|
if (source.includes('\0') || !source.includes('__d(') || !source.includes('__r(')) {
|
|
275
|
-
throw new Error('
|
|
275
|
+
throw new Error('peach Cloud artifact must be a Metro JavaScript bundle')
|
|
276
276
|
}
|
|
277
277
|
return source
|
|
278
278
|
}
|
|
@@ -284,7 +284,7 @@ function readClaim(value: unknown): RnxCloudClaim {
|
|
|
284
284
|
!value.id ||
|
|
285
285
|
typeof value.expiresAt !== 'number'
|
|
286
286
|
) {
|
|
287
|
-
throw new Error('
|
|
287
|
+
throw new Error('Peach Cloud returned an invalid claim')
|
|
288
288
|
}
|
|
289
289
|
return { id: value.id, expiresAt: value.expiresAt }
|
|
290
290
|
}
|
|
@@ -305,7 +305,7 @@ function readArtifact(
|
|
|
305
305
|
!Number.isInteger(value.bytes) ||
|
|
306
306
|
value.bytes !== expectedBytes
|
|
307
307
|
) {
|
|
308
|
-
throw new Error('
|
|
308
|
+
throw new Error('Peach Cloud returned an invalid artifact receipt')
|
|
309
309
|
}
|
|
310
310
|
return { id: expectedId, bytes: expectedBytes }
|
|
311
311
|
}
|
|
@@ -320,14 +320,14 @@ function readSimulatorCreate(
|
|
|
320
320
|
claim: RnxCloudClaim
|
|
321
321
|
artifact: RnxCloudBoxCreateReceipt['artifact']
|
|
322
322
|
} {
|
|
323
|
-
if (!isRecord(value)) throw new Error('
|
|
323
|
+
if (!isRecord(value)) throw new Error('Peach Cloud create returned an invalid simulator')
|
|
324
324
|
const claim = readClaim(value.claim)
|
|
325
325
|
if (
|
|
326
326
|
typeof value.simId !== 'string' ||
|
|
327
327
|
!value.simId ||
|
|
328
328
|
typeof value.token !== 'string'
|
|
329
329
|
) {
|
|
330
|
-
throw new Error('
|
|
330
|
+
throw new Error('Peach Cloud create returned an invalid simulator')
|
|
331
331
|
}
|
|
332
332
|
return {
|
|
333
333
|
simId: value.simId,
|
|
@@ -347,7 +347,7 @@ function readBoxCreate(
|
|
|
347
347
|
simulatorToken: string
|
|
348
348
|
} {
|
|
349
349
|
const artifactId = artifactDigestId(expectedSha256)
|
|
350
|
-
if (!isRecord(value)) throw new Error('
|
|
350
|
+
if (!isRecord(value)) throw new Error('Peach Cloud create returned invalid JSON')
|
|
351
351
|
const simulator = readSimulatorCreate(value.simulator, artifactId, expectedBytes)
|
|
352
352
|
if (
|
|
353
353
|
typeof value.boxId !== 'string' ||
|
|
@@ -356,7 +356,7 @@ function readBoxCreate(
|
|
|
356
356
|
typeof value.token !== 'string' ||
|
|
357
357
|
!value.token
|
|
358
358
|
) {
|
|
359
|
-
throw new Error('
|
|
359
|
+
throw new Error('Peach Cloud create returned an invalid box receipt')
|
|
360
360
|
}
|
|
361
361
|
return {
|
|
362
362
|
receipt: {
|
|
@@ -383,14 +383,14 @@ function cloudCommand(
|
|
|
383
383
|
if (key !== 'id' && key !== 'simId') clean[key] = value
|
|
384
384
|
}
|
|
385
385
|
if (!isRnxCloudCommandType(command.type)) {
|
|
386
|
-
throw new Error(`
|
|
386
|
+
throw new Error(`Peach Cloud does not support bridge command type ${command.type}`)
|
|
387
387
|
}
|
|
388
388
|
// a verb outside this token's scopes never leaves the sdk: the refusal
|
|
389
389
|
// names the scope before a request. the full token carries every scope
|
|
390
390
|
// except debug, so evaluate/call/close stop here for it.
|
|
391
391
|
if (!rnxCloudScopesAllowCommand(scopes, command.type)) {
|
|
392
392
|
throw new Error(
|
|
393
|
-
`
|
|
393
|
+
`Peach Cloud command ${command.type} requires the ${rnxCloudCommandScope(command.type)} scope`,
|
|
394
394
|
)
|
|
395
395
|
}
|
|
396
396
|
return { ...clean, type: command.type }
|
|
@@ -427,14 +427,14 @@ export class RemoteSimulator {
|
|
|
427
427
|
layers: options?.layers ?? 'tenant',
|
|
428
428
|
})
|
|
429
429
|
if (typeof result !== 'string' || !result.startsWith('data:image/')) {
|
|
430
|
-
throw new Error('
|
|
430
|
+
throw new Error('Peach Cloud screenshot did not return an image data url')
|
|
431
431
|
}
|
|
432
432
|
return result
|
|
433
433
|
},
|
|
434
434
|
capture: async () => {
|
|
435
435
|
const result = await this.sendCommand({ type: 'capture' })
|
|
436
436
|
if (!isRnxScreenCapture(result)) {
|
|
437
|
-
throw new Error('
|
|
437
|
+
throw new Error('Peach Cloud capture returned an invalid screen capture')
|
|
438
438
|
}
|
|
439
439
|
return result
|
|
440
440
|
},
|
|
@@ -444,13 +444,13 @@ export class RemoteSimulator {
|
|
|
444
444
|
options.strategy !== 'data' &&
|
|
445
445
|
options.strategy !== 'full'
|
|
446
446
|
) {
|
|
447
|
-
throw new Error('
|
|
447
|
+
throw new Error('Peach Cloud reset strategy must be data or full')
|
|
448
448
|
}
|
|
449
449
|
if (
|
|
450
450
|
options?.from !== undefined &&
|
|
451
451
|
(typeof options.from !== 'string' || options.from.length === 0)
|
|
452
452
|
) {
|
|
453
|
-
throw new Error('
|
|
453
|
+
throw new Error('Peach Cloud reset from must name a simulator')
|
|
454
454
|
}
|
|
455
455
|
return this.sendCommand(
|
|
456
456
|
options === undefined
|
|
@@ -525,7 +525,7 @@ export class RemoteSimulator {
|
|
|
525
525
|
)
|
|
526
526
|
} catch (error) {
|
|
527
527
|
throw new Error(
|
|
528
|
-
`could not reach
|
|
528
|
+
`could not reach Peach Cloud at ${this.session.origin}: ${
|
|
529
529
|
error instanceof Error ? error.message : String(error)
|
|
530
530
|
}`,
|
|
531
531
|
)
|
|
@@ -536,14 +536,14 @@ export class RemoteSimulator {
|
|
|
536
536
|
try {
|
|
537
537
|
parsed = JSON.parse(text)
|
|
538
538
|
} catch {
|
|
539
|
-
throw new Error('
|
|
539
|
+
throw new Error('Peach Cloud command returned unreadable JSON')
|
|
540
540
|
}
|
|
541
541
|
if (!isRecord(parsed) || parsed.id !== body.id) {
|
|
542
|
-
throw new Error('
|
|
542
|
+
throw new Error('Peach Cloud command returned an invalid command receipt')
|
|
543
543
|
}
|
|
544
544
|
if (typeof parsed.error === 'string' && parsed.error) throw new Error(parsed.error)
|
|
545
545
|
if (!Object.hasOwn(parsed, 'result')) {
|
|
546
|
-
throw new Error('
|
|
546
|
+
throw new Error('Peach Cloud command receipt has no result')
|
|
547
547
|
}
|
|
548
548
|
return parsed.result
|
|
549
549
|
}
|
|
@@ -570,11 +570,11 @@ export class RemoteSimulator {
|
|
|
570
570
|
try {
|
|
571
571
|
parsed = JSON.parse(text)
|
|
572
572
|
} catch {
|
|
573
|
-
throw new Error('
|
|
573
|
+
throw new Error('Peach Cloud claim returned unreadable JSON')
|
|
574
574
|
}
|
|
575
575
|
const claim = readClaim(isRecord(parsed) ? parsed.claim : null)
|
|
576
576
|
if (claim.id !== claimId) {
|
|
577
|
-
throw new Error('
|
|
577
|
+
throw new Error('Peach Cloud confirmation replaced the initial claim')
|
|
578
578
|
}
|
|
579
579
|
this.session = { ...this.session, claim }
|
|
580
580
|
return claim
|
|
@@ -611,17 +611,17 @@ export async function createRemoteSimulator(
|
|
|
611
611
|
): Promise<RemoteSimulator> {
|
|
612
612
|
if (input.artifact.byteLength > RNX_CLOUD_MAX_ARTIFACT_BYTES) {
|
|
613
613
|
throw new Error(
|
|
614
|
-
`
|
|
614
|
+
`peach Cloud artifact is ${input.artifact.byteLength} bytes and exceeds the ${RNX_CLOUD_MAX_ARTIFACT_BYTES}-byte limit`,
|
|
615
615
|
)
|
|
616
616
|
}
|
|
617
617
|
if (input.artifact.byteLength <= 0) {
|
|
618
|
-
throw new Error('
|
|
618
|
+
throw new Error('peach Cloud artifact is empty')
|
|
619
619
|
}
|
|
620
620
|
readJsArtifact(input.artifact)
|
|
621
621
|
const origin = resolveOrigin(input.origin)
|
|
622
622
|
const sha256 = await sha256Hex(input.artifact)
|
|
623
623
|
if (input.sha256 && input.sha256 !== sha256) {
|
|
624
|
-
throw new Error('
|
|
624
|
+
throw new Error('peach Cloud artifact sha256 does not match the provided digest')
|
|
625
625
|
}
|
|
626
626
|
const device = input.device?.trim() || DEFAULT_DEVICE
|
|
627
627
|
let response: Response
|
|
@@ -640,7 +640,7 @@ export async function createRemoteSimulator(
|
|
|
640
640
|
})
|
|
641
641
|
} catch (error) {
|
|
642
642
|
throw new Error(
|
|
643
|
-
`could not reach
|
|
643
|
+
`could not reach Peach Cloud at ${origin}: ${
|
|
644
644
|
error instanceof Error ? error.message : String(error)
|
|
645
645
|
}`,
|
|
646
646
|
)
|
|
@@ -651,7 +651,7 @@ export async function createRemoteSimulator(
|
|
|
651
651
|
try {
|
|
652
652
|
parsed = JSON.parse(responseText)
|
|
653
653
|
} catch {
|
|
654
|
-
throw new Error('
|
|
654
|
+
throw new Error('Peach Cloud create returned unreadable JSON')
|
|
655
655
|
}
|
|
656
656
|
const created = readBoxCreate(parsed, input.artifact.byteLength, sha256)
|
|
657
657
|
const simulator = new RemoteSimulator({
|
|
@@ -677,7 +677,7 @@ function readSimArtifact(value: unknown): RnxCloudCreateReceipt['artifact'] {
|
|
|
677
677
|
!Number.isInteger(value.bytes) ||
|
|
678
678
|
value.bytes <= 0
|
|
679
679
|
) {
|
|
680
|
-
throw new Error('
|
|
680
|
+
throw new Error('Peach Cloud returned an invalid simulator artifact')
|
|
681
681
|
}
|
|
682
682
|
return { id: `sha256:${value.sha256}`, bytes: value.bytes }
|
|
683
683
|
}
|
|
@@ -707,7 +707,7 @@ function readConnectScopes(
|
|
|
707
707
|
) ||
|
|
708
708
|
typeof value.expiresAt !== 'number'
|
|
709
709
|
) {
|
|
710
|
-
throw new Error('
|
|
710
|
+
throw new Error('Peach Cloud returned an invalid scoped token')
|
|
711
711
|
}
|
|
712
712
|
return Object.freeze([...value.scopes])
|
|
713
713
|
}
|
|
@@ -718,8 +718,8 @@ function readConnectScopes(
|
|
|
718
718
|
export async function connect(
|
|
719
719
|
input: ConnectRemoteSimulatorInput,
|
|
720
720
|
): Promise<RemoteSimulator> {
|
|
721
|
-
if (!input.id) throw new Error('
|
|
722
|
-
if (!input.token) throw new Error('
|
|
721
|
+
if (!input.id) throw new Error('Peach Cloud connect needs a simulator id')
|
|
722
|
+
if (!input.token) throw new Error('Peach Cloud connect needs a token')
|
|
723
723
|
const origin = resolveOrigin(input.origin)
|
|
724
724
|
let response: Response
|
|
725
725
|
try {
|
|
@@ -729,7 +729,7 @@ export async function connect(
|
|
|
729
729
|
)
|
|
730
730
|
} catch (error) {
|
|
731
731
|
throw new Error(
|
|
732
|
-
`could not reach
|
|
732
|
+
`could not reach Peach Cloud at ${origin}: ${
|
|
733
733
|
error instanceof Error ? error.message : String(error)
|
|
734
734
|
}`,
|
|
735
735
|
)
|
|
@@ -740,14 +740,14 @@ export async function connect(
|
|
|
740
740
|
try {
|
|
741
741
|
parsed = JSON.parse(text)
|
|
742
742
|
} catch {
|
|
743
|
-
throw new Error('
|
|
743
|
+
throw new Error('Peach Cloud connect returned unreadable JSON')
|
|
744
744
|
}
|
|
745
745
|
if (!isRecord(parsed) || !isRecord(parsed.sim)) {
|
|
746
|
-
throw new Error('
|
|
746
|
+
throw new Error('Peach Cloud connect returned an invalid simulator')
|
|
747
747
|
}
|
|
748
748
|
const sim = parsed.sim
|
|
749
749
|
if (typeof sim.simId !== 'string' || !sim.simId) {
|
|
750
|
-
throw new Error('
|
|
750
|
+
throw new Error('Peach Cloud connect returned an invalid simulator')
|
|
751
751
|
}
|
|
752
752
|
return new RemoteSimulator({
|
|
753
753
|
origin,
|
package/src/config.ts
CHANGED
|
@@ -114,7 +114,7 @@ export function readRNXConfigFromSearchParams<
|
|
|
114
114
|
return hasRNXConfig(parsed) ? parsed : undefined
|
|
115
115
|
} catch (error) {
|
|
116
116
|
const message = error instanceof Error ? error.message : String(error)
|
|
117
|
-
console.warn(`[
|
|
117
|
+
console.warn(`[peach] invalid ${RNX_CONFIG_QUERY_PARAM}: ${message}`)
|
|
118
118
|
return undefined
|
|
119
119
|
}
|
|
120
120
|
}
|