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
package/dist-lib/vite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.314 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rnxsim",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.314",
|
|
4
4
|
"description": "rnxsim CLI, Vite and Metro plugins, and agent skills for driving React Native apps in the browser.",
|
|
5
5
|
"author": "Tamagui LLC",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/sootbean/soot.git",
|
|
@@ -88,6 +88,11 @@
|
|
|
88
88
|
"source": "./src/backend-origin.ts",
|
|
89
89
|
"default": "./dist-lib/backend-origin.cjs"
|
|
90
90
|
},
|
|
91
|
+
"./bridge-contract": {
|
|
92
|
+
"types": "./src/bridge-contract.ts",
|
|
93
|
+
"source": "./src/bridge-contract.ts",
|
|
94
|
+
"default": "./dist-lib/bridge-contract.cjs"
|
|
95
|
+
},
|
|
91
96
|
"./bridge-constants": {
|
|
92
97
|
"source": "./src/bridge-constants.ts",
|
|
93
98
|
"default": "./dist-lib/bridge-constants.cjs"
|
|
@@ -155,6 +160,7 @@
|
|
|
155
160
|
"dist-cli/",
|
|
156
161
|
"dist-lib/",
|
|
157
162
|
"src/",
|
|
163
|
+
"cli/",
|
|
158
164
|
"skills/",
|
|
159
165
|
"scripts/dev-server-scanner.ts",
|
|
160
166
|
"scripts/optional-demo-registry.ts",
|
package/src/bridge-constants.ts
CHANGED
|
@@ -74,10 +74,9 @@ function closestLocalDevBaseHttpPort(httpPort: number): number | null {
|
|
|
74
74
|
/** the vite dev shell that shares a world with a bridge port. the two are one
|
|
75
75
|
* offset apart by construction (bridge 7668 + N, shell 5173 + N), so a caller
|
|
76
76
|
* that pinned an explicit bridge port can name its own shell directly. that
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* bridge never connects. null when the port is not a local dev bridge port. */
|
|
77
|
+
* keeps explicit ports independent from default-world selection and lets a
|
|
78
|
+
* page register on the exact bridge its caller will poll. null when the port
|
|
79
|
+
* is not a local dev bridge port. */
|
|
81
80
|
export function resolveSootsimShellBaseUrlForBridgePort(
|
|
82
81
|
bridgePort: number,
|
|
83
82
|
): string | null {
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// the wire contract for the sootsim ws bridge. the engine implements it in
|
|
2
|
+
// sootsim-engine/src/mcp/ws-bridge.ts; the rnxsim cli client, the SimClient
|
|
3
|
+
// sdk, and the flow runner speak it. it is the single owner of these shapes,
|
|
4
|
+
// and neither side redeclares them.
|
|
5
|
+
//
|
|
6
|
+
// it lives in rnxsim rather than the engine because rnxsim is published and
|
|
7
|
+
// sootsim-engine is `private: true`. the engine already depends on rnxsim
|
|
8
|
+
// (see its `rnxsim` dependency and its `rnxsim/bridge-constants` import), so
|
|
9
|
+
// this is the direction that ships.
|
|
10
|
+
|
|
11
|
+
import type { SootSimAgentActionDetail } from '@sootsim/globals'
|
|
12
|
+
|
|
13
|
+
export type BridgeCommandType =
|
|
14
|
+
| 'evaluate'
|
|
15
|
+
| 'call'
|
|
16
|
+
| 'screenshot'
|
|
17
|
+
| 'tap'
|
|
18
|
+
| 'longPress'
|
|
19
|
+
| 'keyboard'
|
|
20
|
+
| 'tree'
|
|
21
|
+
| 'focus'
|
|
22
|
+
| 'close'
|
|
23
|
+
| 'flowStatus'
|
|
24
|
+
| 'perform'
|
|
25
|
+
| 'state'
|
|
26
|
+
| 'reset'
|
|
27
|
+
| 'camera'
|
|
28
|
+
|
|
29
|
+
export interface WsCommand {
|
|
30
|
+
id: string | number
|
|
31
|
+
type: BridgeCommandType
|
|
32
|
+
// flowStatus — live flow-runner progress payload (see flow-run-store)
|
|
33
|
+
status?: Record<string, unknown>
|
|
34
|
+
code?: string
|
|
35
|
+
path?: string
|
|
36
|
+
args?: unknown[]
|
|
37
|
+
x?: number
|
|
38
|
+
y?: number
|
|
39
|
+
durationMs?: number
|
|
40
|
+
target?: SootSimAgentActionDetail['target']
|
|
41
|
+
action?: string
|
|
42
|
+
text?: string
|
|
43
|
+
depth?: number
|
|
44
|
+
// screenshot — which canvas layers to composite. omit for full (today's
|
|
45
|
+
// default: everything including iOS chrome). 'tenant' excludes the shell
|
|
46
|
+
// overlay for a clean app capture.
|
|
47
|
+
layers?: 'full' | 'tenant' | 'shell'
|
|
48
|
+
// screenshot — engine-side layer isolation (hide subtrees / solo one
|
|
49
|
+
// subtree on transparency). see SootSimCaptureOptions.captureFilter.
|
|
50
|
+
captureFilter?: { hideTestIds?: string[]; soloTestId?: string }
|
|
51
|
+
// screenshot — optional crop rect in logical sootsim coords. the capture
|
|
52
|
+
// module scales x/y/w/h by device pixel ratio before cropping.
|
|
53
|
+
crop?: { x: number; y: number; w: number; h: number }
|
|
54
|
+
// perform — ordered batch executed inside the page, no round trips
|
|
55
|
+
steps?: PerformStep[]
|
|
56
|
+
performOptions?: PerformOptions
|
|
57
|
+
// state — compound screenshot + tree + route + errors
|
|
58
|
+
stateOptions?: SimStateOptions
|
|
59
|
+
// reset — two-tier app state wipe
|
|
60
|
+
resetOptions?: ResetOptions
|
|
61
|
+
// camera — fixture injection into the host camera pipeline
|
|
62
|
+
camera?: CameraCommand
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// perform — batched input
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
// every step maps onto a primitive that already exists on
|
|
70
|
+
// SootSim.bridges.interact / .keyboard / .test. the batch runs entirely inside
|
|
71
|
+
// the page so gesture timing is real wall-clock timing, not ws round-trips.
|
|
72
|
+
//
|
|
73
|
+
// deliberately absent: text-based tap resolution. matching by visible text is
|
|
74
|
+
// fuzzy, needs ranking and failure diagnostics, and lives once in the cli's
|
|
75
|
+
// tapByText. the sdk resolves text to coordinates before it builds a batch.
|
|
76
|
+
// testID lookup is exact, so tapId resolves in-engine.
|
|
77
|
+
export type PerformStep =
|
|
78
|
+
// raw pointer steps — full control over gesture physics
|
|
79
|
+
| { type: 'touchDown'; x: number; y: number; pointerId?: number }
|
|
80
|
+
| { type: 'touchMove'; x: number; y: number; pointerId?: number }
|
|
81
|
+
| { type: 'touchUp'; x: number; y: number; pointerId?: number }
|
|
82
|
+
| { type: 'touchCancel'; pointerId?: number }
|
|
83
|
+
// semantic steps
|
|
84
|
+
| { type: 'tap'; x: number; y: number; target?: SootSimAgentActionDetail['target'] }
|
|
85
|
+
| { type: 'tapId'; id: string }
|
|
86
|
+
| { type: 'doubleTap'; x: number; y: number; gapMs?: number }
|
|
87
|
+
| {
|
|
88
|
+
type: 'longPress'
|
|
89
|
+
x: number
|
|
90
|
+
y: number
|
|
91
|
+
durationMs?: number
|
|
92
|
+
target?: SootSimAgentActionDetail['target']
|
|
93
|
+
}
|
|
94
|
+
| {
|
|
95
|
+
type: 'drag'
|
|
96
|
+
fromX: number
|
|
97
|
+
fromY: number
|
|
98
|
+
toX: number
|
|
99
|
+
toY: number
|
|
100
|
+
steps?: number
|
|
101
|
+
stepMs?: number
|
|
102
|
+
}
|
|
103
|
+
| {
|
|
104
|
+
type: 'pinch'
|
|
105
|
+
fromX1: number
|
|
106
|
+
fromY1: number
|
|
107
|
+
fromX2: number
|
|
108
|
+
fromY2: number
|
|
109
|
+
toX1: number
|
|
110
|
+
toY1: number
|
|
111
|
+
toX2: number
|
|
112
|
+
toY2: number
|
|
113
|
+
steps?: number
|
|
114
|
+
stepMs?: number
|
|
115
|
+
}
|
|
116
|
+
| { type: 'scroll'; id: string; x?: number; y?: number; animated?: boolean }
|
|
117
|
+
| { type: 'type'; text: string }
|
|
118
|
+
| { type: 'key'; key: string }
|
|
119
|
+
| { type: 'dismissKeyboard' }
|
|
120
|
+
| { type: 'wait'; ms: number }
|
|
121
|
+
| { type: 'waitFor'; testID?: string; id?: string; timeoutMs?: number }
|
|
122
|
+
|
|
123
|
+
export interface PerformOptions {
|
|
124
|
+
// stop the batch at the first failing step. default true — a gesture whose
|
|
125
|
+
// touchDown missed should not keep dragging.
|
|
126
|
+
stopOnError?: boolean
|
|
127
|
+
// ceiling on batch PROGRESS, checked between steps: once it passes, the
|
|
128
|
+
// batch starts no further steps and reports which one it reached. a step
|
|
129
|
+
// already running is always awaited to completion, never abandoned, because
|
|
130
|
+
// a gesture that keeps dispatching pointer events after the caller has been
|
|
131
|
+
// told the batch failed is worse than a late reply. every step is bounded on
|
|
132
|
+
// its own (drag and pinch by steps * stepMs, wait by ms, waitFor by its own
|
|
133
|
+
// timeout), and a wedged worker is caught by the CLI client's per-command
|
|
134
|
+
// timeout one layer up. default 30000.
|
|
135
|
+
timeoutMs?: number
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface PerformStepResult {
|
|
139
|
+
index: number
|
|
140
|
+
type: PerformStep['type']
|
|
141
|
+
ok: boolean
|
|
142
|
+
// step-specific payload: tap hit info, waitFor match, resolved coordinates
|
|
143
|
+
value?: unknown
|
|
144
|
+
error?: string
|
|
145
|
+
// wall-clock ms this step took inside the page
|
|
146
|
+
durationMs: number
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface PerformResult {
|
|
150
|
+
ok: boolean
|
|
151
|
+
steps: PerformStepResult[]
|
|
152
|
+
// number of steps that ran. less than steps.length when the batch aborted.
|
|
153
|
+
completed: number
|
|
154
|
+
durationMs: number
|
|
155
|
+
error?: string
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// state — one round-trip situational awareness
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
export interface SimStateOptions {
|
|
163
|
+
// omit a section an agent does not need this turn
|
|
164
|
+
screenshot?: boolean
|
|
165
|
+
tree?: boolean
|
|
166
|
+
route?: boolean
|
|
167
|
+
errors?: boolean
|
|
168
|
+
// tree depth, matching the standalone `tree` command. default 5.
|
|
169
|
+
depth?: number
|
|
170
|
+
layers?: WsCommand['layers']
|
|
171
|
+
// cap on recentErrors entries. default 20.
|
|
172
|
+
errorLimit?: number
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface SimStateResult {
|
|
176
|
+
// png data uri, same encoding the `screenshot` command returns
|
|
177
|
+
screenshot?: unknown
|
|
178
|
+
tree?: unknown
|
|
179
|
+
route?: { url?: string; screen?: string | null; stack?: string[] } | null
|
|
180
|
+
recentErrors?: Array<{ level: string; text: string; at?: number }>
|
|
181
|
+
keyboard?: { visible: boolean; mode?: string | null }
|
|
182
|
+
capturedAt: number
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// reset — two-tier app state wipe
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
// 'data' clears what a user could clear from inside the app: async storage,
|
|
190
|
+
// sqlite, cache dirs. 'full' also clears what only a fresh install would:
|
|
191
|
+
// simulated keychain, user defaults, granted permissions.
|
|
192
|
+
//
|
|
193
|
+
// an in-worker remount is preferred over restarting the tenant worker, because
|
|
194
|
+
// a restart is what makes reset too slow to run between tests. it is not
|
|
195
|
+
// guaranteed: the result reports which one actually happened rather than
|
|
196
|
+
// claiming the fast path.
|
|
197
|
+
export type ResetStrategy = 'data' | 'full'
|
|
198
|
+
|
|
199
|
+
export interface ResetOptions {
|
|
200
|
+
strategy?: ResetStrategy
|
|
201
|
+
// relaunch the app after wiping. default true.
|
|
202
|
+
relaunch?: boolean
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface ResetResult {
|
|
206
|
+
ok: boolean
|
|
207
|
+
strategy: ResetStrategy
|
|
208
|
+
// stores verified empty after the wipe, read back rather than assumed
|
|
209
|
+
cleared: string[]
|
|
210
|
+
// stores this strategy deliberately did NOT clear, each with the reason.
|
|
211
|
+
// a named gap is not a failure, so this leaves `ok` true and stays out of
|
|
212
|
+
// `error`; a caller that cares can branch on it.
|
|
213
|
+
skipped?: Array<{ store: string; reason: string }>
|
|
214
|
+
relaunched: boolean
|
|
215
|
+
// true when reset had to restart the tenant worker instead of doing an
|
|
216
|
+
// in-worker cold remount. a restart is correct but slow, so a caller running
|
|
217
|
+
// reset between tests needs to see which path it got.
|
|
218
|
+
workerReloaded?: boolean
|
|
219
|
+
error?: string
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// camera — fixture injection
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
// the host thread owns the camera pipeline (hidden <video> -> <canvas> ->
|
|
227
|
+
// ImageBitmap -> worker). injection replaces the source of that <video>, so
|
|
228
|
+
// every downstream consumer — expo-camera's live frame texture and the
|
|
229
|
+
// BarcodeDetector scan loop — sees the fixture with no seam of its own.
|
|
230
|
+
export interface CameraPlayOptions {
|
|
231
|
+
// url the host can fetch. the cli serves the local file from the daemon.
|
|
232
|
+
url: string
|
|
233
|
+
loop?: boolean
|
|
234
|
+
// playback rate, for making a slow fixture scan faster in ci
|
|
235
|
+
rate?: number
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export type CameraCommand =
|
|
239
|
+
| ({ action: 'play' } & CameraPlayOptions)
|
|
240
|
+
| { action: 'stop' }
|
|
241
|
+
| { action: 'status' }
|
|
242
|
+
|
|
243
|
+
export interface CameraResult {
|
|
244
|
+
ok: boolean
|
|
245
|
+
// 'live' = real getUserMedia, 'fixture' = injected video, 'idle' = no session
|
|
246
|
+
source: 'live' | 'fixture' | 'idle'
|
|
247
|
+
url?: string
|
|
248
|
+
loop?: boolean
|
|
249
|
+
rate?: number
|
|
250
|
+
error?: string
|
|
251
|
+
}
|
package/src/connect.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// `connect()` — the node entrypoint of the rnxsim SDK. resolves the running
|
|
2
|
+
// bridge world the same way the CLI does (development bridge registry, then
|
|
3
|
+
// daemon, then default port), picks a sim, and returns a connected SimClient
|
|
4
|
+
// over the shared ws transport. node-only by design: sim-client.ts stays
|
|
5
|
+
// browser-safe.
|
|
6
|
+
|
|
7
|
+
import { readCurrentSimId } from '../cli/current-sim'
|
|
8
|
+
import { createBridge, resolveBridgeWorld, type BridgeSimInfo } from '../cli/ws-bridge'
|
|
9
|
+
import { SimClient, SimConnectError } from './sim-client'
|
|
10
|
+
|
|
11
|
+
export interface ConnectOptions {
|
|
12
|
+
/** ws bridge port. default: the resolved bridge world (lockfiles). */
|
|
13
|
+
port?: number
|
|
14
|
+
/** sim id to attach to (as shown by `rnxsim list`). default: the primary sim. */
|
|
15
|
+
sim?: string
|
|
16
|
+
/** per-command bridge timeout in ms. default 15000. */
|
|
17
|
+
timeoutMs?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pickSim(
|
|
21
|
+
sims: BridgeSimInfo[],
|
|
22
|
+
wanted: string | undefined,
|
|
23
|
+
port: number,
|
|
24
|
+
): BridgeSimInfo {
|
|
25
|
+
const open = sims.filter((sim) => sim.readyState === 'open')
|
|
26
|
+
if (wanted) {
|
|
27
|
+
const match = open.find((sim) => sim.id === wanted)
|
|
28
|
+
if (!match) {
|
|
29
|
+
throw new SimConnectError(
|
|
30
|
+
`no sim connected with id ${wanted} on bridge :${port}` +
|
|
31
|
+
(open.length
|
|
32
|
+
? ` — connected: ${open.map((sim) => sim.id).join(', ')}`
|
|
33
|
+
: ' — no sims connected; open one with `rnxsim open <port>`'),
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
return match
|
|
37
|
+
}
|
|
38
|
+
// same session affinity as the CLI: a sim pinned by `rnxsim open` /
|
|
39
|
+
// `rnxsim use` in this terminal wins over the primary election, so a script
|
|
40
|
+
// run next to CLI commands drives the sim the terminal is working with. a
|
|
41
|
+
// stale pin (sim gone) falls through to the primary.
|
|
42
|
+
const pinned = readCurrentSimId()
|
|
43
|
+
if (pinned) {
|
|
44
|
+
const match = open.find((sim) => sim.id === pinned)
|
|
45
|
+
if (match) return match
|
|
46
|
+
}
|
|
47
|
+
const primary = open.find((sim) => sim.isPrimary) ?? open[0]
|
|
48
|
+
if (!primary) {
|
|
49
|
+
throw new SimConnectError(
|
|
50
|
+
`no sims connected on bridge :${port} — open one with \`rnxsim open <port>\``,
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
return primary
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function connect(options: ConnectOptions = {}): Promise<SimClient> {
|
|
57
|
+
const port = options.port ?? resolveBridgeWorld().bridgePort
|
|
58
|
+
const commandTimeoutMs = options.timeoutMs ?? 15_000
|
|
59
|
+
|
|
60
|
+
// discovery socket: list sims and pick the target. closed before the pinned
|
|
61
|
+
// client socket is opened so a failed connect never leaks a connection.
|
|
62
|
+
const discovery = createBridge(port, { commandTimeoutMs, cliLabel: 'sdk' })
|
|
63
|
+
let picked: BridgeSimInfo
|
|
64
|
+
try {
|
|
65
|
+
picked = pickSim(await discovery.listSims(), options.sim, port)
|
|
66
|
+
} catch (error) {
|
|
67
|
+
discovery.close()
|
|
68
|
+
if (error instanceof SimConnectError) throw error
|
|
69
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
70
|
+
throw new SimConnectError(`${message} — is the rnxsim daemon or dev shell running?`)
|
|
71
|
+
}
|
|
72
|
+
discovery.close()
|
|
73
|
+
|
|
74
|
+
// pinned socket: every command targets exactly the picked sim, immune to the
|
|
75
|
+
// CLI's saved-pin re-resolution.
|
|
76
|
+
const bridge = createBridge(port, {
|
|
77
|
+
commandTimeoutMs,
|
|
78
|
+
cliLabel: 'sdk',
|
|
79
|
+
simId: picked.id,
|
|
80
|
+
simIdSource: 'flag',
|
|
81
|
+
})
|
|
82
|
+
return new SimClient(bridge, picked.id, { commandTimeoutMs })
|
|
83
|
+
}
|
package/src/disk-cleanup.ts
CHANGED
|
@@ -11,6 +11,7 @@ const AUTOMATIC_CLEANUP_LOCK_STALE_MS = 6 * 60 * 60 * 1000
|
|
|
11
11
|
|
|
12
12
|
export type SootsimCleanupKind =
|
|
13
13
|
| 'browser-cache'
|
|
14
|
+
| 'camera-fixture'
|
|
14
15
|
| 'recording'
|
|
15
16
|
| 'runtime-archive'
|
|
16
17
|
| 'runtime-version'
|
|
@@ -177,6 +178,34 @@ function collectRecordingEntries(
|
|
|
177
178
|
})
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
// videos staged by `rnxsim camera play`. a fixture is only a copy of a file
|
|
182
|
+
// the user still has, so it is cheap to reclaim, but the automatic sweep must
|
|
183
|
+
// not pull one out from under a sim that is playing it right now. aggressive
|
|
184
|
+
// only, matching recordings.
|
|
185
|
+
function collectCameraFixtureEntries(
|
|
186
|
+
home: string,
|
|
187
|
+
aggressive: boolean,
|
|
188
|
+
): SootsimCleanupEntry[] {
|
|
189
|
+
const root = path.join(home, 'camera-fixtures')
|
|
190
|
+
let fixtures: fs.Dirent[]
|
|
191
|
+
try {
|
|
192
|
+
fixtures = fs.readdirSync(root, { withFileTypes: true })
|
|
193
|
+
} catch {
|
|
194
|
+
return []
|
|
195
|
+
}
|
|
196
|
+
return fixtures.map((fixture) => {
|
|
197
|
+
const fixturePath = path.join(root, fixture.name)
|
|
198
|
+
return {
|
|
199
|
+
path: fixturePath,
|
|
200
|
+
bytes: allocatedBytes(fixturePath),
|
|
201
|
+
kind: 'camera-fixture',
|
|
202
|
+
state: 'aggressive',
|
|
203
|
+
selected: aggressive,
|
|
204
|
+
detail: 'staged camera fixture video',
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
180
209
|
function runtimeEntries(aggressive: boolean): {
|
|
181
210
|
activeRuntime: string | null
|
|
182
211
|
entries: SootsimCleanupEntry[]
|
|
@@ -255,6 +284,7 @@ export function planSootsimCleanup(
|
|
|
255
284
|
...collectPlaywrightCaches(home),
|
|
256
285
|
...collectElectronCaches(home),
|
|
257
286
|
...collectRecordingEntries(home, aggressive),
|
|
287
|
+
...collectCameraFixtureEntries(home, aggressive),
|
|
258
288
|
].sort((left, right) => right.bytes - left.bytes || left.path.localeCompare(right.path))
|
|
259
289
|
return {
|
|
260
290
|
home,
|
package/src/home-paths.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// ├── cache/
|
|
15
15
|
// │ └── sootsim-runtime-<version>.tar.gz
|
|
16
16
|
// ├── daemon.json lockfile: pid, ports, active runtime, heartbeat
|
|
17
|
-
// ├── dev-
|
|
17
|
+
// ├── dev-bridges/ live Vite dev bridge records, keyed by bound port
|
|
18
18
|
// ├── runtime-upgraded.json one-shot notice written by the daemon's
|
|
19
19
|
// │ background updater, consumed (printed + deleted)
|
|
20
20
|
// │ by the next interactive CLI run
|
|
@@ -31,7 +31,7 @@ import path from 'node:path'
|
|
|
31
31
|
export const SOOTSIM_HOME_ENV = 'SOOTSIM_HOME'
|
|
32
32
|
export const ACTIVE_RUNTIME_FILE = 'active'
|
|
33
33
|
export const DAEMON_LOCKFILE = 'daemon.json'
|
|
34
|
-
export const
|
|
34
|
+
export const DEV_BRIDGES_DIR = 'dev-bridges'
|
|
35
35
|
export const CONFIG_FILE = 'config.json'
|
|
36
36
|
export const DAEMON_HEARTBEAT_STALE_MS = 30_000
|
|
37
37
|
|
|
@@ -128,6 +128,13 @@ export function profilesDir(): string {
|
|
|
128
128
|
return path.join(sootsimHomeDir(), 'profiles')
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// video fixtures staged for `rnxsim camera play`. the CLI copies the user's
|
|
132
|
+
// file in here and the daemon serves it by basename from this one directory,
|
|
133
|
+
// so the route can never be talked into reading anywhere else on disk.
|
|
134
|
+
export function cameraFixturesDir(): string {
|
|
135
|
+
return path.join(sootsimHomeDir(), 'camera-fixtures')
|
|
136
|
+
}
|
|
137
|
+
|
|
131
138
|
// the launchd-managed daemon spawns ProgramArguments[0] directly, and macOS
|
|
132
139
|
// Background Task Management attributes the entry to whoever code-signed
|
|
133
140
|
// that binary. pointing launchd at bun directly makes Login Items say
|
|
@@ -154,8 +161,15 @@ export function daemonLockfilePath(): string {
|
|
|
154
161
|
return path.join(sootsimHomeDir(), DAEMON_LOCKFILE)
|
|
155
162
|
}
|
|
156
163
|
|
|
157
|
-
export function
|
|
158
|
-
return path.join(sootsimHomeDir(),
|
|
164
|
+
export function devBridgesDir(): string {
|
|
165
|
+
return path.join(sootsimHomeDir(), DEV_BRIDGES_DIR)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function devBridgeLockfilePath(bridgePort: number): string {
|
|
169
|
+
if (!Number.isInteger(bridgePort) || bridgePort <= 0) {
|
|
170
|
+
throw new Error(`invalid development bridge port: ${bridgePort}`)
|
|
171
|
+
}
|
|
172
|
+
return path.join(devBridgesDir(), `${bridgePort}.json`)
|
|
159
173
|
}
|
|
160
174
|
|
|
161
175
|
export function configFilePath(): string {
|
|
@@ -421,40 +435,63 @@ export function readDaemonLockfile(): DaemonLockfile | null {
|
|
|
421
435
|
}
|
|
422
436
|
}
|
|
423
437
|
|
|
424
|
-
|
|
438
|
+
function parseDevBridgeLockfile(raw: string): DevBridgeLockfile | null {
|
|
425
439
|
try {
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
(parsed.servedRuntimes === undefined ||
|
|
444
|
-
(Array.isArray(parsed.servedRuntimes) &&
|
|
445
|
-
parsed.servedRuntimes.every((version) => typeof version === 'string')))
|
|
446
|
-
) {
|
|
447
|
-
return parsed as DevBridgeLockfile
|
|
448
|
-
}
|
|
449
|
-
return null
|
|
450
|
-
} finally {
|
|
451
|
-
fs.closeSync(fd)
|
|
440
|
+
const parsed = JSON.parse(raw)
|
|
441
|
+
if (
|
|
442
|
+
parsed &&
|
|
443
|
+
parsed.schema === 1 &&
|
|
444
|
+
parsed.source === 'vite-dev' &&
|
|
445
|
+
typeof parsed.pid === 'number' &&
|
|
446
|
+
typeof parsed.bridgePort === 'number' &&
|
|
447
|
+
typeof parsed.runtimePort === 'number' &&
|
|
448
|
+
typeof parsed.cwd === 'string' &&
|
|
449
|
+
typeof parsed.startedAt === 'number' &&
|
|
450
|
+
typeof parsed.heartbeatAt === 'number' &&
|
|
451
|
+
(parsed.shellPort === undefined || typeof parsed.shellPort === 'number') &&
|
|
452
|
+
(parsed.servedRuntimes === undefined ||
|
|
453
|
+
(Array.isArray(parsed.servedRuntimes) &&
|
|
454
|
+
parsed.servedRuntimes.every((version: unknown) => typeof version === 'string')))
|
|
455
|
+
) {
|
|
456
|
+
return parsed
|
|
452
457
|
}
|
|
458
|
+
return null
|
|
453
459
|
} catch {
|
|
454
460
|
return null
|
|
455
461
|
}
|
|
456
462
|
}
|
|
457
463
|
|
|
464
|
+
export function readDevBridgeLockfiles(): DevBridgeLockfile[] {
|
|
465
|
+
let entries: fs.Dirent[]
|
|
466
|
+
try {
|
|
467
|
+
entries = fs.readdirSync(devBridgesDir(), { withFileTypes: true })
|
|
468
|
+
} catch {
|
|
469
|
+
return []
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const lockfiles: DevBridgeLockfile[] = []
|
|
473
|
+
for (const entry of entries) {
|
|
474
|
+
if (!entry.isFile() || !/^\d+\.json$/.test(entry.name)) continue
|
|
475
|
+
try {
|
|
476
|
+
const filePath = path.join(devBridgesDir(), entry.name)
|
|
477
|
+
const fd = fs.openSync(filePath, 'r')
|
|
478
|
+
try {
|
|
479
|
+
const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES)
|
|
480
|
+
const bytesRead = fs.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0)
|
|
481
|
+
const lockfile = parseDevBridgeLockfile(
|
|
482
|
+
buf.subarray(0, bytesRead).toString('utf8'),
|
|
483
|
+
)
|
|
484
|
+
if (lockfile && entry.name === `${lockfile.bridgePort}.json`) {
|
|
485
|
+
lockfiles.push(lockfile)
|
|
486
|
+
}
|
|
487
|
+
} finally {
|
|
488
|
+
fs.closeSync(fd)
|
|
489
|
+
}
|
|
490
|
+
} catch {}
|
|
491
|
+
}
|
|
492
|
+
return lockfiles.sort((left, right) => left.bridgePort - right.bridgePort)
|
|
493
|
+
}
|
|
494
|
+
|
|
458
495
|
/** true when the lockfile exists, the named pid is alive, and the heartbeat
|
|
459
496
|
* is recent. callers should reach for this before trusting any of the
|
|
460
497
|
* ports inside.
|
|
@@ -503,8 +540,8 @@ export function readLiveRuntimeVersions(): string[] {
|
|
|
503
540
|
if (daemon.activeRuntime) versions.add(daemon.activeRuntime)
|
|
504
541
|
for (const version of daemon.servedRuntimes ?? []) versions.add(version)
|
|
505
542
|
}
|
|
506
|
-
const devBridge
|
|
507
|
-
|
|
543
|
+
for (const devBridge of readDevBridgeLockfiles()) {
|
|
544
|
+
if (!isDevBridgeLockfileFresh(devBridge)) continue
|
|
508
545
|
for (const version of devBridge.servedRuntimes ?? []) versions.add(version)
|
|
509
546
|
}
|
|
510
547
|
return [...versions].sort()
|
|
@@ -518,10 +555,11 @@ export function writeDaemonLockfile(data: DaemonLockfile): void {
|
|
|
518
555
|
}
|
|
519
556
|
|
|
520
557
|
export function writeDevBridgeLockfile(data: DevBridgeLockfile): void {
|
|
521
|
-
|
|
522
|
-
const
|
|
558
|
+
fs.mkdirSync(devBridgesDir(), { recursive: true })
|
|
559
|
+
const lockfilePath = devBridgeLockfilePath(data.bridgePort)
|
|
560
|
+
const tmp = `${lockfilePath}.${data.pid}.tmp`
|
|
523
561
|
fs.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
|
|
524
|
-
fs.renameSync(tmp,
|
|
562
|
+
fs.renameSync(tmp, lockfilePath)
|
|
525
563
|
}
|
|
526
564
|
|
|
527
565
|
/** try to claim the lockfile atomically on daemon boot. returns true if
|
|
@@ -545,8 +583,13 @@ export function removeDaemonLockfile(): void {
|
|
|
545
583
|
} catch {}
|
|
546
584
|
}
|
|
547
585
|
|
|
548
|
-
export function removeDevBridgeLockfile(
|
|
586
|
+
export function removeDevBridgeLockfile(
|
|
587
|
+
owner: Pick<DevBridgeLockfile, 'bridgePort' | 'pid' | 'startedAt'>,
|
|
588
|
+
): void {
|
|
549
589
|
try {
|
|
550
|
-
|
|
590
|
+
const lockfilePath = devBridgeLockfilePath(owner.bridgePort)
|
|
591
|
+
const lockfile = parseDevBridgeLockfile(fs.readFileSync(lockfilePath, 'utf8'))
|
|
592
|
+
if (lockfile?.pid !== owner.pid || lockfile.startedAt !== owner.startedAt) return
|
|
593
|
+
fs.unlinkSync(lockfilePath)
|
|
551
594
|
} catch {}
|
|
552
595
|
}
|