rnxsim 0.0.0 → 0.1.312
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 +21 -0
- package/README.md +512 -0
- package/detox/colors.ts +54 -0
- package/detox/config-loader.ts +135 -0
- package/detox/element-types.ts +39 -0
- package/detox/expectations.ts +537 -0
- package/detox/gestures.ts +480 -0
- package/detox/index.ts +2118 -0
- package/detox/jest-preset.cjs +51 -0
- package/detox/jest-setup-after-env.cjs +18 -0
- package/detox/matchers.ts +30 -0
- package/detox/motion-change.cjs +109 -0
- package/detox/motion-change.d.cts +35 -0
- package/detox/navigation.ts +43 -0
- package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
- package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
- package/detox/screenshots/color-test-rest-norngh.png +0 -0
- package/detox/screenshots/theme-blue-toggle.png +0 -0
- package/detox/screenshots/theme-blue.png +0 -0
- package/detox/screenshots/theme-red-toggle.png +0 -0
- package/detox/screenshots/theme-red.png +0 -0
- package/dist-cli/bin.js +15 -0
- package/dist-cli/chunks/agent-NXVDDCFG.js +48 -0
- package/dist-cli/chunks/agent-wrapper-DXJCFKL3.js +17 -0
- package/dist-cli/chunks/app-fonts-W5AJZQJF.js +4 -0
- package/dist-cli/chunks/assert-3LOJEUDT.js +49 -0
- package/dist-cli/chunks/auth-MFTYYPNX.js +12 -0
- package/dist-cli/chunks/beta-GT3NN67B.js +4 -0
- package/dist-cli/chunks/camera-6EAK7CUL.js +22 -0
- package/dist-cli/chunks/chunk-274UD5FD.js +4 -0
- package/dist-cli/chunks/chunk-2SA2VTGP.js +5 -0
- package/dist-cli/chunks/chunk-33FKQH54.js +5 -0
- package/dist-cli/chunks/chunk-37I5AIS7.js +3 -0
- package/dist-cli/chunks/chunk-3D57IBPC.js +4 -0
- package/dist-cli/chunks/chunk-4BXCZHAU.js +4 -0
- package/dist-cli/chunks/chunk-5OX7ANRA.js +5 -0
- package/dist-cli/chunks/chunk-66NF2KNG.js +9 -0
- package/dist-cli/chunks/chunk-6UKJB2VC.js +125 -0
- package/dist-cli/chunks/chunk-7INBNA3U.js +26 -0
- package/dist-cli/chunks/chunk-7VWOSTD5.js +5 -0
- package/dist-cli/chunks/chunk-AY47WKS5.js +23 -0
- package/dist-cli/chunks/chunk-B7KMNHX7.js +4 -0
- package/dist-cli/chunks/chunk-BKNOLP3J.js +4 -0
- package/dist-cli/chunks/chunk-BRIZOR4K.js +28 -0
- package/dist-cli/chunks/chunk-DM6KUAOZ.js +4 -0
- package/dist-cli/chunks/chunk-EUAVBTKU.js +4 -0
- package/dist-cli/chunks/chunk-FTC4M26Q.js +4 -0
- package/dist-cli/chunks/chunk-FWYYCUAZ.js +4 -0
- package/dist-cli/chunks/chunk-G74YIEVS.js +7 -0
- package/dist-cli/chunks/chunk-GZDHEONW.js +507 -0
- package/dist-cli/chunks/chunk-HG7ORJMT.js +5 -0
- package/dist-cli/chunks/chunk-HMDOLCKG.js +15 -0
- package/dist-cli/chunks/chunk-JG6S4RSH.js +4 -0
- package/dist-cli/chunks/chunk-KR5BGMBZ.js +4 -0
- package/dist-cli/chunks/chunk-KZW5WBGU.js +983 -0
- package/dist-cli/chunks/chunk-L24Z3CRS.js +30 -0
- package/dist-cli/chunks/chunk-LDQ7HVCW.js +4 -0
- package/dist-cli/chunks/chunk-MENV7VLD.js +4 -0
- package/dist-cli/chunks/chunk-MNGQ42QE.js +568 -0
- package/dist-cli/chunks/chunk-N6A3SXMR.js +4 -0
- package/dist-cli/chunks/chunk-NLEMQ4PY.js +6 -0
- package/dist-cli/chunks/chunk-NPVGMH2N.js +101 -0
- package/dist-cli/chunks/chunk-OR3XTXEB.js +107 -0
- package/dist-cli/chunks/chunk-PEPUGJVI.js +4 -0
- package/dist-cli/chunks/chunk-Q3RGLERJ.js +142 -0
- package/dist-cli/chunks/chunk-SI3BUTQR.js +4 -0
- package/dist-cli/chunks/chunk-T4FT6CM7.js +4 -0
- package/dist-cli/chunks/chunk-TBMVBI26.js +7 -0
- package/dist-cli/chunks/chunk-TCWQJILS.js +5 -0
- package/dist-cli/chunks/chunk-TGQNQ6XT.js +766 -0
- package/dist-cli/chunks/chunk-TYE3WBSR.js +5 -0
- package/dist-cli/chunks/chunk-U4PN6JOT.js +6 -0
- package/dist-cli/chunks/chunk-VUFRB347.js +4 -0
- package/dist-cli/chunks/chunk-VZBLFMYC.js +10 -0
- package/dist-cli/chunks/chunk-WBT32R6N.js +25 -0
- package/dist-cli/chunks/chunk-XRGEEPJ5.js +38 -0
- package/dist-cli/chunks/chunk-Y2FSZSO7.js +71 -0
- package/dist-cli/chunks/chunk-YRV7HR3P.js +74 -0
- package/dist-cli/chunks/chunk-YZRFF4YA.js +14 -0
- package/dist-cli/chunks/chunk-ZMZTHU7V.js +4 -0
- package/dist-cli/chunks/cleanup-H3GWQB63.js +25 -0
- package/dist-cli/chunks/cli-version-3QPAJ3LC.js +4 -0
- package/dist-cli/chunks/compat-5WG2XJV7.js +20 -0
- package/dist-cli/chunks/config-ANMBVVLL.js +13 -0
- package/dist-cli/chunks/control-NSGNQFJG.js +4 -0
- package/dist-cli/chunks/daemon-JHF4BJ3U.js +4 -0
- package/dist-cli/chunks/debug-CHV2O2KF.js +200 -0
- package/dist-cli/chunks/desktop-FIPIK4CH.js +31 -0
- package/dist-cli/chunks/detox-CVUJP7K6.js +52 -0
- package/dist-cli/chunks/device-6WCUM4W6.js +18 -0
- package/dist-cli/chunks/diagnose-I6EMWEII.js +43 -0
- package/dist-cli/chunks/disk-cleanup-P27CYFTN.js +4 -0
- package/dist-cli/chunks/drivers-HQ6AKZT6.js +4 -0
- package/dist-cli/chunks/film-UOQFFYNF.js +74 -0
- package/dist-cli/chunks/flow-N27MRRNL.js +4 -0
- package/dist-cli/chunks/help-SF6FEGEE.js +4 -0
- package/dist-cli/chunks/hidden-runtime-alias-NJYFSJKZ.js +4 -0
- package/dist-cli/chunks/home-paths-XOV44JEF.js +4 -0
- package/dist-cli/chunks/inspect-CWFRFQTY.js +4 -0
- package/dist-cli/chunks/install-desktop-KLQDNXZQ.js +4 -0
- package/dist-cli/chunks/login-BPFQENCX.js +28 -0
- package/dist-cli/chunks/logout-5A45ZGY7.js +4 -0
- package/dist-cli/chunks/maestro-E4CSDMLM.js +104 -0
- package/dist-cli/chunks/maestro-generate-CKMOJQX2.js +36 -0
- package/dist-cli/chunks/mode-SZYNMIQF.js +26 -0
- package/dist-cli/chunks/optional-demo-registry-FRH4SFVG.js +4 -0
- package/dist-cli/chunks/perf-VXU2XJAX.js +48 -0
- package/dist-cli/chunks/permissions-IRIAOVND.js +13 -0
- package/dist-cli/chunks/record-RY7FPLLH.js +72 -0
- package/dist-cli/chunks/report-issue-KNWXOE53.js +29 -0
- package/dist-cli/chunks/runtime-AR3GJTGK.js +4 -0
- package/dist-cli/chunks/screenshot-command-QLALR6GY.js +159 -0
- package/dist-cli/chunks/screenshot-layers-OQFMFVRL.js +20 -0
- package/dist-cli/chunks/screenshots-capture-6TSRJFGB.js +42 -0
- package/dist-cli/chunks/serve-F54J2NQO.js +44 -0
- package/dist-cli/chunks/setup-7V6UKX4U.js +32 -0
- package/dist-cli/chunks/skills-QQBOSETH.js +50 -0
- package/dist-cli/chunks/storage-TAPMKK5O.js +37 -0
- package/dist-cli/chunks/store-7NX3S43H.js +4 -0
- package/dist-cli/chunks/telemetry-E7BUK6RA.js +4 -0
- package/dist-cli/chunks/timeline-IAM564NA.js +24 -0
- package/dist-cli/chunks/upgrade-CD55QXIX.js +7 -0
- package/dist-cli/chunks/upload-GZ6LIMYM.js +4 -0
- package/dist-cli/chunks/version-3DYJ5AS3.js +6 -0
- package/dist-cli/chunks/web-6NW6YLPI.js +4 -0
- package/dist-cli/chunks/what-happened-CZZDOOVG.js +25 -0
- package/dist-lib/agent-daemon-client.cjs +468 -0
- package/dist-lib/agent-events.cjs +48 -0
- package/dist-lib/agent-identity.cjs +53 -0
- package/dist-lib/agent-sessions.cjs +725 -0
- package/dist-lib/attached-projects.cjs +526 -0
- package/dist-lib/auth/shared-session.cjs +195 -0
- package/dist-lib/backend-origin.cjs +70 -0
- package/dist-lib/beta.cjs +41 -0
- package/dist-lib/beta.mjs +13 -0
- package/dist-lib/bridge-constants.cjs +161 -0
- package/dist-lib/cli-constants.cjs +32 -0
- package/dist-lib/config.cjs +96 -0
- package/dist-lib/detox/index.cjs +2863 -0
- package/dist-lib/detox/jest-preset.cjs +51 -0
- package/dist-lib/dev-bundle-resolution.cjs +223 -0
- package/dist-lib/home-paths.cjs +421 -0
- package/dist-lib/host/bridge-host.cjs +5600 -0
- package/dist-lib/host/fetch-proxy-handler.cjs +373 -0
- package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
- package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
- package/dist-lib/host/websocket-proxy.cjs +214 -0
- package/dist-lib/index.cjs +513 -0
- package/dist-lib/jump-to-source-babel.cjs +93 -0
- package/dist-lib/menu.cjs +620 -0
- package/dist-lib/menu.mjs +581 -0
- package/dist-lib/metro.cjs +297 -0
- package/dist-lib/profiles.cjs +246 -0
- package/dist-lib/public-brand.cjs +39 -0
- package/dist-lib/render-mode.cjs +54 -0
- package/dist-lib/scripts/dev-server-scanner.cjs +1034 -0
- package/dist-lib/sdk.cjs +2333 -0
- package/dist-lib/sdk.mjs +2240 -0
- package/dist-lib/skills.cjs +23911 -0
- package/dist-lib/vite.cjs +358 -0
- package/package.json +189 -18
- package/scripts/dev-server-scanner.ts +941 -0
- package/scripts/optional-demo-registry.ts +113 -0
- package/scripts/postinstall.cjs +3 -0
- package/scripts/preuninstall.cjs +118 -0
- package/skills/contrast/SKILL.md +171 -0
- package/skills/rnx-debug/SKILL.md +425 -0
- package/skills/rnx-perf/SKILL.md +295 -0
- package/skills/rnx-setup/SKILL.md +160 -0
- package/skills/rnx-test/SKILL.md +292 -0
- package/skills/rnx-visual/SKILL.md +122 -0
- package/src/agent-daemon-client.ts +410 -0
- package/src/agent-events.ts +71 -0
- package/src/agent-identity.ts +23 -0
- package/src/agent-prompt.ts +71 -0
- package/src/agent-sessions.ts +573 -0
- package/src/attached-projects.ts +539 -0
- package/src/auth/shared-session.ts +204 -0
- package/src/backend-origin.ts +49 -0
- package/src/beta.ts +19 -0
- package/src/bridge-constants.ts +171 -0
- package/src/browser-cache.ts +16 -0
- package/src/cli-constants.ts +1 -0
- package/src/cli-version.ts +32 -0
- package/src/codex-client.ts +215 -0
- package/src/config.ts +134 -0
- package/src/dev-bundle-resolution.ts +236 -0
- package/src/disk-cleanup.ts +466 -0
- package/src/home-paths.ts +552 -0
- package/src/host/agent-host.ts +666 -0
- package/src/host/bridge-host.ts +2626 -0
- package/src/host/fetch-proxy-handler.ts +397 -0
- package/src/host/fetch-proxy-overrides.ts +51 -0
- package/src/host/open-url.ts +89 -0
- package/src/host/websocket-proxy.ts +210 -0
- package/src/index.ts +12 -705
- package/src/jump-to-source-babel.ts +135 -0
- package/src/menu.ts +734 -0
- package/src/metro-plugin.ts +139 -0
- package/src/native-dev-bundle-url.ts +96 -0
- package/src/native-seam-manifest.ts +341 -0
- package/src/plist.ts +27 -0
- package/src/poll-until.ts +92 -0
- package/src/profiles.ts +179 -0
- package/src/public-brand.ts +9 -0
- package/src/render-mode.ts +27 -0
- package/src/runtime-assets.ts +228 -0
- package/src/runtime-delivery.ts +54 -0
- package/src/screenshots/compose.ts +422 -0
- package/src/screenshots/frame-compose.ts +454 -0
- package/src/screenshots/orchestrate.ts +256 -0
- package/src/screenshots/org-deck.ts +91 -0
- package/src/screenshots/registry.ts +58 -0
- package/src/screenshots/schema.ts +364 -0
- package/src/sdk.ts +7 -0
- package/src/settings-bundle.ts +418 -0
- package/src/skills/builtin/a11y-review.ts +132 -0
- package/src/skills/builtin/compat-check.ts +71 -0
- package/src/skills/builtin/maestro-test.ts +121 -0
- package/src/skills/builtin/perf-profile.ts +90 -0
- package/src/skills/builtin/screenshot-all.ts +52 -0
- package/src/skills/builtin/visual-diff.ts +100 -0
- package/src/skills/registry.ts +107 -0
- package/src/skills/types.ts +41 -0
- package/src/team-machine-prompt.ts +73 -0
- package/src/vite-plugin-one.ts +190 -0
- package/src/vite-plugin.ts +1750 -0
- package/src/worklets-babel.ts +134 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*! rnx v0.1.312 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import { createRequire as __sootsimCreateRequire } from 'node:module'
|
|
3
|
+
var require = __sootsimCreateRequire(import.meta.url)
|
|
4
|
+
import{execFileSync as h,spawn as g}from"child_process";import{existsSync as s,readdirSync as k}from"fs";import{dirname as m,join as l,resolve as a}from"path";import{fileURLToPath as w}from"url";var c;function p(){if(c!==void 0)return c;try{c=m(w(import.meta.resolve("sootsim-engine/package.json")))}catch{c=null}return c}var v="dev.sootsim.simulator";function I(){let e=p();if(!e)return null;let n=l(e,"dist-electron/main.cjs");if(!s(n))return null;let i=P(e);return i?{path:i,platform:process.platform,kind:"dev-electron",engineDir:e}:null}function x(){let e=p();return e?s(l(e,"src-electron/main.ts")):!1}function P(e){let n=e;for(let i=0;i<6;i++){let t=l(n,"node_modules/.bin/electron");if(s(t))return t;let r=m(n);if(r===n)break;n=r}return null}function b(){let e=p(),i=["/Applications/sootsim.app",a(process.env.HOME||"","Applications/sootsim.app"),...e?[a(e,"app/sootsim.app")]:[]].find(t=>s(t));if(i)return{path:i,platform:"darwin",kind:"mac-app"};try{let t=h("mdfind",[`kMDItemCFBundleIdentifier == "${v}"`],{encoding:"utf8",timeout:3e3}).trim();if(t)return{path:t.split(`
|
|
5
|
+
`)[0],platform:"darwin",kind:"mac-app"}}catch{}return null}function f(e){if(!s(e))return null;let n=k(e).filter(i=>i.startsWith("sootsim")&&i.endsWith(".AppImage")).sort().reverse()[0];return n?l(e,n):null}function D(){let e=p(),i=[a(process.env.HOME||"","Applications/sootsim.AppImage"),a(process.env.HOME||"",".local/bin/sootsim.AppImage"),"/opt/sootsim/sootsim.AppImage","/opt/sootsim/sootsim",...e?[a(e,"app/sootsim.AppImage"),a(e,"release/linux-unpacked/sootsim")]:[]].find(r=>s(r));if(i)return{path:i,platform:"linux",kind:i.endsWith(".AppImage")?"linux-appimage":"linux-binary"};let t=e?f(a(e,"app"))||f(a(e,"release")):null;return t?{path:t,platform:"linux",kind:"linux-appimage"}:null}function C(){let e=I();if(e)return e;if(x()){let n=p(),i=l(n,"dist-electron/main.cjs");return s(i)?console.error(` no node_modules/.bin/electron found near ${n}. run \`bun install\`.`):console.error(` dist-electron/main.cjs missing in ${n}.
|
|
6
|
+
run: bun run --cwd packages/sootsim-engine build:electron-main
|
|
7
|
+
(or keep it fresh with: bun run watch:sootsim:electron-main)`),null}return process.platform==="darwin"?b():process.platform==="linux"?D():null}async function d(e,n,i={}){let t=i.inheritStdio?["ignore","inherit","inherit"]:"ignore";await new Promise((r,o)=>{let u=g(e,n,{detached:!0,stdio:t});u.once("error",o),u.once("spawn",()=>{u.unref(),r()})})}async function O(e,n=C(),i={}){if(!n)return{launched:!1};let t=[];if(i.device&&t.push(`--device=${i.device}`),i.profileId&&t.push(`--profile=${i.profileId}`),i.ephemeralProfile&&t.push("--ephemeral-profile"),i.ownerPid&&i.ownerPid>1&&t.push(`--owner-pid=${i.ownerPid}`),n.kind==="mac-app"){let o=["-g","-a",n.path];return e&&o.push(A(e,i)),t.length>0&&o.push("--args",...t),await d("open",o),{launched:!0,via:"mac-app",target:n.path}}if(n.kind==="dev-electron"){let o=["--no-sandbox",n.engineDir||p()];return e&&o.push(e),t.length>0&&o.push(...t),await d(n.path,o,{inheritStdio:!0}),{launched:!0,via:"dev-electron",target:n.path}}let r=[];return e&&r.push(e),t.length>0&&r.push(...t),await d(n.path,r),{launched:!0,via:n.kind,target:n.path}}function A(e,n){if(!n.profileId&&!n.ephemeralProfile&&!n.ownerPid)return e;let i=new URL("sootsim://dev");return i.searchParams.set("url",e),n.profileId&&i.searchParams.set("profile",n.profileId),n.ephemeralProfile&&i.searchParams.set("ephemeral","1"),n.ownerPid&&n.ownerPid>1&&i.searchParams.set("ownerPid",String(n.ownerPid)),i.toString()}export{C as a,O as b};
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/*! rnx v0.1.312 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import { createRequire as __sootsimCreateRequire } from 'node:module'
|
|
3
|
+
var require = __sootsimCreateRequire(import.meta.url)
|
|
4
|
+
import{a as y,b as M}from"./chunk-G74YIEVS.js";import{a as A}from"./chunk-B7KMNHX7.js";import{b as C}from"./chunk-7VWOSTD5.js";import{G as T,o as E,q as b}from"./chunk-66NF2KNG.js";import v from"fs";import S from"path";var h="default",H="profiles.json",u=1;function d(e){let t=e.trim();if(!t)throw new Error("profile id is required");if(!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(t))throw new Error("profile ids must start with a letter or number and contain only letters, numbers, dot, dash, or underscore");if(t==="."||t==="..")throw new Error(`invalid profile id: ${t}`);return t}function P(){return S.join(b(),H)}function z(e){return`sootsim-profile-${d(e)}`}function J(e){return S.join(E(),"Partitions",z(e))}function x(e){return S.join(b(),"playwright",d(e))}function k(){try{let e=JSON.parse(v.readFileSync(P(),"utf8"));if(!e||e.version!==u)return{version:u,profiles:[]};let t=Array.isArray(e.profiles)?e.profiles.filter(r=>!!r&&typeof r.id=="string"&&typeof r.createdAt=="string"&&typeof r.updatedAt=="string").map(r=>({id:d(r.id),createdAt:r.createdAt,updatedAt:r.updatedAt})):[];return{version:u,profiles:t}}catch{return{version:u,profiles:[]}}}function R(e){return[...e].sort((t,r)=>t.id===h?-1:r.id===h?1:t.id.localeCompare(r.id))}function O(e){if(e.profiles.some(r=>r.id===h))return{...e,profiles:R(e.profiles)};let t=new Date().toISOString();return{version:u,profiles:R([{id:h,createdAt:t,updatedAt:t},...e.profiles])}}function D(e){T();let t=O(e),r=`${P()}.tmp`;return v.writeFileSync(r,`${JSON.stringify(t,null,2)}
|
|
5
|
+
`,"utf8"),v.renameSync(r,P()),t}function K(){return D(k()).profiles}function Y(e){let t=d(e);return K().find(r=>r.id===t)??null}function L(e=h){let t=d(e),r=Y(t);return r||V(t)}function V(e){let t=d(e),r=O(k());if(r.profiles.some(i=>i.id===t))throw new Error(`profile already exists: ${t}`);let o=new Date().toISOString(),n={id:t,createdAt:o,updatedAt:o};return D({version:u,profiles:[...r.profiles,n]}),n}function ve(e){let t=d(e);if(t===h)throw new Error("the default profile cannot be deleted; clear it instead");let r=O(k()),o=r.profiles.find(n=>n.id===t);if(!o)throw new Error(`profile not found: ${t}`);return D({version:u,profiles:r.profiles.filter(n=>n.id!==t)}),X(t),o}function X(e){let t=d(e);for(let r of[J(t),x(t)])try{v.rmSync(r,{recursive:!0,force:!0})}catch{}}function Z(){let e=y();return e?{available:!0,reason:null,detail:`${e.kind} @ ${e.path}`}:{available:!1,reason:"rnxsim desktop app not installed (run `rnxsim desktop install`)"}}async function Q(e){let t=y();if(!t)return{launched:!1,message:"rnxsim desktop app not installed"};try{let r=await M(e.url,t,{device:e.device,profileId:e.profileId,ephemeralProfile:e.ephemeralProfile,ownerPid:e.ownerPid});return r.launched?{launched:!0,message:e.url?`electron launched via ${r.via} \u2192 ${e.url}`:`electron launched via ${r.via}`,target:r.target,attachUrl:e.url}:{launched:!1,message:"desktop companion failed to start"}}catch(r){return{launched:!1,message:`electron launch failed: ${r instanceof Error?r.message:String(r)}`}}}var W={id:"electron",name:"electron",description:"rnxsim desktop companion app (native window, menu bar)",kind:"native",availability:Z,launch:Q};import{spawn as ie}from"child_process";import{closeSync as se,mkdtempSync as ae,openSync as le,readFileSync as ce}from"fs";import{tmpdir as G}from"os";import{join as B}from"path";import{spawnSync as $}from"node:child_process";import{existsSync as F,readFileSync as ee}from"node:fs";import{createRequire as re}from"node:module";import{dirname as te,join as oe}from"node:path";var ne=`
|
|
6
|
+
const { existsSync } = require('node:fs');
|
|
7
|
+
const modulePath = process.env.SOOTSIM_PW_MODULE;
|
|
8
|
+
try {
|
|
9
|
+
const playwright = require(modulePath);
|
|
10
|
+
const browser = playwright && playwright.chromium;
|
|
11
|
+
if (!browser || typeof browser.executablePath !== 'function') {
|
|
12
|
+
throw new Error('resolved package does not expose browser "chromium"');
|
|
13
|
+
}
|
|
14
|
+
const executablePath = browser.executablePath();
|
|
15
|
+
process.stdout.write(JSON.stringify({ executablePath, exists: existsSync(executablePath) }));
|
|
16
|
+
} catch (error) {
|
|
17
|
+
process.stderr.write(String((error && error.stack) || error));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
`;function _(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function I(){let e=[`${process.cwd()}/`];process.argv[1]&&e.push(process.argv[1]);for(let t of e)try{let r=re(t);for(let o of["playwright","playwright-chromium","playwright-core"])try{let n=r.resolve(o),i=r.resolve(`${o}/package.json`),a=JSON.parse(ee(i,"utf8"));if(!_(a)||typeof a.version!="string")continue;let l=a.bin,s=null;if(typeof l=="string")s=l;else if(_(l)){let f=l.playwright??l["playwright-core"],g=Object.values(l).find(c=>typeof c=="string");s=typeof f=="string"?f:typeof g=="string"?g:null}if(!s)continue;let p=oe(te(i),s);if(!F(p))continue;return{spec:o,modulePath:n,cliPath:p,version:a.version}}catch{}}catch{}return null}function N(e,t){let r=$(process.execPath,["-e",ne],{env:{...t,SOOTSIM_PW_MODULE:e.modulePath},encoding:"utf8",timeout:3e4});if(r.status!==0){let o=(r.stderr||r.error?.message||"browser probe failed").trim();return{ok:!1,message:`could not inspect ${e.spec}@${e.version} chromium: ${o}`}}try{let o=JSON.parse(r.stdout);if(!_(o)||typeof o.executablePath!="string"||typeof o.exists!="boolean")throw new Error("probe returned an invalid result");return{ok:!0,executablePath:o.executablePath,exists:o.exists&&F(o.executablePath)}}catch(o){return{ok:!1,message:`could not inspect ${e.spec}@${e.version} chromium: ${o instanceof Error?o.message:String(o)}`}}}function U(e,t=process.env){let r=N(e,t);if(!r.ok)return r;if(r.exists)return{ok:!0,executablePath:r.executablePath,installed:!1};let o=$(process.execPath,[e.cliPath,"install","chromium"],{env:t,encoding:"utf8",timeout:10*6e4});if(o.status!==0){let i=(o.stderr||o.stdout||o.error?.message||"browser install failed").trim(),a=i.length<=4e3?i:i.slice(-4e3);return{ok:!1,message:`${e.spec}@${e.version} requires chromium at ${r.executablePath}, but its browser install failed${a?`:
|
|
21
|
+
${a}`:""}`}}let n=N(e,t);return n.ok?n.exists?{ok:!0,executablePath:n.executablePath,installed:!0}:{ok:!1,message:`${e.spec}@${e.version} browser install completed, but its required chromium executable is still missing at ${n.executablePath}`}:n}var de=`
|
|
22
|
+
const modulePath = process.env.SOOTSIM_PW_MODULE;
|
|
23
|
+
const url = process.env.SOOTSIM_PW_URL;
|
|
24
|
+
// the WS bridge port the sim page must register with, injected below as
|
|
25
|
+
// window.__sootsimBridgePort so the page skips its shell-port heuristic (which
|
|
26
|
+
// assumes the default offset and breaks when the dev bridge drifted).
|
|
27
|
+
const bridgePort = process.env.SOOTSIM_PW_BRIDGE_PORT || '';
|
|
28
|
+
const headless = process.env.SOOTSIM_PW_HEADLESS === '1';
|
|
29
|
+
const callerUserDataDir = process.env.SOOTSIM_PW_USERDATADIR || '';
|
|
30
|
+
// pid of the session that launched us (its topmost non-init ancestor). when
|
|
31
|
+
// that process exits we tear Chrome down \u2014 see startOwnerWatch below. it is a
|
|
32
|
+
// required lifetime bound: an invalid value aborts before Chrome launches.
|
|
33
|
+
// SOOTSIM_PW_OWNER_POLL_MS is a test-only override of the 5s poll interval;
|
|
34
|
+
// production never sets it.
|
|
35
|
+
const ownerPid = Number(process.env.SOOTSIM_PW_OWNER_PID || 0);
|
|
36
|
+
const ownerPollMs = Number(process.env.SOOTSIM_PW_OWNER_POLL_MS || 5000);
|
|
37
|
+
// optional CDP port \u2014 exposes Chrome's remote-debugging endpoint so a
|
|
38
|
+
// bridge-driven sim can also be cpu-profiled (sootsim perf cpu --cdp-port).
|
|
39
|
+
// playwright keeps its own pipe connection; the port is an additional endpoint.
|
|
40
|
+
const cdpPort = process.env.SOOTSIM_PW_CDP_PORT || '';
|
|
41
|
+
// extra chromium launch args, space-separated. a linux GPU runner injects
|
|
42
|
+
// '--use-angle=vulkan --enable-features=Vulkan' here so chrome binds the
|
|
43
|
+
// real GPU instead of silently falling back to swiftshader software
|
|
44
|
+
// rendering (which pegs cores and produces ~1fps canvas capture).
|
|
45
|
+
const extraChromeArgs = (process.env.SOOTSIM_PW_CHROME_ARGS || '')
|
|
46
|
+
.split(' ')
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
// Local CanvasKit work must never fall back to Playwright's software
|
|
49
|
+
// renderer. CI may use the bundled browser for non-interactive coverage, but
|
|
50
|
+
// local test drives fail closed unless the hardware-backed test browser is
|
|
51
|
+
// proven after launch.
|
|
52
|
+
const hardwareGpuRequired = process.env.CI !== '1' && process.env.CI !== 'true';
|
|
53
|
+
const softwareRendererPattern = /swiftshader|software(?: rasterizer)?|llvmpipe|lavapipe|softpipe/i;
|
|
54
|
+
// page viewport as '<w>x<h>'. the caller sizes it from the device profile;
|
|
55
|
+
// the fallback is phone-shaped because the page hosts a phone sim \u2014 the
|
|
56
|
+
// playwright default (1280x720 desktop) letterboxes the device frame.
|
|
57
|
+
const viewportSpec = /^(\\d+)x(\\d+)$/.exec(process.env.SOOTSIM_PW_VIEWPORT || '');
|
|
58
|
+
const viewport = viewportSpec
|
|
59
|
+
? { width: Number(viewportSpec[1]), height: Number(viewportSpec[2]) }
|
|
60
|
+
: { width: 600, height: 900 };
|
|
61
|
+
// device pixels per css pixel. playwright's default is 1, which makes the
|
|
62
|
+
// engine allocate its canvas backing store at HALF the resolution a normal
|
|
63
|
+
// retina-mac chrome would (dpr 2) \u2014 the sim looks visibly soft. default to 2
|
|
64
|
+
// so a driver-launched sim matches what the user sees in their own browser.
|
|
65
|
+
const deviceScaleFactor = Number(process.env.SOOTSIM_PW_DSF || 2);
|
|
66
|
+
const closeTimeoutMs = Number(process.env.SOOTSIM_PW_CLOSE_TIMEOUT_MS || 2500);
|
|
67
|
+
const connectAckFile = process.env.SOOTSIM_PW_CONNECTED_ACK_FILE || '';
|
|
68
|
+
const disposableBrowserCacheDirs = new Set(${JSON.stringify(A)});
|
|
69
|
+
const { existsSync, mkdtempSync, readdirSync, rmSync, unlinkSync } = require('fs');
|
|
70
|
+
const { execSync } = require('child_process');
|
|
71
|
+
const { tmpdir } = require('os');
|
|
72
|
+
const { join } = require('path');
|
|
73
|
+
// chrome tree reaper \u2014 playwright's browser.close() and even SIGKILLing the
|
|
74
|
+
// root chrome process do NOT reliably take down the helper tree (gpu, renderer,
|
|
75
|
+
// network/storage utilities). they reparent to launchd/init and survive, each
|
|
76
|
+
// one busy enough to peg CPU at 100%+. close() only reaches the root.
|
|
77
|
+
//
|
|
78
|
+
// the prior approach was \`pgrep -f ms-playwright/[c]hromium\`. it never matched
|
|
79
|
+
// system Chrome, so the reaper was a no-op in the common case and every
|
|
80
|
+
// aborted/closed \`sootsim open\` leaked a renderer + gpu helper burning 100%+
|
|
81
|
+
// CPU until manually killed (multiple incidents 2026-06).
|
|
82
|
+
//
|
|
83
|
+
// fix: identify our chrome tree by its UNIQUE user-data-dir. every helper a
|
|
84
|
+
// chrome root spawns inherits \`--user-data-dir=<dir>\` in its cmdline, so
|
|
85
|
+
// \`pgrep -f <profileDir>\` finds exactly the tree we launched and nothing else
|
|
86
|
+
// \u2014 Chrome for Testing and bundled playwright chromium alike. as a side benefit we
|
|
87
|
+
// no longer need the launch / launchPersistentContext fork: we ALWAYS use a
|
|
88
|
+
// profile dir (mkdtemp'd if the caller didn't pass one, then removed at exit).
|
|
89
|
+
const reapEnabled = process.platform !== 'win32';
|
|
90
|
+
let profileDir = callerUserDataDir;
|
|
91
|
+
let ephemeralProfile = false;
|
|
92
|
+
if (!profileDir) {
|
|
93
|
+
profileDir = mkdtempSync(join(tmpdir(), 'sootsim-playwright-host-'));
|
|
94
|
+
ephemeralProfile = true;
|
|
95
|
+
}
|
|
96
|
+
// browser HTTP caches are acceleration data, not profile identity. give each
|
|
97
|
+
// host a bounded temporary cache so persistent named profiles retain cookies
|
|
98
|
+
// and storage without multiplying a private cache for every profile.
|
|
99
|
+
const diskCacheDir = mkdtempSync(join(tmpdir(), 'sootsim-playwright-cache-'));
|
|
100
|
+
const chromePidsByProfile = () => {
|
|
101
|
+
if (!reapEnabled) return [];
|
|
102
|
+
try {
|
|
103
|
+
return execSync('pgrep -f ' + profileDir + ' || true', { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
104
|
+
.toString().split('\\n').filter(Boolean).map((p) => Number(p))
|
|
105
|
+
.filter((p) => p && p !== process.pid);
|
|
106
|
+
} catch { return []; }
|
|
107
|
+
};
|
|
108
|
+
const chromeCommandsByProfile = () => {
|
|
109
|
+
if (!reapEnabled) return [];
|
|
110
|
+
const pids = chromePidsByProfile();
|
|
111
|
+
if (pids.length === 0) return [];
|
|
112
|
+
try {
|
|
113
|
+
return execSync('ps -p ' + pids.join(',') + ' -o command=', {
|
|
114
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
115
|
+
}).toString().split('\\n').filter(Boolean);
|
|
116
|
+
} catch { return []; }
|
|
117
|
+
};
|
|
118
|
+
const assertNoUnsafeSwiftShaderFlag = () => {
|
|
119
|
+
if (!hardwareGpuRequired) return;
|
|
120
|
+
const unsafe = chromeCommandsByProfile().find((command) =>
|
|
121
|
+
command.includes('--enable-unsafe-swiftshader') ||
|
|
122
|
+
/--use-angle=(?:swiftshader|swiftshader-webgl)(?:\\s|$)/i.test(command)
|
|
123
|
+
);
|
|
124
|
+
if (unsafe) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
'[playwright-driver] refused software-rendering Chrome: launched process contains a SwiftShader flag'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const killTree = (pid) => {
|
|
131
|
+
let kids = [];
|
|
132
|
+
try {
|
|
133
|
+
kids = execSync('pgrep -P ' + pid + ' || true', { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
134
|
+
.toString().split('\\n').filter(Boolean);
|
|
135
|
+
} catch {}
|
|
136
|
+
for (const k of kids) killTree(Number(k));
|
|
137
|
+
try { process.kill(pid, 'SIGKILL'); } catch {}
|
|
138
|
+
};
|
|
139
|
+
const reapChrome = () => { for (const pid of chromePidsByProfile()) killTree(pid); };
|
|
140
|
+
const removeDisposableCaches = (root) => {
|
|
141
|
+
let children = [];
|
|
142
|
+
try { children = readdirSync(root, { withFileTypes: true }); } catch { return; }
|
|
143
|
+
for (const child of children) {
|
|
144
|
+
if (!child.isDirectory() || child.isSymbolicLink()) continue;
|
|
145
|
+
const childPath = join(root, child.name);
|
|
146
|
+
if (disposableBrowserCacheDirs.has(child.name)) {
|
|
147
|
+
try { rmSync(childPath, { recursive: true, force: true }); } catch {}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
removeDisposableCaches(childPath);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const cleanupProfile = () => {
|
|
154
|
+
try { rmSync(diskCacheDir, { recursive: true, force: true }); } catch {}
|
|
155
|
+
if (ephemeralProfile) {
|
|
156
|
+
try { rmSync(profileDir, { recursive: true, force: true }); } catch {}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
removeDisposableCaches(profileDir);
|
|
160
|
+
};
|
|
161
|
+
(async () => {
|
|
162
|
+
const pw = require(modulePath);
|
|
163
|
+
let context;
|
|
164
|
+
let keepAlive = null;
|
|
165
|
+
let connectTimer = null;
|
|
166
|
+
let connectAckPoll = null;
|
|
167
|
+
let ownerWatch = null;
|
|
168
|
+
let connected = false;
|
|
169
|
+
let shuttingDown = false;
|
|
170
|
+
const clearConnectTimer = () => {
|
|
171
|
+
if (connectTimer) {
|
|
172
|
+
clearTimeout(connectTimer);
|
|
173
|
+
connectTimer = null;
|
|
174
|
+
}
|
|
175
|
+
if (connectAckPoll) {
|
|
176
|
+
clearInterval(connectAckPoll);
|
|
177
|
+
connectAckPoll = null;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const markConnected = () => {
|
|
181
|
+
connected = true;
|
|
182
|
+
clearConnectTimer();
|
|
183
|
+
};
|
|
184
|
+
const ackFileExists = () => {
|
|
185
|
+
if (!connectAckFile) return false;
|
|
186
|
+
try { return existsSync(connectAckFile); } catch { return false; }
|
|
187
|
+
};
|
|
188
|
+
const bounded = async (label, work, timeoutMs) => {
|
|
189
|
+
let timer = null;
|
|
190
|
+
try {
|
|
191
|
+
const timeout = new Promise((resolve) => {
|
|
192
|
+
timer = setTimeout(() => resolve({ timedOut: true }), timeoutMs);
|
|
193
|
+
});
|
|
194
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
195
|
+
const result = await Promise.race([
|
|
196
|
+
Promise.resolve().then(work).then(
|
|
197
|
+
() => ({ timedOut: false }),
|
|
198
|
+
(error) => ({ timedOut: false, error }),
|
|
199
|
+
),
|
|
200
|
+
timeout,
|
|
201
|
+
]);
|
|
202
|
+
if (result && result.timedOut) {
|
|
203
|
+
process.stderr.write('[shutdown-timeout] ' + label + ' exceeded ' + timeoutMs + 'ms\\n');
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (result && result.error) {
|
|
207
|
+
process.stderr.write('[shutdown-error] ' + label + ': ' + String((result.error && result.error.message) || result.error) + '\\n');
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
} finally {
|
|
212
|
+
if (timer) clearTimeout(timer);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const browserProcess = () => {
|
|
216
|
+
if (context && typeof context.browser === 'function') {
|
|
217
|
+
try {
|
|
218
|
+
const b = context.browser();
|
|
219
|
+
if (b && typeof b.process === 'function') return b.process();
|
|
220
|
+
} catch {}
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
};
|
|
224
|
+
const processIsAlive = (proc) => {
|
|
225
|
+
if (!proc || !proc.pid) return false;
|
|
226
|
+
try {
|
|
227
|
+
process.kill(proc.pid, 0);
|
|
228
|
+
return true;
|
|
229
|
+
} catch (err) {
|
|
230
|
+
return err && err.code === 'EPERM';
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const terminateBrowserProcess = async () => {
|
|
234
|
+
const proc = browserProcess();
|
|
235
|
+
if (!proc || !proc.pid || !processIsAlive(proc)) return;
|
|
236
|
+
try { proc.kill('SIGTERM'); } catch {}
|
|
237
|
+
await bounded('browser process exit after SIGTERM', async () => {
|
|
238
|
+
while (processIsAlive(proc)) {
|
|
239
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
240
|
+
}
|
|
241
|
+
}, 600);
|
|
242
|
+
if (!processIsAlive(proc)) return;
|
|
243
|
+
try { proc.kill('SIGKILL'); } catch {}
|
|
244
|
+
};
|
|
245
|
+
const shutdown = async (code = 0) => {
|
|
246
|
+
if (shuttingDown) return;
|
|
247
|
+
shuttingDown = true;
|
|
248
|
+
clearConnectTimer();
|
|
249
|
+
if (keepAlive) clearInterval(keepAlive);
|
|
250
|
+
if (ownerWatch) clearInterval(ownerWatch);
|
|
251
|
+
const timeoutMs = Number.isFinite(closeTimeoutMs) && closeTimeoutMs > 0 ? closeTimeoutMs : 2500;
|
|
252
|
+
if (context) await bounded('context.close', () => context.close(), timeoutMs);
|
|
253
|
+
await terminateBrowserProcess();
|
|
254
|
+
// close() + root-SIGKILL above handle the graceful case; the helper tree
|
|
255
|
+
// (gpu/renderer/utilities) reparents and survives every one of those, so
|
|
256
|
+
// we always sweep by our unique profile dir as the load-bearing teardown.
|
|
257
|
+
reapChrome();
|
|
258
|
+
cleanupProfile();
|
|
259
|
+
if (connectAckFile) {
|
|
260
|
+
try { unlinkSync(connectAckFile); } catch {}
|
|
261
|
+
}
|
|
262
|
+
process.exit(code);
|
|
263
|
+
};
|
|
264
|
+
process.once('SIGTERM', () => { void shutdown(0); });
|
|
265
|
+
process.once('SIGINT', () => { void shutdown(0); });
|
|
266
|
+
// the launch-failure path at the bottom of this script exits without going
|
|
267
|
+
// through shutdown(); the exit hook (sync sweep) still reaps the tree.
|
|
268
|
+
process.on('exit', () => { reapChrome(); cleanupProfile(); });
|
|
269
|
+
// owner-liveness watchdog \u2014 the load-bearing fix for leaked Chrome hosts.
|
|
270
|
+
// we are spawned detached + unref'd so \`sootsim open\` returns immediately,
|
|
271
|
+
// which means nothing in the OS process tree ties Chrome's lifetime to the
|
|
272
|
+
// session that asked for it. once the sim connects, the connect-timeout
|
|
273
|
+
// safety net is cleared, so absent an explicit \`sootsim close\` (or the
|
|
274
|
+
// daemon's idle reaper closing the sim socket) the host would hold Chrome
|
|
275
|
+
// open forever after its owner exits. poll the owning session's pid and tear
|
|
276
|
+
// down when it's gone. ESRCH (kill throws) = owner exited; EPERM = alive but
|
|
277
|
+
// not signalable by us, which still counts as alive.
|
|
278
|
+
const startOwnerWatch = () => {
|
|
279
|
+
if (!Number.isInteger(ownerPid) || ownerPid <= 1) {
|
|
280
|
+
throw new Error('[playwright-driver] missing valid owning process; refusing unsupervised browser host');
|
|
281
|
+
}
|
|
282
|
+
const intervalMs = Number.isFinite(ownerPollMs) && ownerPollMs > 0 ? ownerPollMs : 5000;
|
|
283
|
+
ownerWatch = setInterval(() => {
|
|
284
|
+
try {
|
|
285
|
+
process.kill(ownerPid, 0);
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (err && err.code === 'EPERM') return;
|
|
288
|
+
process.stderr.write('[owner-gone] owning process ' + ownerPid + ' exited; closing playwright host\\n');
|
|
289
|
+
void shutdown(0);
|
|
290
|
+
}
|
|
291
|
+
}, intervalMs);
|
|
292
|
+
if (typeof ownerWatch.unref === 'function') ownerWatch.unref();
|
|
293
|
+
};
|
|
294
|
+
startOwnerWatch();
|
|
295
|
+
// Local rendering uses Playwright's full Chrome for Testing app. On macOS its
|
|
296
|
+
// com.google.chrome.for.testing identity cannot intercept open/quit events for
|
|
297
|
+
// the user's com.google.Chrome browser. CI may use chromium_headless_shell;
|
|
298
|
+
// local rendering fails closed because that shell has no GPU and silently
|
|
299
|
+
// chooses SwiftShader. NOTE: this is NOT enough to make \`sootsim perf cpu\`
|
|
300
|
+
// work \u2014 that profiles the tenant *worker*, and the JS Self-Profiler API
|
|
301
|
+
// cannot be constructed in a dedicated worker (see
|
|
302
|
+
// worker-handlers/cpu-profile.ts). cpu-profile needs a CDP-based profiler
|
|
303
|
+
// instead.
|
|
304
|
+
if (
|
|
305
|
+
hardwareGpuRequired &&
|
|
306
|
+
extraChromeArgs.some((arg) =>
|
|
307
|
+
arg === '--enable-unsafe-swiftshader' ||
|
|
308
|
+
/^--use-angle=(?:swiftshader|swiftshader-webgl)$/i.test(arg)
|
|
309
|
+
)
|
|
310
|
+
) {
|
|
311
|
+
throw new Error('[playwright-driver] refused SwiftShader launch arguments');
|
|
312
|
+
}
|
|
313
|
+
// one browser path per environment: the full, separately identified test app
|
|
314
|
+
// locally; chromium_headless_shell on CI. never launch a branded user browser.
|
|
315
|
+
const channelsToTry = hardwareGpuRequired ? ['chromium'] : [null];
|
|
316
|
+
const launchWith = async (channel) => {
|
|
317
|
+
const opts = channel
|
|
318
|
+
? { headless, channel, viewport, deviceScaleFactor }
|
|
319
|
+
: { headless, viewport, deviceScaleFactor };
|
|
320
|
+
// playwright injects --force-color-profile=srgb by default, which reads
|
|
321
|
+
// visibly washed-out next to a normal chrome window on a wide-gamut mac
|
|
322
|
+
// display. strip it so driver-launched sims color-match the user's own
|
|
323
|
+
// browser (real incident 2026-07-09: "low res and whitewashed" sim).
|
|
324
|
+
opts.ignoreDefaultArgs = ['--force-color-profile=srgb'];
|
|
325
|
+
// headless chromium backgrounds/occludes the renderer and throttles rAF +
|
|
326
|
+
// timers to ~1fps, which silently starves combined/video frame capture
|
|
327
|
+
// (was 47 frames over 63s -> frozen-looking mp4). keep the render loop at
|
|
328
|
+
// full rate so the recorder captures real motion. document.hidden stays
|
|
329
|
+
// false in headless, so assertTabVisibleForCapture never caught this \u2014 it
|
|
330
|
+
// has to be fixed at the launch flags.
|
|
331
|
+
opts.args = [
|
|
332
|
+
// the compositor renders through skia graphite on webgpu, so a launch
|
|
333
|
+
// without navigator.gpu boots to a fatal instead of a sim.
|
|
334
|
+
'--enable-unsafe-webgpu',
|
|
335
|
+
'--disk-cache-dir=' + diskCacheDir,
|
|
336
|
+
'--disk-cache-size=67108864',
|
|
337
|
+
'--disable-background-timer-throttling',
|
|
338
|
+
'--disable-backgrounding-occluded-windows',
|
|
339
|
+
'--disable-renderer-backgrounding',
|
|
340
|
+
];
|
|
341
|
+
if (channel) {
|
|
342
|
+
// the full test browser can bind the real platform GPU \u2014 force the native
|
|
343
|
+
// ANGLE backend and strip playwright's --enable-unsafe-swiftshader
|
|
344
|
+
// default arg. without both, headless chrome silently falls back to
|
|
345
|
+
// swiftshader software rendering for the CanvasKit/WebGL canvas and
|
|
346
|
+
// pegs every core. SOOTSIM_PW_CHROME_ARGS is appended later, so an
|
|
347
|
+
// env-injected --use-angle=... still overrides (last flag wins).
|
|
348
|
+
const angle = process.platform === 'darwin' ? 'metal'
|
|
349
|
+
: process.platform === 'linux' ? 'vulkan' : 'default';
|
|
350
|
+
opts.args.unshift(
|
|
351
|
+
'--use-angle=' + angle,
|
|
352
|
+
'--enable-gpu',
|
|
353
|
+
'--enable-gpu-rasterization',
|
|
354
|
+
'--enable-zero-copy',
|
|
355
|
+
'--ignore-gpu-blocklist',
|
|
356
|
+
);
|
|
357
|
+
opts.ignoreDefaultArgs.push('--enable-unsafe-swiftshader');
|
|
358
|
+
} else {
|
|
359
|
+
// playwright's bundled software chromium exposes no navigator.gpu until
|
|
360
|
+
// Vulkan is named explicitly, and webgpu is the only backend there is.
|
|
361
|
+
opts.args.push('--enable-features=Vulkan');
|
|
362
|
+
}
|
|
363
|
+
if (cdpPort) opts.args.push('--remote-debugging-port=' + cdpPort);
|
|
364
|
+
if (extraChromeArgs.length) opts.args.push(...extraChromeArgs);
|
|
365
|
+
// headed: size the OS window to the (phone-shaped) viewport too, plus
|
|
366
|
+
// room for chrome's own toolbar, so the window isn't desktop-shaped
|
|
367
|
+
// around a letterboxed page.
|
|
368
|
+
if (!headless) opts.args.push('--window-size=' + viewport.width + ',' + (viewport.height + 88));
|
|
369
|
+
// ALWAYS persistent: every chrome process inherits --user-data-dir=<dir>
|
|
370
|
+
// in its cmdline, which is how reapChrome() finds the whole tree (root +
|
|
371
|
+
// helpers) on shutdown. without a unique dir there is no way to attribute
|
|
372
|
+
// an orphaned helper back to us, which is the leak we just fixed.
|
|
373
|
+
return await pw.chromium.launchPersistentContext(profileDir, opts);
|
|
374
|
+
};
|
|
375
|
+
let launchErr = null;
|
|
376
|
+
let launchedChannel = null;
|
|
377
|
+
for (const channel of channelsToTry) {
|
|
378
|
+
try {
|
|
379
|
+
context = await launchWith(channel);
|
|
380
|
+
launchedChannel = channel || 'chromium';
|
|
381
|
+
launchErr = null;
|
|
382
|
+
break;
|
|
383
|
+
} catch (err) {
|
|
384
|
+
launchErr = err;
|
|
385
|
+
const msg = err && err.message ? String(err.message) : '';
|
|
386
|
+
const isMissingChannel =
|
|
387
|
+
/not found at/i.test(msg) ||
|
|
388
|
+
/not installed/i.test(msg) ||
|
|
389
|
+
/Chromium distribution/i.test(msg);
|
|
390
|
+
if (!isMissingChannel) throw err; // unexpected failure: bubble up
|
|
391
|
+
if (channel) {
|
|
392
|
+
const next = hardwareGpuRequired
|
|
393
|
+
? '; bundled Chromium is disabled for local GPU work\\n'
|
|
394
|
+
: ', falling back to bundled chromium\\n';
|
|
395
|
+
process.stderr.write('[playwright-driver] channel "' + channel + '" not available' + next);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (launchErr) throw launchErr;
|
|
400
|
+
if (launchedChannel) {
|
|
401
|
+
process.stderr.write('[playwright-driver] launched ' + launchedChannel + '\\n');
|
|
402
|
+
}
|
|
403
|
+
// grant camera up front so the sim's Camera app reaches the host webcam
|
|
404
|
+
// through getUserMedia, mirroring a real iOS simulator where the camera just
|
|
405
|
+
// works. without this the unhandled chrome permission prompt auto-denies and
|
|
406
|
+
// CameraApp renders its "Camera Access Required" placeholder.
|
|
407
|
+
await context.grantPermissions(['camera']);
|
|
408
|
+
const hostMeta = {
|
|
409
|
+
sootsimHostDriver: 'playwright',
|
|
410
|
+
sootsimHostPid: process.pid,
|
|
411
|
+
sootsimHostOwnerPid: ownerPid,
|
|
412
|
+
sootsimHostStartedAt: Date.now(),
|
|
413
|
+
};
|
|
414
|
+
// inject the resolved bridge port (numeric literal) BEFORE the page runs, so
|
|
415
|
+
// the shell/engine talk to the daemon this driver owns. addInitScript runs
|
|
416
|
+
// ahead of every page script, and the vite dev plugin only fills the value in
|
|
417
|
+
// when it is still unset, so this one wins. plain assignment, NOT
|
|
418
|
+
// defineProperty: other injectors also assign to it, and a non-writable
|
|
419
|
+
// property would throw at that assignment and crash boot.
|
|
420
|
+
const bridgePortInject = bridgePort
|
|
421
|
+
? 'window.__sootsimBridgePort = ' + Number(bridgePort) + ';'
|
|
422
|
+
: '';
|
|
423
|
+
await context.addInitScript({
|
|
424
|
+
content: 'Object.defineProperty(window, "__sootsimHostMeta", { value: ' + JSON.stringify(hostMeta) + ', configurable: true });' + bridgePortInject,
|
|
425
|
+
});
|
|
426
|
+
await context.exposeBinding('__sootsimHostClose', async () => {
|
|
427
|
+
await shutdown(0);
|
|
428
|
+
});
|
|
429
|
+
const connectTimeoutMs = Number(process.env.SOOTSIM_PW_CONNECT_TIMEOUT_MS || 120000);
|
|
430
|
+
if (connectAckFile) {
|
|
431
|
+
connectAckPoll = setInterval(() => {
|
|
432
|
+
if (ackFileExists()) markConnected();
|
|
433
|
+
}, 500);
|
|
434
|
+
if (typeof connectAckPoll.unref === 'function') connectAckPoll.unref();
|
|
435
|
+
}
|
|
436
|
+
connectTimer = setTimeout(() => {
|
|
437
|
+
if (ackFileExists()) {
|
|
438
|
+
markConnected();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (connected) return;
|
|
442
|
+
process.stderr.write('[connect-timeout] sim did not register with the bridge within ' + connectTimeoutMs + 'ms; closing playwright host\\n');
|
|
443
|
+
void shutdown(2);
|
|
444
|
+
}, Number.isFinite(connectTimeoutMs) && connectTimeoutMs > 0 ? connectTimeoutMs : 120000);
|
|
445
|
+
if (typeof connectTimer.unref === 'function') connectTimer.unref();
|
|
446
|
+
const page = await context.newPage();
|
|
447
|
+
if (hardwareGpuRequired) {
|
|
448
|
+
assertNoUnsafeSwiftShaderFlag();
|
|
449
|
+
const gpu = await page.evaluate(() => {
|
|
450
|
+
const canvas = document.createElement('canvas');
|
|
451
|
+
const gl =
|
|
452
|
+
canvas.getContext('webgl2', { powerPreference: 'high-performance' }) ||
|
|
453
|
+
canvas.getContext('webgl', { powerPreference: 'high-performance' });
|
|
454
|
+
if (!gl) return { renderer: null, vendor: null };
|
|
455
|
+
const info = gl.getExtension('WEBGL_debug_renderer_info');
|
|
456
|
+
return {
|
|
457
|
+
renderer: info ? String(gl.getParameter(info.UNMASKED_RENDERER_WEBGL)) : null,
|
|
458
|
+
vendor: info ? String(gl.getParameter(info.UNMASKED_VENDOR_WEBGL)) : null,
|
|
459
|
+
};
|
|
460
|
+
});
|
|
461
|
+
if (!gpu.renderer) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
'[playwright-driver] could not prove a hardware WebGL renderer; refusing local render-heavy launch'
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
if (softwareRendererPattern.test(gpu.renderer)) {
|
|
467
|
+
throw new Error('[playwright-driver] refused software WebGL renderer: ' + gpu.renderer);
|
|
468
|
+
}
|
|
469
|
+
assertNoUnsafeSwiftShaderFlag();
|
|
470
|
+
process.stderr.write(
|
|
471
|
+
'[playwright-driver] hardware WebGL renderer: ' + gpu.renderer +
|
|
472
|
+
(gpu.vendor ? ' (' + gpu.vendor + ')' : '') + '\\n'
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
// surface page-side failures (engine boot crash, failed WS registration)
|
|
476
|
+
// to stderr so a sim that never connects produces a real diagnostic
|
|
477
|
+
// instead of a silent "timed out waiting for opened sim to connect".
|
|
478
|
+
page.on('pageerror', (e) => {
|
|
479
|
+
process.stderr.write('[pageerror] ' + ((e && e.stack) || e) + '\\n');
|
|
480
|
+
});
|
|
481
|
+
page.on('console', (m) => {
|
|
482
|
+
if (m.type() === 'error') {
|
|
483
|
+
process.stderr.write('[console.error] ' + m.text() + '\\n');
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
page.on('crash', () => process.stderr.write('[page] renderer crashed\\n'));
|
|
487
|
+
try {
|
|
488
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
489
|
+
} catch (err) {
|
|
490
|
+
process.stderr.write('[goto] ' + String((err && err.message) || err) + '\\n');
|
|
491
|
+
}
|
|
492
|
+
keepAlive = setInterval(() => {}, 1 << 30);
|
|
493
|
+
// browser disconnect (e.g. user killed chrome from outside) \u2192 tear down.
|
|
494
|
+
// we are persistent-only now, so .browser() is the only handle to it.
|
|
495
|
+
try {
|
|
496
|
+
const b = context.browser && context.browser();
|
|
497
|
+
if (b && typeof b.on === 'function') b.on('disconnected', () => { void shutdown(0); });
|
|
498
|
+
} catch {}
|
|
499
|
+
context.on('close', () => { void shutdown(0); });
|
|
500
|
+
page.on('close', () => { void shutdown(0); });
|
|
501
|
+
})().catch((err) => {
|
|
502
|
+
process.stderr.write(String((err && err.stack) || err) + '\\n');
|
|
503
|
+
process.exit(1);
|
|
504
|
+
});
|
|
505
|
+
`;function ue(){let e=I();return e?{available:!0,reason:null,detail:`resolved via ${e.spec}@${e.version}`}:{available:!1,reason:"playwright not installed in the current workspace"}}function he(e,t){return process.platform!=="linux"||e.headless||process.env.DISPLAY||process.env.WAYLAND_DISPLAY||!/missing x server|cannot open display|x server|x11/i.test(t)?null:" hint: linux without DISPLAY/WAYLAND_DISPLAY \u2014 rerun without --headed (or add --headless) to run playwright headless."}function pe(e){return e.profileId?x(L(e.profileId).id):e.ephemeralProfile?ae(B(G(),"sootsim-playwright-profile-")):""}async function fe(e){let t=I();if(!t)return{launched:!1,message:"playwright not installed \u2014 run `bun add -D playwright` first"};if(!e.url)return{launched:!1,message:"playwright driver requires a target url"};let r=e.ownerPid;if(r===void 0||!Number.isInteger(r)||r<=1)return{launched:!1,message:"playwright driver requires a valid owning session process"};let o={...process.env},n=U(t,o);if(!n.ok)return{launched:!1,message:`playwright browser provisioning failed: ${n.message}`};let i=B(G(),`sootsim-playwright-host-${Date.now().toString(36)}-${process.pid}.log`),a=`${i}.connected`,l=le(i,"a");try{let s=ie(process.execPath,["-e",de],{detached:!0,stdio:["ignore","ignore",l],env:{...o,SOOTSIM_PW_MODULE:t.modulePath,SOOTSIM_PW_URL:e.url,SOOTSIM_PW_BRIDGE_PORT:e.bridgePort?String(e.bridgePort):"",SOOTSIM_PW_HEADLESS:e.headless??!0?"1":"0",SOOTSIM_PW_USERDATADIR:pe(e),SOOTSIM_PW_CDP_PORT:e.cdpPort?String(e.cdpPort):"",SOOTSIM_PW_VIEWPORT:e.viewport?`${e.viewport.width}x${e.viewport.height}`:"",SOOTSIM_PW_CONNECTED_ACK_FILE:a,SOOTSIM_PW_CONNECT_TIMEOUT_MS:String(e.connectTimeoutMs??12e4),SOOTSIM_PW_OWNER_PID:String(r)}});s.unref();let p=await new Promise(c=>{let w=setTimeout(()=>c(null),4e3);s.once("exit",q=>{clearTimeout(w),c(q??0)})});if(p!==null){let c=ce(i,"utf8").trim(),w=he(e,c);return{launched:!1,message:`playwright host exited early (code ${p}) \u2014 host log ${i}${c?`:
|
|
506
|
+
${c}`:""}${w?`
|
|
507
|
+
${w}`:""}`}}let f=C(r),g=` \u2014 browser closes when pid ${r}${f?` (${f})`:""} exits`;return{launched:!0,message:`playwright chrome launched \u2192 ${e.url}${g}`,pid:s.pid,target:t.spec,attachUrl:e.url,connectAckFile:a,diagnosticLogPath:i}}catch(s){return{launched:!1,message:`playwright launch failed: ${s instanceof Error?s.message:String(s)}`}}finally{se(l)}}var j={id:"playwright",name:"playwright",description:"programmatic chromium via the playwright package \u2014 headless default",kind:"automation",availability:ue,launch:fe};var m=[W,j];function $e(){return m}function Fe(e){return m.find(t=>t.id===e)??null}function Ue(e,t){if(e)return m.find(o=>o.id===e)??null;for(let r of t){let o=m.find(n=>n.id===r);if(o&&o.availability().available)return o}return null}function Ge(){return m.map(e=>{let t=e.availability();return{id:e.id,name:e.name,kind:e.kind,description:e.description,available:t.available,reason:t.reason,detail:t.detail??null}})}export{K as a,L as b,V as c,ve as d,X as e,W as f,j as g,m as h,$e as i,Fe as j,Ue as k,Ge as l};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/*! rnx v0.1.312 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import { createRequire as __sootsimCreateRequire } from 'node:module'
|
|
3
|
+
var require = __sootsimCreateRequire(import.meta.url)
|
|
4
|
+
import{f}from"./chunk-33FKQH54.js";import{existsSync as w,mkdirSync as g,readFileSync as h,writeFileSync as S,rmSync as F}from"fs";import{tmpdir as y}from"os";import{dirname as b,join as p}from"path";var m=new Map;function o(e,n){m.set(e,n)}var s=1;function a(){return p(y(),`sootsim-cli-hints-${f()}.json`)}function d(){return p(y(),"sootsim-cli-hints-global.json")}function u(e){if(!w(e))return{version:s,shown:{}};try{let n=JSON.parse(h(e,"utf8"));return n?.version!==s||!n?.shown?{version:s,shown:{}}:n}catch{return{version:s,shown:{}}}}function l(e,n){try{g(b(e),{recursive:!0}),S(e,JSON.stringify(n)+`
|
|
5
|
+
`)}catch{}}function v(){let e=(process.env.SOOTSIM_HINTS||"").toLowerCase();return e==="off"||e==="0"||e==="false"?"off":e==="always"||e==="verbose"?"always":"normal"}function x(e,n){let i=v();if(i==="off")return!1;if(i==="always"||n==="always")return!0;let t=Date.now();if(n==="once-per-identity"){let r=u(a());return r.shown[e]?!1:(r.shown[e]=t,l(a(),r),!0)}if(n==="once-ever"){let r=u(d());return r.shown[e]?!1:(r.shown[e]=t,l(d(),r),!0)}if(typeof n=="object"&&"cooldownMs"in n){let r=u(a()),c=r.shown[e]??0;return t-c<n.cooldownMs?!1:(r.shown[e]=t,l(a(),r),!0)}return!0}function A(e,...n){let i=m.get(e);if(!i)return process.env.SOOTSIM_HINTS_DEBUG&&console.error(` [hints] no hint registered for id "${e}"`),!1;if(!x(e,i.frequency))return!1;let t=i.render(...n);if(t==null)return!1;let r=Array.isArray(t)?t:[t];for(let c of r)console.error(` hint: ${c}`);return!0}o("app-still-loading",{frequency:{cooldownMs:6e4},render:e=>`app may still be loading (${e} nodes). run \`rnxsim wait ready\` first.`});o("wait-selector-for-missing-testid",{frequency:"once-per-identity",render:e=>`rnxsim wait selector ${e}`});o("prefer-cli-over-eval",{frequency:"once-per-identity",render:e=>e.length?["try the CLI shortcut instead:",...e.map(n=>` ${n}`)]:null});o("describe-use-filters",{frequency:"once-per-identity",render:()=>["describe output is long. narrow it with:"," rnxsim describe --only '*Bottom Sheet*'"," rnxsim describe --testid-like 'swap-*'"," rnxsim describe --subtree <testID>"]});o("describe-filter-context",{frequency:"once-per-identity",render:()=>["filtered describe is search output, not current-route proof.","run `rnxsim describe` without filters after navigation; clipped nodes are marked `(clipped:...)`."]});o("subtree-root-not-found",{frequency:"always",render:e=>`no node with testID/id "${e}" \u2014 try \`rnxsim find --testid ${e}\` to discover available ids.`});o("clean-uninstall",{frequency:"once-ever",render:()=>["to remove the background daemon and all ~/.sootsim data, run `rnxsim daemon uninstall`.","the optional desktop app and its macOS preference plist are separate from the daemon."]});export{A as a};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*! rnx v0.1.312 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import { createRequire as __sootsimCreateRequire } from 'node:module'
|
|
3
|
+
var require = __sootsimCreateRequire(import.meta.url)
|
|
4
|
+
import{a as I,b as ce,c as Ne}from"./chunk-SI3BUTQR.js";import{m as A}from"./chunk-XRGEEPJ5.js";import{m as Ee,p as Ue}from"./chunk-TGQNQ6XT.js";import{b as Le,j as Ve}from"./chunk-GZDHEONW.js";import{a as We,b as Ke,d as qe}from"./chunk-T4FT6CM7.js";import{a as De,b as L,c as ae,d as le,e as Be,f as Ae,h as je,i as K,j as He}from"./chunk-YZRFF4YA.js";import{a as Fe}from"./chunk-AY47WKS5.js";import{a as de}from"./chunk-3D57IBPC.js";import{b as _e,c as Te,d as ie,f as Oe,g as Me,h as U,i as P,j as _}from"./chunk-7INBNA3U.js";import{f as Ie}from"./chunk-U4PN6JOT.js";import{b as se,d as ke}from"./chunk-5OX7ANRA.js";import{b as Ce,g as Re,h as D,i as $,j as B}from"./chunk-33FKQH54.js";import{H as re,K as oe,M,O as Pe,l as ne}from"./chunk-66NF2KNG.js";import{a as te}from"./chunk-ZMZTHU7V.js";import{spawn as Ze,spawnSync as Xe}from"node:child_process";import T from"node:fs";import kt from"node:os";import C from"node:path";import{WebSocket as ue}from"ws";function ze(e){return new Error(`could not resolve a native bundle for ${e}. pass an explicit bundle URL or open Connect and choose the app there.`)}function yt(e,n){return e?.includes("/one/metro-entry.bundle")?"one":typeof n=="string"&&n?"expo":"unknown"}function Je(e,n,t){return`${e}//${n}:${t}`}function St(e){if(typeof window>"u")return!1;try{let n=new URL(e);return I(n.hostname)&&I(window.location.hostname)&&n.origin!==window.location.origin}catch{return!1}}async function Ge(e,n){let t={...n,cache:n?.cache??"no-store"};return St(e)?fetch(`/__fetch-proxy?url=${encodeURIComponent(e)}`,t):fetch(e,t)}function xt(e){return e==="https:"?443:80}function $t(e){let n=e.pathname||"/";return(n==="/"||n==="")&&!e.search&&!e.hash}async function Pt(e){try{let n=await fetch("/__server-scan",{cache:"no-store"});if(!n.ok||!(n.headers.get("content-type")||"").toLowerCase().includes("json"))return null;let t=await n.json();if(!Array.isArray(t))return null;let r=t.find(o=>!!o&&typeof o=="object"&&o.port===e&&typeof o.bundleUrl=="string");return r?{bundleUrl:le(r.bundleUrl),port:e,framework:typeof r.framework=="string"?r.framework:"unknown",projectName:typeof r.projectName=="string"?r.projectName:void 0}:null}catch{return null}}async function Qe(e,n){let t=e.replace(/\/+$/,"");try{if(I(new URL(t).hostname)){let r=await Pt(n);if(r)return r}}catch{}try{let r=await Ge(`${t}/`,{headers:{"expo-platform":"ios"}});if(r.ok){let o=await r.json(),s=o?.extra?.expoClient||o?.extra||{},i=typeof o?.launchAsset?.url=="string"?o.launchAsset.url:void 0;if(i||s.name)return{bundleUrl:le(i||`${t}${ae}`),port:n,framework:yt(i,s.sdkVersion),projectName:s.name}}}catch{}try{let r=await Ge(`${t}/status`);if(r.ok&&(await r.text()).includes("packager-status:running"))return{bundleUrl:`${t}${ae}`,port:n,framework:"metro"}}catch{}return null}async function It(e){return Qe(Je("http:","localhost",e),e)}async function Ye(e){let n=e.trim();if(/^\d+$/.test(n)){let a=parseInt(n,10),l=await It(a);if(l)return l;throw ze(`localhost:${a}`)}let t=n.startsWith("http")?n:`http://${n}`,r;try{r=new URL(t)}catch{throw new Error(`could not parse "${e}". pass a dev-server port, a dev-server base URL, or a full bundle URL.`)}let o=r.protocol||"http:",s=r.port?parseInt(r.port,10):xt(o),i=Je(o,r.hostname,s);if($t(r)){let a=await Qe(i,s);if(a)return a;throw ze(i)}return{bundleUrl:r.toString(),port:s,framework:"unknown"}}var Ct=12e4,et=100,Rt=5e3,tt=1e4,Et={rn:"/rn",connectrn:"/rn","connect-rn":"/rn",clock:"/app/clock","native-ui":"/app/native-ui",tamagui:"/app/tamagui",settings:"/app/settings",photos:"/app/photos",camera:"/app/camera"};function y(e){return new Promise(n=>setTimeout(n,e))}function nt(e){let n=process.env[e]?.trim();if(!n)return null;let t=Number(n);return Number.isInteger(t)&&t>0?t:null}function Ut(){let e=nt("SOOTSIM_DRIVER_CONNECT_TIMEOUT_MS")??nt("SOOTSIM_PW_CONNECT_TIMEOUT_MS")??Ct;return{timeoutMs:e,hostTimeoutMs:e+Rt,intervalMs:et,attempts:Math.max(1,Math.ceil(e/et))}}function rt(e){return e==="electron"||e==="playwright"}function me(e,n){return n==="playwright"?e.meta?.sootsimHostDriver==="playwright":e.userAgent?.includes("Electron/")===!0}function _t(e,n){return e.includes("--headless")?!0:e.includes("--headed")?!1:n==="playwright"&&process.platform==="linux"&&!process.env.DISPLAY&&!process.env.WAYLAND_DISPLAY?(console.error(" no display detected \u2192 running playwright headless (override with --headed)"),!0):!1}function z(e){return e.trim()}var dt=/^[A-Za-z][A-Za-z0-9+.-]*:/;function Tt(e){let n=e.trim();if(!n)throw new Error("expected <url-or-route>");return dt.test(n)||n.startsWith("/")?n:`/${n}`}function Ot(e){return e!==null&&typeof e=="object"}function Mt(e){let n=e.trim();return n?n.startsWith("/")?!0:dt.test(n)?!/^https?:\/\//i.test(n):!1:!1}async function Dt(e,n=!1){if(await V(e,250))return e;let t=oe();if(ot(e,n,t),t&&M(t)&&await V(t.bridgePort,500))return t.bridgePort;if(t&&M(t))throw new Error(`rnx bridge lockfile is fresh (port ${t.bridgePort}) but the bridge is not reachable. run \`rnxsim daemon restart\` or \`rnxsim daemon uninstall\`.`);await Bt();let r=At(["serve","--quiet","--port",String(e)]),o=null;r.once("exit",(i,a)=>{o=String(i??a??"unknown")}),r.unref();let s=Date.now()+tt;for(;Date.now()<s;){let i=oe();if(ot(e,n,i),i&&M(i)&&await V(i.bridgePort,250))return console.error(` started local bridge on port ${i.bridgePort}; run \`sootsim setup\` to install the background daemon`),i.bridgePort;if(await V(e,250))return console.error(` started local bridge on port ${e}; run \`sootsim setup\` to install the background daemon`),e;if(o)throw new Error(`rnx bridge exited before becoming ready (exit ${o})`);await y(150)}throw new Error(`rnx bridge did not start within ${Math.round(tt/1e3)}s`)}function ot(e,n,t){if(!(!n||!t||!M(t)||t.bridgePort===e))throw new Error(`${te.commandName} open: requested bridge port ${e}, but the active daemon owns port ${t.bridgePort}. explicit --port never selects another bridge; stop that daemon or use an isolated SOOTSIM_HOME.`)}async function Bt(){let e=re();if(e&&T.existsSync(ne(e)))return;console.error(" installing rnx engine runtime...");let{runRuntime:n}=await import("./runtime-AR3GJTGK.js");await n(["install"],{})}function At(e){let n=process.argv[1];return n&&T.existsSync(n)?Ze(process.execPath,[n,...e],{detached:!0,stdio:"ignore",env:process.env}):Ze(te.commandName,e,{detached:!0,stdio:"ignore",env:process.env})}function V(e,n){return new Promise(t=>{let r=new ue(`ws://127.0.0.1:${e}`,{handshakeTimeout:n}),o=!1,s=i=>{o||(o=!0,r.readyState===ue.OPEN?r.close():r.readyState===ue.CONNECTING&&r.terminate(),t(i))};r.once("open",()=>s(!0)),r.on("error",()=>s(!1)),setTimeout(()=>s(!1),n)})}function fe(e){try{let n=new URL(e),t=n.pathname.replace(/\/+$/,"")||"/";return n.searchParams.has("open")||n.searchParams.has("port")||n.searchParams.has("bundle")||n.searchParams.has("demo")||n.pathname.includes("/sootsim/index.html")||t==="/sootsim"||n.pathname==="/__soot"||n.pathname==="/__soot/"||t==="/rn"||/^\/rn\/[^/]+$/i.test(t)||/^\/app\/[^/]+$/i.test(t)||t==="/__soot/rn"||/^\/__soot\/rn\/[^/]+$/i.test(t)||/^\/__soot\/app\/[^/]+$/i.test(t)}catch{return!1}}function Lt(e){try{let n=new URL(e,"http://sootsim.local");if(n.origin!=="http://sootsim.local")return!1;let t=n.pathname.replace(/\/+$/,"")||"/";return t==="/rn"||/^\/rn\/[^/]+$/i.test(t)||/^\/app\/[^/]+$/i.test(t)||t==="/__soot/rn"||/^\/__soot\/rn\/[^/]+$/i.test(t)||/^\/__soot\/app\/[^/]+$/i.test(t)}catch{return!1}}function Nt(e,n){let t=new URL(e,"http://sootsim.local"),r=new URL(n),o=G(r.pathname),s=t.pathname.startsWith("/__soot/")?t.pathname.slice(7):t.pathname;return r.pathname=`${o}${s}`,r.search=t.search,r.hash=t.hash,r.toString()}function Ft(e){try{let n=new URL(e).pathname.replace(/\/+$/,"")||"/";return/^\/preview\/[^/]+$/.test(n)||/^\/build\/.+/.test(n)}catch{return!1}}async function Gn(e){let n=z(e);return(await he(n)).bundleUrl}function jt(e){try{let n=new URL(e.startsWith("http")?e:`http://${e}`),t=n.pathname||"/";if(t!=="/"&&t!==""||n.search||n.hash||!I(n.hostname))return null;let r=n.port?Number(n.port):n.protocol==="https:"?443:80;return Number.isFinite(r)&&r>0?r:null}catch{return null}}async function he(e){let n=z(e),t=/^\d+$/.test(n)?Number(n):jt(n);if(t&&t>0){let r=await Ae(t);if(r)return{bundleUrl:r.bundleUrl,port:r.port,framework:r.framework,projectName:r.projectName}}return Ye(n)}function G(e){let n=e.replace(/\/+$/,"")||"/";return n==="/__soot"||n.startsWith("/__soot/")?"/__soot":""}function Ht(e,n){let t=z(e),r=new URL(n),o=G(r.pathname);return r.pathname=`${o}/rn`,r.searchParams.delete("bundle"),r.searchParams.delete("demo"),r.searchParams.delete("app"),r.searchParams.delete("open"),r.searchParams.delete("port"),/^\d+$/.test(t)?r.pathname=`${o}/rn/${t}`:(r.pathname=`${o}/rn`,r.searchParams.set("open",t)),r.toString()}function ut(e){let n=z(e);return/^\d+$/.test(n)||/^https?:\/\//i.test(n)?!0:/^(localhost|127\.0\.0\.1|\[::1\]|[^/]+\.localhost):\d+(?:\/.*)?$/i.test(n)}async function Wt(e,n,t={}){let r=t.resolvedBundle??await he(e),o=new URL(n),s=G(o.pathname);o.pathname=`${s}/rn`,o.searchParams.delete("open"),o.searchParams.delete("port"),o.searchParams.delete("demo"),o.searchParams.delete("app");let i=t.hot===void 0?r.bundleUrl:Be(r.bundleUrl,t.hot);return o.searchParams.set("bundle",i),o.toString()}function Kt(e){return e.startsWith("~/")?C.join(kt.homedir(),e.slice(2)):C.isAbsolute(e)?e:C.resolve(process.cwd(),e)}function Vt(e){let n={},t={};for(let o=0;o<e.length;o++){if(e[o]==="--replace"){let s=e[o+1];s||(console.error(" rnxsim open: --replace expects <module>=<file>"),process.exit(1));let i=s.indexOf("=");(i<=0||i===s.length-1)&&(console.error(" rnxsim open: --replace expects <module>=<file>"),process.exit(1));let a=s.slice(0,i).trim(),l=Kt(s.slice(i+1).trim());T.existsSync(l)||(console.error(` rnxsim open: replacement file not found: ${l}`),process.exit(1)),n[a]={file:l},o++;continue}if(e[o]==="--remap"){let s=e[o+1],i=s?s.indexOf("="):-1;(!s||i<=0||i===s.length-1)&&(console.error(" rnxsim open: --remap expects <host:port>=<host:port>"),process.exit(1)),t[s.slice(0,i).trim()]=s.slice(i+1).trim(),o++;continue}}let r={};return Object.keys(n).length>0&&(r.modules=n),Object.keys(t).length>0&&(r.network={remap:t}),r.modules||r.network?r:void 0}async function qt(e){if(e){let n=await K(e.bundleUrl);if(n)return de(n,{trustStart:!0})}return de(process.env.CONTRAST_REPO_DIR||process.cwd())}async function zt(e){if(T.existsSync(C.join(ne(e),"index.html")))return;console.error(` installing configured engine runtime ${e}...`);let n=await Ie.install({version:e,setActive:!1,protectVersions:Pe()});console.error(` installed engine runtime ${n.version}`)}function Gt(e,n){let t=n??(ce()?void 0:re()??void 0);if(!t)return e;let r=new URL(e);if(!I(r.hostname)){if(!n)return e;throw new Error(`runtimeVersion ${t} requires a local rnx host; got ${r.origin}`)}return r.hostname=Ne(t),r.toString()}function mt(){return _e().shellBaseUrl}function Jt(e){return Te(e)?.shellBaseUrl??Ce(e)??mt()}async function Qt(e,n){try{let t=new URL(e),r=t.searchParams.get("bundle")||"";if(!r)return e;let o=n?.appDir??await K(r)??void 0,s=je({bundleUrl:r,repoDir:o,exactProjectDir:!!n||!!o});if(s.length===0)return e;let i=await Yt(s);return i.length===0?e:(t.searchParams.set("appFonts",He(i)),t.toString())}catch{return e}}async function Yt(e){let n=await Promise.all(e.map(async r=>{let o=new AbortController,s=setTimeout(()=>o.abort(),1500);try{let i=await fetch(r.url,{method:"HEAD",signal:o.signal});return i.status>=200&&i.status<400?r:null}catch{return null}finally{clearTimeout(s)}})),t=[];for(let r of n)r!==null&&t.push(r);return t}async function Zt(e,n,t={}){if(!e)return new URL(n).toString();if(fe(e))return new URL(e).toString();if(Lt(e))return Nt(e,n);let r=Et[e.toLowerCase()];if(r){let o=new URL(n),s=G(o.pathname);return o.pathname=`${s}${r}`,o.toString()}return ut(e)?Wt(e,n,t):Ht(e,n)}async function Xt(e,n){try{let t=new URL(e),r=t.searchParams.get("bundle")||"";if(!r)return e;let o=n?.appDir??await K(r)??void 0,s=We({bundleUrl:r,repoDir:o,exactProjectDir:!!n||!!o});return!s||s.imageUrl&&!await en(s.imageUrl)?e:(t.searchParams.set("appSplash",Ke(s)),t.toString())}catch{return e}}async function en(e){let n=new AbortController,t=setTimeout(()=>n.abort(),1500);try{let r=await fetch(e,{method:"HEAD",signal:n.signal});return r.status>=200&&r.status<400}catch{return!1}finally{clearTimeout(t)}}async function q(e,n=mt(),t={}){let r=await Zt(e,n,t),o=await Xt(await Qt(r,t.project),t.project);if(!t.device)return o;let s=new URL(o);return s.searchParams.set("device",t.device),s.toString()}function st(e,n){let t=e?.url||e?.origin||n;try{let r=new URL(t);return r.searchParams.delete("bundle"),r.searchParams.delete("demo"),r.searchParams.delete("app"),r.searchParams.delete("open"),r.searchParams.delete("port"),r.searchParams.delete("inspectOpen"),r.searchParams.delete("appFonts"),r.searchParams.delete("appSplash"),r.toString()}catch{return n}}async function tn(e,n,t,r={}){let o=new URL(await q(e,n,r));return o.searchParams.set("inspectOpen",t),o.toString()}async function nn(e,n,t,r={}){let o=r.attempts??30,s=r.intervalMs??500,i=r.minNodeCount??10;for(let a=0;a<o;a++){let l=P(e,{commandTimeoutMs:n,simId:t,simIdSource:"flag"});try{let d=await l.send({type:"evaluate",code:"(async () => (await window.__sootsimTest?.getNodeCount()) || 0)()"});if(typeof d=="number"&&d>i)return{bridge:l,count:d}}catch{}l.close(),await y(s)}return null}function rn(e){if(!e)return null;try{let n=new URL(e);if(n.searchParams.has("bundle")){let t=n.searchParams.get("bundle")||"";try{let r=new URL(t),o=r.pathname.length>36?`...${r.pathname.slice(-36)}`:r.pathname;return`bundle ${r.host}${o}`}catch{return"bundle"}}return n.searchParams.has("port")?`connect :${n.searchParams.get("port")||""}`:n.searchParams.has("open")?`connect ${n.searchParams.get("open")||""}`:n.searchParams.has("demo")?`demo ${n.searchParams.get("demo")||"default"}`:n.pathname.includes("/sootsim/index.html")||n.pathname==="/sootsim/"||n.pathname==="/sootsim"?"embedded rnx":null}catch{return null}}function on(e){let n=e.indexOf(":");if(n<=0)return e.length>24?`${e.slice(0,21)}\u2026`:e;let t=e.slice(0,n),r=e.slice(n+1),o=r.length>12?`${r.slice(0,8)}\u2026`:r;return t==="TM_SESSION"||t==="CLAUDE_CODE_SESSION_ID"||t==="CODEX_THREAD_ID"?`agent ${o}`:`${t} ${o}`}function Jn(e,n){if(e.length===0){console.log(" no sims connected");return}console.log(` connected sims (${e.length}):
|
|
5
|
+
`);for(let t of e){let r=t.lockedBy&&t.lockExpiresAt?`locked by ${on(t.lockedBy)} (${Math.max(0,Math.round((t.lockExpiresAt-Date.now())/1e3))}s)`:"",o=[t.isPrimary?"primary":"",t.id===n?"selected":"",t.readyState,t.userVisible===!1?"hidden":t.userVisible===!0?"visible":"",t.attachedCliCount&&t.attachedCliCount>0?"in use":"",t.userFocused?"focused":"",r].filter(Boolean);console.log(` ${t.id}${o.length?` [${o.join(", ")}]`:""}`);let s=rn(t.url);if(s)console.log(` loaded: ${s}`);else if(t.url){let a=t.url.length>96?`${t.url.slice(0,93)}\u2026`:t.url;console.log(` url: ${a}`)}else t.origin&&console.log(` origin: ${t.origin}`);let i=sn(t);i&&console.log(` engine: ${i}`),t.title&&console.log(` title: ${t.title}`),t.visibilityState&&t.visibilityState!=="visible"&&console.log(` visibility: ${t.visibilityState}`),console.log(` connected: ${it(Date.now()-t.connectedAt)}`),t.lastActiveAt&&t.lastActiveAt>0&&console.log(` last active: ${it(Date.now()-t.lastActiveAt)}`)}}function sn(e){let n=e.url||e.origin||"";if(!n)return null;try{let t=new URL(n).port;return t==="5173"?"dev \u2014 local source (:5173)":t==="3000"?"Contrast app embedded (:3000)":ce()?`dev \u2014 local build (:${t||"?"})`:`runtime \u2014 published engine (:${t||"?"})`}catch{return null}}function it(e){let n=Math.max(0,Math.round(e/1e3));return n<60?`${n}s ago`:n<3600?`${Math.round(n/60)}m ago`:n<86400?`${Math.round(n/3600)}h ago`:`${Math.round(n/86400)}d ago`}async function Qn(e,n,t,r={}){let o=r.attempts??30,s=r.intervalMs??500;for(let i=0;i<o;i++){let a=P(e,{commandTimeoutMs:n});try{let d=(await a.listSims()).find(t);if(d)return d}catch{}finally{a.close()}await y(s)}return null}async function an(e,n,t,r,o={}){let s=o.attempts??30,i=o.intervalMs??500,a=!!r&&Number.isInteger(r)&&r>1,l=()=>a&&r!==void 0&&!ge(r);for(let d=0;d<s;d++){if(l())return{kind:"host-exit"};let c=P(e,{commandTimeoutMs:n});try{let g=(await c.listSims()).find(t);if(g)return l()?{kind:"host-exit"}:{kind:"match",sim:g}}catch{}finally{c.close()}if(l())return{kind:"host-exit"};await y(i)}return{kind:"timeout"}}async function ln(e,n,t,r={}){let o=r.attempts??20,s=r.intervalMs??250;for(let i=0;i<o;i++){let a=P(e,{commandTimeoutMs:n});try{let d=(await a.listSims()).find(c=>c.id===t);if(!d||d.readyState!=="open")return!0}catch{return!0}finally{a.close()}await y(s)}return!1}function cn(e){let n=e.meta;if(!n||n.sootsimHostDriver!=="playwright")return null;let t=Number(n.sootsimHostPid);return!Number.isInteger(t)||t<=1||t===process.pid?null:t}function dn(e){if(!e.connectAckFile)return!1;try{return T.writeFileSync(e.connectAckFile,`${JSON.stringify({connectedAt:Date.now(),pid:e.pid??null})}
|
|
6
|
+
`,{flag:"w"}),!0}catch{return!1}}function at(e){if(e.diagnosticLogPath)try{let n=T.readFileSync(e.diagnosticLogPath,"utf8").trim();if(!n){console.error(` ${C.basename(e.diagnosticLogPath)} was empty`);return}let t=n.length>4e3?`\u2026
|
|
7
|
+
${n.slice(-4e3)}`:n;console.error(` ${C.basename(e.diagnosticLogPath)}:`),console.error(t)}catch(n){let t=n instanceof Error?n.message:String(n);console.error(` failed to read ${C.basename(e.diagnosticLogPath)}: ${t}`)}}function ge(e){try{return process.kill(e,0),!0}catch(n){return n?.code==="EPERM"}}async function un(e,n=2500){let t=Date.now()+n;for(;Date.now()<t;){if(!ge(e))return!0;await y(100)}return!ge(e)}function mn(e){let n=Xe("ps",["-o","command=","-p",String(e)],{encoding:"utf8",stdio:["ignore","pipe","pipe"]}),t=Xe("ps",["eww","-o","command=","-p",String(e)],{encoding:"utf8",stdio:["ignore","pipe","pipe"]});if(n.status!==0||t.status!==0)return!1;let r=(n.stdout||"").trimEnd(),o=(t.stdout||"").trimEnd();return(o.startsWith(r)?o.slice(r.length):o).includes("SOOTSIM_PW_URL=")}async function pe(e,n){let t=new Set(n),r=new Set;for(let o of e){if(!t.has(o.id))continue;let s=cn(o);s&&r.add(s)}for(let o of r){if(!mn(o)){console.log(` skipped pid ${o}: not a rnx playwright host (stale sim record)`);continue}try{process.kill(o,"SIGTERM")}catch{}if(await un(o)){console.log(` closed playwright host process ${o}`);continue}try{process.kill(o,"SIGKILL"),console.log(` force-closed playwright host process ${o}`)}catch{}}}function J(e,n){if(n){let o=n.trim(),s=e.find(i=>i.id===o);if(!s)throw new Error(`no sim connected with id ${o}`);return s}let t=e.find(o=>o.isPrimary&&o.readyState==="open");if(t)return t;let r=e.find(o=>o.readyState==="open");if(r)return r;throw new Error("no sim connected")}function lt(e,n,t){console.log(` ${t==="current sim"?"loaded":"opened"}: ${e} [${t}]`),console.log(` current sim: ${n.id}`),console.log(JSON.stringify({simId:n.id,url:n.url},null,2))}async function ct(e,n,t){if(t.includes("--no-describe"))return;let r=process.env.SOOTSIM_QUIET_TARGET_NOTICE;process.env.SOOTSIM_QUIET_TARGET_NOTICE="1";try{await pn(e,n,{stableMs:150,maxMs:400});let o=P(e,{commandTimeoutMs:3e3,simId:n,cliLabel:"open --describe",simIdSource:"flag"});try{let s=null;try{s=await o.send({type:"evaluate",code:Ee})}catch{}if(s&&!(s.flag===!0&&!s.loadingText&&Ue(s))){let c=s.loadingText?`still showing "${s.loadingText}"`:s.flag!==!0?"guest ready event has not fired":s.targets<=0?"no visible app content is inspectable yet":"app tree is still stabilizing";console.log(` app still loading: ${c} (nodes: ${s.nodes}, targets: ${s.targets})`),console.log(" before interacting, run: rnxsim wait ready --max-ms 120000")}let a=`(async () => {
|
|
8
|
+
const t = window.__sootsimTest
|
|
9
|
+
const ms = window.SootSim?.bridges?.mainShell
|
|
10
|
+
if (!t) return null
|
|
11
|
+
let shell = null
|
|
12
|
+
try { shell = ms?.getState ? await ms.getState() : null } catch {}
|
|
13
|
+
const tree = await t.dumpTree(12, ${JSON.stringify({describe:!0,verbose:!1,filter:""})})
|
|
14
|
+
return { tree, shell }
|
|
15
|
+
})()`,l=await o.send({type:"evaluate",code:a});if(!l?.tree)return;if(console.log(""),l.shell?.state){let d=[`state=${l.shell.state}`,l.shell.activeApp?`app=${l.shell.activeApp}`:null].filter(Boolean);console.log(` shell: ${d.join(" ")}`)}console.log(l.tree)}finally{o.close()}}catch{}finally{r===void 0?delete process.env.SOOTSIM_QUIET_TARGET_NOTICE:process.env.SOOTSIM_QUIET_TARGET_NOTICE=r}}async function pn(e,n,t){let r=Date.now()+t.maxMs,o="(async () => (await window.__sootsimTest?.getNodeCount?.()) || 0)()",s=P(e,{commandTimeoutMs:2e3,simId:n,simIdSource:"flag"});try{let i=-1,a=0;for(;Date.now()<r;){let l=-1;try{let d=await s.send({type:"evaluate",code:o});typeof d=="number"&&(l=d)}catch{}if(l>=0&&l===i){if(Date.now()-a>=t.stableMs)return}else i=l,a=Date.now();await y(50)}}finally{s.close()}}async function fn(e,n={}){let t=U(e,{port:n.port,commandTimeoutMs:n.timeoutMs});t.positional.length!==1&&(console.error(" rnxsim open: expected exactly one <url-or-route>"),console.error(" examples: rnxsim open /settings | rnxsim open myapp://settings"),process.exit(1));let r;try{r=Tt(t.positional[0]??"")}catch(i){console.error(` rnxsim open: ${i instanceof Error?i.message:String(i)}`),process.exit(1)}let o=_(t),s=t.simId?` --sim ${t.simId}`:"";try{let i=await o.send({type:"call",path:"SootSim.bridges.test.openDeepLink",args:[r]});if(Ot(i)&&i.ok===!1)throw new Error(typeof i.error=="string"?i.error:"deep-link dispatch failed");console.log(` opened deep link: ${r}`)}catch(i){console.error(` link failed: ${i instanceof Error?i.message:String(i)}`),await A(o,{errorsCommand:`rnxsim get errors 5${s}`,warningsCommand:`rnxsim get warnings 5${s}`,requestsCommand:`rnxsim get requests 5${s}`}),process.exit(1)}finally{o.close()}}async function Yn(e,n={}){let t=U(e,{port:n.port,commandTimeoutMs:n.timeoutMs,stripBooleanFlags:["--new","--headless","--headed","--ephemeral","--hot","--no-hmr","--no-describe"],stripValueFlags:["--base-url","--replace","--driver","--profile","--cdp-port","--device"]}),r=e.find((f,m)=>e[m-1]==="--profile"),o=e.includes("--ephemeral"),s=e.find((f,m)=>e[m-1]==="--cdp-port"),i=s?Number(s):void 0;s&&(!Number.isFinite(i)||i<=0)&&(console.error(` rnxsim open: --cdp-port must be a positive port number, got "${s}"`),process.exit(1)),r&&o&&(console.error(" rnxsim open: --profile cannot be combined with --ephemeral"),process.exit(1));let a=r?Le(r).id:void 0,l=e.includes("--new")||!!a||o,d=Vt(e);l&&t.simIdSource==="flag"&&(console.error(" rnxsim open: --new, --profile, and --ephemeral cannot be combined with --sim"),process.exit(1));let c=t.positional[0]||"";if(Mt(c)){l&&(console.error(" rnxsim open: deep links target the current app; remove --new, --profile, or --ephemeral"),process.exit(1)),await fn(e,n);return}if(Ft(c)){let f=(()=>{try{return I(new URL(c).hostname)}catch{return!1}})();console.log(" that\u2019s an rnxsim preview/build link \u2014 a full player page, not a driveable sim."),console.log(" opening it in your browser for viewing\u2026");try{await qe(c,{background:!1})}catch(m){console.error(` could not launch a browser: ${m instanceof Error?m.message:String(m)}`),console.error(` open it yourself: ${c}`),process.exit(1)}console.log(f?" to drive it under the CLI, point `rnxsim open` at the app\u2019s dev port instead (e.g. `rnxsim open 8081`).":" the CLI can\u2019t drive a remote preview (the engine only attaches to the local bridge from localhost). to drive the app, run it locally and `rnxsim open <port>`.");return}let u=ut(c)?await he(c):void 0,g=await qt(u),O=await Fe(g),N=O?.runtimeVersion,F=De(O?{...O,runtimeVersion:void 0}:void 0,d);N&&await zt(N);let Q=e.find((f,m)=>e[m-1]==="--base-url");if(Q&&!t.explicitPort){let f=ie(Q);f&&(t.wsPort=f)}t.wsPort=await Dt(t.wsPort,t.explicitPort);let R=e.find((f,m)=>e[m-1]==="--driver")||"";R&&!rt(R)&&(console.error(` unknown driver "${R}" \u2014 run \`rnxsim list --drivers\``),process.exit(1));let S=rt(R)?R:"playwright",ft=_t(e,S);i&&S!=="playwright"&&(console.error(" rnxsim open: --cdp-port is only honored by the playwright driver"),process.exit(1));let we=e.includes("--hot"),be=e.includes("--no-hmr");we&&be&&(console.error(" rnxsim open: --hot and --no-hmr cannot be combined"),process.exit(1));let gt=we?!0:be?!1:R!=="playwright",k=e.find((f,m)=>e[m-1]==="--device");k&&!(k in se)&&(console.error(` rnxsim open: unknown device "${k}"`),console.error(` known devices: ${ke().join(", ")}`),process.exit(1));let Y=k?se[k]:void 0,j={hot:gt,device:k,resolvedBundle:u,project:g},ht=N?`http://localhost:${t.wsPort}/`:Q||Jt(t.wsPort),x=Gt(ht,N),ve=e.includes("--base-url");Oe(t.wsPort,x);let wt=D();if(!l){let f=_(t),m=t.simId?` --sim ${t.simId}`:"",ee=!1;try{let p=null,W=null;try{let h=await f.listSims();if(t.simIdSource==="saved")p=h.find(w=>w.id===wt&&w.readyState==="open"&&me(w,S))??null,p||B();else if(t.simIdSource==="flag"&&(p=J(h,t.simId),!me(p,S)))throw new Error(`sim ${p.id} is not managed by the ${S} driver`);if(!p&&t.simIdSource!=="flag"){W=await q(c,L(x,F),j);let w=new URL(W);w.searchParams.delete("inspectOpen"),p=h.find(v=>{if(v.readyState!=="open"||!v.url||!me(v,S))return!1;try{let $e=new URL(v.url);return $e.searchParams.delete("inspectOpen"),$e.toString()===w.toString()}catch{return!1}})??null}if(!p)if(t.simIdSource!=="flag")ee=!0;else throw new Error("no sim connected");if(!ee&&p){let w=ve||fe(c)?x:st(p,x),v=W??await q(c,L(w,F),j);f.send({type:"evaluate",simId:p.id,code:`window.location.href = ${JSON.stringify(v)}`}).catch(()=>{})}}catch(h){console.error(` open failed: ${h instanceof Error?h.message:String(h)}`),await A(f,{errorsCommand:`rnxsim get errors 5${m}`,warningsCommand:`rnxsim get warnings 5${m}`,requestsCommand:`rnxsim get requests 5${m}`}),process.exit(1)}if(!ee&&p){await y(1500);let h=await nn(t.wsPort,t.commandTimeoutMs,p.id);h||(console.error(" timed out waiting for current sim to load target"),process.exit(1)),h.bridge.close(),$(p.id);let w=ve||fe(c)?x:st(p,x),v=W??await q(c,L(w,F),j);lt(v,{...p,url:v},"current sim"),await ct(t.wsPort,p.id,e);return}}finally{f.close()}}let bt=L(x,F),ye=`cli-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`,Se=await tn(c,bt,ye,j),xe=Re();xe||(console.error(" could not resolve a stable owning process; refusing unsupervised sim host"),process.exit(1));let vt=f=>f.url?.includes(`inspectOpen=${ye}`)??!1,E=Ve(S);E||(console.error(` unknown driver "${S}" \u2014 run \`rnxsim list --drivers\``),process.exit(1));let H=Ut(),b=await E.launch({url:Se,bridgePort:ie(x)??t.wsPort,headless:ft,profileId:a,ephemeralProfile:o,cdpPort:i,device:k,ownerPid:xe,viewport:Y?{width:Y.width+120,height:Y.height+60}:void 0,connectTimeoutMs:H.hostTimeoutMs});b.launched||(console.error(` ${E.name} driver: ${b.message}`),process.exit(1));let Z=await an(t.wsPort,t.commandTimeoutMs,vt,b.pid,{attempts:H.attempts,intervalMs:H.intervalMs});if(Z.kind==="host-exit"&&(at(b),console.error(` ${E.name} host crashed \u2014 sim is no longer attached`),process.exit(1)),Z.kind==="timeout"){if(b.pid)try{process.kill(b.pid,"SIGTERM"),console.error(` closed ${E.name} host process ${b.pid}`)}catch{}at(b),console.error(` timed out after ${H.timeoutMs}ms waiting for opened sim to connect`),process.exit(1)}let X=Z.sim;$(X.id),dn(b),lt(Se,X,`${E.name} driver`),await ct(t.wsPort,X.id,e)}async function pt(e,n={}){let t=U(e,{port:n.port,commandTimeoutMs:n.timeoutMs,stripBooleanFlags:["--force"]}),r=e.includes("--force"),o=_(t),s=t.simId?` --sim ${t.simId}`:"";try{try{let i=await o.listSims(),a=J(i,t.positional[0]||t.simId);if(a.lockedBy&&a.lockExpiresAt&&a.lockExpiresAt>Date.now()){let l=Math.max(0,Math.round((a.lockExpiresAt-Date.now())/1e3));a.lockedByKind==="user-active"&&(console.error(` refused: ${a.id} is locked by the active user (${l}s) \u2014 that's a live human tab`),console.error(" run `rnxsim open --new` for a fresh investigation sim"),process.exit(1)),r||(console.error(` refused: ${a.id} is leased by ${a.lockedBy} (${l}s)`),console.error(` \`sootsim use ${a.id} --force\` to take it, or \`rnxsim open --new\` for a fresh sim`),process.exit(1))}await o.focusSim(a.id),$(a.id),console.log(` using: ${a.id}`)}catch(i){console.error(` use failed: ${i instanceof Error?i.message:String(i)}`),await A(o,{errorsCommand:`rnxsim get errors 5${s}`,warningsCommand:`rnxsim get warnings 5${s}`,requestsCommand:`rnxsim get requests 5${s}`}),process.exit(1)}}finally{o.close()}}async function Zn(e,n={}){await pt(e,n)}async function Xn(e,n={}){await pt(e,n)}async function er(e,n={}){let t=U(e,{port:n.port,commandTimeoutMs:n.timeoutMs,stripBooleanFlags:["--force"]}),r=e.includes("--force"),o=_(t);try{try{let s=await o.listSims(),i=J(s,t.positional[0]||t.simId),a=r&&i.lockedBy&&i.lockedByKind!=="user-active"?i.lockedBy:null,l=await o.claim(i.id,{force:r});$(i.id);let d=Math.max(0,Math.round((l.lockExpiresAt-Date.now())/1e3)),c=l.bootedCount>0?` (booted ${l.bootedCount})`:"";console.log(` claimed: ${l.simId} [${d}s]${c}`),a&&console.log(` took over from: ${a}`)}catch(s){if(s instanceof Me){let i=Math.max(0,Math.round(s.lock.expiresInMs/1e3));console.error(` claim failed: locked by ${s.lock.by} for ${i}s more`),console.error(" use --force to take it, or `rnxsim open --new` for a fresh sim"),process.exit(1)}console.error(` claim failed: ${s instanceof Error?s.message:String(s)}`),process.exit(1)}}finally{o.close()}}async function gn(e,n,t,r){if(r.length===0)return{closed:[],remaining:[]};await Promise.all(r.map(s=>e.closeSim(s).catch(()=>{})));let o=new Set(r);for(let s=0;s<40;s++){let i=[];try{let a=P(n,{commandTimeoutMs:t});try{i=(await a.listSims()).filter(d=>o.has(d.id)&&d.readyState==="open").map(d=>d.id)}finally{a.close()}}catch{i=[]}if(i.length===0)return{closed:[...o],remaining:[]};if(s===39)return{closed:[...o].filter(a=>!i.includes(a)),remaining:i};await y(250)}return{closed:[...o],remaining:[]}}function hn(e,n){let t=e.filter(u=>u.readyState==="open"),r=t.map(u=>u.id),o=n.explicitKeepId?.trim()||"",s=n.savedKeepId?.trim()||"",i=s?t.some(u=>u.id===s):!1,a=s&&!i?s:null,l=null,d=null,c=null;if(n.closeOthers)if(o){let u=t.find(g=>g.id===o);u?l=u.id:d=o}else if(s&&i)l=s;else{let u=t.find(g=>g.isPrimary)??t[0]??null;u&&(l=u.id,s&&!i&&(c=u.id))}return{openIds:r,keepId:l,targets:d?[]:r.filter(u=>u!==l),staleSavedId:a,missingExplicitKeepId:d,fallbackKeepId:c}}async function tr(e,n={}){let t=U(e,{port:n.port,commandTimeoutMs:n.timeoutMs,stripBooleanFlags:["--all","--others"]}),r=_(t),o=t.simId?` --sim ${t.simId}`:"",s=e.includes("--all"),i=e.includes("--others");if(s||i){try{let a=await r.listSims(),l=D(),d=t.positional[0]||(t.simIdSource==="flag"?t.simId:void 0),c=hn(a,{closeOthers:i,explicitKeepId:d,savedKeepId:l});if(c.staleSavedId&&B(),c.fallbackKeepId&&($(c.fallbackKeepId),console.log(` saved sim ${c.staleSavedId} is gone \u2014 keeping primary ${c.fallbackKeepId}`)),c.missingExplicitKeepId&&(console.error(` close failed: keep sim ${c.missingExplicitKeepId} is not connected; not closing other sims`),process.exit(1)),c.targets.length===0){console.log(c.keepId?` nothing to close \u2014 only the kept sim ${c.keepId} is connected`:c.staleSavedId?` nothing to close \u2014 no sims connected (cleared stale current sim ${c.staleSavedId})`:" nothing to close \u2014 no sims connected");return}let u=await gn(r,t.wsPort,t.commandTimeoutMs,c.targets);await pe(a,c.targets);let g=D();g&&u.closed.includes(g)&&(c.keepId?$(c.keepId):B());let O=` closed ${u.closed.length} sim(s)${c.keepId?` (kept ${c.keepId})`:""}`;console.log(O),u.remaining.length>0&&(console.error(` close failed: still connected: ${u.remaining.join(", ")}`),process.exit(1))}catch(a){console.error(` close failed: ${a instanceof Error?a.message:String(a)}`),process.exit(1)}finally{r.close()}return}try{try{let a=await r.listSims(),l=J(a,t.positional[0]||t.simId),d=a.find(u=>u.id!==l.id&&u.readyState==="open");await r.closeSim(l.id),await ln(t.wsPort,t.commandTimeoutMs,l.id)||(await pe(a,[l.id]),console.error(` close failed: ${l.id} is still connected`),process.exit(1)),await pe(a,[l.id]),D()===l.id&&(d?$(d.id):B()),console.log(` closed: ${l.id}`)}catch(a){console.error(` close failed: ${a instanceof Error?a.message:String(a)}`),await A(r,{errorsCommand:`rnxsim get errors 5${o}`,warningsCommand:`rnxsim get warnings 5${o}`,requestsCommand:`rnxsim get requests 5${o}`}),process.exit(1)}}finally{r.close()}}export{Ut as a,Tt as b,Dt as c,Gn as d,Gt as e,mt as f,Jt as g,q as h,tn as i,rn as j,Jn as k,Qn as l,an as m,cn as n,dn as o,pe as p,fn as q,Yn as r,Zn as s,Xn as t,er as u,gn as v,hn as w,tr as x};
|