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,300 @@
|
|
|
1
|
+
import { execFileSync, spawn } from 'child_process'
|
|
2
|
+
import { existsSync, readdirSync } from 'fs'
|
|
3
|
+
import { dirname, join, resolve } from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
// resolved lazily — sootsim-engine is a workspace package in dev, but absent
|
|
7
|
+
// from the published npm tarball. don't crash module load just because the
|
|
8
|
+
// engine package isn't installed; commands that don't need the dev-electron
|
|
9
|
+
// path (most of them) shouldn't care.
|
|
10
|
+
let _sootsimRoot: string | null | undefined
|
|
11
|
+
function getSootsimRoot(): string | null {
|
|
12
|
+
if (_sootsimRoot !== undefined) return _sootsimRoot
|
|
13
|
+
try {
|
|
14
|
+
_sootsimRoot = dirname(
|
|
15
|
+
fileURLToPath(import.meta.resolve('sootsim-engine/package.json')),
|
|
16
|
+
)
|
|
17
|
+
} catch {
|
|
18
|
+
_sootsimRoot = null
|
|
19
|
+
}
|
|
20
|
+
return _sootsimRoot
|
|
21
|
+
}
|
|
22
|
+
const SOOTSIM_BUNDLE_ID = 'dev.sootsim.simulator'
|
|
23
|
+
|
|
24
|
+
export interface DesktopCompanionInstall {
|
|
25
|
+
path: string
|
|
26
|
+
platform: NodeJS.Platform
|
|
27
|
+
kind: 'mac-app' | 'linux-appimage' | 'linux-binary' | 'dev-electron'
|
|
28
|
+
// present for dev-electron: the sootsim-engine dir that electron treats
|
|
29
|
+
// as the app root (its package.json "main" points at dist-electron/main.cjs).
|
|
30
|
+
engineDir?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DesktopCompanionLaunchResult {
|
|
34
|
+
launched: boolean
|
|
35
|
+
via?: 'mac-app' | 'linux-appimage' | 'linux-binary' | 'dev-electron'
|
|
36
|
+
target?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// dev-electron: when running from inside the Contrast checkout with sootsim-engine
|
|
40
|
+
// built and the electron dev dep present, prefer launching the local unpacked
|
|
41
|
+
// electron instead of the stale packaged /Applications/sootsim.app.
|
|
42
|
+
//
|
|
43
|
+
// rationale: in dev we want electron to hit http://localhost:5173
|
|
44
|
+
// (src-electron/main.ts does this when !app.isPackaged).
|
|
45
|
+
// using the local `node_modules/.bin/electron` binary against the engine dir
|
|
46
|
+
// gives us that: isPackaged=false → loads the live dev server.
|
|
47
|
+
function findDevElectron(): DesktopCompanionInstall | null {
|
|
48
|
+
// engine dir must have a built main.cjs for electron to boot. the dev build
|
|
49
|
+
// chain (`bun run dev:electron` / `bun run build:electron-main`) produces
|
|
50
|
+
// this; bun dev keeps it fresh via a one-shot step added to run-all.
|
|
51
|
+
const engineDir = getSootsimRoot()
|
|
52
|
+
if (!engineDir) return null
|
|
53
|
+
const engineMain = join(engineDir, 'dist-electron/main.cjs')
|
|
54
|
+
if (!existsSync(engineMain)) return null
|
|
55
|
+
|
|
56
|
+
// walk up from engine dir looking for a node_modules/.bin/electron.
|
|
57
|
+
// the repo layout is packages/sootsim-engine next to a root node_modules,
|
|
58
|
+
// but also works when installed as a dependency.
|
|
59
|
+
const electronBin = findElectronBin(engineDir)
|
|
60
|
+
if (!electronBin) return null
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
path: electronBin,
|
|
64
|
+
platform: process.platform,
|
|
65
|
+
kind: 'dev-electron',
|
|
66
|
+
engineDir,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// detect whether the sootsim-engine we resolved is the in-repo workspace
|
|
71
|
+
// checkout (has src-electron/main.ts) vs something pulled in as a dep.
|
|
72
|
+
// this matters because repo development has one supported path: dev electron
|
|
73
|
+
// against the live :5173 renderer.
|
|
74
|
+
function isInRepoCheckout(): boolean {
|
|
75
|
+
const root = getSootsimRoot()
|
|
76
|
+
if (!root) return false
|
|
77
|
+
return existsSync(join(root, 'src-electron/main.ts'))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function findElectronBin(startDir: string): string | null {
|
|
81
|
+
let dir = startDir
|
|
82
|
+
for (let i = 0; i < 6; i++) {
|
|
83
|
+
const candidate = join(dir, 'node_modules/.bin/electron')
|
|
84
|
+
if (existsSync(candidate)) return candidate
|
|
85
|
+
const parent = dirname(dir)
|
|
86
|
+
if (parent === dir) break
|
|
87
|
+
dir = parent
|
|
88
|
+
}
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function findMacApp(): DesktopCompanionInstall | null {
|
|
93
|
+
const root = getSootsimRoot()
|
|
94
|
+
const candidates = [
|
|
95
|
+
'/Applications/sootsim.app',
|
|
96
|
+
resolve(process.env.HOME || '', 'Applications/sootsim.app'),
|
|
97
|
+
...(root ? [resolve(root, 'app/sootsim.app')] : []),
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
const found = candidates.find((candidate) => existsSync(candidate))
|
|
101
|
+
if (found) {
|
|
102
|
+
return {
|
|
103
|
+
path: found,
|
|
104
|
+
platform: 'darwin',
|
|
105
|
+
kind: 'mac-app',
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const result = execFileSync(
|
|
111
|
+
'mdfind',
|
|
112
|
+
[`kMDItemCFBundleIdentifier == "${SOOTSIM_BUNDLE_ID}"`],
|
|
113
|
+
{ encoding: 'utf8', timeout: 3000 },
|
|
114
|
+
).trim()
|
|
115
|
+
if (result) {
|
|
116
|
+
return {
|
|
117
|
+
path: result.split('\n')[0],
|
|
118
|
+
platform: 'darwin',
|
|
119
|
+
kind: 'mac-app',
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch {}
|
|
123
|
+
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function findFirstAppImage(dir: string): string | null {
|
|
128
|
+
if (!existsSync(dir)) return null
|
|
129
|
+
const match = readdirSync(dir)
|
|
130
|
+
.filter((entry) => entry.startsWith('sootsim') && entry.endsWith('.AppImage'))
|
|
131
|
+
.sort()
|
|
132
|
+
.reverse()[0]
|
|
133
|
+
return match ? join(dir, match) : null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function findLinuxInstall(): DesktopCompanionInstall | null {
|
|
137
|
+
const root = getSootsimRoot()
|
|
138
|
+
const directCandidates = [
|
|
139
|
+
resolve(process.env.HOME || '', 'Applications/sootsim.AppImage'),
|
|
140
|
+
resolve(process.env.HOME || '', '.local/bin/sootsim.AppImage'),
|
|
141
|
+
'/opt/sootsim/sootsim.AppImage',
|
|
142
|
+
'/opt/sootsim/sootsim',
|
|
143
|
+
...(root
|
|
144
|
+
? [
|
|
145
|
+
resolve(root, 'app/sootsim.AppImage'),
|
|
146
|
+
resolve(root, 'release/linux-unpacked/sootsim'),
|
|
147
|
+
]
|
|
148
|
+
: []),
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
const directMatch = directCandidates.find((candidate) => existsSync(candidate))
|
|
152
|
+
if (directMatch) {
|
|
153
|
+
return {
|
|
154
|
+
path: directMatch,
|
|
155
|
+
platform: 'linux',
|
|
156
|
+
kind: directMatch.endsWith('.AppImage') ? 'linux-appimage' : 'linux-binary',
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const scannedMatch = root
|
|
161
|
+
? findFirstAppImage(resolve(root, 'app')) ||
|
|
162
|
+
findFirstAppImage(resolve(root, 'release'))
|
|
163
|
+
: null
|
|
164
|
+
|
|
165
|
+
if (!scannedMatch) return null
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
path: scannedMatch,
|
|
169
|
+
platform: 'linux',
|
|
170
|
+
kind: 'linux-appimage',
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function findDesktopCompanion(): DesktopCompanionInstall | null {
|
|
175
|
+
const dev = findDevElectron()
|
|
176
|
+
if (dev) return dev
|
|
177
|
+
if (isInRepoCheckout()) {
|
|
178
|
+
const engineDir = getSootsimRoot()!
|
|
179
|
+
const engineMain = join(engineDir, 'dist-electron/main.cjs')
|
|
180
|
+
if (!existsSync(engineMain)) {
|
|
181
|
+
console.error(
|
|
182
|
+
` dist-electron/main.cjs missing in ${engineDir}.\n` +
|
|
183
|
+
` run: bun run --cwd packages/sootsim-engine build:electron-main\n` +
|
|
184
|
+
` (or keep it fresh with: bun run watch:sootsim:electron-main)`,
|
|
185
|
+
)
|
|
186
|
+
} else {
|
|
187
|
+
console.error(
|
|
188
|
+
` no node_modules/.bin/electron found near ${engineDir}. run \`bun install\`.`,
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
if (process.platform === 'darwin') return findMacApp()
|
|
194
|
+
if (process.platform === 'linux') return findLinuxInstall()
|
|
195
|
+
return null
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function spawnDetached(
|
|
199
|
+
command: string,
|
|
200
|
+
args: string[],
|
|
201
|
+
opts: { inheritStdio?: boolean } = {},
|
|
202
|
+
) {
|
|
203
|
+
// dev-electron streams main-process console.log + renderer crash dumps to
|
|
204
|
+
// the terminal so the developer sees them. inherit stdout/stderr but
|
|
205
|
+
// ignore stdin so the child doesn't fight the shell for keystrokes.
|
|
206
|
+
// detached + unref means the CLI still returns the prompt immediately;
|
|
207
|
+
// electron keeps writing to the inherited fds until the user quits it
|
|
208
|
+
// or closes the terminal.
|
|
209
|
+
const stdio: 'ignore' | ['ignore', 'inherit', 'inherit'] = opts.inheritStdio
|
|
210
|
+
? ['ignore', 'inherit', 'inherit']
|
|
211
|
+
: 'ignore'
|
|
212
|
+
await new Promise<void>((resolve, reject) => {
|
|
213
|
+
const child = spawn(command, args, { detached: true, stdio })
|
|
214
|
+
child.once('error', reject)
|
|
215
|
+
child.once('spawn', () => {
|
|
216
|
+
child.unref()
|
|
217
|
+
resolve()
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface LaunchCompanionOptions {
|
|
223
|
+
// seeds the initial device model for the first-created window. wins
|
|
224
|
+
// over any persisted window state; read by main.ts readCliDeviceOverride.
|
|
225
|
+
device?: string
|
|
226
|
+
profileId?: string
|
|
227
|
+
ephemeralProfile?: boolean
|
|
228
|
+
// set by `sootsim open`, whose native window belongs to the launching
|
|
229
|
+
// session. `sootsim desktop` omits it and remains a persistent desktop app.
|
|
230
|
+
ownerPid?: number
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function launchDesktopCompanion(
|
|
234
|
+
url?: string,
|
|
235
|
+
install = findDesktopCompanion(),
|
|
236
|
+
opts: LaunchCompanionOptions = {},
|
|
237
|
+
): Promise<DesktopCompanionLaunchResult> {
|
|
238
|
+
if (!install) return { launched: false }
|
|
239
|
+
|
|
240
|
+
// electron argv extra: main.ts reads --device <model> from process.argv
|
|
241
|
+
// and uses it as the seed for the first createWindow.
|
|
242
|
+
//
|
|
243
|
+
// ⚠ chromium reorders argv before electron's main handler (and before any
|
|
244
|
+
// `second-instance` event) sees it: `--flag value` becomes the bare switch
|
|
245
|
+
// `--flag` followed by `value` as a stray positional. it also injects its
|
|
246
|
+
// own switches (e.g. `--allow-file-access-from-files`) between the no-value
|
|
247
|
+
// flags, so the original pairing is lost entirely. anything that takes a
|
|
248
|
+
// value MUST use the `--flag=value` single-arg form so chromium preserves
|
|
249
|
+
// it intact. ephemeral-profile has no value so it survives as-is.
|
|
250
|
+
const extraArgs: string[] = []
|
|
251
|
+
if (opts.device) extraArgs.push(`--device=${opts.device}`)
|
|
252
|
+
if (opts.profileId) extraArgs.push(`--profile=${opts.profileId}`)
|
|
253
|
+
if (opts.ephemeralProfile) extraArgs.push('--ephemeral-profile')
|
|
254
|
+
if (opts.ownerPid && opts.ownerPid > 1) extraArgs.push(`--owner-pid=${opts.ownerPid}`)
|
|
255
|
+
|
|
256
|
+
if (install.kind === 'mac-app') {
|
|
257
|
+
// `open -a` splits app args before and after `--args`. anything after
|
|
258
|
+
// --args is forwarded to electron's process.argv.
|
|
259
|
+
const openArgs: string[] = ['-g', '-a', install.path]
|
|
260
|
+
if (url) openArgs.push(buildMacLaunchUrl(url, opts))
|
|
261
|
+
if (extraArgs.length > 0) openArgs.push('--args', ...extraArgs)
|
|
262
|
+
await spawnDetached('open', openArgs)
|
|
263
|
+
return { launched: true, via: 'mac-app', target: install.path }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (install.kind === 'dev-electron') {
|
|
267
|
+
// electron <app-dir> loads the dir's package.json "main" (dist-electron/main.cjs).
|
|
268
|
+
// main.ts checks app.isPackaged and loads http://localhost:5173 in dev.
|
|
269
|
+
// local dev shells commonly run under sandbox-exec/safehouse. chromium's
|
|
270
|
+
// own sandbox cannot initialize inside that outer sandbox, so pass the
|
|
271
|
+
// standard electron switch for the unpacked dev app only.
|
|
272
|
+
const args: string[] = ['--no-sandbox', install.engineDir || getSootsimRoot()!]
|
|
273
|
+
if (url) args.push(url)
|
|
274
|
+
if (extraArgs.length > 0) args.push(...extraArgs)
|
|
275
|
+
await spawnDetached(install.path, args, { inheritStdio: true })
|
|
276
|
+
return { launched: true, via: 'dev-electron', target: install.path }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const binaryArgs: string[] = []
|
|
280
|
+
if (url) binaryArgs.push(url)
|
|
281
|
+
if (extraArgs.length > 0) binaryArgs.push(...extraArgs)
|
|
282
|
+
await spawnDetached(install.path, binaryArgs)
|
|
283
|
+
return {
|
|
284
|
+
launched: true,
|
|
285
|
+
via: install.kind,
|
|
286
|
+
target: install.path,
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function buildMacLaunchUrl(url: string, opts: LaunchCompanionOptions): string {
|
|
291
|
+
if (!opts.profileId && !opts.ephemeralProfile && !opts.ownerPid) return url
|
|
292
|
+
const deepLink = new URL('sootsim://dev')
|
|
293
|
+
deepLink.searchParams.set('url', url)
|
|
294
|
+
if (opts.profileId) deepLink.searchParams.set('profile', opts.profileId)
|
|
295
|
+
if (opts.ephemeralProfile) deepLink.searchParams.set('ephemeral', '1')
|
|
296
|
+
if (opts.ownerPid && opts.ownerPid > 1) {
|
|
297
|
+
deepLink.searchParams.set('ownerPid', String(opts.ownerPid))
|
|
298
|
+
}
|
|
299
|
+
return deepLink.toString()
|
|
300
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// electron driver — wraps the existing desktop-companion helper. availability
|
|
2
|
+
// is "is the sootsim desktop app installed on this machine". launch delegates
|
|
3
|
+
// to launchDesktopCompanion, which already handles mac `open -a`, linux
|
|
4
|
+
// AppImage, and the --device seed.
|
|
5
|
+
|
|
6
|
+
import { findDesktopCompanion, launchDesktopCompanion } from '../desktop-companion'
|
|
7
|
+
import type {
|
|
8
|
+
Driver,
|
|
9
|
+
DriverAvailability,
|
|
10
|
+
DriverLaunchOptions,
|
|
11
|
+
DriverLaunchResult,
|
|
12
|
+
} from './types'
|
|
13
|
+
|
|
14
|
+
function probe(): DriverAvailability {
|
|
15
|
+
const install = findDesktopCompanion()
|
|
16
|
+
if (!install) {
|
|
17
|
+
return {
|
|
18
|
+
available: false,
|
|
19
|
+
reason: 'rnxsim desktop app not installed (run `rnxsim desktop install`)',
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
available: true,
|
|
24
|
+
reason: null,
|
|
25
|
+
detail: `${install.kind} @ ${install.path}`,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function launch(opts: DriverLaunchOptions): Promise<DriverLaunchResult> {
|
|
30
|
+
const install = findDesktopCompanion()
|
|
31
|
+
if (!install) {
|
|
32
|
+
return {
|
|
33
|
+
launched: false,
|
|
34
|
+
message: 'rnxsim desktop app not installed',
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const result = await launchDesktopCompanion(opts.url, install, {
|
|
39
|
+
device: opts.device,
|
|
40
|
+
profileId: opts.profileId,
|
|
41
|
+
ephemeralProfile: opts.ephemeralProfile,
|
|
42
|
+
ownerPid: opts.ownerPid,
|
|
43
|
+
})
|
|
44
|
+
if (!result.launched) {
|
|
45
|
+
return { launched: false, message: 'desktop companion failed to start' }
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
launched: true,
|
|
49
|
+
message: opts.url
|
|
50
|
+
? `electron launched via ${result.via} → ${opts.url}`
|
|
51
|
+
: `electron launched via ${result.via}`,
|
|
52
|
+
target: result.target,
|
|
53
|
+
attachUrl: opts.url,
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return {
|
|
57
|
+
launched: false,
|
|
58
|
+
message: `electron launch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const electronDriver: Driver = {
|
|
64
|
+
id: 'electron',
|
|
65
|
+
name: 'electron',
|
|
66
|
+
description: 'rnxsim desktop companion app (native window, menu bar)',
|
|
67
|
+
kind: 'native',
|
|
68
|
+
availability: probe,
|
|
69
|
+
launch,
|
|
70
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// sootsim driver registry — public entry.
|
|
2
|
+
|
|
3
|
+
export type {
|
|
4
|
+
Driver,
|
|
5
|
+
DriverId,
|
|
6
|
+
DriverKind,
|
|
7
|
+
DriverAvailability,
|
|
8
|
+
DriverLaunchOptions,
|
|
9
|
+
DriverLaunchResult,
|
|
10
|
+
} from './types'
|
|
11
|
+
export {
|
|
12
|
+
ALL_DRIVERS,
|
|
13
|
+
buildDriverListRows,
|
|
14
|
+
getAllDrivers,
|
|
15
|
+
getDriver,
|
|
16
|
+
resolveDriver,
|
|
17
|
+
type DriverListRow,
|
|
18
|
+
} from './registry'
|
|
19
|
+
export { electronDriver } from './electron'
|
|
20
|
+
export { playwrightDriver } from './playwright'
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
|
+
export interface ResolvedPlaywright {
|
|
6
|
+
// package name that resolved (for human-facing messaging)
|
|
7
|
+
spec: string
|
|
8
|
+
// absolute package entry loaded by the detached browser host
|
|
9
|
+
modulePath: string
|
|
10
|
+
// absolute package-owned CLI entry used to install this package's browser
|
|
11
|
+
cliPath: string
|
|
12
|
+
// package version shown in availability and provisioning failures
|
|
13
|
+
version: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type PlaywrightBrowserProvisioningResult =
|
|
17
|
+
| {
|
|
18
|
+
ok: true
|
|
19
|
+
executablePath: string
|
|
20
|
+
installed: boolean
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
ok: false
|
|
24
|
+
message: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const PLAYWRIGHT_BROWSER_PROBE = `
|
|
28
|
+
const { existsSync } = require('node:fs');
|
|
29
|
+
const modulePath = process.env.SOOTSIM_PW_MODULE;
|
|
30
|
+
try {
|
|
31
|
+
const playwright = require(modulePath);
|
|
32
|
+
const browser = playwright && playwright.chromium;
|
|
33
|
+
if (!browser || typeof browser.executablePath !== 'function') {
|
|
34
|
+
throw new Error('resolved package does not expose browser "chromium"');
|
|
35
|
+
}
|
|
36
|
+
const executablePath = browser.executablePath();
|
|
37
|
+
process.stdout.write(JSON.stringify({ executablePath, exists: existsSync(executablePath) }));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
process.stderr.write(String((error && error.stack) || error));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
45
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolvePlaywright(): ResolvedPlaywright | null {
|
|
49
|
+
// resolve from several roots, not just cwd. a user can invoke a global CLI
|
|
50
|
+
// from an arbitrary directory while Playwright is installed beside the CLI.
|
|
51
|
+
const roots = [`${process.cwd()}/`]
|
|
52
|
+
if (process.argv[1]) roots.push(process.argv[1])
|
|
53
|
+
for (const root of roots) {
|
|
54
|
+
try {
|
|
55
|
+
const requireFromRoot = createRequire(root)
|
|
56
|
+
for (const spec of ['playwright', 'playwright-chromium', 'playwright-core']) {
|
|
57
|
+
try {
|
|
58
|
+
const modulePath = requireFromRoot.resolve(spec)
|
|
59
|
+
const packageJsonPath = requireFromRoot.resolve(`${spec}/package.json`)
|
|
60
|
+
const parsed: unknown = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
|
61
|
+
if (!isRecord(parsed) || typeof parsed.version !== 'string') continue
|
|
62
|
+
const bin = parsed.bin
|
|
63
|
+
let binPath: string | null = null
|
|
64
|
+
if (typeof bin === 'string') {
|
|
65
|
+
binPath = bin
|
|
66
|
+
} else if (isRecord(bin)) {
|
|
67
|
+
const named = bin.playwright ?? bin['playwright-core']
|
|
68
|
+
const first = Object.values(bin).find((value) => typeof value === 'string')
|
|
69
|
+
binPath =
|
|
70
|
+
typeof named === 'string' ? named : typeof first === 'string' ? first : null
|
|
71
|
+
}
|
|
72
|
+
if (!binPath) continue
|
|
73
|
+
const cliPath = join(dirname(packageJsonPath), binPath)
|
|
74
|
+
if (!existsSync(cliPath)) continue
|
|
75
|
+
return {
|
|
76
|
+
spec,
|
|
77
|
+
modulePath,
|
|
78
|
+
cliPath,
|
|
79
|
+
version: parsed.version,
|
|
80
|
+
}
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
83
|
+
} catch {}
|
|
84
|
+
}
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type BrowserProbeResult =
|
|
89
|
+
| { ok: true; executablePath: string; exists: boolean }
|
|
90
|
+
| { ok: false; message: string }
|
|
91
|
+
|
|
92
|
+
function probeBrowser(
|
|
93
|
+
resolved: ResolvedPlaywright,
|
|
94
|
+
env: NodeJS.ProcessEnv,
|
|
95
|
+
): BrowserProbeResult {
|
|
96
|
+
const probe = spawnSync(process.execPath, ['-e', PLAYWRIGHT_BROWSER_PROBE], {
|
|
97
|
+
env: {
|
|
98
|
+
...env,
|
|
99
|
+
SOOTSIM_PW_MODULE: resolved.modulePath,
|
|
100
|
+
},
|
|
101
|
+
encoding: 'utf8',
|
|
102
|
+
timeout: 30_000,
|
|
103
|
+
})
|
|
104
|
+
if (probe.status !== 0) {
|
|
105
|
+
const detail = (probe.stderr || probe.error?.message || 'browser probe failed').trim()
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
message: `could not inspect ${resolved.spec}@${resolved.version} chromium: ${detail}`,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const parsed: unknown = JSON.parse(probe.stdout)
|
|
113
|
+
if (
|
|
114
|
+
!isRecord(parsed) ||
|
|
115
|
+
typeof parsed.executablePath !== 'string' ||
|
|
116
|
+
typeof parsed.exists !== 'boolean'
|
|
117
|
+
) {
|
|
118
|
+
throw new Error('probe returned an invalid result')
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
ok: true,
|
|
122
|
+
executablePath: parsed.executablePath,
|
|
123
|
+
exists: parsed.exists && existsSync(parsed.executablePath),
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
message: `could not inspect ${resolved.spec}@${resolved.version} chromium: ${
|
|
129
|
+
error instanceof Error ? error.message : String(error)
|
|
130
|
+
}`,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function ensurePlaywrightBrowser(
|
|
136
|
+
resolved: ResolvedPlaywright,
|
|
137
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
138
|
+
): PlaywrightBrowserProvisioningResult {
|
|
139
|
+
const before = probeBrowser(resolved, env)
|
|
140
|
+
if (!before.ok) return before
|
|
141
|
+
if (before.exists) {
|
|
142
|
+
return { ok: true, executablePath: before.executablePath, installed: false }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// the resolved package is the authority for both the required executable
|
|
146
|
+
// path and the installer. never scan a shared cache or select a neighboring
|
|
147
|
+
// revision: Playwright's own CLI installs the exact revision its API named.
|
|
148
|
+
const install = spawnSync(process.execPath, [resolved.cliPath, 'install', 'chromium'], {
|
|
149
|
+
env,
|
|
150
|
+
encoding: 'utf8',
|
|
151
|
+
timeout: 10 * 60_000,
|
|
152
|
+
})
|
|
153
|
+
if (install.status !== 0) {
|
|
154
|
+
const rawDetail = (
|
|
155
|
+
install.stderr ||
|
|
156
|
+
install.stdout ||
|
|
157
|
+
install.error?.message ||
|
|
158
|
+
'browser install failed'
|
|
159
|
+
).trim()
|
|
160
|
+
const detail = rawDetail.length <= 4_000 ? rawDetail : rawDetail.slice(-4_000)
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
message:
|
|
164
|
+
`${resolved.spec}@${resolved.version} requires chromium at ` +
|
|
165
|
+
`${before.executablePath}, but its browser install failed${detail ? `:\n${detail}` : ''}`,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const after = probeBrowser(resolved, env)
|
|
170
|
+
if (!after.ok) return after
|
|
171
|
+
if (!after.exists) {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
message:
|
|
175
|
+
`${resolved.spec}@${resolved.version} browser install completed, but its required ` +
|
|
176
|
+
`chromium executable is still missing at ${after.executablePath}`,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return { ok: true, executablePath: after.executablePath, installed: true }
|
|
180
|
+
}
|