rnxsim 0.1.376 → 0.1.377
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 +28 -25
- package/cli/app-config.ts +9 -12
- package/cli/app-fonts.ts +3 -3
- package/cli/app-project.ts +8 -8
- package/cli/auth.ts +7 -9
- package/cli/bin.ts +16 -31
- package/cli/bridge-flow-runner.ts +3 -3
- package/cli/browser-backend.ts +44 -0
- package/cli/command-contract.ts +221 -0
- package/cli/command-registry.ts +291 -0
- package/cli/commands/agent-wrapper.ts +2 -2
- package/cli/commands/app-fonts.ts +2 -2
- package/cli/commands/assert.ts +3 -3
- package/cli/commands/auth.ts +1 -1
- package/cli/commands/box/cloud-box.ts +1 -1
- package/cli/commands/box/plane-share.ts +2 -2
- package/cli/commands/box.ts +4 -6
- package/cli/commands/compat.ts +1 -1
- package/cli/commands/config.ts +2 -2
- package/cli/commands/control.ts +29 -29
- package/cli/commands/cpu-profile.ts +7 -7
- package/cli/commands/daemon-mac-app.ts +7 -7
- package/cli/commands/daemon.ts +10 -10
- package/cli/commands/debug.ts +11 -11
- package/cli/commands/detox.ts +13 -13
- package/cli/commands/diagnose.ts +6 -6
- package/cli/commands/do-chain.ts +1 -2
- package/cli/commands/electron.ts +1 -3
- package/cli/commands/film.ts +3 -3
- package/cli/commands/flow.ts +43 -43
- 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/get-layout.ts +1 -1
- package/cli/commands/inspect/keyboard.ts +1 -1
- package/cli/commands/inspect/list.ts +1 -1
- 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/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 +34 -34
- package/cli/commands/install-desktop.ts +3 -3
- package/cli/commands/login.ts +6 -6
- package/cli/commands/maestro-generate.ts +5 -5
- package/cli/commands/maestro.ts +5 -5
- package/cli/commands/profile.ts +1 -1
- package/cli/commands/record.ts +9 -9
- package/cli/commands/runtime.ts +18 -18
- package/cli/commands/screenshot-command.ts +1 -1
- package/cli/commands/screenshot-layers.ts +5 -5
- package/cli/commands/screenshot-mode.ts +1 -1
- package/cli/commands/screenshot.ts +8 -8
- package/cli/commands/screenshots-capture.ts +5 -5
- package/cli/commands/serve.ts +3 -3
- package/cli/commands/setup.ts +20 -12
- package/cli/commands/skills.ts +5 -5
- package/cli/commands/slides.ts +1 -1
- package/cli/commands/telemetry.ts +1 -1
- package/cli/commands/three-mode.ts +1 -1
- package/cli/commands/timeline.ts +8 -8
- package/cli/commands/upgrade.ts +116 -131
- package/cli/commands/upload.ts +12 -12
- package/cli/commands/version.ts +7 -7
- package/cli/commands/what-happened.ts +9 -9
- package/cli/current-sim.ts +3 -3
- package/cli/desktop-companion.ts +1 -1
- package/cli/drivers/playwright.ts +9 -9
- package/cli/drivers/registry.ts +1 -1
- package/cli/drivers/types.ts +1 -1
- package/cli/flow-file.ts +2 -2
- package/cli/flow-session.ts +1 -1
- package/cli/help-core.ts +136 -0
- package/cli/help.ts +17 -53
- package/cli/hints.ts +5 -5
- package/cli/inspect-notice-state.ts +1 -1
- package/cli/maestro-js.ts +1 -1
- package/cli/outbound-endpoints.ts +23 -5
- package/cli/parse-args.ts +19 -66
- package/cli/recording-access.ts +1 -1
- package/cli/registry.ts +1 -0
- package/cli/run-registry.ts +1 -1
- package/cli/run-rnx-core.ts +148 -0
- package/cli/run-rnx.ts +6 -9
- package/cli/runtime-notes.ts +3 -3
- package/cli/standalone.ts +4 -0
- package/cli/ws-bridge.ts +8 -8
- package/detox/config-loader.ts +2 -2
- package/detox/index.ts +8 -8
- package/detox/jest-preset.cjs +1 -1
- package/detox/navigation.ts +2 -2
- package/dist-lib/agent-daemon-client.cjs +5 -5
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +2 -2
- package/dist-lib/agent-sessions.cjs +10 -10
- package/dist-lib/attached-projects.cjs +6 -6
- package/dist-lib/auth/shared-session.cjs +6 -6
- 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/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +25 -25
- package/dist-lib/detox/index.cjs +9 -9
- package/dist-lib/detox/jest-preset.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +29 -29
- package/dist-lib/host/bridge-host.cjs +128 -49
- 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 +14 -14
- 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 +8 -8
- package/dist-lib/profiles.cjs +12 -12
- 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 +8 -8
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +262 -135
- package/dist-lib/vite.cjs +9 -9
- package/package.json +4 -11
- package/scripts/dev-server-scanner.ts +2 -2
- package/scripts/optional-demo-registry.ts +1 -1
- package/skills/contrast/SKILL.md +3 -3
- package/skills/rnx-debug/SKILL.md +1 -1
- package/skills/rnx-test/SKILL.md +2 -2
- package/src/agent-identity.ts +1 -1
- package/src/agent-sessions.ts +7 -5
- package/src/cli-version.ts +12 -4
- package/src/config.ts +26 -28
- package/src/disk-cleanup.ts +11 -11
- package/src/home-paths.ts +28 -28
- package/src/host/bridge-host.ts +16 -16
- package/src/metro-plugin.ts +2 -2
- package/src/profiles.ts +2 -2
- package/src/runtime-assets.ts +7 -7
- package/src/runtime-delivery.ts +13 -13
- package/src/screenshots/orchestrate.ts +1 -1
- package/src/screenshots/schema.ts +2 -2
- package/src/skills/builtin/a11y-review.ts +2 -2
- package/src/skills/builtin/compat-check.ts +2 -2
- package/src/skills/builtin/maestro-test.ts +2 -2
- package/src/skills/builtin/perf-profile.ts +2 -2
- package/src/skills/builtin/screenshot-all.ts +2 -2
- package/src/skills/builtin/visual-diff.ts +2 -2
- package/src/skills/registry.ts +8 -8
- package/src/skills/types.ts +1 -1
- package/src/vite-plugin-one.ts +3 -3
- package/src/vite-plugin.ts +15 -2
- package/dist-cli/bin.js +0 -15
- package/dist-cli/chunks/agent-H42FCKFF.js +0 -48
- package/dist-cli/chunks/agent-wrapper-BBZKTZS6.js +0 -17
- package/dist-cli/chunks/app-fonts-43VXIKF4.js +0 -4
- package/dist-cli/chunks/assert-IXOLVO5Z.js +0 -49
- package/dist-cli/chunks/auth-KFMFB6FU.js +0 -12
- package/dist-cli/chunks/beta-5AO3SJB7.js +0 -4
- package/dist-cli/chunks/box-ZHM66L6K.js +0 -1730
- package/dist-cli/chunks/camera-FNJVFCDQ.js +0 -33
- package/dist-cli/chunks/chunk-37MCSPTE.js +0 -71
- package/dist-cli/chunks/chunk-3PEMSUAQ.js +0 -30
- package/dist-cli/chunks/chunk-4TQ5HOK3.js +0 -7
- package/dist-cli/chunks/chunk-5YZYDEMI.js +0 -4
- package/dist-cli/chunks/chunk-6CV3R3XD.js +0 -15
- package/dist-cli/chunks/chunk-7LGK5XU6.js +0 -4
- package/dist-cli/chunks/chunk-7R2TK2NY.js +0 -4
- package/dist-cli/chunks/chunk-7VZZQQEE.js +0 -497
- package/dist-cli/chunks/chunk-AFOVILIL.js +0 -130
- package/dist-cli/chunks/chunk-BEU7CD7E.js +0 -9
- package/dist-cli/chunks/chunk-CE4JQJ6Y.js +0 -6
- package/dist-cli/chunks/chunk-CIE6CZXL.js +0 -142
- package/dist-cli/chunks/chunk-CSJ7L7ZL.js +0 -312
- package/dist-cli/chunks/chunk-CWCQP53A.js +0 -27
- package/dist-cli/chunks/chunk-DCS5FK5N.js +0 -7
- package/dist-cli/chunks/chunk-DEV3E3MV.js +0 -4
- package/dist-cli/chunks/chunk-DGJMIVZM.js +0 -5
- package/dist-cli/chunks/chunk-DGX3MN65.js +0 -5
- package/dist-cli/chunks/chunk-DNPQMTW5.js +0 -4
- package/dist-cli/chunks/chunk-DOVBJBVD.js +0 -5
- package/dist-cli/chunks/chunk-DPBJ3S3J.js +0 -3
- package/dist-cli/chunks/chunk-FVZBGPCU.js +0 -4
- package/dist-cli/chunks/chunk-GDKMOIUT.js +0 -4
- package/dist-cli/chunks/chunk-GIF2NJCS.js +0 -32
- package/dist-cli/chunks/chunk-GZE57ORL.js +0 -4
- package/dist-cli/chunks/chunk-H3U56DKI.js +0 -24
- package/dist-cli/chunks/chunk-HZLOYAPH.js +0 -4
- package/dist-cli/chunks/chunk-J2IKAEU2.js +0 -4
- package/dist-cli/chunks/chunk-JXAXYK7W.js +0 -11
- package/dist-cli/chunks/chunk-KJE2O2GK.js +0 -4
- package/dist-cli/chunks/chunk-KMNVDZZI.js +0 -423
- package/dist-cli/chunks/chunk-LC3QRIRB.js +0 -10
- package/dist-cli/chunks/chunk-LGFTAQYC.js +0 -4
- package/dist-cli/chunks/chunk-LQMUA5IY.js +0 -4
- package/dist-cli/chunks/chunk-MFIQTWC5.js +0 -4
- package/dist-cli/chunks/chunk-MGALNF2F.js +0 -7
- package/dist-cli/chunks/chunk-MLBZZHOY.js +0 -4
- package/dist-cli/chunks/chunk-NNRF7GIB.js +0 -107
- package/dist-cli/chunks/chunk-NORLYTTO.js +0 -7
- package/dist-cli/chunks/chunk-P6KVGV3H.js +0 -14
- package/dist-cli/chunks/chunk-PAJTUK33.js +0 -4
- package/dist-cli/chunks/chunk-QELIC5XP.js +0 -4
- package/dist-cli/chunks/chunk-QRDF33WD.js +0 -11
- package/dist-cli/chunks/chunk-R76BA7C7.js +0 -535
- package/dist-cli/chunks/chunk-RVEB4CZ2.js +0 -4
- package/dist-cli/chunks/chunk-SK4M7NXT.js +0 -5
- package/dist-cli/chunks/chunk-SKMG5DD7.js +0 -5
- package/dist-cli/chunks/chunk-TEESPOYR.js +0 -4
- package/dist-cli/chunks/chunk-TH2UFGVP.js +0 -4
- package/dist-cli/chunks/chunk-TLP3T3VO.js +0 -4
- package/dist-cli/chunks/chunk-TZUABDIJ.js +0 -38
- package/dist-cli/chunks/chunk-UB2W6OQM.js +0 -99
- package/dist-cli/chunks/chunk-UYVXALPB.js +0 -506
- package/dist-cli/chunks/chunk-VQDYFFIG.js +0 -5
- package/dist-cli/chunks/chunk-VWCNJPCJ.js +0 -5
- package/dist-cli/chunks/chunk-VZIK4WIS.js +0 -74
- package/dist-cli/chunks/chunk-W3FNXAQP.js +0 -4
- package/dist-cli/chunks/chunk-XCDNVNP3.js +0 -4
- package/dist-cli/chunks/chunk-XSVILA4Z.js +0 -4
- package/dist-cli/chunks/chunk-XUTJ7ZWD.js +0 -4
- package/dist-cli/chunks/chunk-YTXY3MHW.js +0 -4
- package/dist-cli/chunks/chunk-ZF7PVX44.js +0 -28
- package/dist-cli/chunks/chunk-ZSSG7OR7.js +0 -4
- package/dist-cli/chunks/cleanup-MLOK2OZA.js +0 -25
- package/dist-cli/chunks/cli-version-OBNR366V.js +0 -4
- package/dist-cli/chunks/compat-SHMFSKU7.js +0 -23
- package/dist-cli/chunks/config-TPEAEUJ2.js +0 -13
- package/dist-cli/chunks/connect-3LEXWTE5.js +0 -4
- package/dist-cli/chunks/control-Z3FWA6BO.js +0 -4
- package/dist-cli/chunks/daemon-RSVYSC65.js +0 -4
- package/dist-cli/chunks/debug-GUVAK2KR.js +0 -200
- package/dist-cli/chunks/desktop-OOP2PTKP.js +0 -31
- package/dist-cli/chunks/detox-C2ILVBEC.js +0 -52
- package/dist-cli/chunks/device-NXIQQJWR.js +0 -18
- package/dist-cli/chunks/diagnose-2JBGYE5L.js +0 -43
- package/dist-cli/chunks/disk-cleanup-VURWJYQV.js +0 -4
- package/dist-cli/chunks/do-chain-DJ4UQC7A.js +0 -5
- package/dist-cli/chunks/drivers-JMA2OIKU.js +0 -4
- package/dist-cli/chunks/env-KTNPKEZR.js +0 -4
- package/dist-cli/chunks/film-DCPS7EID.js +0 -74
- package/dist-cli/chunks/flow-4DZ7B2OA.js +0 -4
- package/dist-cli/chunks/help-34CJMQY2.js +0 -4
- package/dist-cli/chunks/hidden-runtime-alias-HHT2C7WE.js +0 -4
- package/dist-cli/chunks/home-paths-KTPGCPMS.js +0 -4
- package/dist-cli/chunks/inspect-JF5WYJRD.js +0 -4
- package/dist-cli/chunks/install-desktop-ZIC4CDXI.js +0 -4
- package/dist-cli/chunks/login-TTJAOI2Q.js +0 -26
- package/dist-cli/chunks/logout-ERAQ6SKW.js +0 -4
- package/dist-cli/chunks/maestro-W46QCF77.js +0 -108
- package/dist-cli/chunks/maestro-generate-VBLM3MTU.js +0 -36
- package/dist-cli/chunks/mode-ZEDZK7R7.js +0 -26
- package/dist-cli/chunks/optional-demo-registry-ZUTPSVYQ.js +0 -4
- package/dist-cli/chunks/perf-YVT2WXT4.js +0 -48
- package/dist-cli/chunks/permissions-L7VVDHFP.js +0 -13
- package/dist-cli/chunks/platform-7TJOU2FB.js +0 -6
- package/dist-cli/chunks/privacy-57UOTB5J.js +0 -4
- package/dist-cli/chunks/record-VXG37P7U.js +0 -72
- package/dist-cli/chunks/report-issue-WNNOBZHC.js +0 -29
- package/dist-cli/chunks/reset-H5NBFE5U.js +0 -4
- package/dist-cli/chunks/resolve-target-57TK5WLE.js +0 -4
- package/dist-cli/chunks/runtime-QU2NIF2K.js +0 -4
- package/dist-cli/chunks/screenshot-command-AN56TTG7.js +0 -159
- package/dist-cli/chunks/screenshot-layers-3W5R7DB2.js +0 -20
- package/dist-cli/chunks/screenshots-capture-6AWJY7XZ.js +0 -42
- package/dist-cli/chunks/serve-GLYO5Q7J.js +0 -56
- package/dist-cli/chunks/setup-UA2HLD7S.js +0 -4
- package/dist-cli/chunks/sim-client-5C2RDQQO.js +0 -4
- package/dist-cli/chunks/skills-EYXWBRNQ.js +0 -50
- package/dist-cli/chunks/state-REJ7W6K3.js +0 -14
- package/dist-cli/chunks/storage-CFMDHI3V.js +0 -37
- package/dist-cli/chunks/store-XMCUA45G.js +0 -4
- package/dist-cli/chunks/telemetry-FBL3NAKB.js +0 -4
- package/dist-cli/chunks/timeline-HMCHN5FO.js +0 -24
- package/dist-cli/chunks/upgrade-2QSPAA3P.js +0 -7
- package/dist-cli/chunks/upload-DUQBKZYV.js +0 -4
- package/dist-cli/chunks/version-IOA6QFGB.js +0 -6
- package/dist-cli/chunks/web-NCGUYVCJ.js +0 -4
- package/dist-cli/chunks/welcome-QYGSQCWQ.js +0 -5
- package/dist-cli/chunks/what-happened-YTOUK4LO.js +0 -25
- package/dist-cli/chunks/ws-bridge-OF4OZARG.js +0 -4
- package/scripts/postinstall.cjs +0 -3
- package/scripts/preuninstall.cjs +0 -118
package/cli/commands/upgrade.ts
CHANGED
|
@@ -1,57 +1,43 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// the
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// command checks and updates both:
|
|
8
|
-
//
|
|
9
|
-
// 1. CLI — query the npm registry; if behind, self-update the global
|
|
10
|
-
// install with the same package manager that installed it
|
|
11
|
-
// (bun/pnpm/npm, detected from the running binary's path).
|
|
12
|
-
// npx-cache runs can't self-update in place, so those get
|
|
13
|
-
// the exact command to run instead.
|
|
14
|
-
// 2. runtime — delegate to `sootsim runtime install`, which resolves the
|
|
15
|
-
// channel's latest, downloads + verifies
|
|
16
|
-
// it, and activates.
|
|
17
|
-
//
|
|
18
|
-
// each half is a no-op when already current, so the command is safe to
|
|
19
|
-
// run anytime.
|
|
20
|
-
|
|
21
|
-
import { spawn } from 'node:child_process'
|
|
1
|
+
// rnx upgrade updates the separately delivered runtime first, then atomically
|
|
2
|
+
// replaces the standalone cli executable. runtime stays first because replacing
|
|
3
|
+
// the running cli's files before later dynamic imports can strand the command.
|
|
4
|
+
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import { createHash } from 'node:crypto'
|
|
22
7
|
import fs from 'node:fs'
|
|
23
|
-
import os from 'node:os'
|
|
24
8
|
import path from 'node:path'
|
|
25
9
|
import { compareSemver } from '@contrast/runtime-delivery'
|
|
26
10
|
import { getCliVersion } from '../../src/cli-version'
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
readActiveRuntime,
|
|
30
|
-
runtimeDir,
|
|
31
|
-
sootsimHomeDir,
|
|
32
|
-
} from '../../src/home-paths'
|
|
33
|
-
import { resolveSootSimAppConfig } from '../app-config'
|
|
11
|
+
import { isSootsimDevCheckout, readActiveRuntime, runtimeDir } from '../../src/home-paths'
|
|
12
|
+
import { resolveRNXAppConfig } from '../app-config'
|
|
34
13
|
import { fetchRuntimeReleaseNotes, formatRuntimeReleaseNotes } from '../runtime-notes'
|
|
14
|
+
import { IS_STANDALONE } from '../standalone'
|
|
35
15
|
import { runRuntime } from './runtime'
|
|
36
16
|
|
|
37
|
-
const
|
|
17
|
+
const DEFAULT_CLI_RELEASE_ORIGIN = 'https://r2.rnxsim.com/rnx-cli'
|
|
18
|
+
const CLI_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
|
|
19
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/
|
|
20
|
+
|
|
21
|
+
export interface UpgradeOptions {
|
|
22
|
+
cliExecutablePath?: string
|
|
23
|
+
cliReleaseOrigin?: string
|
|
24
|
+
standalone?: boolean
|
|
25
|
+
includeReleaseNotes?: boolean
|
|
26
|
+
}
|
|
38
27
|
|
|
39
|
-
export async function runUpgrade(
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
export async function runUpgrade(
|
|
29
|
+
args: string[],
|
|
30
|
+
options: UpgradeOptions = {},
|
|
31
|
+
): Promise<void> {
|
|
42
32
|
const channelIdx = args.indexOf('--channel')
|
|
43
33
|
const channel =
|
|
44
34
|
channelIdx >= 0 && args[channelIdx + 1] ? args[channelIdx + 1] : undefined
|
|
45
35
|
|
|
46
36
|
console.log('rnx upgrade\n')
|
|
47
37
|
|
|
48
|
-
// --- 1. engine runtime --------------------------------------------------
|
|
49
|
-
// runtime first, CLI last: the CLI self-update replaces this process's
|
|
50
|
-
// own dist files on disk, and any dynamic import after the swap resolves
|
|
51
|
-
// against renamed chunk hashes and crashes (observed in a container run).
|
|
52
38
|
console.log('runtime:')
|
|
53
39
|
const beforeRuntime = readActiveRuntime()
|
|
54
|
-
const { config } = await
|
|
40
|
+
const { config } = await resolveRNXAppConfig()
|
|
55
41
|
const configuredRuntime = config?.runtimeVersion
|
|
56
42
|
const configuredRuntimeWasInstalled = configuredRuntime
|
|
57
43
|
? fs.existsSync(path.join(runtimeDir(configuredRuntime), 'index.html'))
|
|
@@ -84,125 +70,124 @@ export async function runUpgrade(args: string[]): Promise<void> {
|
|
|
84
70
|
: ` result: v${installedRuntime} already current`,
|
|
85
71
|
)
|
|
86
72
|
}
|
|
87
|
-
const releaseNotes =
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
const releaseNotes =
|
|
74
|
+
installedRuntime && options.includeReleaseNotes !== false
|
|
75
|
+
? await fetchRuntimeReleaseNotes(installedRuntime)
|
|
76
|
+
: null
|
|
90
77
|
|
|
91
|
-
// --- 2. CLI npm package -------------------------------------------------
|
|
92
78
|
console.log('\ncli:')
|
|
93
|
-
await upgradeCli()
|
|
79
|
+
await upgradeCli(options)
|
|
94
80
|
|
|
95
|
-
if (installedRuntime) {
|
|
81
|
+
if (installedRuntime && options.includeReleaseNotes !== false) {
|
|
96
82
|
console.log(`\n${formatRuntimeReleaseNotes(installedRuntime, releaseNotes)}`)
|
|
97
83
|
}
|
|
98
84
|
}
|
|
99
85
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// entry script — global installs land under each manager's global root,
|
|
107
|
-
// npx/bunx runs land in their throwaway caches, and the Contrast monorepo dev
|
|
108
|
-
// checkout runs from packages/sootsim.
|
|
109
|
-
function detectCliInstall(): CliInstallKind {
|
|
110
|
-
if (isSootsimDevCheckout()) return { kind: 'dev' }
|
|
111
|
-
let real = process.argv[1] ?? ''
|
|
112
|
-
try {
|
|
113
|
-
real = fs.realpathSync(real)
|
|
114
|
-
} catch {}
|
|
115
|
-
const sep = path.sep
|
|
116
|
-
if (real.includes(`${sep}_npx${sep}`) || real.includes(`${sep}.bunx${sep}`)) {
|
|
117
|
-
return { kind: 'npx' }
|
|
118
|
-
}
|
|
119
|
-
if (real.includes(`${sep}.bun${sep}`)) {
|
|
120
|
-
return { kind: 'global', command: 'bun', args: ['add', '-g', 'rnxsim@latest'] }
|
|
121
|
-
}
|
|
122
|
-
if (real.includes(`${sep}pnpm${sep}`)) {
|
|
123
|
-
return { kind: 'global', command: 'pnpm', args: ['add', '-g', 'rnxsim@latest'] }
|
|
124
|
-
}
|
|
125
|
-
// the desktop splash installs the CLI into a user-writable prefix under the
|
|
126
|
-
// sootsim home (`~/.sootsim/cli`) to dodge the EACCES a plain `npm i -g` hits
|
|
127
|
-
// when npm's global prefix is a system-owned dir. self-update into that same
|
|
128
|
-
// prefix so upgrade doesn't fall back to the unwritable global prefix.
|
|
129
|
-
const cliPrefix = path.join(sootsimHomeDir(), 'cli')
|
|
130
|
-
if (real.startsWith(`${cliPrefix}${sep}`)) {
|
|
131
|
-
return {
|
|
132
|
-
kind: 'global',
|
|
133
|
-
command: 'npm',
|
|
134
|
-
args: ['i', '-g', '--prefix', cliPrefix, 'rnxsim@latest'],
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
const installerPrefix = path.resolve(
|
|
138
|
-
process.env.SOOTSIM_CLI_PREFIX || path.join(os.homedir(), '.local'),
|
|
139
|
-
)
|
|
140
|
-
if (real.startsWith(`${installerPrefix}${sep}`)) {
|
|
141
|
-
return {
|
|
142
|
-
kind: 'global',
|
|
143
|
-
command: 'npm',
|
|
144
|
-
args: ['i', '-g', '--prefix', installerPrefix, 'rnxsim@latest'],
|
|
86
|
+
export async function upgradeCli(options: UpgradeOptions = {}): Promise<void> {
|
|
87
|
+
const standalone = options.standalone ?? IS_STANDALONE
|
|
88
|
+
if (!standalone) {
|
|
89
|
+
if (isSootsimDevCheckout()) {
|
|
90
|
+
console.log(' running from the Contrast checkout; pull instead of upgrading')
|
|
91
|
+
return
|
|
145
92
|
}
|
|
93
|
+
throw new Error(
|
|
94
|
+
'this is not the standalone rnx executable; reinstall from https://rnxsim.com/install.sh',
|
|
95
|
+
)
|
|
146
96
|
}
|
|
147
|
-
return { kind: 'global', command: 'npm', args: ['i', '-g', 'rnxsim@latest'] }
|
|
148
|
-
}
|
|
149
97
|
|
|
150
|
-
|
|
98
|
+
const releaseOrigin = (options.cliReleaseOrigin ?? DEFAULT_CLI_RELEASE_ORIGIN).replace(
|
|
99
|
+
/\/+$/,
|
|
100
|
+
'',
|
|
101
|
+
)
|
|
151
102
|
const current = getCliVersion()
|
|
152
|
-
const latest = await
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
103
|
+
const latest = (await fetchRequired(`${releaseOrigin}/channels/stable`, 8_000))
|
|
104
|
+
.toString('utf8')
|
|
105
|
+
.trim()
|
|
106
|
+
if (!CLI_VERSION_PATTERN.test(latest)) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`rnx update service returned an invalid version: ${latest || '(empty)'}`,
|
|
109
|
+
)
|
|
157
110
|
}
|
|
158
111
|
if (compareSemver(latest, current) <= 0) {
|
|
159
112
|
console.log(` v${current} (latest)`)
|
|
160
113
|
return
|
|
161
114
|
}
|
|
162
115
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
116
|
+
const artifact = cliArtifactName()
|
|
117
|
+
const artifactUrl = `${releaseOrigin}/${latest}/${artifact}`
|
|
118
|
+
const expectedSha = (await fetchRequired(`${artifactUrl}.sha256`, 8_000))
|
|
119
|
+
.toString('utf8')
|
|
120
|
+
.trim()
|
|
121
|
+
if (!SHA256_PATTERN.test(expectedSha)) {
|
|
122
|
+
throw new Error('rnx update service returned an invalid executable checksum')
|
|
169
123
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
124
|
+
|
|
125
|
+
console.log(` v${current} → v${latest}`)
|
|
126
|
+
console.log(` downloading ${artifact}...`)
|
|
127
|
+
const executable = await fetchRequired(artifactUrl, 120_000)
|
|
128
|
+
const actualSha = createHash('sha256').update(executable).digest('hex')
|
|
129
|
+
if (actualSha !== expectedSha) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`downloaded rnx executable failed verification (expected ${expectedSha}, got ${actualSha})`,
|
|
132
|
+
)
|
|
174
133
|
}
|
|
175
134
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
135
|
+
const executablePath = path.resolve(options.cliExecutablePath ?? process.execPath)
|
|
136
|
+
const tempDir = fs.mkdtempSync(path.join(path.dirname(executablePath), '.rnx-update-'))
|
|
137
|
+
const candidatePath = path.join(tempDir, path.basename(executablePath))
|
|
138
|
+
try {
|
|
139
|
+
fs.writeFileSync(candidatePath, executable, { mode: 0o755 })
|
|
140
|
+
fs.chmodSync(candidatePath, 0o755)
|
|
141
|
+
verifyCandidate(candidatePath, latest)
|
|
142
|
+
fs.renameSync(candidatePath, executablePath)
|
|
143
|
+
} finally {
|
|
144
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
184
145
|
}
|
|
146
|
+
console.log(` updated to v${latest}`)
|
|
185
147
|
}
|
|
186
148
|
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
149
|
+
function cliArtifactName(): string {
|
|
150
|
+
const platform = process.platform === 'darwin' ? 'darwin' : process.platform
|
|
151
|
+
if (platform !== 'darwin' && platform !== 'linux') {
|
|
152
|
+
throw new Error('standalone rnx self-update currently supports macOS and Linux')
|
|
153
|
+
}
|
|
154
|
+
if (process.arch !== 'arm64' && process.arch !== 'x64') {
|
|
155
|
+
throw new Error(`standalone rnx does not support ${process.arch}`)
|
|
156
|
+
}
|
|
157
|
+
return `rnx-${platform}-${process.arch}`
|
|
193
158
|
}
|
|
194
159
|
|
|
195
|
-
async function
|
|
160
|
+
async function fetchRequired(url: string, timeoutMs: number): Promise<Buffer> {
|
|
161
|
+
let response: Response
|
|
196
162
|
try {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
signal: AbortSignal.timeout(8000),
|
|
163
|
+
response = await fetch(url, {
|
|
164
|
+
headers: { accept: 'application/octet-stream' },
|
|
165
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
201
166
|
})
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`could not reach the rnx update service: ${
|
|
170
|
+
error instanceof Error ? error.message : String(error)
|
|
171
|
+
}`,
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
if (!response.ok) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`rnx update download failed: ${response.status} ${response.statusText}`,
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
return Buffer.from(await response.arrayBuffer())
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function verifyCandidate(candidatePath: string, expectedVersion: string): void {
|
|
183
|
+
const result = spawnSync(candidatePath, ['--version'], {
|
|
184
|
+
encoding: 'utf8',
|
|
185
|
+
timeout: 15_000,
|
|
186
|
+
})
|
|
187
|
+
const reported = result.stdout.match(/^rnx v([^\s]+)/m)?.[1]
|
|
188
|
+
if (result.status !== 0 || reported !== expectedVersion) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`downloaded rnx executable reported ${reported ? `v${reported}` : 'no version'}; expected v${expectedVersion}`,
|
|
191
|
+
)
|
|
207
192
|
}
|
|
208
193
|
}
|
package/cli/commands/upload.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// rnx record upload — capture the currently-loaded bundle + device spec from
|
|
2
2
|
// a running sootsim sim and POST it to the Contrast upload endpoint. returns a
|
|
3
3
|
// /preview/<id> url that anyone can open to load this bundle in a frame.
|
|
4
4
|
//
|
|
@@ -108,16 +108,16 @@ function readSnapshotManifest(manifestPath: string): AttachedInteractionSnapshot
|
|
|
108
108
|
})
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const REMOTE_DEFAULT_ORIGIN = process.env.
|
|
111
|
+
const REMOTE_DEFAULT_ORIGIN = process.env.RNX_UPLOAD_ORIGIN || 'https://contrast.dev'
|
|
112
112
|
// the canonical public preview link. Uploads go to the contrast backend, but
|
|
113
113
|
// reviewers/users open SootSim preview pages on sootsim.com.
|
|
114
114
|
const REMOTE_DEFAULT_PUBLIC_PREVIEW_ORIGIN =
|
|
115
|
-
process.env.
|
|
115
|
+
process.env.RNX_PREVIEW_ORIGIN || rnxPublicBrand.origin
|
|
116
116
|
export async function resolveDefaultUploadOrigin(
|
|
117
117
|
explicitOrigin?: string,
|
|
118
118
|
): Promise<string> {
|
|
119
119
|
if (explicitOrigin) return explicitOrigin
|
|
120
|
-
if (process.env.
|
|
120
|
+
if (process.env.RNX_UPLOAD_ORIGIN) return process.env.RNX_UPLOAD_ORIGIN
|
|
121
121
|
return REMOTE_DEFAULT_ORIGIN
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -155,8 +155,8 @@ export function resolvePublicPreviewOrigin(
|
|
|
155
155
|
explicitOrigin?: string,
|
|
156
156
|
): string {
|
|
157
157
|
if (explicitOrigin) return normalizeOrigin(explicitOrigin)
|
|
158
|
-
if (process.env.
|
|
159
|
-
return normalizeOrigin(process.env.
|
|
158
|
+
if (process.env.RNX_PREVIEW_ORIGIN) {
|
|
159
|
+
return normalizeOrigin(process.env.RNX_PREVIEW_ORIGIN)
|
|
160
160
|
}
|
|
161
161
|
if (isLoopbackOrigin(uploadOrigin)) return normalizeOrigin(uploadOrigin)
|
|
162
162
|
if (isContrastOrigin(uploadOrigin)) {
|
|
@@ -174,11 +174,11 @@ usage:
|
|
|
174
174
|
|
|
175
175
|
options:
|
|
176
176
|
--origin <url> upload target (default: ${REMOTE_DEFAULT_ORIGIN})
|
|
177
|
-
override with
|
|
177
|
+
override with RNX_UPLOAD_ORIGIN env var
|
|
178
178
|
--public-origin <url>
|
|
179
179
|
public /preview link origin (default: ${REMOTE_DEFAULT_PUBLIC_PREVIEW_ORIGIN}
|
|
180
180
|
for prod uploads, upload origin for localhost/custom origins).
|
|
181
|
-
override with
|
|
181
|
+
override with RNX_PREVIEW_ORIGIN env var
|
|
182
182
|
--events <path> path to a gzipped events .jsonl.gz file to attach
|
|
183
183
|
required unless --video is present; previews must
|
|
184
184
|
have replay or recording playback data
|
|
@@ -216,7 +216,7 @@ options:
|
|
|
216
216
|
examples:
|
|
217
217
|
rnx record upload --events ./my-session.jsonl.gz
|
|
218
218
|
rnx record upload --origin https://contrast.localhost:3000 --events ./my-session.jsonl.gz --open
|
|
219
|
-
rnx record upload --video /tmp/
|
|
219
|
+
rnx record upload --video /tmp/rnx-flow.mp4 --video-duration-ms 12000
|
|
220
220
|
`)
|
|
221
221
|
}
|
|
222
222
|
|
|
@@ -911,7 +911,7 @@ export async function runUpload(
|
|
|
911
911
|
// asset-registry and are fetched once at engine boot — before the recorder
|
|
912
912
|
// starts — so neither the manifest re-fetch nor recordedFetches captures
|
|
913
913
|
// them and the /preview replay 404s. backfill them here via the shared
|
|
914
|
-
// fetcher (same code path as the build's `
|
|
914
|
+
// fetcher (same code path as the build's `rnx app-fonts stage`) so both
|
|
915
915
|
// pipelines store identical bytes keyed by sha256(url).
|
|
916
916
|
const appFontFiles: PreparedFile[] = []
|
|
917
917
|
const appFontsWire = appFontsWireFromPageHref(snapshot.pageHref) || ''
|
|
@@ -1015,11 +1015,11 @@ export async function runUpload(
|
|
|
1015
1015
|
bundleMinified: false,
|
|
1016
1016
|
bundleOrigin,
|
|
1017
1017
|
runtimeVersion: snapshot.runtimeVersion ?? undefined,
|
|
1018
|
-
// config-only fonts resolved by `
|
|
1018
|
+
// config-only fonts resolved by `rnx open` and carried on the engine
|
|
1019
1019
|
// URL. persist them onto the share meta so /preview/<id> re-applies
|
|
1020
1020
|
// `?appFonts=` and registers the same typefaces.
|
|
1021
1021
|
appFonts: appFontsWire || undefined,
|
|
1022
|
-
// configured splash (image + bg) resolved by `
|
|
1022
|
+
// configured splash (image + bg) resolved by `rnx open`. persist it so
|
|
1023
1023
|
// /preview/<id> re-applies `?appSplash=` and paints the same splash; the
|
|
1024
1024
|
// image bytes are staged above so the engine's <img> resolves in preview.
|
|
1025
1025
|
appSplash: appSplashWire || undefined,
|
package/cli/commands/version.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// rnx version — one overview of both halves you're running.
|
|
2
2
|
//
|
|
3
3
|
// the `sootsim` CLI (npm package) and the engine runtime (versioned tarball
|
|
4
4
|
// rendered in the browser/electron shell) are versioned independently, which
|
|
@@ -10,8 +10,8 @@ import fs from 'node:fs'
|
|
|
10
10
|
import path from 'node:path'
|
|
11
11
|
import { getCliVersion } from '../../src/cli-version'
|
|
12
12
|
import { runtimeDir } from '../../src/home-paths'
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { RNX_CHANGELOG_URL, rnxRuntime } from '../../src/runtime-delivery'
|
|
14
|
+
import { resolveRNXAppConfig } from '../app-config'
|
|
15
15
|
import { getRuntimeSummary } from '../runtime-summary'
|
|
16
16
|
|
|
17
17
|
export async function runVersion(_args: string[]): Promise<void> {
|
|
@@ -20,7 +20,7 @@ export async function runVersion(_args: string[]): Promise<void> {
|
|
|
20
20
|
console.log(`rnx v${getCliVersion()}${suffix}`)
|
|
21
21
|
|
|
22
22
|
const runtime = getRuntimeSummary()
|
|
23
|
-
const channel =
|
|
23
|
+
const channel = rnxRuntime.resolveChannel()
|
|
24
24
|
if (runtime.isDevBridge) {
|
|
25
25
|
console.log(runtime.primary)
|
|
26
26
|
if (runtime.installedRuntime) console.log(`${runtime.installedRuntime} · ${channel}`)
|
|
@@ -30,7 +30,7 @@ export async function runVersion(_args: string[]): Promise<void> {
|
|
|
30
30
|
console.log(`${runtime.primary} · ${channel}`)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const { config } = await
|
|
33
|
+
const { config } = await resolveRNXAppConfig()
|
|
34
34
|
if (config?.runtimeVersion) {
|
|
35
35
|
const installed = fs.existsSync(
|
|
36
36
|
path.join(runtimeDir(config.runtimeVersion), 'index.html'),
|
|
@@ -43,10 +43,10 @@ export async function runVersion(_args: string[]): Promise<void> {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// best-effort network check; never fail the command on a flaky network.
|
|
46
|
-
const check = await
|
|
46
|
+
const check = await rnxRuntime.checkUpToDate({ channel })
|
|
47
47
|
if (check.outdated && check.latest) {
|
|
48
48
|
console.log(`\n ↑ default runtime v${check.latest} available; run \`rnx upgrade\``)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
console.log(`\n what's new: ${
|
|
51
|
+
console.log(`\n what's new: ${RNX_CHANGELOG_URL}`)
|
|
52
52
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// rnx what-happened — show recent events from the semantic timeline
|
|
2
2
|
//
|
|
3
3
|
// the agent-facing read verb for SootSim.bridges.timeline. by default
|
|
4
4
|
// shows everything since the previous CLI call from the same agent
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
// the events it displayed so the next call shows only new events.
|
|
7
7
|
//
|
|
8
8
|
// usage:
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
9
|
+
// rnx what-happened # since last call
|
|
10
|
+
// rnx what-happened --summary # one-line counts
|
|
11
|
+
// rnx what-happened --all # full ring (no cursor)
|
|
12
|
+
// rnx what-happened --since 5s # absolute window
|
|
13
|
+
// rnx what-happened --kinds toast,fetch # filter
|
|
14
|
+
// rnx what-happened --json # structured
|
|
15
|
+
// rnx what-happened --limit 50 # cap (default 200)
|
|
16
16
|
|
|
17
17
|
import { getCliIdentityKey } from '../current-sim'
|
|
18
18
|
import { rnxExit } from '../run-rnx'
|
|
@@ -157,7 +157,7 @@ export async function runWhatHappened(args: string[], opts: WhatHappenedOptions)
|
|
|
157
157
|
'--flow',
|
|
158
158
|
// by default we hide high-frequency render/layout/scroll noise so
|
|
159
159
|
// a single tap doesn't get buried under 1000+ rows. enable
|
|
160
|
-
// `react-commit` with `
|
|
160
|
+
// `react-commit` with `rnx timeline start react-commit`, then pass
|
|
161
161
|
// `--noisy` (or an explicit `--kinds react-commit,…` filter) when you
|
|
162
162
|
// actually want to see them.
|
|
163
163
|
'--noisy',
|
package/cli/current-sim.ts
CHANGED
|
@@ -16,7 +16,7 @@ interface CurrentSimState {
|
|
|
16
16
|
// checked in order — the first one present wins. anything further down is a
|
|
17
17
|
// progressively weaker signal of "same caller across invocations".
|
|
18
18
|
const IDENTITY_ENV_VARS = [
|
|
19
|
-
'
|
|
19
|
+
'RNX_CLI_IDENTITY', // explicit override — fastest path, skips all pid lookups
|
|
20
20
|
'TM_SESSION', // Team Machine session id; routes prompt-bar followups back to this agent
|
|
21
21
|
'CLAUDE_CODE_SESSION_ID', // claude code, if ever set
|
|
22
22
|
'CODEX_THREAD_ID', // codex / openai agent thread, stable across tool invocations
|
|
@@ -54,7 +54,7 @@ export interface CliIdentity {
|
|
|
54
54
|
// pids drift across invocations:
|
|
55
55
|
// - claude code's bash tool spawns a fresh shell per call, so the
|
|
56
56
|
// immediate gppid (the shell's parent) can change
|
|
57
|
-
// - shell pipelines (`
|
|
57
|
+
// - shell pipelines (`rnx list | grep`) wrap the command in a
|
|
58
58
|
// subshell, adding an extra ppid layer that shifts gppid down by one
|
|
59
59
|
// the topmost user process (just below init/launchd) is stable across
|
|
60
60
|
// all of these — same agent / login shell / terminal across calls.
|
|
@@ -145,7 +145,7 @@ export function getCurrentSimPath() {
|
|
|
145
145
|
return resolve(process.env[CURRENT_SIM_PATH_ENV])
|
|
146
146
|
}
|
|
147
147
|
const key = getCliIdentityKey()
|
|
148
|
-
return join(tmpdir(), `
|
|
148
|
+
return join(tmpdir(), `rnx-current-sim-${key}.json`)
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
export function readCurrentSim(): CurrentSimState | null {
|
package/cli/desktop-companion.ts
CHANGED
|
@@ -240,7 +240,7 @@ export interface LaunchCompanionOptions {
|
|
|
240
240
|
device?: string
|
|
241
241
|
profileId?: string
|
|
242
242
|
ephemeralProfile?: boolean
|
|
243
|
-
// set by `
|
|
243
|
+
// set by `rnx open`, whose native window belongs to the launching
|
|
244
244
|
// session. `sootsim desktop` omits it and remains a persistent desktop app.
|
|
245
245
|
ownerPid?: number
|
|
246
246
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// CLI invoked from a directory with no node_modules).
|
|
6
6
|
//
|
|
7
7
|
// the browser runs in a *detached* node subprocess (see PLAYWRIGHT_SIM_HOST)
|
|
8
|
-
// rather than in this CLI process. that is load-bearing: `
|
|
8
|
+
// rather than in this CLI process. that is load-bearing: `rnx open`
|
|
9
9
|
// returns the moment the sim connects, and playwright tears its browser
|
|
10
10
|
// down when the launching process exits. an in-process launch would mean
|
|
11
11
|
// the sim vanished the instant `open` finished. the detached host keeps the
|
|
@@ -49,7 +49,7 @@ const callerUserDataDir = process.env.SOOTSIM_PW_USERDATADIR || '';
|
|
|
49
49
|
const ownerPid = Number(process.env.SOOTSIM_PW_OWNER_PID || 0);
|
|
50
50
|
const ownerPollMs = Number(process.env.SOOTSIM_PW_OWNER_POLL_MS || 5000);
|
|
51
51
|
// optional CDP port — exposes Chrome's remote-debugging endpoint so a
|
|
52
|
-
// bridge-driven sim can also be cpu-profiled (
|
|
52
|
+
// bridge-driven sim can also be cpu-profiled (rnx perf cpu --cdp-port).
|
|
53
53
|
// playwright keeps its own pipe connection; the port is an additional endpoint.
|
|
54
54
|
const cdpPort = process.env.SOOTSIM_PW_CDP_PORT || '';
|
|
55
55
|
// extra chromium launch args, space-separated. a linux GPU runner injects
|
|
@@ -93,7 +93,7 @@ const { join } = require('path');
|
|
|
93
93
|
//
|
|
94
94
|
// the prior approach was \`pgrep -f ms-playwright/[c]hromium\`. it never matched
|
|
95
95
|
// system Chrome, so the reaper was a no-op in the common case and every
|
|
96
|
-
// aborted/closed \`
|
|
96
|
+
// aborted/closed \`rnx open\` leaked a renderer + gpu helper burning 100%+
|
|
97
97
|
// CPU until manually killed (multiple incidents 2026-06).
|
|
98
98
|
//
|
|
99
99
|
// fix: identify our chrome tree by its UNIQUE user-data-dir. every helper a
|
|
@@ -106,13 +106,13 @@ const reapEnabled = process.platform !== 'win32';
|
|
|
106
106
|
let profileDir = callerUserDataDir;
|
|
107
107
|
let ephemeralProfile = false;
|
|
108
108
|
if (!profileDir) {
|
|
109
|
-
profileDir = mkdtempSync(join(tmpdir(), '
|
|
109
|
+
profileDir = mkdtempSync(join(tmpdir(), 'rnx-playwright-host-'));
|
|
110
110
|
ephemeralProfile = true;
|
|
111
111
|
}
|
|
112
112
|
// browser HTTP caches are acceleration data, not profile identity. give each
|
|
113
113
|
// host a bounded temporary cache so persistent named profiles retain cookies
|
|
114
114
|
// and storage without multiplying a private cache for every profile.
|
|
115
|
-
const diskCacheDir = mkdtempSync(join(tmpdir(), '
|
|
115
|
+
const diskCacheDir = mkdtempSync(join(tmpdir(), 'rnx-playwright-cache-'));
|
|
116
116
|
const chromePidsByProfile = () => {
|
|
117
117
|
if (!reapEnabled) return [];
|
|
118
118
|
try {
|
|
@@ -283,7 +283,7 @@ const cleanupProfile = () => {
|
|
|
283
283
|
// through shutdown(); the exit hook (sync sweep) still reaps the tree.
|
|
284
284
|
process.on('exit', () => { reapChrome(); cleanupProfile(); });
|
|
285
285
|
// owner-liveness watchdog — the load-bearing fix for leaked Chrome hosts.
|
|
286
|
-
// we are spawned detached + unref'd so \`
|
|
286
|
+
// we are spawned detached + unref'd so \`rnx open\` returns immediately,
|
|
287
287
|
// which means nothing in the OS process tree ties Chrome's lifetime to the
|
|
288
288
|
// session that asked for it. once the sim connects, the connect-timeout
|
|
289
289
|
// safety net is cleared, so absent an explicit \`sootsim close\` (or the
|
|
@@ -312,7 +312,7 @@ const cleanupProfile = () => {
|
|
|
312
312
|
// com.google.chrome.for.testing identity cannot intercept open/quit events for
|
|
313
313
|
// the user's com.google.Chrome browser. CI may use chromium_headless_shell;
|
|
314
314
|
// local rendering fails closed because that shell has no GPU and silently
|
|
315
|
-
// chooses SwiftShader. NOTE: this is NOT enough to make \`
|
|
315
|
+
// chooses SwiftShader. NOTE: this is NOT enough to make \`rnx perf cpu\`
|
|
316
316
|
// work — that profiles the tenant *worker*, and the JS Self-Profiler API
|
|
317
317
|
// cannot be constructed in a dedicated worker (see
|
|
318
318
|
// worker-handlers/cpu-profile.ts). cpu-profile needs a CDP-based profiler
|
|
@@ -559,7 +559,7 @@ function resolveUserDataDir(opts: DriverLaunchOptions): string {
|
|
|
559
559
|
return playwrightProfileUserDataDir(ensureProfile(opts.profileId).id)
|
|
560
560
|
}
|
|
561
561
|
if (opts.ephemeralProfile) {
|
|
562
|
-
return mkdtempSync(join(tmpdir(), '
|
|
562
|
+
return mkdtempSync(join(tmpdir(), 'rnx-playwright-profile-'))
|
|
563
563
|
}
|
|
564
564
|
return ''
|
|
565
565
|
}
|
|
@@ -596,7 +596,7 @@ async function launch(opts: DriverLaunchOptions): Promise<DriverLaunchResult> {
|
|
|
596
596
|
|
|
597
597
|
const errLog = join(
|
|
598
598
|
tmpdir(),
|
|
599
|
-
`
|
|
599
|
+
`rnx-playwright-host-${Date.now().toString(36)}-${process.pid}.log`,
|
|
600
600
|
)
|
|
601
601
|
const connectAckFile = `${errLog}.connected`
|
|
602
602
|
const errFd = openSync(errLog, 'a')
|
package/cli/drivers/registry.ts
CHANGED
|
@@ -37,7 +37,7 @@ export function resolveDriver(
|
|
|
37
37
|
return null
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// utility for the `
|
|
40
|
+
// utility for the `rnx list --drivers` view. returns a rich row per
|
|
41
41
|
// driver with availability + kind + description, suitable for a table.
|
|
42
42
|
export interface DriverListRow {
|
|
43
43
|
id: DriverId
|
package/cli/drivers/types.ts
CHANGED
|
@@ -85,7 +85,7 @@ export interface DriverLaunchResult {
|
|
|
85
85
|
|
|
86
86
|
export interface Driver {
|
|
87
87
|
id: DriverId
|
|
88
|
-
// short human name shown in `
|
|
88
|
+
// short human name shown in `rnx list --drivers`.
|
|
89
89
|
name: string
|
|
90
90
|
// one-line description. keep under 80 chars so the list command reads
|
|
91
91
|
// nicely at default terminal width.
|
package/cli/flow-file.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'fs'
|
|
|
2
2
|
import yaml from 'yaml'
|
|
3
3
|
|
|
4
4
|
export interface FlowFrontmatter {
|
|
5
|
-
// raw `
|
|
5
|
+
// raw `rnx open` target: port, dev-server URL, bundle URL, or shell URL
|
|
6
6
|
app?: string | number
|
|
7
7
|
device?: string
|
|
8
8
|
// pin the sim color scheme for this flow run (screenshot baselines are
|
|
@@ -116,7 +116,7 @@ export function validateFlowSteps(steps: any[]): string[] {
|
|
|
116
116
|
// structural-only validator used by CI runners before uploading the flow
|
|
117
117
|
// artifact. we only check: file exists, parseable yaml, has at least one
|
|
118
118
|
// real interaction step. playback validation is a separate concern handled
|
|
119
|
-
// by `
|
|
119
|
+
// by `rnx maestro end --validate` during the authoring session.
|
|
120
120
|
export function validateFlowFile(flowPath: string): string[] {
|
|
121
121
|
if (!fs.existsSync(flowPath)) {
|
|
122
122
|
return [`file not found: ${flowPath}`]
|
package/cli/flow-session.ts
CHANGED
|
@@ -25,7 +25,7 @@ const FLOW_SESSION_VERSION = 1 as const
|
|
|
25
25
|
|
|
26
26
|
export function getFlowSessionPath() {
|
|
27
27
|
return resolve(
|
|
28
|
-
process.env.
|
|
28
|
+
process.env.RNX_FLOW_SESSION_PATH || join(tmpdir(), 'rnx-flow-session.json'),
|
|
29
29
|
)
|
|
30
30
|
}
|
|
31
31
|
|