rnxsim 0.1.313 → 0.1.314
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-7YBDCYMA.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-2GHFBHCR.js} +2 -2
- package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-RSNVPQSU.js} +2 -2
- package/dist-cli/chunks/{assert-54T5SK5F.js → assert-XCMX3XJX.js} +2 -2
- package/dist-cli/chunks/{auth-FI5UDI45.js → auth-B442HRAX.js} +2 -2
- package/dist-cli/chunks/{beta-JV6UKADW.js → beta-XJ55JK3M.js} +2 -2
- package/dist-cli/chunks/camera-UGYSSLIK.js +33 -0
- package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-277AEQZX.js} +2 -2
- package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-2JNSK774.js} +2 -2
- package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-32WOTSTR.js} +3 -3
- package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-3S753SNQ.js} +2 -2
- package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-46ZOLOYA.js} +2 -2
- package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-5L7ELDQL.js} +8 -9
- package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-7OOPFSQS.js} +2 -2
- package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-7SV3RPRW.js} +2 -2
- package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-7ZC35MOU.js} +1 -1
- package/dist-cli/chunks/chunk-AMG5E6CC.js +9 -0
- package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-APWNH3A4.js} +1 -1
- package/dist-cli/chunks/chunk-B57XUKY3.js +4 -0
- package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-C2NL26TD.js} +1 -1
- package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-D2FNUWAB.js} +4 -4
- package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-E5T4XSJ3.js} +2 -2
- package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-EAC34EQS.js} +1 -1
- package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-EG32ML36.js} +2 -2
- package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-FXUAC6D5.js} +1 -1
- package/dist-cli/chunks/chunk-GQSL4USA.js +6 -0
- package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-HAXW27SS.js} +2 -2
- package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-IZAHPAN6.js} +1 -1
- package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-J62KM5TB.js} +2 -2
- package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-JEMCD5E4.js} +1 -1
- package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-JZS3Q37N.js} +2 -2
- package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-KR4JON7D.js} +1 -1
- package/dist-cli/chunks/chunk-KWCYKQIQ.js +4 -0
- package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-MCWPL644.js} +2 -2
- package/dist-cli/chunks/chunk-MJYK3N2I.js +4 -0
- package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-O6TRIZNS.js} +2 -2
- package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-P7XL2E73.js} +3 -3
- package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-PFQTUKQ4.js} +62 -87
- package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-PG5RZCTN.js} +2 -2
- package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-QGRI2Z4M.js} +2 -2
- package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-QLXXE7GE.js} +1 -1
- package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-QOJJJWJE.js} +89 -133
- package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-RWZY5427.js} +2 -2
- package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RZKU2K3J.js} +2 -2
- package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-SBV4IK4H.js} +1 -1
- package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-SGMVFFMK.js} +1 -1
- package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-TAX4UT2N.js} +1 -1
- package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-TUOFAWXT.js} +1 -1
- package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-UHZLOHGP.js} +1 -1
- package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-VC7V76U3.js} +1 -1
- package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-VQVMLW4U.js} +1 -1
- package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-VUKKYPZN.js} +2 -2
- package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-WGGRJDRE.js} +1 -1
- package/dist-cli/chunks/chunk-X6H76EKP.js +15 -0
- package/dist-cli/chunks/chunk-XLZ5FNRT.js +27 -0
- package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-XULEACM4.js} +2 -2
- package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-YFSDM7AX.js} +4 -4
- package/dist-cli/chunks/chunk-YWI3UEVX.js +5 -0
- package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-ZBSJO4NB.js} +10 -9
- package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-EYLGCA6Z.js} +2 -2
- package/dist-cli/chunks/cli-version-LL2UGIHE.js +4 -0
- package/dist-cli/chunks/{compat-ZD65FED3.js → compat-TLJYHB4E.js} +2 -2
- package/dist-cli/chunks/{config-XMJRNM2A.js → config-4JWUOEXK.js} +2 -2
- package/dist-cli/chunks/{control-KMIQT3QP.js → control-HPAOYF4N.js} +2 -2
- package/dist-cli/chunks/daemon-OYLASXLE.js +4 -0
- package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-4BKXF6KI.js} +5 -5
- package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-ZQ6ZD2S6.js} +3 -3
- package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-WNPASSS3.js} +2 -2
- package/dist-cli/chunks/{device-XBNDSB2R.js → device-BXLXVG3V.js} +2 -2
- package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-3QX7W5RW.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KSWKI7WB.js} +2 -2
- package/dist-cli/chunks/drivers-EPIEFF7P.js +4 -0
- package/dist-cli/chunks/{film-BJGTBYZB.js → film-GDMR33VO.js} +3 -3
- package/dist-cli/chunks/flow-NKMPBYCJ.js +4 -0
- package/dist-cli/chunks/help-OGCBHOKA.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-S2GTDX3T.js} +2 -2
- package/dist-cli/chunks/home-paths-QRCDLTTV.js +4 -0
- package/dist-cli/chunks/inspect-FUYMOZPY.js +4 -0
- package/dist-cli/chunks/install-desktop-FR6YKW7Y.js +4 -0
- package/dist-cli/chunks/{login-FJ737MWG.js → login-IJAPUZHI.js} +4 -4
- package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-QMXDFU2X.js} +2 -2
- package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-ZXU3YCVX.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-PYB5QY7K.js} +3 -3
- package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-WGUCL5FZ.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-WH2O6H36.js} +2 -2
- package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-TOD3UFAH.js} +2 -2
- package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-I5BRJGTB.js} +2 -2
- package/dist-cli/chunks/{record-QPPC2S4E.js → record-ZYL2FYSK.js} +3 -3
- package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-TAI6DYZO.js} +2 -2
- package/dist-cli/chunks/reset-7YGYKQPQ.js +4 -0
- package/dist-cli/chunks/runtime-B4JO6QRI.js +4 -0
- package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7ANLODZY.js} +7 -7
- package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-A7FYXSVU.js} +3 -3
- package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-WT2ZY6CB.js} +2 -2
- package/dist-cli/chunks/serve-TG5WKA4V.js +44 -0
- package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-PCWLD22V.js} +2 -2
- package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-27ZHWCQS.js} +2 -2
- package/dist-cli/chunks/state-ZQVY46ZO.js +14 -0
- package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-OYC57C4X.js} +6 -6
- package/dist-cli/chunks/store-32HSPZHI.js +4 -0
- package/dist-cli/chunks/telemetry-PBJR7XHR.js +4 -0
- package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-RAJJFQT6.js} +2 -2
- package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-OEFNZRIP.js} +2 -2
- package/dist-cli/chunks/upload-LQJITLKK.js +4 -0
- package/dist-cli/chunks/version-NIXTY6PL.js +6 -0
- package/dist-cli/chunks/{web-DG3WBYD3.js → web-XBUTBVGR.js} +2 -2
- package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-YNHRFUDX.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,589 @@
|
|
|
1
|
+
// sootsim get diagnosis — composed read commands for "what just went wrong?"
|
|
2
|
+
//
|
|
3
|
+
// the demo-friendly compact diagnostic surface called out in
|
|
4
|
+
// plans/expensify-sootsim-agent-device-demo.md. one command, one round
|
|
5
|
+
// of bridge calls, one paste-able output that beats the agent-device
|
|
6
|
+
// sed/grep workflow in both speed and information density.
|
|
7
|
+
//
|
|
8
|
+
// usage:
|
|
9
|
+
// sootsim get diagnosis [--since 90s] [--include react,network,console,screen,fetch,alert]
|
|
10
|
+
// sootsim get diagnosis --json
|
|
11
|
+
//
|
|
12
|
+
// the output is grouped by source (timeline, network, react, console)
|
|
13
|
+
// rather than chronological. for chronological order use
|
|
14
|
+
// `sootsim what-happened` directly. `recent` is the only subcommand
|
|
15
|
+
// today; `diagnose <something-else>` may be added later (e.g. boot,
|
|
16
|
+
// scrolling-jank, idle-burn).
|
|
17
|
+
//
|
|
18
|
+
// design note: every dimension here is a single bridge call against an
|
|
19
|
+
// existing primitive. this command exists to remove the agent's tax of
|
|
20
|
+
// chaining commands and reformatting four output styles, not to add a
|
|
21
|
+
// new data source. when a new data source becomes worth surfacing in a
|
|
22
|
+
// "diagnose" pass, add it as one more parallel call here.
|
|
23
|
+
|
|
24
|
+
import { getCliIdentityKey } from '../current-sim'
|
|
25
|
+
import {
|
|
26
|
+
callInBridge,
|
|
27
|
+
createBridgeFromParsed,
|
|
28
|
+
parseBridgeCliArgs,
|
|
29
|
+
type WsBridge,
|
|
30
|
+
} from '../ws-bridge'
|
|
31
|
+
import type {
|
|
32
|
+
SootSimTimelineEvent,
|
|
33
|
+
SootSimTimelineQuery,
|
|
34
|
+
SootSimTimelineQueryResult,
|
|
35
|
+
} from '@sootsim/globals'
|
|
36
|
+
|
|
37
|
+
interface DiagnoseOptions {
|
|
38
|
+
port?: number
|
|
39
|
+
verbose?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface NetworkEntry {
|
|
43
|
+
id: string
|
|
44
|
+
url: string
|
|
45
|
+
displayUrl?: string
|
|
46
|
+
method?: string
|
|
47
|
+
status?: number | null
|
|
48
|
+
durationMs?: number | null
|
|
49
|
+
error?: string | null
|
|
50
|
+
// observability store uses startTs (epoch ms) as the entry's start
|
|
51
|
+
// time. accept either name for forward-compat with any future
|
|
52
|
+
// rename.
|
|
53
|
+
startTs?: number
|
|
54
|
+
startedAt?: number
|
|
55
|
+
responseAt?: number | null
|
|
56
|
+
responseSize?: number | null
|
|
57
|
+
size?: number | null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SlowRow {
|
|
61
|
+
displayName: string
|
|
62
|
+
totalMs: number
|
|
63
|
+
avgMs: number
|
|
64
|
+
commitCount: number
|
|
65
|
+
slowestCommit: number
|
|
66
|
+
slowestMs: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface RerenderRow {
|
|
70
|
+
displayName: string
|
|
71
|
+
count?: number
|
|
72
|
+
renders?: number
|
|
73
|
+
causes?: Record<string, number>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface LogEntry {
|
|
77
|
+
level: string
|
|
78
|
+
// observability ring uses `ts` (epoch ms) and `args` (string[]).
|
|
79
|
+
// older shapes had `t` + `message`. accept both.
|
|
80
|
+
ts?: number
|
|
81
|
+
t?: number
|
|
82
|
+
args?: unknown[]
|
|
83
|
+
message?: string
|
|
84
|
+
source?: string
|
|
85
|
+
context?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function logTs(e: LogEntry): number {
|
|
89
|
+
return e.ts ?? e.t ?? 0
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function logMessage(e: LogEntry): string {
|
|
93
|
+
if (typeof e.message === 'string' && e.message.length > 0) return e.message
|
|
94
|
+
if (Array.isArray(e.args)) {
|
|
95
|
+
return e.args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')
|
|
96
|
+
}
|
|
97
|
+
return ''
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const DEFAULT_INCLUDES = new Set(['timeline', 'network', 'react', 'console', 'screen'])
|
|
101
|
+
|
|
102
|
+
function printHelp(): void {
|
|
103
|
+
console.log(`
|
|
104
|
+
rnxsim get diagnosis — compact "what just went wrong?" report
|
|
105
|
+
|
|
106
|
+
usage:
|
|
107
|
+
rnxsim get diagnosis [--since <duration>] [--include <csv>] [--json]
|
|
108
|
+
|
|
109
|
+
options:
|
|
110
|
+
--since <d> absolute window for timeline + network (e.g. 90s, 2m, 500ms; default 90s)
|
|
111
|
+
--include <csv> comma-separated dimensions: timeline, network, react, console, screen
|
|
112
|
+
(default: all)
|
|
113
|
+
--slow-threshold <n> network slow-request threshold in ms (default 1000)
|
|
114
|
+
--limit <n> rows to print per react table (default 8)
|
|
115
|
+
--json emit a single structured JSON document
|
|
116
|
+
|
|
117
|
+
the diagnose report runs every dimension in parallel against the same
|
|
118
|
+
sim and returns a grouped summary. use \`rnxsim what-happened\`
|
|
119
|
+
for chronological events, \`rnxsim network\` for full request inspection,
|
|
120
|
+
\`rnxsim perf react summary\` for the React tables on their own.
|
|
121
|
+
|
|
122
|
+
examples:
|
|
123
|
+
rnxsim get diagnosis
|
|
124
|
+
rnxsim get diagnosis --since 30s --include network,react
|
|
125
|
+
rnxsim get diagnosis --json > /tmp/diagnosis.json
|
|
126
|
+
`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function parseSinceMs(value: string | undefined, fallback: number): number {
|
|
130
|
+
if (!value) return fallback
|
|
131
|
+
const m = /^(\d+(?:\.\d+)?)(ms|s|m)?$/.exec(value.trim())
|
|
132
|
+
if (!m) return fallback
|
|
133
|
+
const n = Number(m[1])
|
|
134
|
+
const unit = m[2] ?? 'ms'
|
|
135
|
+
return unit === 's' ? n * 1000 : unit === 'm' ? n * 60_000 : n
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseCsv(value: string | undefined): Set<string> {
|
|
139
|
+
if (!value) return DEFAULT_INCLUDES
|
|
140
|
+
const out = new Set<string>()
|
|
141
|
+
for (const raw of value.split(',')) {
|
|
142
|
+
const trimmed = raw.trim().toLowerCase()
|
|
143
|
+
if (trimmed) out.add(trimmed)
|
|
144
|
+
}
|
|
145
|
+
return out
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function valueOf(args: string[], flag: string): string | undefined {
|
|
149
|
+
const idx = args.indexOf(flag)
|
|
150
|
+
if (idx >= 0 && idx + 1 < args.length) return args[idx + 1]
|
|
151
|
+
const prefix = `${flag}=`
|
|
152
|
+
return args.find((a) => a.startsWith(prefix))?.slice(prefix.length)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function numberFlag(args: string[], flag: string, fallback: number): number {
|
|
156
|
+
const raw = valueOf(args, flag)
|
|
157
|
+
if (raw === undefined) return fallback
|
|
158
|
+
const n = Number(raw)
|
|
159
|
+
return Number.isFinite(n) && n > 0 ? Math.round(n) : fallback
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface TimelineSlice {
|
|
163
|
+
events: SootSimTimelineEvent[]
|
|
164
|
+
byKind: Record<string, number>
|
|
165
|
+
total: number
|
|
166
|
+
errors: SootSimTimelineEvent[]
|
|
167
|
+
alerts: SootSimTimelineEvent[]
|
|
168
|
+
screens: SootSimTimelineEvent[]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function fetchTimeline(bridge: WsBridge, sinceMs: number): Promise<TimelineSlice> {
|
|
172
|
+
const query: SootSimTimelineQuery = {
|
|
173
|
+
limit: 500,
|
|
174
|
+
since: Date.now() - sinceMs,
|
|
175
|
+
}
|
|
176
|
+
const result = await callInBridge<SootSimTimelineQueryResult>(
|
|
177
|
+
bridge,
|
|
178
|
+
'SootSim.bridges.timeline.recent',
|
|
179
|
+
query,
|
|
180
|
+
)
|
|
181
|
+
const events = result.events ?? []
|
|
182
|
+
const byKind: Record<string, number> = {}
|
|
183
|
+
const errors: SootSimTimelineEvent[] = []
|
|
184
|
+
const alerts: SootSimTimelineEvent[] = []
|
|
185
|
+
const screens: SootSimTimelineEvent[] = []
|
|
186
|
+
for (const ev of events) {
|
|
187
|
+
byKind[ev.kind] = (byKind[ev.kind] ?? 0) + 1
|
|
188
|
+
const data = ev.data as Record<string, unknown> | null
|
|
189
|
+
const level = data && typeof data === 'object' ? (data.level as string) : undefined
|
|
190
|
+
if (ev.kind === 'console' && (level === 'error' || level === 'warn')) {
|
|
191
|
+
errors.push(ev)
|
|
192
|
+
}
|
|
193
|
+
if (ev.kind === 'alert' || ev.kind === 'actionsheet' || ev.kind === 'picker') {
|
|
194
|
+
alerts.push(ev)
|
|
195
|
+
}
|
|
196
|
+
if (ev.kind === 'screen' || ev.kind === 'route') {
|
|
197
|
+
screens.push(ev)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
events,
|
|
202
|
+
byKind,
|
|
203
|
+
total: events.length,
|
|
204
|
+
errors,
|
|
205
|
+
alerts,
|
|
206
|
+
screens,
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
interface NetworkSlice {
|
|
211
|
+
total: number
|
|
212
|
+
failed: NetworkEntry[]
|
|
213
|
+
slow: NetworkEntry[]
|
|
214
|
+
inFlight: number
|
|
215
|
+
slowThresholdMs: number
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function fetchNetwork(
|
|
219
|
+
bridge: WsBridge,
|
|
220
|
+
sinceMs: number,
|
|
221
|
+
slowThresholdMs: number,
|
|
222
|
+
): Promise<NetworkSlice | { error: string }> {
|
|
223
|
+
// re-uses the observability store the existing `network` command
|
|
224
|
+
// reads from. local filtering keeps this command independent of any
|
|
225
|
+
// new bridge surface and lets it run against today's engine.
|
|
226
|
+
const result = (await bridge.send({
|
|
227
|
+
type: 'evaluate',
|
|
228
|
+
code: `(() => {
|
|
229
|
+
const obs = window.__sootsimObservability;
|
|
230
|
+
if (!obs) return { ok: false };
|
|
231
|
+
return { ok: true, entries: obs.network.getSnapshot() };
|
|
232
|
+
})()`,
|
|
233
|
+
})) as { ok: boolean; entries?: NetworkEntry[] }
|
|
234
|
+
if (!result || !result.ok) {
|
|
235
|
+
return { error: 'observability bridge not installed' }
|
|
236
|
+
}
|
|
237
|
+
const all = result.entries ?? []
|
|
238
|
+
const cutoff = Date.now() - sinceMs
|
|
239
|
+
// observability ring uses startTs; older shapes used startedAt. accept
|
|
240
|
+
// both so this command works against both shapes without a feature
|
|
241
|
+
// detection round-trip.
|
|
242
|
+
const inWindow = all.filter((e) => (e.startTs ?? e.startedAt ?? 0) >= cutoff)
|
|
243
|
+
const failed = inWindow.filter(
|
|
244
|
+
(e) => !!e.error || (e.status != null && e.status >= 400),
|
|
245
|
+
)
|
|
246
|
+
const slow = inWindow
|
|
247
|
+
.filter((e) => e.durationMs != null && e.durationMs >= slowThresholdMs)
|
|
248
|
+
.sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0))
|
|
249
|
+
const inFlight = inWindow.filter((e) => e.durationMs == null).length
|
|
250
|
+
return {
|
|
251
|
+
total: inWindow.length,
|
|
252
|
+
failed,
|
|
253
|
+
slow,
|
|
254
|
+
inFlight,
|
|
255
|
+
slowThresholdMs,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
interface ReactSlice {
|
|
260
|
+
commits: number
|
|
261
|
+
slow: SlowRow[]
|
|
262
|
+
rerenders: RerenderRow[]
|
|
263
|
+
durationWarning?: string
|
|
264
|
+
error?: string
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function fetchReact(bridge: WsBridge, limit: number): Promise<ReactSlice> {
|
|
268
|
+
const [slowResult, rerendersResult] = (await Promise.all([
|
|
269
|
+
callInBridge(bridge, 'SootSim.bridges.reactProfile.slow', { limit }),
|
|
270
|
+
callInBridge(bridge, 'SootSim.bridges.reactProfile.rerenders', { limit }),
|
|
271
|
+
])) as [
|
|
272
|
+
{
|
|
273
|
+
rows?: SlowRow[]
|
|
274
|
+
commits?: number
|
|
275
|
+
error?: string
|
|
276
|
+
durationWarning?: string
|
|
277
|
+
},
|
|
278
|
+
{ rows?: RerenderRow[]; commits?: number; error?: string },
|
|
279
|
+
]
|
|
280
|
+
return {
|
|
281
|
+
commits: slowResult.commits ?? rerendersResult.commits ?? 0,
|
|
282
|
+
slow: slowResult.rows ?? [],
|
|
283
|
+
rerenders: rerendersResult.rows ?? [],
|
|
284
|
+
durationWarning: slowResult.durationWarning,
|
|
285
|
+
error: slowResult.error ?? rerendersResult.error,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function fetchConsoleErrors(
|
|
290
|
+
bridge: WsBridge,
|
|
291
|
+
sinceMs: number,
|
|
292
|
+
): Promise<{ entries: LogEntry[]; total: number } | { error: string }> {
|
|
293
|
+
const result = (await bridge.send({
|
|
294
|
+
type: 'evaluate',
|
|
295
|
+
code: `(() => {
|
|
296
|
+
const obs = window.__sootsimObservability;
|
|
297
|
+
if (!obs) return { ok: false };
|
|
298
|
+
return { ok: true, entries: obs.logs.getSnapshot() };
|
|
299
|
+
})()`,
|
|
300
|
+
})) as { ok: boolean; entries?: LogEntry[] }
|
|
301
|
+
if (!result || !result.ok) {
|
|
302
|
+
return { error: 'observability bridge not installed' }
|
|
303
|
+
}
|
|
304
|
+
const cutoff = Date.now() - sinceMs
|
|
305
|
+
const all = result.entries ?? []
|
|
306
|
+
const errs = all.filter(
|
|
307
|
+
(e) => logTs(e) >= cutoff && (e.level === 'error' || e.level === 'warn'),
|
|
308
|
+
)
|
|
309
|
+
return { entries: errs.slice(-20), total: errs.length }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function formatNetworkRow(entry: NetworkEntry): string {
|
|
313
|
+
const status = entry.error ? 'err' : entry.status != null ? String(entry.status) : '...'
|
|
314
|
+
const method = (entry.method ?? 'GET').padEnd(6)
|
|
315
|
+
const dur = entry.durationMs != null ? `${entry.durationMs.toFixed(0)}ms` : '...'
|
|
316
|
+
const url = entry.displayUrl ?? entry.url
|
|
317
|
+
// shorten long urls to 96 chars in the diagnose report — full
|
|
318
|
+
// detail is one `sootsim network get <id>` away.
|
|
319
|
+
const shortUrl = url.length > 96 ? `${url.slice(0, 92)}…` : url
|
|
320
|
+
return ` ${status.padStart(3)} ${method} ${dur.padStart(7)} ${shortUrl} (${entry.id})`
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function formatTimelineRow(ev: SootSimTimelineEvent, anchorMs: number): string {
|
|
324
|
+
const dt = ((ev.t - anchorMs) / 1000).toFixed(2)
|
|
325
|
+
const sign = ev.t >= anchorMs ? '-' : '+'
|
|
326
|
+
const data = ev.data as Record<string, unknown> | null
|
|
327
|
+
let payload = ''
|
|
328
|
+
if (data && typeof data === 'object') {
|
|
329
|
+
const message = (data.message ?? '').toString().slice(0, 120)
|
|
330
|
+
const title = (data.title ?? data.name ?? data.path ?? data.url ?? '').toString()
|
|
331
|
+
const phase = (data.phase ?? '').toString()
|
|
332
|
+
payload = [phase, title, message].filter(Boolean).join(' · ')
|
|
333
|
+
}
|
|
334
|
+
return ` -${dt.padStart(5)}s [${ev.kind}] ${payload}`
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function formatTable(headers: string[], rows: string[][], indent: string): string {
|
|
338
|
+
if (rows.length === 0) return `${indent}(no rows)`
|
|
339
|
+
const widths = headers.map((h, i) =>
|
|
340
|
+
Math.max(h.length, ...rows.map((row) => row[i]?.length ?? 0)),
|
|
341
|
+
)
|
|
342
|
+
const line = (cells: string[]) =>
|
|
343
|
+
cells
|
|
344
|
+
.map((cell, i) => cell.padEnd(widths[i]))
|
|
345
|
+
.join(' ')
|
|
346
|
+
.trimEnd()
|
|
347
|
+
return [
|
|
348
|
+
line(headers),
|
|
349
|
+
line(headers.map((h) => '-'.repeat(h.length))),
|
|
350
|
+
...rows.map(line),
|
|
351
|
+
]
|
|
352
|
+
.map((row) => `${indent}${row}`)
|
|
353
|
+
.join('\n')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export async function runDiagnose(
|
|
357
|
+
args: string[],
|
|
358
|
+
opts: DiagnoseOptions,
|
|
359
|
+
): Promise<number> {
|
|
360
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
361
|
+
printHelp()
|
|
362
|
+
return 0
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const parsed = parseBridgeCliArgs(args, {
|
|
366
|
+
port: opts.port,
|
|
367
|
+
stripBooleanFlags: ['--json', '--help', '-h'],
|
|
368
|
+
stripValueFlags: ['--since', '--include', '--limit', '--slow-threshold'],
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
const sub = parsed.positional[0] ?? 'recent'
|
|
372
|
+
if (sub !== 'recent') {
|
|
373
|
+
console.error(` unknown subcommand: ${sub}`)
|
|
374
|
+
console.error(' did you mean: rnxsim get diagnosis ?')
|
|
375
|
+
return 1
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const sinceMs = parseSinceMs(valueOf(args, '--since'), 90_000)
|
|
379
|
+
const include = parseCsv(valueOf(args, '--include'))
|
|
380
|
+
const reactLimit = numberFlag(args, '--limit', 8)
|
|
381
|
+
const slowThresholdMs = numberFlag(args, '--slow-threshold', 1000)
|
|
382
|
+
const wantsJson = args.includes('--json')
|
|
383
|
+
|
|
384
|
+
const bridge = createBridgeFromParsed(parsed)
|
|
385
|
+
try {
|
|
386
|
+
// run every requested dimension in parallel — one bridge round-trip
|
|
387
|
+
// per primitive, no chained CLI invocations. on a healthy sim
|
|
388
|
+
// this finishes in a single tick.
|
|
389
|
+
const tasks: Array<Promise<unknown>> = []
|
|
390
|
+
const ordered: string[] = []
|
|
391
|
+
if (include.has('timeline') || include.has('screen') || include.has('alert')) {
|
|
392
|
+
ordered.push('timeline')
|
|
393
|
+
tasks.push(fetchTimeline(bridge, sinceMs))
|
|
394
|
+
}
|
|
395
|
+
if (include.has('network') || include.has('fetch')) {
|
|
396
|
+
ordered.push('network')
|
|
397
|
+
tasks.push(fetchNetwork(bridge, sinceMs, slowThresholdMs))
|
|
398
|
+
}
|
|
399
|
+
if (include.has('react')) {
|
|
400
|
+
ordered.push('react')
|
|
401
|
+
tasks.push(fetchReact(bridge, reactLimit))
|
|
402
|
+
}
|
|
403
|
+
if (include.has('console')) {
|
|
404
|
+
ordered.push('console')
|
|
405
|
+
tasks.push(fetchConsoleErrors(bridge, sinceMs))
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// always probe the engine build id (one cheap eval). a reused sim
|
|
409
|
+
// session on a stale engine silently produced false QA verdicts
|
|
410
|
+
// (QA F20-5); surfacing the build here makes the staleness checkable.
|
|
411
|
+
const engineBuild = await bridge
|
|
412
|
+
.send({
|
|
413
|
+
type: 'evaluate',
|
|
414
|
+
code: `globalThis.__sootsimEngineBuild ?? null`,
|
|
415
|
+
})
|
|
416
|
+
.then((v) => (typeof v === 'string' ? v : null))
|
|
417
|
+
.catch(() => null)
|
|
418
|
+
|
|
419
|
+
const results = await Promise.all(tasks)
|
|
420
|
+
const slices: Record<string, unknown> = {}
|
|
421
|
+
for (let i = 0; i < ordered.length; i++) slices[ordered[i]] = results[i]
|
|
422
|
+
|
|
423
|
+
if (wantsJson) {
|
|
424
|
+
console.log(
|
|
425
|
+
JSON.stringify(
|
|
426
|
+
{
|
|
427
|
+
generatedAt: Date.now(),
|
|
428
|
+
engineBuild,
|
|
429
|
+
sinceMs,
|
|
430
|
+
include: Array.from(include),
|
|
431
|
+
slices,
|
|
432
|
+
},
|
|
433
|
+
null,
|
|
434
|
+
2,
|
|
435
|
+
),
|
|
436
|
+
)
|
|
437
|
+
return 0
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// human render — one section per slice. terse, scannable, demo-paste-friendly.
|
|
441
|
+
const now = Date.now()
|
|
442
|
+
console.log(
|
|
443
|
+
` diagnose recent (last ${(sinceMs / 1000).toFixed(0)}s, include: ${Array.from(include).sort().join(',')}):`,
|
|
444
|
+
)
|
|
445
|
+
console.log(
|
|
446
|
+
` engine build: ${engineBuild ?? 'unknown (no build stamp — likely a stale or pre-F20-5 engine)'}`,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
if (slices.timeline) {
|
|
450
|
+
const t = slices.timeline as TimelineSlice
|
|
451
|
+
console.log(`\n timeline — ${t.total} event(s) in window`)
|
|
452
|
+
const summary = Object.entries(t.byKind)
|
|
453
|
+
.sort((a, b) => b[1] - a[1])
|
|
454
|
+
.map(([k, n]) => `${n} ${k}`)
|
|
455
|
+
.join(' · ')
|
|
456
|
+
if (summary) console.log(` summary: ${summary}`)
|
|
457
|
+
if (t.screens.length) {
|
|
458
|
+
const last = t.screens[t.screens.length - 1]
|
|
459
|
+
const data = last.data as Record<string, unknown> | null
|
|
460
|
+
const name = data ? (data.name ?? data.activeName ?? data.path ?? '?') : '?'
|
|
461
|
+
console.log(` last screen: ${String(name)}`)
|
|
462
|
+
}
|
|
463
|
+
if (t.alerts.length) {
|
|
464
|
+
console.log(` alerts (${t.alerts.length}):`)
|
|
465
|
+
for (const a of t.alerts.slice(-5)) {
|
|
466
|
+
console.log(formatTimelineRow(a, now))
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (t.errors.length) {
|
|
470
|
+
console.log(` console errors/warns from timeline (${t.errors.length}):`)
|
|
471
|
+
for (const e of t.errors.slice(-5)) {
|
|
472
|
+
console.log(formatTimelineRow(e, now))
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (slices.network) {
|
|
478
|
+
const n = slices.network as NetworkSlice | { error: string }
|
|
479
|
+
if ('error' in n) {
|
|
480
|
+
console.log(`\n network — ${n.error}`)
|
|
481
|
+
} else {
|
|
482
|
+
console.log(
|
|
483
|
+
`\n network — ${n.total} request(s), ${n.failed.length} failed, ${n.slow.length} slow (>${n.slowThresholdMs}ms), ${n.inFlight} in-flight`,
|
|
484
|
+
)
|
|
485
|
+
if (n.failed.length) {
|
|
486
|
+
console.log(` failed:`)
|
|
487
|
+
for (const e of n.failed.slice(-8)) console.log(formatNetworkRow(e))
|
|
488
|
+
}
|
|
489
|
+
if (n.slow.length) {
|
|
490
|
+
console.log(` slowest:`)
|
|
491
|
+
for (const e of n.slow.slice(0, 5)) console.log(formatNetworkRow(e))
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (slices.react) {
|
|
497
|
+
const r = slices.react as ReactSlice
|
|
498
|
+
console.log(`\n react — ${r.commits} commit(s)`)
|
|
499
|
+
if (r.error) console.log(` warning: ${r.error}`)
|
|
500
|
+
if (r.durationWarning) console.log(` warning: ${r.durationWarning}`)
|
|
501
|
+
const allZero =
|
|
502
|
+
r.slow.length > 0 && r.slow.every((row) => row.totalMs <= 0 && row.slowestMs <= 0)
|
|
503
|
+
if (allZero && !r.durationWarning) {
|
|
504
|
+
console.log(
|
|
505
|
+
` warning: every commit reports actualDuration=0ms — bundle has React profiler timer disabled`,
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
if (r.slow.length) {
|
|
509
|
+
console.log(' slowest:')
|
|
510
|
+
console.log(
|
|
511
|
+
formatTable(
|
|
512
|
+
['component', 'total', 'commits', 'slowest'],
|
|
513
|
+
r.slow
|
|
514
|
+
.slice(0, reactLimit)
|
|
515
|
+
.map((row) => [
|
|
516
|
+
row.displayName,
|
|
517
|
+
`${row.totalMs.toFixed(2)}ms`,
|
|
518
|
+
String(row.commitCount),
|
|
519
|
+
`${row.slowestMs.toFixed(2)}ms`,
|
|
520
|
+
]),
|
|
521
|
+
' ',
|
|
522
|
+
),
|
|
523
|
+
)
|
|
524
|
+
}
|
|
525
|
+
if (r.rerenders.length) {
|
|
526
|
+
console.log(' most rerenders:')
|
|
527
|
+
console.log(
|
|
528
|
+
formatTable(
|
|
529
|
+
['component', 'renders', 'top causes'],
|
|
530
|
+
r.rerenders.slice(0, reactLimit).map((row) => {
|
|
531
|
+
const causes = Object.entries(row.causes ?? {})
|
|
532
|
+
.filter(([, v]) => v > 0)
|
|
533
|
+
.sort((a, b) => b[1] - a[1])
|
|
534
|
+
.slice(0, 3)
|
|
535
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
536
|
+
.join(', ')
|
|
537
|
+
return [
|
|
538
|
+
row.displayName,
|
|
539
|
+
String(row.renders ?? row.count ?? 0),
|
|
540
|
+
causes || '-',
|
|
541
|
+
]
|
|
542
|
+
}),
|
|
543
|
+
' ',
|
|
544
|
+
),
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (slices.console) {
|
|
550
|
+
const c = slices.console as
|
|
551
|
+
| { entries: LogEntry[]; total: number }
|
|
552
|
+
| { error: string }
|
|
553
|
+
if ('error' in c) {
|
|
554
|
+
console.log(`\n console — ${c.error}`)
|
|
555
|
+
} else {
|
|
556
|
+
console.log(`\n console errors/warns — ${c.total} in window`)
|
|
557
|
+
for (const log of c.entries.slice(-8)) {
|
|
558
|
+
const dt = ((logTs(log) - now) / 1000).toFixed(2)
|
|
559
|
+
const msg = logMessage(log).slice(0, 160)
|
|
560
|
+
console.log(` -${dt.padStart(5)}s [${log.level}] ${msg}`)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// close with the per-identity cursor advance hint, mirroring
|
|
566
|
+
// what-happened's footer.
|
|
567
|
+
const identityKey = getCliIdentityKey()
|
|
568
|
+
if (identityKey) {
|
|
569
|
+
console.log(
|
|
570
|
+
`\n next: rnxsim what-happened (cursor advances) · rnxsim network get <id> · rnxsim perf react why <fiber-id>`,
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
return 0
|
|
574
|
+
} catch (err) {
|
|
575
|
+
// a bridge call rejecting (most commonly "no sim connected") must not
|
|
576
|
+
// escape as a raw stack trace with a 0 exit code — `get diagnosis` is the
|
|
577
|
+
// "what just went wrong" command; failing it should read cleanly and
|
|
578
|
+
// exit non-zero so `sootsim get diagnosis && …` chains behave.
|
|
579
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
580
|
+
if (/no sim connected/i.test(msg)) {
|
|
581
|
+
console.error(' no sim connected — open one first: rnxsim open <metroPort>')
|
|
582
|
+
} else {
|
|
583
|
+
console.error(` diagnose failed: ${msg}`)
|
|
584
|
+
}
|
|
585
|
+
return 1
|
|
586
|
+
} finally {
|
|
587
|
+
bridge.close()
|
|
588
|
+
}
|
|
589
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// sootsim desktop — launch the installed desktop companion via the
|
|
2
|
+
// electron driver. the driver wraps findDesktopCompanion +
|
|
3
|
+
// launchDesktopCompanion, so this command is a thin CLI surface that
|
|
4
|
+
// parses options, prompts for install, and delegates everything else.
|
|
5
|
+
|
|
6
|
+
import { ensureProfile } from '../../src/profiles'
|
|
7
|
+
import { findDesktopCompanion } from '../desktop-companion'
|
|
8
|
+
import { electronDriver } from '../drivers'
|
|
9
|
+
import { confirm } from '../prompt'
|
|
10
|
+
import { buildShellUrl } from './control'
|
|
11
|
+
import { printMissingCompanionHint } from './no-bridge-hint'
|
|
12
|
+
|
|
13
|
+
interface ElectronOptions {
|
|
14
|
+
port?: number
|
|
15
|
+
device?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseInlinePort(args: string[]): number | undefined {
|
|
19
|
+
const portIdx = args.indexOf('--port')
|
|
20
|
+
if (portIdx < 0) return undefined
|
|
21
|
+
const raw = args[portIdx + 1]
|
|
22
|
+
const port = raw ? Number(raw) : Number.NaN
|
|
23
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
24
|
+
console.error(` invalid --port value: ${raw || '(missing)'}`)
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
return port
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function resolveElectronLaunchUrl(
|
|
31
|
+
args: string[],
|
|
32
|
+
opts: ElectronOptions,
|
|
33
|
+
): Promise<string | undefined> {
|
|
34
|
+
const targetPort = parseInlinePort(args) ?? opts.port
|
|
35
|
+
return targetPort ? buildShellUrl(String(targetPort)) : undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function runElectron(args: string[], opts: ElectronOptions) {
|
|
39
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
40
|
+
console.log(`
|
|
41
|
+
rnxsim desktop — launch the desktop companion
|
|
42
|
+
|
|
43
|
+
usage:
|
|
44
|
+
rnxsim desktop [options]
|
|
45
|
+
|
|
46
|
+
options:
|
|
47
|
+
--port <number> connect to running dev server on this port
|
|
48
|
+
--profile <id> launch with an isolated persistent storage profile
|
|
49
|
+
--ephemeral launch with a temporary isolated storage profile
|
|
50
|
+
|
|
51
|
+
examples:
|
|
52
|
+
rnxsim desktop
|
|
53
|
+
rnxsim desktop --port 5173
|
|
54
|
+
rnxsim desktop --profile qa
|
|
55
|
+
`)
|
|
56
|
+
process.exit(0)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const profileArg = args.find((_, i) => args[i - 1] === '--profile')
|
|
60
|
+
const ephemeralProfile = args.includes('--ephemeral')
|
|
61
|
+
if (profileArg && ephemeralProfile) {
|
|
62
|
+
console.error(' rnxsim desktop: --profile cannot be combined with --ephemeral')
|
|
63
|
+
process.exit(1)
|
|
64
|
+
}
|
|
65
|
+
const profileId = profileArg ? ensureProfile(profileArg).id : undefined
|
|
66
|
+
let install = findDesktopCompanion()
|
|
67
|
+
if (!install) {
|
|
68
|
+
printMissingCompanionHint()
|
|
69
|
+
const canPrompt =
|
|
70
|
+
process.stdin.isTTY &&
|
|
71
|
+
process.env.CI !== '1' &&
|
|
72
|
+
process.env.SOOTSIM_NO_PROMPT !== '1'
|
|
73
|
+
if (canPrompt && (await confirm('run rnxsim desktop install now?', true))) {
|
|
74
|
+
console.log()
|
|
75
|
+
const { runInstallDesktop } = await import('./install-desktop')
|
|
76
|
+
await runInstallDesktop(['--yes'])
|
|
77
|
+
install = findDesktopCompanion()
|
|
78
|
+
}
|
|
79
|
+
if (!install) process.exit(1)
|
|
80
|
+
}
|
|
81
|
+
console.log(` launching ${install.path}`)
|
|
82
|
+
|
|
83
|
+
const url = await resolveElectronLaunchUrl(args, opts)
|
|
84
|
+
const result = await electronDriver.launch({
|
|
85
|
+
url,
|
|
86
|
+
device: opts.device,
|
|
87
|
+
profileId,
|
|
88
|
+
ephemeralProfile,
|
|
89
|
+
})
|
|
90
|
+
if (!result.launched) {
|
|
91
|
+
console.error(` ${result.message}`)
|
|
92
|
+
process.exit(1)
|
|
93
|
+
}
|
|
94
|
+
console.log(` ${result.message}`)
|
|
95
|
+
}
|