rnxsim 0.1.380 → 0.1.382
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 +43 -26
- package/cli/app-fonts.ts +1 -1
- package/cli/bin.ts +118 -26
- package/cli/bridge-diagnostics.ts +1 -1
- package/cli/bridge-flow-runner.ts +9 -9
- package/cli/browser-evals.ts +3 -3
- package/cli/cli-update.ts +261 -0
- package/cli/cloud-client.ts +407 -0
- package/cli/cloud-dispatch.ts +165 -0
- package/cli/cloud-session.ts +156 -0
- package/cli/command-contract.ts +2 -2
- package/cli/command-registry.ts +9 -0
- package/cli/commands/agent-wrapper.ts +4 -4
- package/cli/commands/agent.ts +3 -3
- package/cli/commands/box/checkout-plane.ts +16 -8
- package/cli/commands/box/rnx-command.ts +2 -2
- package/cli/commands/box/search-command.ts +2 -2
- package/cli/commands/box.ts +123 -70
- package/cli/commands/config.ts +3 -2
- package/cli/commands/control.ts +6 -6
- package/cli/commands/cpu-profile.ts +2 -2
- package/cli/commands/daemon.ts +7 -7
- package/cli/commands/debug.ts +2 -2
- package/cli/commands/detox.ts +7 -7
- package/cli/commands/diagnose.ts +1 -1
- package/cli/commands/electron.ts +1 -1
- package/cli/commands/film.ts +1 -1
- package/cli/commands/flow.ts +4 -4
- package/cli/commands/inspect/core.ts +58 -7
- package/cli/commands/inspect/env.ts +5 -3
- package/cli/commands/inspect/shared.ts +2 -1
- package/cli/commands/inspect/wait-event.ts +1 -1
- package/cli/commands/inspect/wait-ready.ts +15 -5
- package/cli/commands/inspect.ts +31 -9
- package/cli/commands/install-desktop.ts +2 -2
- package/cli/commands/maestro.ts +3 -3
- package/cli/commands/permissions.ts +1 -1
- package/cli/commands/platform.ts +91 -0
- package/cli/commands/record.ts +3 -3
- package/cli/commands/report-issue.ts +1 -1
- package/cli/commands/runtime.ts +1 -1
- package/cli/commands/screenshot-mode.ts +1 -1
- package/cli/commands/screenshot.ts +25 -0
- package/cli/commands/serve.ts +1 -1
- package/cli/commands/skills.ts +1 -1
- package/cli/commands/timeline.ts +1 -1
- package/cli/commands/upgrade.ts +8 -34
- package/cli/commands/upload.ts +3 -3
- package/cli/commands/version.ts +14 -3
- package/cli/commands/what-happened.ts +2 -2
- package/cli/current-sim.ts +43 -30
- package/cli/desktop-companion.ts +1 -1
- package/cli/drivers/electron.ts +1 -1
- package/cli/drivers/playwright.ts +3 -3
- package/cli/drivers/types.ts +4 -4
- package/cli/flow-file.ts +3 -3
- package/cli/help-core.ts +18 -11
- package/cli/hidden-runtime-alias.ts +1 -1
- package/cli/hints.ts +2 -2
- package/cli/maestro-js.ts +2 -2
- package/cli/outbound-endpoints.ts +40 -6
- package/cli/parse-args.ts +1 -1
- package/cli/registry.ts +1 -1
- package/cli/resolve-assets.ts +2 -2
- package/cli/secure-file.ts +31 -0
- package/cli/shell-init.ts +98 -0
- package/cli/ws-bridge.ts +18 -8
- package/detox/expectations.ts +1 -1
- package/detox/gestures.ts +3 -3
- package/detox/index.ts +3 -3
- 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 +15 -8
- package/dist-lib/bridge-contract-input.mjs +15 -8
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +107 -0
- package/dist-lib/capture-contract.mjs +80 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +32 -0
- package/dist-lib/cloud-contract.mjs +7 -0
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +4 -4
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +9 -1
- package/dist-lib/host/bridge-host.cjs +15 -10
- 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 +248 -73
- 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-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +44 -6
- package/dist-lib/sdk.mjs +44 -6
- package/dist-lib/skills.cjs +435 -155
- package/dist-lib/vite.cjs +1 -1
- package/package.json +21 -5
- package/scripts/dev-server-scanner.ts +3 -3
- package/skills/contrast/SKILL.md +1 -1
- package/skills/rnx-perf/SKILL.md +68 -52
- package/src/app-url.ts +2 -3
- package/src/bridge-contract-input.ts +17 -6
- package/src/bridge-contract.ts +25 -4
- package/src/capture-contract.ts +423 -0
- package/src/cloud-contract.ts +15 -0
- package/src/home-paths.ts +9 -3
- package/src/host/agent-host.ts +1 -1
- package/src/host/bridge-host.ts +13 -11
- package/src/host/plane-host.ts +2 -2
- package/src/index.ts +21 -0
- package/src/native-dev-bundle-url.ts +2 -2
- package/src/screenshots/orchestrate.ts +1 -1
- package/src/screenshots/org-deck.ts +1 -1
- package/src/settings-bundle.ts +1 -1
- package/src/sim-client.ts +4 -0
- package/src/vite-plugin-one.ts +2 -2
- package/src/vite-plugin.ts +1 -1
- package/cli/commands/box/cloud-box.ts +0 -294
package/README.md
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
# rnx
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
React Native simulator.
|
|
3
|
+
The source owner for the standalone `rnx` executable and the public `rnxsim`
|
|
4
|
+
integration library used to drive a browser-native React Native simulator.
|
|
6
5
|
|
|
7
6
|
## Overview
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
The CLI and library are separate public artifacts. The recommended installer
|
|
9
|
+
downloads one Bun-compiled `rnx` executable that needs no Node.js, npm, or Bun
|
|
10
|
+
installation. The `rnxsim` npm package contains bundler plugins, testing
|
|
11
|
+
drivers, and library exports, with no command-line binary.
|
|
12
|
+
|
|
13
|
+
The executable updates itself with `rnx upgrade`. Successful interactive human
|
|
14
|
+
commands refresh a cached stable CLI version in a detached process at most once
|
|
15
|
+
every 20 hours. A newer version produces one short notice on stderr, once per
|
|
16
|
+
version and no more than weekly. CI, agents, JSON output, pipes, and
|
|
17
|
+
noninteractive commands stay silent. Set `RNX_UPDATE_CHECK=off` to disable the
|
|
18
|
+
check and notice; `rnx version` checks explicitly, while `rnx --version` remains
|
|
19
|
+
offline.
|
|
20
|
+
|
|
21
|
+
Neither artifact contains rendering code. The canvas engine (CanvasKit
|
|
22
|
+
renderer, Yoga layout, iOS/Android shell chrome, Electron shell) lives in the
|
|
23
|
+
private `sootsim-engine` workspace package and is shipped at runtime as a
|
|
24
|
+
versioned tarball, fetched from the contrast.dev CDN and unpacked into `~/.rnx`.
|
|
25
|
+
|
|
26
|
+
What this workspace gives you:
|
|
27
|
+
|
|
28
|
+
- the standalone `rnx` CLI source and binary build, which provides the primary
|
|
29
|
+
debugging and automation surface (inspect the UI tree, tap elements, capture
|
|
30
|
+
flows, screenshot, record, run agents)
|
|
19
31
|
- `rnxsim/vite` and `rnxsim/metro` — bundler plugins that serve the installed
|
|
20
32
|
engine runtime from your own dev server
|
|
21
33
|
- `rnxsim/jump-to-source-babel` — an optional Babel plugin that annotates JSX
|
|
@@ -33,14 +45,14 @@ chrome, gesture + three-button nav, system UI/window metrics with cutout-safe
|
|
|
33
45
|
insets) but is not full parity until the Android launch gates in the Contrast
|
|
34
46
|
repo plan pass.
|
|
35
47
|
|
|
36
|
-
## What's in
|
|
48
|
+
## What's in each distribution
|
|
37
49
|
|
|
38
|
-
|
|
|
39
|
-
| --- | --- |
|
|
40
|
-
| CLI
|
|
41
|
-
| bridge daemon host
|
|
42
|
-
|
|
|
43
|
-
|
|
|
50
|
+
| standalone `rnx` executable | `rnxsim` npm package | private engine runtime |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| CLI commands and bridge client | Vite and Metro plugins | CanvasKit renderer and Yoga layout |
|
|
53
|
+
| bridge daemon host | SDK and bridge contracts | iOS/Android shell chrome, home grid, app switcher |
|
|
54
|
+
| runtime, browser, and desktop launchers | Detox driver and Jest preset | Electron shell and wasm |
|
|
55
|
+
| Maestro, screenshots, recording, skills | native-resolution helpers and skills registry | the actual pixels |
|
|
44
56
|
|
|
45
57
|
The engine is delivered at runtime, never bundled into this package. A
|
|
46
58
|
committed-but-unbuilt engine fix has no effect on a running sim until the
|
|
@@ -197,6 +209,7 @@ bridge always addresses the **shell worker**, never an undifferentiated sim.
|
|
|
197
209
|
| AgentHost + sessions | Agent-routing extension of the host: owns the single FIFO reader per agent session and fans `agent:event`/`session-status` pushes to every WS subscriber. Backed by agent-sessions + attached-projects stores. | `src/host/agent-host.ts`, `src/agent-sessions.ts`, `src/attached-projects.ts`, `src/agent-daemon-client.ts` |
|
|
198
210
|
| host proxies + dev-server scan | Host-side helpers so a cross-origin tenant worker can reach local dev servers and same-origin APIs: `/__server-scan` discovers running metro/expo/vxrn/one servers; fetch + websocket proxies relay guest-app network through the daemon. | `src/host/fetch-proxy-handler.ts`, `src/host/websocket-proxy.ts`, `scripts/dev-server-scanner.ts`, `src/dev-bundle-resolution.ts` |
|
|
199
211
|
| runtime delivery + rnx home | Versioned engine runtime management. The shared machinery owns manifest fetch, sha256 verification, channels, and auto-update. rnx binds it to `https://contrast.dev` and `~/.rnx/runtimes/<version>`. A repo can select `runtimeVersion` without changing the machine default. | `src/runtime-delivery.ts`, `packages/contrast-runtime-delivery/`, `src/home-paths.ts`, `src/runtime-assets.ts` |
|
|
212
|
+
| standalone CLI delivery | Cached stable-version checks, bounded interactive notices, and verified atomic executable replacement. | `cli/cli-update.ts`, `cli/commands/upgrade.ts`, `cli/commands/version.ts`, `src/cli-version.ts` |
|
|
200
213
|
| `rnxsim/vite` + `rnxsim/metro` (runtime serving) | Published bundler plugins. Both serve the installed engine runtime at `/__soot/` from `~/.rnx/runtimes/<version>` so an app's own dev server can host the sim shell; either plugin can opt into a named Electron window. | `src/vite-plugin-one.ts`, `src/metro-plugin.ts`, `src/runtime-assets.ts` |
|
|
201
214
|
| `sootsim()` vite resolver plugin (internal) | The large RN-resolution / native-stub vite plugin: aliases `react-native` to the engine shim, native packages to `@sootsim/compat` stubs, applies worklets/babel transforms, and instantiates `SootSimBridgeHost` in its dev server. Distinct from the published `rnxsim/vite` export. | `src/vite-plugin.ts`, `src/worklets-babel.ts`, `packages/compat/src/stub-manifest.ts` |
|
|
202
215
|
| skills registry | rnx agent skills (`rnx-setup`, `rnx-debug`, `rnx-test`, `rnx-visual`, and the generated `contrast` index) installable with `rnx skill`. | `skills/*.md`, `src/skills/registry.ts`, `src/skills/types.ts` |
|
|
@@ -244,10 +257,13 @@ window for a selected device (same device list as the `Window >` menu).
|
|
|
244
257
|
|
|
245
258
|
### Privacy
|
|
246
259
|
|
|
247
|
-
rnx
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
260
|
+
rnx sends no analytics, crash report, command event, app data, project data, or
|
|
261
|
+
identity by default. An unsigned-in interactive run may request the runtime it
|
|
262
|
+
is about to execute and the stable standalone CLI version document. The latter
|
|
263
|
+
runs after the command in a detached process at most once every 20 hours and can
|
|
264
|
+
be disabled with `RNX_UPDATE_CHECK=off`. The first human run asks once about
|
|
265
|
+
bounded diagnostics, defaults to no, and never prompts in a non-TTY shell, CI,
|
|
266
|
+
or an agent. An automated first run is declined for that process without
|
|
251
267
|
consuming the later human choice. Legacy browser and Electron telemetry senders
|
|
252
268
|
are removed rather than being enabled by this consent.
|
|
253
269
|
|
|
@@ -515,12 +531,12 @@ frame for quick art-direction passes.
|
|
|
515
531
|
`<skill-name>/SKILL.md` layout used by Codex and Claude Code. The bundled
|
|
516
532
|
skills cover setup, debugging/perf/accessibility, testing, visual review, and
|
|
517
533
|
the generated `contrast` command index. The published CLI registry and generated
|
|
518
|
-
website docs both come from `packages/
|
|
534
|
+
website docs both come from `packages/rnx-skills/`:
|
|
519
535
|
|
|
520
536
|
- `packages/sootsim/skills/contrast/SKILL.md` is generated from that registry
|
|
521
537
|
- `src/features/site/docs/rnx/cli/*` is generated output, not hand-edited
|
|
522
538
|
|
|
523
|
-
When command docs or examples drift, update `packages/
|
|
539
|
+
When command docs or examples drift, update `packages/rnx-skills/` and run:
|
|
524
540
|
|
|
525
541
|
```sh
|
|
526
542
|
bun run generate:sootsim-docs
|
|
@@ -545,6 +561,7 @@ verifies its sha256, and unpacks it under `~/.rnx`:
|
|
|
545
561
|
├── profiles/ persistent named app storage, without browser caches
|
|
546
562
|
├── electron/userData/ desktop app storage, with bounded browser caches
|
|
547
563
|
├── daemon.json lockfile: pid, ports, active runtime, heartbeat
|
|
564
|
+
├── cli-update.json cached standalone CLI version and notice state
|
|
548
565
|
├── automatic-cleanup-v1.json completed legacy cleanup generation
|
|
549
566
|
└── config.json user prefs: update channel, cdn origin override
|
|
550
567
|
```
|
package/cli/app-fonts.ts
CHANGED
|
@@ -124,7 +124,7 @@ export function discoverNativeLinkedAppFonts(opts: {
|
|
|
124
124
|
|
|
125
125
|
// @react-native-vector-icons links fonts from its configured local font
|
|
126
126
|
// directory and each installed icon package into the native app. those files
|
|
127
|
-
// never enter the metro module graph, so
|
|
127
|
+
// never enter the metro module graph, so rnx must stage them through the
|
|
128
128
|
// same native-linked font wire as expo-font config-plugin assets. Font Awesome
|
|
129
129
|
// Pro installs its licensed faces under the local directory rather than
|
|
130
130
|
// publishing them in the npm package.
|
package/cli/bin.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
// registry at `packages/
|
|
3
|
+
// rnx CLI entry point. the full command and verb surface lives in the
|
|
4
|
+
// registry at `packages/rnx-skills/src/cli/meta.ts` — help output,
|
|
5
5
|
// website docs, and agent skills all read from there. do not duplicate
|
|
6
|
-
// command listings in this file; run `
|
|
6
|
+
// command listings in this file; run `rnx --help` for the canonical
|
|
7
7
|
// surface.
|
|
8
8
|
|
|
9
9
|
import { rnxPublicBrand } from '../src/public-brand'
|
|
@@ -11,6 +11,37 @@ import { parseArgs, TOP_LEVEL_RUNTIME_COMMANDS } from './parse-args'
|
|
|
11
11
|
import { RnxExit } from './run-rnx'
|
|
12
12
|
import { IS_STANDALONE } from './standalone'
|
|
13
13
|
|
|
14
|
+
if (process.argv[2] === '__shell-init') {
|
|
15
|
+
const { renderRnxShellInit } = await import('./shell-init')
|
|
16
|
+
try {
|
|
17
|
+
process.stdout.write(renderRnxShellInit(process.argv[3] ?? ''))
|
|
18
|
+
process.exit(0)
|
|
19
|
+
} catch (error) {
|
|
20
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
21
|
+
process.exit(1)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (process.argv[2] === '__cloud-confirm') {
|
|
26
|
+
try {
|
|
27
|
+
const [{ confirmCloudSimulator }, { readCloudSession }] = await Promise.all([
|
|
28
|
+
import('./cloud-client'),
|
|
29
|
+
import('./cloud-session'),
|
|
30
|
+
])
|
|
31
|
+
const session = readCloudSession()
|
|
32
|
+
if (!session) throw new Error('RNX cloud confirmation has no shell session')
|
|
33
|
+
await confirmCloudSimulator(session)
|
|
34
|
+
process.exit(0)
|
|
35
|
+
} catch (error) {
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
`RNX cloud confirmation failed: ${
|
|
38
|
+
error instanceof Error ? error.message : String(error)
|
|
39
|
+
}\n`,
|
|
40
|
+
)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
14
45
|
// `./help` is loaded lazily (only on help paths) — it statically pulls the full
|
|
15
46
|
// CLI registry renderers and engine settings, which would otherwise add ~400ms
|
|
16
47
|
// of eager chunk load to every command, including hot-path reads like
|
|
@@ -25,7 +56,7 @@ import { IS_STANDALONE } from './standalone'
|
|
|
25
56
|
// don't paper over real bugs.
|
|
26
57
|
function handleTopLevelError(err: unknown): never {
|
|
27
58
|
const msg = err instanceof Error ? err.message : String(err)
|
|
28
|
-
//
|
|
59
|
+
// rnx's known top-level errors are all single-line, narrow strings.
|
|
29
60
|
// everything else gets the full stack so unexpected crashes still surface.
|
|
30
61
|
const isKnownCliFailure =
|
|
31
62
|
/^command timed out after \d+s$/.test(msg) ||
|
|
@@ -70,10 +101,24 @@ const isAutomaticCleanupWorker =
|
|
|
70
101
|
parsed.command === 'cleanup' &&
|
|
71
102
|
parsed.commandArgs.length === 1 &&
|
|
72
103
|
parsed.commandArgs[0] === '--automatic-worker'
|
|
104
|
+
const isCliUpdateWorker =
|
|
105
|
+
parsed.command === 'upgrade' &&
|
|
106
|
+
parsed.commandArgs.length === 1 &&
|
|
107
|
+
parsed.commandArgs[0] === '--check-worker'
|
|
73
108
|
const isBareWelcome = !parsed.command && parsed.commandArgs.length === 0
|
|
74
109
|
const isReadOnlyInvocation =
|
|
75
110
|
parsed.version || parsed.help || parsed.commandArgs.includes('--dry-run')
|
|
76
111
|
|
|
112
|
+
if (isCliUpdateWorker) {
|
|
113
|
+
if (IS_STANDALONE) {
|
|
114
|
+
try {
|
|
115
|
+
const { refreshCliUpdateCache } = await import('./cli-update')
|
|
116
|
+
await refreshCliUpdateCache()
|
|
117
|
+
} catch {}
|
|
118
|
+
}
|
|
119
|
+
process.exit(0)
|
|
120
|
+
}
|
|
121
|
+
|
|
77
122
|
if (
|
|
78
123
|
!isAutomaticCleanupWorker &&
|
|
79
124
|
!isBareWelcome &&
|
|
@@ -105,6 +150,25 @@ if (!isReadOnlyInvocation && parsed.command !== 'serve' && parsed.command !== 'd
|
|
|
105
150
|
}
|
|
106
151
|
|
|
107
152
|
let automaticCleanupFinished = false
|
|
153
|
+
async function spawnDetachedSelf(
|
|
154
|
+
args: string[],
|
|
155
|
+
stderr: 'ignore' | 'inherit',
|
|
156
|
+
): Promise<import('node:child_process').ChildProcess> {
|
|
157
|
+
const { spawn } = await import('node:child_process')
|
|
158
|
+
const options: import('node:child_process').SpawnOptions = {
|
|
159
|
+
detached: true,
|
|
160
|
+
stdio: ['ignore', 'ignore', stderr],
|
|
161
|
+
env: process.env,
|
|
162
|
+
}
|
|
163
|
+
const entry = process.argv[1]
|
|
164
|
+
const child = IS_STANDALONE
|
|
165
|
+
? spawn(process.execPath, args, options)
|
|
166
|
+
: entry
|
|
167
|
+
? spawn(process.execPath, [entry, ...args], options)
|
|
168
|
+
: spawn(rnxPublicBrand.commandName, args, options)
|
|
169
|
+
return child
|
|
170
|
+
}
|
|
171
|
+
|
|
108
172
|
async function scheduleAutomaticCleanup(): Promise<void> {
|
|
109
173
|
if (automaticCleanupFinished) return
|
|
110
174
|
automaticCleanupFinished = true
|
|
@@ -122,26 +186,8 @@ async function scheduleAutomaticCleanup(): Promise<void> {
|
|
|
122
186
|
const { shouldSkipAutomaticSootsimCleanup } = await import('../src/disk-cleanup')
|
|
123
187
|
if (shouldSkipAutomaticSootsimCleanup()) return
|
|
124
188
|
|
|
125
|
-
const { spawn } = await import('node:child_process')
|
|
126
189
|
const workerArgs = ['cleanup', '--automatic-worker']
|
|
127
|
-
const
|
|
128
|
-
const child = IS_STANDALONE
|
|
129
|
-
? spawn(process.execPath, workerArgs, {
|
|
130
|
-
detached: true,
|
|
131
|
-
stdio: ['ignore', 'ignore', 'inherit'],
|
|
132
|
-
env: process.env,
|
|
133
|
-
})
|
|
134
|
-
: entry
|
|
135
|
-
? spawn(process.execPath, [entry, ...workerArgs], {
|
|
136
|
-
detached: true,
|
|
137
|
-
stdio: ['ignore', 'ignore', 'inherit'],
|
|
138
|
-
env: process.env,
|
|
139
|
-
})
|
|
140
|
-
: spawn(rnxPublicBrand.commandName, workerArgs, {
|
|
141
|
-
detached: true,
|
|
142
|
-
stdio: ['ignore', 'ignore', 'inherit'],
|
|
143
|
-
env: process.env,
|
|
144
|
-
})
|
|
190
|
+
const child = await spawnDetachedSelf(workerArgs, 'inherit')
|
|
145
191
|
child.once('error', (error) => {
|
|
146
192
|
process.stderr.write(
|
|
147
193
|
` ${rnxPublicBrand.name} automatic cleanup will retry on the next run: ${error.message}\n`,
|
|
@@ -157,9 +203,48 @@ async function scheduleAutomaticCleanup(): Promise<void> {
|
|
|
157
203
|
}
|
|
158
204
|
}
|
|
159
205
|
|
|
206
|
+
let automaticCliUpdateFinished = false
|
|
207
|
+
async function scheduleAutomaticCliUpdate(exitCode: number): Promise<void> {
|
|
208
|
+
if (automaticCliUpdateFinished) return
|
|
209
|
+
automaticCliUpdateFinished = true
|
|
210
|
+
if (!IS_STANDALONE) return
|
|
211
|
+
const {
|
|
212
|
+
claimAutomaticCliUpdateCheck,
|
|
213
|
+
shouldUseAutomaticCliUpdateCheck,
|
|
214
|
+
takeCliUpdateNotification,
|
|
215
|
+
} = await import('./cli-update')
|
|
216
|
+
if (
|
|
217
|
+
!shouldUseAutomaticCliUpdateCheck({
|
|
218
|
+
command: parsed.command,
|
|
219
|
+
commandArgs: parsed.commandArgs,
|
|
220
|
+
exitCode,
|
|
221
|
+
standalone: IS_STANDALONE,
|
|
222
|
+
stderrIsTTY: process.stderr.isTTY === true,
|
|
223
|
+
stdoutIsTTY: process.stdout.isTTY === true,
|
|
224
|
+
version: parsed.version,
|
|
225
|
+
})
|
|
226
|
+
) {
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const update = takeCliUpdateNotification()
|
|
231
|
+
if (update) {
|
|
232
|
+
process.stderr.write(
|
|
233
|
+
` update: rnx v${update.currentVersion} → v${update.latestVersion} · run \`rnx upgrade\`\n`,
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
if (!claimAutomaticCliUpdateCheck()) return
|
|
237
|
+
try {
|
|
238
|
+
const child = await spawnDetachedSelf(['upgrade', '--check-worker'], 'ignore')
|
|
239
|
+
child.once('error', () => {})
|
|
240
|
+
child.unref()
|
|
241
|
+
} catch {}
|
|
242
|
+
}
|
|
243
|
+
|
|
160
244
|
async function exitWithFlush(code: number): Promise<never> {
|
|
161
245
|
const { flushCliTelemetry } = await import('./telemetry')
|
|
162
246
|
await flushCliTelemetry()
|
|
247
|
+
await scheduleAutomaticCliUpdate(code)
|
|
163
248
|
await scheduleAutomaticCleanup()
|
|
164
249
|
process.exit(code)
|
|
165
250
|
}
|
|
@@ -222,7 +307,7 @@ if (!parsed.command && parsed.commandArgs.length === 0) {
|
|
|
222
307
|
}
|
|
223
308
|
|
|
224
309
|
// route to command. an empty command at this point only happens if the
|
|
225
|
-
// caller passed bare positional args without a verb (e.g. `
|
|
310
|
+
// caller passed bare positional args without a verb (e.g. `rnx -- foo`),
|
|
226
311
|
// which used to fall through to the bundler-wrap behavior in `dev`. now that
|
|
227
312
|
// `dev` is gone, treat it as "show help".
|
|
228
313
|
const command = parsed.command ?? ''
|
|
@@ -280,7 +365,7 @@ if (
|
|
|
280
365
|
// shell and perf subcommands are documented by their runtime dispatchers.
|
|
281
366
|
// don't try to route through generated per-verb docs.
|
|
282
367
|
} else if (isGroupingVerb && !subVerb) {
|
|
283
|
-
// bare `
|
|
368
|
+
// bare `rnx <group> --help` — render from the registry.
|
|
284
369
|
const { printGroupHelp } = await import('./help')
|
|
285
370
|
if (printGroupHelp(command)) await exitWithFlush(0)
|
|
286
371
|
// fall through — runInspect shows the full help for anything the
|
|
@@ -296,6 +381,12 @@ if (
|
|
|
296
381
|
}
|
|
297
382
|
}
|
|
298
383
|
|
|
384
|
+
const { dispatchCloudBoundary } = await import('./cloud-dispatch')
|
|
385
|
+
const cloudBoundary = await dispatchCloudBoundary({
|
|
386
|
+
command,
|
|
387
|
+
args: commandArgsWithSim,
|
|
388
|
+
})
|
|
389
|
+
if (cloudBoundary.handled) await exitWithFlush(cloudBoundary.code)
|
|
299
390
|
// the command modules stop by throwing RnxExit rather than ending the
|
|
300
391
|
// process, because the same command has to run in a box shell and in
|
|
301
392
|
// workerd, where process.exit() cancels the whole request. this is the
|
|
@@ -331,7 +422,7 @@ try {
|
|
|
331
422
|
const { runMaestro } = await import('./commands/maestro')
|
|
332
423
|
// Maestro forwards playback argv to the shared runner, so it must
|
|
333
424
|
// receive the global `--sim`. passing the bare
|
|
334
|
-
// parsed.commandArgs dropped an explicit `
|
|
425
|
+
// parsed.commandArgs dropped an explicit `rnx --sim <id> maestro
|
|
335
426
|
// …`, silently falling back to the current/saved sim (QA F21-4).
|
|
336
427
|
const code = await runMaestro(commandArgsWithSim, { port, verbose })
|
|
337
428
|
// bridge + dynamic imports leave handles open that keep node alive.
|
|
@@ -589,4 +680,5 @@ try {
|
|
|
589
680
|
throw error
|
|
590
681
|
}
|
|
591
682
|
|
|
683
|
+
await scheduleAutomaticCliUpdate(0)
|
|
592
684
|
await scheduleAutomaticCleanup()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MERGED_CONSOLE_COUNT_EVAL, inspectErrors } from './commands/inspect/core'
|
|
2
2
|
import { formatLogTimestamp } from './commands/inspect/shared'
|
|
3
3
|
import type { WsBridge } from './ws-bridge'
|
|
4
|
-
import type { SootSimRequestEntry } from '@
|
|
4
|
+
import type { SootSimRequestEntry } from '@rnx/globals'
|
|
5
5
|
|
|
6
6
|
interface ConsoleSummaryOptions {
|
|
7
7
|
includeTail?: boolean
|
|
@@ -18,7 +18,7 @@ import type { WsBridge } from './ws-bridge'
|
|
|
18
18
|
import type {
|
|
19
19
|
SootSimExternalAppLifecycleOptions,
|
|
20
20
|
SootSimLaunchArguments,
|
|
21
|
-
} from '@
|
|
21
|
+
} from '@rnx/globals'
|
|
22
22
|
|
|
23
23
|
const DEFAULT_TIMEOUT = 10000
|
|
24
24
|
// hosted-runner previews reach first-render much slower than local dev: the
|
|
@@ -174,7 +174,7 @@ export interface MaestroStep {
|
|
|
174
174
|
launchApp?: any
|
|
175
175
|
// maestro canonical form: object with `file` XOR inline `commands`, plus
|
|
176
176
|
// optional `when` gate and `env` vars scoped to the sub-flow. legacy
|
|
177
|
-
//
|
|
177
|
+
// rnx also accepts a bare path string.
|
|
178
178
|
runFlow?:
|
|
179
179
|
| string
|
|
180
180
|
| {
|
|
@@ -1087,7 +1087,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
1087
1087
|
// single tap can land on a transitioning node and never grant focus —
|
|
1088
1088
|
// the keys then go nowhere and the field stays empty, silently breaking
|
|
1089
1089
|
// submit (the bluesky login-submit RCA, 2026-06-11). re-resolve + re-tap
|
|
1090
|
-
// until the input reports focused, the same gate
|
|
1090
|
+
// until the input reports focused, the same gate rnx's `type-into`
|
|
1091
1091
|
// command uses. non-text-input taps keep the single-tap path.
|
|
1092
1092
|
const tapInputAttempts = element.isTextInput ? 4 : 1
|
|
1093
1093
|
for (let attempt = 0; attempt < tapInputAttempts; attempt++) {
|
|
@@ -1198,14 +1198,14 @@ export class SootSimBridgeFlowRunner {
|
|
|
1198
1198
|
|
|
1199
1199
|
async inputText(text: string) {
|
|
1200
1200
|
// real maestro types into the currently-focused field, and its driver's
|
|
1201
|
-
// tap synchronously focuses it. in
|
|
1201
|
+
// tap synchronously focuses it. in rnx, focus propagates async across
|
|
1202
1202
|
// the tenant→shell worker boundary (tap → focusTextInput → keyboard show),
|
|
1203
1203
|
// so a preceding `tapOn` on a text input may not have landed focus by the
|
|
1204
1204
|
// time we dispatch keystrokes — especially right after a re-render storm
|
|
1205
1205
|
// (e.g. bluesky dismissing the custom-server dialog before tapping the
|
|
1206
1206
|
// username field). without a focused input the keys go nowhere and the
|
|
1207
1207
|
// field stays empty, silently breaking submit. wait for a focused text
|
|
1208
|
-
// input first — the same focus gate
|
|
1208
|
+
// input first — the same focus gate rnx's own `type-into` uses (see
|
|
1209
1209
|
// the 2026-04-17 bluesky tap-routing regression in commands/inspect.ts).
|
|
1210
1210
|
if (!(await this.waitForFocusedTextInput())) {
|
|
1211
1211
|
throw new Error(
|
|
@@ -1572,7 +1572,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
1572
1572
|
)
|
|
1573
1573
|
if (typeof tree === 'string') write('tree.txt', tree)
|
|
1574
1574
|
|
|
1575
|
-
// 6. console buffer + failed requests (captured by
|
|
1575
|
+
// 6. console buffer + failed requests (captured by rnx's native-globals
|
|
1576
1576
|
// hook / request recorder; same shape as `rnx get errors` etc.).
|
|
1577
1577
|
const consoleBuf = await safeEval<{
|
|
1578
1578
|
errors: unknown[]
|
|
@@ -1922,7 +1922,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
1922
1922
|
})
|
|
1923
1923
|
await this.waitForTree(120000)
|
|
1924
1924
|
} else if (wantsResetRuntime) {
|
|
1925
|
-
//
|
|
1925
|
+
// rnx benchmark flows need app auth/storage kept warm while the
|
|
1926
1926
|
// guest React runtime starts fresh inside the timed/recorded boundary.
|
|
1927
1927
|
// the reload re-runs the app entry inside the mounted app surface, so
|
|
1928
1928
|
// it only does anything while the shell is showing the app. issued from
|
|
@@ -2096,7 +2096,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
2096
2096
|
await this.rearmCaptureAfterRuntimeReload()
|
|
2097
2097
|
}
|
|
2098
2098
|
|
|
2099
|
-
// maestro clearKeychain: there is no real keychain in
|
|
2099
|
+
// maestro clearKeychain: there is no real keychain in rnx's browser
|
|
2100
2100
|
// sandbox. log loudly so the author notices, then continue — matching the
|
|
2101
2101
|
// "loud warning, not silent skip" policy in the plan.
|
|
2102
2102
|
clearKeychain() {
|
|
@@ -2187,7 +2187,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
2187
2187
|
return !el
|
|
2188
2188
|
}
|
|
2189
2189
|
if (when.platform !== undefined) {
|
|
2190
|
-
//
|
|
2190
|
+
// rnx emulates iOS — match against the context's platform the way
|
|
2191
2191
|
// upstream compares against cachedDeviceInfo.platform. keeps
|
|
2192
2192
|
// cross-platform maestro flows running without stripping guards.
|
|
2193
2193
|
return when.platform.toLowerCase() === this.js.maestro.platform.toLowerCase()
|
package/cli/browser-evals.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// browser-side evaluator snippets for CLI inspect commands.
|
|
2
2
|
//
|
|
3
|
-
// the
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// the rnx CLI ships some commands that need to observe the live
|
|
4
|
+
// rnx tree or its rendered pixels. we send these strings over the rnx host's
|
|
5
|
+
// `evaluate` handler.
|
|
6
6
|
//
|
|
7
7
|
// cropping happens once, in the capture module (`bridges.screenshot`),
|
|
8
8
|
// via its `crop` option. these evals intentionally defer to that bridge
|