rnxsim 0.1.313 → 0.1.315
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 +11 -10
- package/README.md +5 -0
- package/cli/app-config.ts +65 -0
- package/cli/app-fonts.ts +408 -0
- package/cli/app-project.ts +231 -0
- package/cli/app-splash.ts +185 -0
- package/cli/app-state-reset.ts +24 -0
- package/cli/auth.ts +155 -0
- package/cli/bin.ts +594 -0
- package/cli/bridge-diagnostics.ts +226 -0
- package/cli/bridge-flow-runner.ts +2830 -0
- package/cli/browser-evals.ts +96 -0
- package/cli/commands/agent-wrapper.ts +986 -0
- package/cli/commands/agent.ts +423 -0
- package/cli/commands/app-fonts.ts +98 -0
- package/cli/commands/assert.ts +541 -0
- package/cli/commands/auth.ts +59 -0
- package/cli/commands/camera.ts +266 -0
- package/cli/commands/cleanup.ts +169 -0
- package/cli/commands/compat.ts +87 -0
- package/cli/commands/config.ts +32 -0
- package/cli/commands/control.ts +2142 -0
- package/cli/commands/cpu-profile.ts +269 -0
- package/cli/commands/daemon-mac-app.ts +169 -0
- package/cli/commands/daemon.ts +874 -0
- package/cli/commands/debug.ts +719 -0
- package/cli/commands/desktop.ts +39 -0
- package/cli/commands/detect.ts +197 -0
- package/cli/commands/detox.ts +385 -0
- package/cli/commands/device.ts +133 -0
- package/cli/commands/diagnose.ts +589 -0
- package/cli/commands/electron.ts +95 -0
- package/cli/commands/film.ts +379 -0
- package/cli/commands/flow.ts +1124 -0
- package/cli/commands/inspect/actions.ts +622 -0
- package/cli/commands/inspect/core.ts +2405 -0
- package/cli/commands/inspect/count.ts +17 -0
- package/cli/commands/inspect/describe.ts +192 -0
- package/cli/commands/inspect/env.ts +23 -0
- package/cli/commands/inspect/find.ts +171 -0
- package/cli/commands/inspect/get-layout.ts +39 -0
- package/cli/commands/inspect/keyboard.ts +52 -0
- package/cli/commands/inspect/list.ts +58 -0
- package/cli/commands/inspect/memory.ts +215 -0
- package/cli/commands/inspect/redaction.ts +39 -0
- package/cli/commands/inspect/resolve-target.ts +82 -0
- package/cli/commands/inspect/screens.ts +78 -0
- package/cli/commands/inspect/settle.ts +22 -0
- package/cli/commands/inspect/settling.ts +158 -0
- package/cli/commands/inspect/shared.ts +353 -0
- package/cli/commands/inspect/sleep.ts +14 -0
- package/cli/commands/inspect/tree.ts +32 -0
- package/cli/commands/inspect/url.ts +17 -0
- package/cli/commands/inspect/wait-event.ts +210 -0
- package/cli/commands/inspect/wait-idle.ts +24 -0
- package/cli/commands/inspect/wait-ready.ts +74 -0
- package/cli/commands/inspect/wait-selector.ts +54 -0
- package/cli/commands/inspect/wait.ts +31 -0
- package/cli/commands/inspect.ts +4519 -0
- package/cli/commands/install-desktop.ts +351 -0
- package/cli/commands/login.ts +331 -0
- package/cli/commands/logout.ts +31 -0
- package/cli/commands/maestro-generate.ts +361 -0
- package/cli/commands/maestro.ts +453 -0
- package/cli/commands/mode.ts +57 -0
- package/cli/commands/no-bridge-hint.ts +80 -0
- package/cli/commands/perf.ts +66 -0
- package/cli/commands/permissions.ts +203 -0
- package/cli/commands/profile.ts +108 -0
- package/cli/commands/react.ts +353 -0
- package/cli/commands/record.ts +1434 -0
- package/cli/commands/report-issue.ts +305 -0
- package/cli/commands/reset.ts +85 -0
- package/cli/commands/runtime.ts +351 -0
- package/cli/commands/screenshot-command.ts +106 -0
- package/cli/commands/screenshot-layers.ts +143 -0
- package/cli/commands/screenshot-mode.ts +37 -0
- package/cli/commands/screenshot.ts +488 -0
- package/cli/commands/screenshots-capture.ts +607 -0
- package/cli/commands/screenshots.ts +127 -0
- package/cli/commands/serve.ts +168 -0
- package/cli/commands/setup.ts +545 -0
- package/cli/commands/shell-boolean-mode.ts +81 -0
- package/cli/commands/skills.ts +467 -0
- package/cli/commands/slides.ts +361 -0
- package/cli/commands/state.ts +87 -0
- package/cli/commands/storage.ts +58 -0
- package/cli/commands/telemetry.ts +54 -0
- package/cli/commands/three-mode.ts +763 -0
- package/cli/commands/timeline.ts +122 -0
- package/cli/commands/upgrade.ts +208 -0
- package/cli/commands/upload.ts +1225 -0
- package/cli/commands/version.ts +54 -0
- package/cli/commands/what-happened.ts +327 -0
- package/cli/current-sim.ts +204 -0
- package/cli/desktop-companion.ts +300 -0
- package/cli/drivers/electron.ts +70 -0
- package/cli/drivers/index.ts +20 -0
- package/cli/drivers/playwright-provisioning.ts +180 -0
- package/cli/drivers/playwright.ts +698 -0
- package/cli/drivers/registry.ts +65 -0
- package/cli/drivers/types.ts +102 -0
- package/cli/flow-file.ts +142 -0
- package/cli/flow-live-status.ts +120 -0
- package/cli/flow-session.ts +187 -0
- package/cli/help.ts +80 -0
- package/cli/hidden-runtime-alias.ts +19 -0
- package/cli/hints.ts +216 -0
- package/cli/inspect-notice-state.ts +114 -0
- package/cli/maestro-js.ts +334 -0
- package/cli/open-url.ts +8 -0
- package/cli/parent-pid.ts +204 -0
- package/cli/parse-args.ts +211 -0
- package/cli/prompt.ts +51 -0
- package/cli/recording-access.ts +107 -0
- package/cli/registry.ts +1 -0
- package/cli/resolve-assets.ts +63 -0
- package/cli/run-registry.ts +226 -0
- package/cli/runtime-notes.ts +66 -0
- package/cli/runtime-summary.ts +25 -0
- package/cli/setup-repository.ts +187 -0
- package/cli/telemetry.ts +187 -0
- package/cli/ws-bridge.ts +798 -0
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-XZ2KTPCU.js → agent-CBU2WDKV.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-UNB4R27L.js} +2 -2
- package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-YMLRF3JD.js} +2 -2
- package/dist-cli/chunks/{assert-54T5SK5F.js → assert-5N6HUCL5.js} +2 -2
- package/dist-cli/chunks/{auth-FI5UDI45.js → auth-BHDR3A6G.js} +2 -2
- package/dist-cli/chunks/{beta-JV6UKADW.js → beta-LWUVCPYK.js} +2 -2
- package/dist-cli/chunks/camera-CNO6B2JZ.js +33 -0
- package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-25VCR44B.js} +1 -1
- package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-26CSIR76.js} +8 -9
- package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-26WMWUH4.js} +1 -1
- package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-2JZ53D4A.js} +1 -1
- package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-2POODCTW.js} +2 -2
- package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-4LTBK3ZN.js} +2 -2
- package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-54MIRTFE.js} +4 -4
- package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-56H6EVO4.js} +2 -2
- package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-657SQG4D.js} +1 -1
- package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-6ZMRJPKI.js} +1 -1
- package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-AOGGOC4V.js} +2 -2
- package/dist-cli/chunks/chunk-BPCDO7ST.js +15 -0
- package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-C5O5CTXJ.js} +1 -1
- package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-CMM64PPR.js} +1 -1
- package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-D3M36D7O.js} +62 -87
- package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-DNCNFZ7V.js} +1 -1
- package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-FNTTIR2P.js} +2 -2
- package/dist-cli/chunks/chunk-H362IX2I.js +4 -0
- package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-J6HMTUZH.js} +2 -2
- package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-JZ6VTZ5B.js} +3 -3
- package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-KQSJVNFE.js} +4 -4
- package/dist-cli/chunks/chunk-L4JZKVCC.js +6 -0
- package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-LP2BTEJ6.js} +2 -2
- package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-LT6UC7I3.js} +2 -2
- package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-MCKQH4AM.js} +2 -2
- package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-MF7WUHQ3.js} +2 -2
- package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-MSSY4EEZ.js} +1 -1
- package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-N5ZCYP5F.js} +2 -2
- package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-NCKAD3V7.js} +2 -2
- package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-O2KRHVTU.js} +2 -2
- package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-OF2HTH25.js} +2 -2
- package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-OXS2IWO2.js} +2 -2
- package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-PVL7XSPN.js} +1 -1
- package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-PVPAP3TV.js} +1 -1
- package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-PXNAZYGV.js} +1 -1
- package/dist-cli/chunks/chunk-QR3HTFRB.js +5 -0
- package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-QR7MWAWE.js} +1 -1
- package/dist-cli/chunks/chunk-RJIEZ2NY.js +4 -0
- package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RVM4EZ4E.js} +2 -2
- package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-SZKSFMWS.js} +1 -1
- package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-TEL7G234.js} +2 -2
- package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-U2HNIHBY.js} +1 -1
- package/dist-cli/chunks/chunk-UROXJVY3.js +27 -0
- package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-WM32MGTJ.js} +2 -2
- package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-WMZ7NJAO.js} +89 -133
- package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-WUXCH6WN.js} +10 -9
- package/dist-cli/chunks/chunk-XOXGC3EH.js +9 -0
- package/dist-cli/chunks/chunk-XSSHENNY.js +4 -0
- package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-XZ6O67AR.js} +1 -1
- package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-Y2MWQKOS.js} +3 -3
- package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-YY24QI25.js} +1 -1
- package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-ZWX6DDIS.js} +2 -2
- package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-F3NUJ3SL.js} +2 -2
- package/dist-cli/chunks/cli-version-XIW6O3FG.js +4 -0
- package/dist-cli/chunks/{compat-ZD65FED3.js → compat-AX2SEPTH.js} +2 -2
- package/dist-cli/chunks/{config-XMJRNM2A.js → config-4QQO5JVU.js} +2 -2
- package/dist-cli/chunks/{control-KMIQT3QP.js → control-OCKKSOWA.js} +2 -2
- package/dist-cli/chunks/daemon-N5VLMYKQ.js +4 -0
- package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-T6MWVEVX.js} +5 -5
- package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-NCW7R7CB.js} +3 -3
- package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-G6GITO2X.js} +2 -2
- package/dist-cli/chunks/{device-XBNDSB2R.js → device-7BTEJCTH.js} +2 -2
- package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-SC5LACE3.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KK56QQZA.js} +2 -2
- package/dist-cli/chunks/drivers-NPGVBWMG.js +4 -0
- package/dist-cli/chunks/{film-BJGTBYZB.js → film-WGYEHGIA.js} +3 -3
- package/dist-cli/chunks/flow-KQBIBM3N.js +4 -0
- package/dist-cli/chunks/help-G7CAKI65.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-CWTZWH3L.js} +2 -2
- package/dist-cli/chunks/home-paths-EBT4XHAS.js +4 -0
- package/dist-cli/chunks/inspect-CCPZXVDU.js +4 -0
- package/dist-cli/chunks/install-desktop-NZ3VGTQZ.js +4 -0
- package/dist-cli/chunks/{login-FJ737MWG.js → login-GSYOTGVQ.js} +4 -4
- package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-JPRIR7YK.js} +2 -2
- package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-LXKDNO5M.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-5ZIKOU53.js} +3 -3
- package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-N3QHXCES.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-XDH6X24N.js} +2 -2
- package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-VOPL26FM.js} +2 -2
- package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-CZFKM4UD.js} +2 -2
- package/dist-cli/chunks/{record-QPPC2S4E.js → record-FODSOGMA.js} +3 -3
- package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-FBPCZKOV.js} +2 -2
- package/dist-cli/chunks/reset-3H35GFO3.js +4 -0
- package/dist-cli/chunks/runtime-4OVFRXV5.js +4 -0
- package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7S3FYVO7.js} +7 -7
- package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-CDYHPNC3.js} +3 -3
- package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-EBIIY2YU.js} +2 -2
- package/dist-cli/chunks/serve-NELNSCZP.js +44 -0
- package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-DF5ZDDIV.js} +2 -2
- package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-E2QZF6WZ.js} +2 -2
- package/dist-cli/chunks/state-MBZDKZ7U.js +14 -0
- package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-J5BMNVXM.js} +6 -6
- package/dist-cli/chunks/store-U3DXC7HH.js +4 -0
- package/dist-cli/chunks/telemetry-XIPJVT5E.js +4 -0
- package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-MC6N5BHY.js} +2 -2
- package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-7UTU53DH.js} +2 -2
- package/dist-cli/chunks/upload-2M2KOTYI.js +4 -0
- package/dist-cli/chunks/version-VNACNYQK.js +6 -0
- package/dist-cli/chunks/{web-DG3WBYD3.js → web-MGJ2TUKR.js} +2 -2
- package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-7DLKH3OD.js} +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.cjs +20 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +67 -28
- package/dist-lib/host/bridge-host.cjs +140 -12
- 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/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +2815 -40
- 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.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +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 +2549 -2061
- package/dist-lib/sdk.mjs +2543 -2061
- package/dist-lib/skills.cjs +480 -280
- package/dist-lib/vite.cjs +1 -1
- package/package.json +8 -2
- package/src/bridge-constants.ts +3 -4
- package/src/bridge-contract.ts +251 -0
- package/src/connect.ts +83 -0
- package/src/disk-cleanup.ts +30 -0
- package/src/home-paths.ts +81 -38
- package/src/host/bridge-host.ts +134 -6
- package/src/index.ts +27 -1
- package/src/sdk.ts +8 -0
- package/src/sim-client.ts +660 -0
- package/dist-cli/chunks/camera-VL73YIKP.js +0 -22
- package/dist-cli/chunks/chunk-4NPPOV2N.js +0 -5
- package/dist-cli/chunks/chunk-FSUYIVJ6.js +0 -9
- package/dist-cli/chunks/chunk-G2WW6L2C.js +0 -23
- package/dist-cli/chunks/chunk-KTHV3RUS.js +0 -26
- package/dist-cli/chunks/chunk-LF2ZVT7O.js +0 -6
- package/dist-cli/chunks/chunk-NFK7T35W.js +0 -4
- package/dist-cli/chunks/chunk-TIVZIMMW.js +0 -4
- package/dist-cli/chunks/cli-version-WWLPBDQ7.js +0 -4
- package/dist-cli/chunks/daemon-G2ME7NLB.js +0 -4
- package/dist-cli/chunks/drivers-LDECZGP2.js +0 -4
- package/dist-cli/chunks/flow-UEQNVTU7.js +0 -4
- package/dist-cli/chunks/help-T5FYSVGB.js +0 -4
- package/dist-cli/chunks/home-paths-GT3LFNOR.js +0 -4
- package/dist-cli/chunks/inspect-ZA6XF5LD.js +0 -4
- package/dist-cli/chunks/install-desktop-TIMUDHPL.js +0 -4
- package/dist-cli/chunks/runtime-XOAXMSTU.js +0 -4
- package/dist-cli/chunks/serve-BI2NBAXG.js +0 -44
- package/dist-cli/chunks/store-JTHEJLAZ.js +0 -4
- package/dist-cli/chunks/telemetry-ZYJGD2DB.js +0 -4
- package/dist-cli/chunks/upload-GMSZPWM6.js +0 -4
- package/dist-cli/chunks/version-HOCHZ37L.js +0 -6
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// sootsim runtime — manage engine runtimes under ~/.sootsim/runtimes/.
|
|
2
|
+
//
|
|
3
|
+
// runtimes are versioned tarballs hosted behind the configured CDN origin
|
|
4
|
+
// (default https://contrast.dev/runtimes/). each install:
|
|
5
|
+
// 1. fetches manifest.json to resolve a version (or confirm the literal)
|
|
6
|
+
// 2. downloads sootsim-runtime-<version>.tar.gz to an operation temp directory
|
|
7
|
+
// 3. verifies sha256
|
|
8
|
+
// 4. extracts to ~/.sootsim/runtimes/<version>/
|
|
9
|
+
// 5. (optionally) sets it active + tells the daemon to hot-swap
|
|
10
|
+
//
|
|
11
|
+
// the daemon-side state (active runtime) is mirrored in ~/.sootsim/runtimes/active
|
|
12
|
+
// so the CLI can change it even when the daemon isn't running.
|
|
13
|
+
|
|
14
|
+
import fs from 'fs'
|
|
15
|
+
import { compareSemver } from '@contrast/runtime-delivery'
|
|
16
|
+
import { WebSocket } from 'ws'
|
|
17
|
+
import {
|
|
18
|
+
ensureSootsimHome,
|
|
19
|
+
readActiveRuntime,
|
|
20
|
+
readLiveRuntimeVersions,
|
|
21
|
+
runtimeDir,
|
|
22
|
+
writeActiveRuntime,
|
|
23
|
+
} from '../../src/home-paths'
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_RUNTIME_CDN_ORIGIN,
|
|
26
|
+
SOOTSIM_CHANGELOG_URL,
|
|
27
|
+
sootsimRuntime,
|
|
28
|
+
} from '../../src/runtime-delivery'
|
|
29
|
+
import { resolveSootSimAppConfig } from '../app-config'
|
|
30
|
+
import { fetchRuntimeReleaseNotes, formatRuntimeReleaseNotes } from '../runtime-notes'
|
|
31
|
+
import { resolveDefaultBridgePort } from '../ws-bridge'
|
|
32
|
+
|
|
33
|
+
interface RuntimeOptions {
|
|
34
|
+
channel?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function runRuntime(args: string[], opts: RuntimeOptions = {}) {
|
|
38
|
+
const [sub, ...rest] = args
|
|
39
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
40
|
+
printHelp()
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
switch (sub) {
|
|
44
|
+
case 'install':
|
|
45
|
+
return runtimeInstall(rest, opts)
|
|
46
|
+
case 'list':
|
|
47
|
+
case 'ls':
|
|
48
|
+
return runtimeList(rest)
|
|
49
|
+
case 'use':
|
|
50
|
+
return runtimeUse(rest)
|
|
51
|
+
case 'remove':
|
|
52
|
+
case 'rm':
|
|
53
|
+
return runtimeRemove(rest)
|
|
54
|
+
case 'which':
|
|
55
|
+
case 'active':
|
|
56
|
+
return runtimeWhich()
|
|
57
|
+
case 'notes':
|
|
58
|
+
case 'changelog':
|
|
59
|
+
return runtimeNotes(rest)
|
|
60
|
+
default:
|
|
61
|
+
console.error(` unknown runtime subcommand: ${sub}`)
|
|
62
|
+
printHelp()
|
|
63
|
+
process.exit(1)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function printHelp() {
|
|
68
|
+
console.log(`
|
|
69
|
+
rnxsim runtime — manage engine runtimes under ~/.sootsim/runtimes/
|
|
70
|
+
|
|
71
|
+
usage:
|
|
72
|
+
rnxsim runtime install [version] install a version (default: channel latest)
|
|
73
|
+
rnxsim runtime list show installed + available versions
|
|
74
|
+
rnxsim runtime use <version> switch active runtime
|
|
75
|
+
rnxsim runtime remove <version> delete an installed runtime
|
|
76
|
+
rnxsim runtime which print active runtime version
|
|
77
|
+
rnxsim runtime notes print repo or default engine release notes
|
|
78
|
+
|
|
79
|
+
flags:
|
|
80
|
+
--channel <name> channel to resolve 'latest' from (default: stable)
|
|
81
|
+
--force reinstall even if the version is already on disk
|
|
82
|
+
--set-active=false do not switch active runtime after install
|
|
83
|
+
|
|
84
|
+
environment:
|
|
85
|
+
SOOTSIM_CDN_ORIGIN override the CDN base URL (default: ${DEFAULT_RUNTIME_CDN_ORIGIN})
|
|
86
|
+
|
|
87
|
+
examples:
|
|
88
|
+
rnxsim runtime install
|
|
89
|
+
rnxsim runtime install 1.2.3
|
|
90
|
+
rnxsim runtime install --channel beta
|
|
91
|
+
rnxsim runtime use 1.2.3
|
|
92
|
+
rnxsim runtime notes
|
|
93
|
+
`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- install --------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
async function runtimeInstall(args: string[], opts: RuntimeOptions) {
|
|
99
|
+
const { version: versionArg, flags } = parseVersionAndFlags(args)
|
|
100
|
+
const channel = flags.channel ?? opts.channel ?? 'stable'
|
|
101
|
+
const force = flags.force === true
|
|
102
|
+
const setActive = flags.setActive !== false
|
|
103
|
+
|
|
104
|
+
ensureSootsimHome()
|
|
105
|
+
|
|
106
|
+
console.log(`rnxsim runtime install`)
|
|
107
|
+
console.log(` cdn: ${sootsimRuntime.resolveCdnOrigin()}`)
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const result = await sootsimRuntime.install({
|
|
111
|
+
version: versionArg,
|
|
112
|
+
channel,
|
|
113
|
+
force,
|
|
114
|
+
setActive,
|
|
115
|
+
protectVersions: readLiveRuntimeVersions(),
|
|
116
|
+
})
|
|
117
|
+
console.log(` version: ${result.version} (channel: ${result.channel})`)
|
|
118
|
+
if (result.installed) {
|
|
119
|
+
console.log(` installed ${result.version}`)
|
|
120
|
+
} else {
|
|
121
|
+
console.log(` already installed at ${result.runtimeDir}`)
|
|
122
|
+
}
|
|
123
|
+
if (setActive) await activateVersion(result.version)
|
|
124
|
+
return result
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error(` ${describeError(err)}`)
|
|
127
|
+
process.exit(1)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function activateVersion(version: string) {
|
|
132
|
+
writeActiveRuntime(version)
|
|
133
|
+
console.log(` active: ${version}`)
|
|
134
|
+
// if a daemon is running, tell it to hot-swap its http root. the
|
|
135
|
+
// daemon re-reads the active runtime per-request anyway, so this is
|
|
136
|
+
// mostly a nudge — but it also broadcasts runtime:changed so any
|
|
137
|
+
// connected electron webContents can reload. silent failure is fine;
|
|
138
|
+
// the next daemon start picks up the active file from disk.
|
|
139
|
+
const notified = await tellDaemonRuntimeChanged(version)
|
|
140
|
+
if (!notified) {
|
|
141
|
+
console.log(
|
|
142
|
+
` (no daemon running — next sootsim/electron launch will pick up ${version})`,
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** notify a running daemon that the active runtime changed so it hot-swaps
|
|
148
|
+
* its http root + pushes a runtime:changed message to every connected
|
|
149
|
+
* browser (electron). uses a raw WebSocket to skip the bridge's
|
|
150
|
+
* primary-browser resolution — this message is daemon-only, not routed
|
|
151
|
+
* to a browser. returns true when the daemon acknowledged; false when
|
|
152
|
+
* no fresh daemon was reachable (caller logs accordingly).
|
|
153
|
+
*
|
|
154
|
+
* skips the ws probe entirely when the lockfile isn't fresh — the
|
|
155
|
+
* default-port fallback in resolveDefaultBridgePort() would otherwise
|
|
156
|
+
* eat a full handshakeTimeout on every `runtime use` when no daemon
|
|
157
|
+
* is running. */
|
|
158
|
+
async function tellDaemonRuntimeChanged(version: string): Promise<boolean> {
|
|
159
|
+
const { isDaemonLockfileFresh, readDaemonLockfile } =
|
|
160
|
+
await import('../../src/home-paths')
|
|
161
|
+
const lock = readDaemonLockfile()
|
|
162
|
+
if (!isDaemonLockfileFresh(lock)) return false
|
|
163
|
+
const port = resolveDefaultBridgePort()
|
|
164
|
+
return new Promise<boolean>((resolve) => {
|
|
165
|
+
let settled = false
|
|
166
|
+
let success = false
|
|
167
|
+
const finish = (ok: boolean) => {
|
|
168
|
+
if (settled) return
|
|
169
|
+
settled = true
|
|
170
|
+
resolve(ok)
|
|
171
|
+
}
|
|
172
|
+
const ws = new WebSocket(`ws://127.0.0.1:${port}`, {
|
|
173
|
+
handshakeTimeout: 800,
|
|
174
|
+
})
|
|
175
|
+
const timer = setTimeout(() => {
|
|
176
|
+
try {
|
|
177
|
+
ws.close()
|
|
178
|
+
} catch {}
|
|
179
|
+
finish(success)
|
|
180
|
+
}, 1500)
|
|
181
|
+
ws.on('open', () => {
|
|
182
|
+
try {
|
|
183
|
+
ws.send(JSON.stringify({ type: 'runtime:use', version, id: 0 }))
|
|
184
|
+
success = true
|
|
185
|
+
} catch {}
|
|
186
|
+
// give the daemon a beat to process, then close
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
try {
|
|
189
|
+
ws.close()
|
|
190
|
+
} catch {}
|
|
191
|
+
}, 100)
|
|
192
|
+
})
|
|
193
|
+
ws.on('close', () => {
|
|
194
|
+
clearTimeout(timer)
|
|
195
|
+
finish(success)
|
|
196
|
+
})
|
|
197
|
+
ws.on('error', () => {
|
|
198
|
+
clearTimeout(timer)
|
|
199
|
+
finish(false)
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// --- list -----------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
async function runtimeList(_args: string[]) {
|
|
207
|
+
ensureSootsimHome()
|
|
208
|
+
const installed = sootsimRuntime.listInstalled()
|
|
209
|
+
const active = readActiveRuntime()
|
|
210
|
+
console.log(`installed:`)
|
|
211
|
+
if (installed.length === 0) {
|
|
212
|
+
console.log(` (none)`)
|
|
213
|
+
} else {
|
|
214
|
+
for (const v of installed) {
|
|
215
|
+
const marker = v === active ? '*' : ' '
|
|
216
|
+
console.log(` ${marker} ${v}`)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const manifest = await sootsimRuntime.fetchManifest()
|
|
221
|
+
console.log(`available (latest per channel):`)
|
|
222
|
+
for (const [name, ch] of Object.entries(manifest.channels)) {
|
|
223
|
+
console.log(` ${name.padEnd(8)} ${ch.latest}`)
|
|
224
|
+
}
|
|
225
|
+
const hosted = Object.keys(manifest.versions).sort(compareSemver).reverse()
|
|
226
|
+
if (hosted.length > 1) {
|
|
227
|
+
console.log(`hosted versions:`)
|
|
228
|
+
for (const v of hosted) console.log(` ${v}`)
|
|
229
|
+
}
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.log(`available: (could not fetch manifest: ${describeError(err)})`)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// --- use ------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
async function runtimeUse(args: string[]) {
|
|
238
|
+
const version = args[0]
|
|
239
|
+
if (!version) {
|
|
240
|
+
console.error(` usage: rnxsim runtime use <version>`)
|
|
241
|
+
process.exit(1)
|
|
242
|
+
}
|
|
243
|
+
const installed = sootsimRuntime.listInstalled()
|
|
244
|
+
if (!installed.includes(version)) {
|
|
245
|
+
console.error(` version ${version} is not installed`)
|
|
246
|
+
console.error(` installed: ${installed.join(', ') || '(none)'}`)
|
|
247
|
+
console.error(` run \`rnxsim runtime install ${version}\` first`)
|
|
248
|
+
process.exit(1)
|
|
249
|
+
}
|
|
250
|
+
await activateVersion(version)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// --- remove ---------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
async function runtimeRemove(args: string[]) {
|
|
256
|
+
const version = args[0]
|
|
257
|
+
if (!version) {
|
|
258
|
+
console.error(` usage: rnxsim runtime remove <version>`)
|
|
259
|
+
process.exit(1)
|
|
260
|
+
}
|
|
261
|
+
const active = readActiveRuntime()
|
|
262
|
+
if (active === version) {
|
|
263
|
+
console.error(` cannot remove active runtime ${version}`)
|
|
264
|
+
console.error(
|
|
265
|
+
` switch with \`rnxsim runtime use <other>\` first, or install another version`,
|
|
266
|
+
)
|
|
267
|
+
process.exit(1)
|
|
268
|
+
}
|
|
269
|
+
if (readLiveRuntimeVersions().includes(version)) {
|
|
270
|
+
console.error(` cannot remove runtime ${version} while a project is using it`)
|
|
271
|
+
process.exit(1)
|
|
272
|
+
}
|
|
273
|
+
const dir = runtimeDir(version)
|
|
274
|
+
if (!fs.existsSync(dir)) {
|
|
275
|
+
console.error(` ${version} is not installed`)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
fs.rmSync(dir, { recursive: true, force: true })
|
|
279
|
+
console.log(` removed ${version}`)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// --- which ----------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
async function runtimeWhich() {
|
|
285
|
+
const active = readActiveRuntime()
|
|
286
|
+
if (!active) {
|
|
287
|
+
console.log(` no active runtime`)
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
console.log(active)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// --- notes ----------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
async function runtimeNotes(args: string[]) {
|
|
296
|
+
if (args.length > 0) {
|
|
297
|
+
console.error(` usage: rnxsim runtime notes`)
|
|
298
|
+
process.exit(1)
|
|
299
|
+
}
|
|
300
|
+
const { config } = await resolveSootSimAppConfig()
|
|
301
|
+
const version = config?.runtimeVersion ?? readActiveRuntime()
|
|
302
|
+
if (!version) {
|
|
303
|
+
console.log(` no active runtime`)
|
|
304
|
+
console.log(` release notes: ${SOOTSIM_CHANGELOG_URL}`)
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
console.log(formatRuntimeReleaseNotes(version, await fetchRuntimeReleaseNotes(version)))
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// --- helpers --------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
interface InstallFlags {
|
|
313
|
+
channel?: string
|
|
314
|
+
force?: boolean
|
|
315
|
+
setActive?: boolean
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function parseVersionAndFlags(args: string[]): {
|
|
319
|
+
version: string | null
|
|
320
|
+
flags: InstallFlags
|
|
321
|
+
} {
|
|
322
|
+
const flags: InstallFlags = {}
|
|
323
|
+
const positional: string[] = []
|
|
324
|
+
for (let i = 0; i < args.length; i++) {
|
|
325
|
+
const arg = args[i]
|
|
326
|
+
if (arg === '--channel' && i + 1 < args.length) {
|
|
327
|
+
flags.channel = args[i + 1]
|
|
328
|
+
i++
|
|
329
|
+
continue
|
|
330
|
+
}
|
|
331
|
+
if (arg.startsWith('--channel=')) {
|
|
332
|
+
flags.channel = arg.slice('--channel='.length)
|
|
333
|
+
continue
|
|
334
|
+
}
|
|
335
|
+
if (arg === '--force') {
|
|
336
|
+
flags.force = true
|
|
337
|
+
continue
|
|
338
|
+
}
|
|
339
|
+
if (arg === '--set-active=false' || arg === '--no-set-active') {
|
|
340
|
+
flags.setActive = false
|
|
341
|
+
continue
|
|
342
|
+
}
|
|
343
|
+
positional.push(arg)
|
|
344
|
+
}
|
|
345
|
+
return { version: positional[0] ?? null, flags }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function describeError(err: unknown): string {
|
|
349
|
+
if (err instanceof Error) return err.message
|
|
350
|
+
return String(err)
|
|
351
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { runScreenshot } from './screenshot'
|
|
2
|
+
import { runScreenshots } from './screenshots'
|
|
3
|
+
import { runSlides } from './slides'
|
|
4
|
+
|
|
5
|
+
interface ScreenshotCommandOptions {
|
|
6
|
+
port?: number
|
|
7
|
+
verbose?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const VALUE_FLAGS = new Set([
|
|
11
|
+
'--sim',
|
|
12
|
+
'--port',
|
|
13
|
+
'-p',
|
|
14
|
+
'--output',
|
|
15
|
+
'-o',
|
|
16
|
+
'--out',
|
|
17
|
+
'--plan',
|
|
18
|
+
'--app',
|
|
19
|
+
'--device',
|
|
20
|
+
'--org',
|
|
21
|
+
'--repo',
|
|
22
|
+
'--api-origin',
|
|
23
|
+
'--branch',
|
|
24
|
+
'--platform',
|
|
25
|
+
'--slides',
|
|
26
|
+
'--shell-origin',
|
|
27
|
+
'--timeout',
|
|
28
|
+
'--ids',
|
|
29
|
+
'--out-dir',
|
|
30
|
+
])
|
|
31
|
+
const SUBCOMMANDS = new Set(['capture', 'slides', 'appstore', 'layers'])
|
|
32
|
+
|
|
33
|
+
function findSubcommand(args: string[]): { name: string; index: number } | null {
|
|
34
|
+
for (let i = 0; i < args.length; i++) {
|
|
35
|
+
const arg = args[i]
|
|
36
|
+
if (arg.startsWith('-')) {
|
|
37
|
+
if (VALUE_FLAGS.has(arg)) i++
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
return SUBCOMMANDS.has(arg) ? { name: arg, index: i } : null
|
|
41
|
+
}
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function removeAt(args: string[], index: number): string[] {
|
|
46
|
+
return [...args.slice(0, index), ...args.slice(index + 1)]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function stripSimFlag(args: string[]): string[] {
|
|
50
|
+
const out: string[] = []
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
if (args[i] === '--sim') {
|
|
53
|
+
i++
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
out.push(args[i])
|
|
57
|
+
}
|
|
58
|
+
return out
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function runScreenshotCommand(
|
|
62
|
+
args: string[],
|
|
63
|
+
opts: ScreenshotCommandOptions,
|
|
64
|
+
): Promise<number | void> {
|
|
65
|
+
const sub = findSubcommand(args)
|
|
66
|
+
if (!sub || sub.name === 'capture') {
|
|
67
|
+
await runScreenshot(sub ? removeAt(args, sub.index) : args, opts)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (sub.name === 'slides') {
|
|
72
|
+
await runSlides(removeAt(args, sub.index), opts)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (sub.name === 'layers') {
|
|
77
|
+
const { runScreenshotLayers } = await import('./screenshot-layers')
|
|
78
|
+
await runScreenshotLayers(removeAt(args, sub.index), opts)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const appstoreArgs = removeAt(args, sub.index)
|
|
83
|
+
const nested = findSubcommand(appstoreArgs)
|
|
84
|
+
if (nested?.name === 'capture') {
|
|
85
|
+
// repo-only tooling: screenshots-capture statically imports playwright-core,
|
|
86
|
+
// which the published CLI never ships (it's external in build-cli.ts). a
|
|
87
|
+
// static import here would put that bare import at the top of THIS chunk,
|
|
88
|
+
// so every published `sootsim screenshot` printed ERR_MODULE_NOT_FOUND —
|
|
89
|
+
// keep it a runtime dynamic import that only evaluates when capture runs.
|
|
90
|
+
try {
|
|
91
|
+
const { runScreenshotsCapture } = await import('./screenshots-capture')
|
|
92
|
+
return await runScreenshotsCapture(
|
|
93
|
+
stripSimFlag(removeAt(appstoreArgs, nested.index)),
|
|
94
|
+
)
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err instanceof Error && 'code' in err && err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
97
|
+
console.error(
|
|
98
|
+
'screenshot appstore capture is repo-only tooling — it needs the rnx workspace (playwright is not shipped with the published CLI).',
|
|
99
|
+
)
|
|
100
|
+
return 1
|
|
101
|
+
}
|
|
102
|
+
throw err
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return runScreenshots(appstoreArgs, opts)
|
|
106
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// sootsim screenshot layers — engine-rendered layer isolation export.
|
|
2
|
+
//
|
|
3
|
+
// captures one screen as a stack the 3d stage can pull apart: `full.png` is
|
|
4
|
+
// the normal frame, `base.png` re-renders the frame with the target subtrees
|
|
5
|
+
// hidden (content and glass blur behind them are real, not inpainted), and
|
|
6
|
+
// each `layer-<testID>.png` draws exactly that subtree alone on transparency
|
|
7
|
+
// at its on-screen position. `manifest.json` records device metrics and the
|
|
8
|
+
// logical rect of every layer.
|
|
9
|
+
|
|
10
|
+
import { mkdirSync, writeFileSync } from 'fs'
|
|
11
|
+
import { resolve } from 'path'
|
|
12
|
+
import { devices } from 'sootsim-engine/settings'
|
|
13
|
+
import { buildResolveRectEval, type SampleRect } from '../browser-evals'
|
|
14
|
+
import { callInBridge, createBridgeFromParsed, parseBridgeCliArgs } from '../ws-bridge'
|
|
15
|
+
import { inspectWaitReady, waitReadyReason } from './inspect/core'
|
|
16
|
+
|
|
17
|
+
interface ScreenshotLayersOptions {
|
|
18
|
+
port?: number
|
|
19
|
+
verbose?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function runScreenshotLayers(
|
|
23
|
+
args: string[],
|
|
24
|
+
opts: ScreenshotLayersOptions,
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
27
|
+
console.log(`
|
|
28
|
+
sootsim screenshot layers — export a screen as engine-isolated layers
|
|
29
|
+
|
|
30
|
+
usage:
|
|
31
|
+
sootsim screenshot layers --ids <testID,testID,...> [--out-dir <dir>]
|
|
32
|
+
|
|
33
|
+
options:
|
|
34
|
+
--ids <list> comma-separated testIDs of the subtrees to isolate
|
|
35
|
+
--out-dir <dir> output directory (default: /tmp/sootsim-layers)
|
|
36
|
+
|
|
37
|
+
output:
|
|
38
|
+
full.png the normal frame
|
|
39
|
+
base.png the frame re-rendered with every listed subtree hidden
|
|
40
|
+
layer-<id>.png each subtree rendered alone on a transparent frame
|
|
41
|
+
manifest.json device metrics + logical rect per layer
|
|
42
|
+
`)
|
|
43
|
+
process.exit(0)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const idsArg = args.find((_, i) => args[i - 1] === '--ids')
|
|
47
|
+
if (!idsArg) {
|
|
48
|
+
console.error(' --ids is required (comma-separated testIDs)')
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
const testIds = idsArg
|
|
52
|
+
.split(',')
|
|
53
|
+
.map((entry) => entry.trim())
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
if (testIds.length === 0) {
|
|
56
|
+
console.error(' --ids resolved to an empty list')
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
const outDirArg = args.find((_, i) => args[i - 1] === '--out-dir')
|
|
60
|
+
const outDir = resolve(process.cwd(), outDirArg ?? '/tmp/sootsim-layers')
|
|
61
|
+
|
|
62
|
+
const parsed = parseBridgeCliArgs(args, {
|
|
63
|
+
port: opts.port,
|
|
64
|
+
stripValueFlags: ['--ids', '--out-dir'],
|
|
65
|
+
})
|
|
66
|
+
const bridge = createBridgeFromParsed({ ...parsed, commandTimeoutMs: 45000 })
|
|
67
|
+
try {
|
|
68
|
+
const readiness = await inspectWaitReady(bridge, 90_000)
|
|
69
|
+
if (!readiness.ready) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`app not ready to capture after ${Math.round(readiness.elapsedMs / 1000)}s ` +
|
|
72
|
+
`(${waitReadyReason(readiness)})`,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const rects: Record<string, SampleRect> = {}
|
|
77
|
+
for (const testId of testIds) {
|
|
78
|
+
const node = await bridge.send({
|
|
79
|
+
type: 'evaluate',
|
|
80
|
+
code: buildResolveRectEval({ id: testId }),
|
|
81
|
+
})
|
|
82
|
+
if (!node) throw new Error(`no node with testID "${testId}"`)
|
|
83
|
+
rects[testId] = node as SampleRect
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
mkdirSync(outDir, { recursive: true })
|
|
87
|
+
const savePng = (name: string, dataUrl: unknown) => {
|
|
88
|
+
if (typeof dataUrl !== 'string' || !dataUrl.startsWith('data:image/png;base64,')) {
|
|
89
|
+
throw new Error(`capture for ${name} returned no image data`)
|
|
90
|
+
}
|
|
91
|
+
const path = resolve(outDir, name)
|
|
92
|
+
writeFileSync(
|
|
93
|
+
path,
|
|
94
|
+
Buffer.from(dataUrl.slice('data:image/png;base64,'.length), 'base64'),
|
|
95
|
+
)
|
|
96
|
+
console.log(` saved: ${path}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
savePng('full.png', await bridge.send({ type: 'screenshot' }))
|
|
100
|
+
savePng(
|
|
101
|
+
'base.png',
|
|
102
|
+
await bridge.send({
|
|
103
|
+
type: 'screenshot',
|
|
104
|
+
captureFilter: { hideTestIds: testIds },
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
107
|
+
for (const testId of testIds) {
|
|
108
|
+
// every other listed target hides inside this solo, so nested targets
|
|
109
|
+
// (a button inside a lifted card) land in exactly one layer.
|
|
110
|
+
savePng(
|
|
111
|
+
`layer-${testId}.png`,
|
|
112
|
+
await bridge.send({
|
|
113
|
+
type: 'screenshot',
|
|
114
|
+
captureFilter: {
|
|
115
|
+
soloTestId: testId,
|
|
116
|
+
hideTestIds: testIds.filter((other) => other !== testId),
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const settings = (await callInBridge<Record<string, unknown> | null>(
|
|
123
|
+
bridge,
|
|
124
|
+
'SootSim.bridges.settings.get',
|
|
125
|
+
)) ?? { deviceModel: null }
|
|
126
|
+
const model =
|
|
127
|
+
typeof settings.deviceModel === 'string' && settings.deviceModel in devices
|
|
128
|
+
? settings.deviceModel
|
|
129
|
+
: null
|
|
130
|
+
const device = model ? devices[model as keyof typeof devices] : null
|
|
131
|
+
const manifest = {
|
|
132
|
+
device: device
|
|
133
|
+
? { model, width: device.width, height: device.height, scale: device.scale }
|
|
134
|
+
: { model },
|
|
135
|
+
layers: testIds.map((testId) => ({ testId, rect: rects[testId] })),
|
|
136
|
+
}
|
|
137
|
+
const manifestPath = resolve(outDir, 'manifest.json')
|
|
138
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
|
|
139
|
+
console.log(` saved: ${manifestPath}`)
|
|
140
|
+
} finally {
|
|
141
|
+
bridge.close()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// sootsim mode screenshot — toggle the "screenshot mode" chrome overlay
|
|
2
|
+
// in the running sootsim sim. orthogonal to `--no-shell` on the
|
|
3
|
+
// `screenshot` command: this hides the DOM browser chrome (rail, menubar,
|
|
4
|
+
// window controls) while still rendering the iOS shell inside the canvas.
|
|
5
|
+
//
|
|
6
|
+
// wraps the existing `toggle-screenshot-mode` shell action so the CLI
|
|
7
|
+
// stays behavior-compatible with the menu item / keyboard shortcut.
|
|
8
|
+
|
|
9
|
+
import { runShellBooleanMode, type ShellBooleanModeOptions } from './shell-boolean-mode'
|
|
10
|
+
|
|
11
|
+
export async function runScreenshotMode(args: string[], opts: ShellBooleanModeOptions) {
|
|
12
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
13
|
+
console.log(`
|
|
14
|
+
rnxsim mode screenshot — toggle the screenshot-mode chrome overlay
|
|
15
|
+
|
|
16
|
+
usage:
|
|
17
|
+
rnxsim mode screenshot [on|off|toggle]
|
|
18
|
+
|
|
19
|
+
arguments:
|
|
20
|
+
on enable screenshot mode (enables showFrame if needed)
|
|
21
|
+
off disable screenshot mode
|
|
22
|
+
toggle flip the current state (default)
|
|
23
|
+
|
|
24
|
+
examples:
|
|
25
|
+
rnxsim mode screenshot
|
|
26
|
+
rnxsim mode screenshot on
|
|
27
|
+
rnxsim mode screenshot off
|
|
28
|
+
`)
|
|
29
|
+
process.exit(0)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await runShellBooleanMode(args, opts, {
|
|
33
|
+
modeKey: 'screenshotMode',
|
|
34
|
+
displayName: 'screenshot-mode',
|
|
35
|
+
actionId: 'toggle-screenshot-mode',
|
|
36
|
+
})
|
|
37
|
+
}
|