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,39 @@
|
|
|
1
|
+
import { runElectron } from './electron'
|
|
2
|
+
import { runInstallDesktop } from './install-desktop'
|
|
3
|
+
|
|
4
|
+
interface DesktopOptions {
|
|
5
|
+
port?: number
|
|
6
|
+
device?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function printHelp(): void {
|
|
10
|
+
console.log(`
|
|
11
|
+
rnxsim desktop — launch or install the desktop companion
|
|
12
|
+
|
|
13
|
+
usage:
|
|
14
|
+
rnxsim desktop [options]
|
|
15
|
+
rnxsim desktop install [options]
|
|
16
|
+
|
|
17
|
+
options:
|
|
18
|
+
--port <number> connect to a running dev server on this port
|
|
19
|
+
--profile <id> launch with an isolated persistent storage profile
|
|
20
|
+
--ephemeral launch with a temporary isolated storage profile
|
|
21
|
+
-y, --yes install without prompting
|
|
22
|
+
`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function runDesktop(args: string[], opts: DesktopOptions): Promise<void> {
|
|
26
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
27
|
+
printHelp()
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const sub = args.find((arg) => !arg.startsWith('-'))
|
|
32
|
+
if (sub === 'install') {
|
|
33
|
+
const index = args.indexOf(sub)
|
|
34
|
+
await runInstallDesktop([...args.slice(0, index), ...args.slice(index + 1)])
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await runElectron(args, opts)
|
|
39
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// project detection — finds project root, detects framework, scans monorepo workspaces
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
|
|
4
|
+
import { basename, dirname, join } from 'path'
|
|
5
|
+
|
|
6
|
+
export type Framework = 'one' | 'expo' | 'bare' | 'unknown'
|
|
7
|
+
export type PackageManager = 'bun' | 'pnpm' | 'npm' | 'yarn'
|
|
8
|
+
export type MonorepoType = 'turbo' | 'pnpm' | 'nx' | 'npm-workspaces' | 'single'
|
|
9
|
+
|
|
10
|
+
export interface DetectedProject {
|
|
11
|
+
dir: string
|
|
12
|
+
name: string
|
|
13
|
+
framework: Framework
|
|
14
|
+
hasViteConfig: boolean
|
|
15
|
+
hasMetroConfig: boolean
|
|
16
|
+
hasSootsimDependency: boolean
|
|
17
|
+
devCommand: string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DetectedWorkspace {
|
|
21
|
+
root: string
|
|
22
|
+
type: MonorepoType
|
|
23
|
+
packageManager: PackageManager
|
|
24
|
+
apps: DetectedProject[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function findProjectRoot(cwd: string): string {
|
|
28
|
+
let dir = cwd
|
|
29
|
+
while (true) {
|
|
30
|
+
if (
|
|
31
|
+
existsSync(join(dir, 'turbo.json')) ||
|
|
32
|
+
existsSync(join(dir, 'nx.json')) ||
|
|
33
|
+
existsSync(join(dir, 'lerna.json')) ||
|
|
34
|
+
existsSync(join(dir, 'pnpm-workspace.yaml'))
|
|
35
|
+
) {
|
|
36
|
+
return dir
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const pkgPath = join(dir, 'package.json')
|
|
40
|
+
if (existsSync(pkgPath)) {
|
|
41
|
+
try {
|
|
42
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
43
|
+
if (pkg.workspaces) return dir
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parent = dirname(dir)
|
|
48
|
+
if (parent === dir) break
|
|
49
|
+
dir = parent
|
|
50
|
+
}
|
|
51
|
+
return cwd
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function detectPackageManager(root: string): PackageManager {
|
|
55
|
+
if (existsSync(join(root, 'bun.lock')) || existsSync(join(root, 'bun.lockb'))) {
|
|
56
|
+
return 'bun'
|
|
57
|
+
}
|
|
58
|
+
if (existsSync(join(root, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
59
|
+
if (existsSync(join(root, 'yarn.lock'))) return 'yarn'
|
|
60
|
+
return 'npm'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function detectMonorepoType(root: string): MonorepoType {
|
|
64
|
+
if (existsSync(join(root, 'turbo.json'))) return 'turbo'
|
|
65
|
+
if (existsSync(join(root, 'nx.json'))) return 'nx'
|
|
66
|
+
if (existsSync(join(root, 'pnpm-workspace.yaml'))) return 'pnpm'
|
|
67
|
+
|
|
68
|
+
const pkgPath = join(root, 'package.json')
|
|
69
|
+
if (existsSync(pkgPath)) {
|
|
70
|
+
try {
|
|
71
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
72
|
+
if (pkg.workspaces) return 'npm-workspaces'
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
return 'single'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function detectFramework(deps: Record<string, string>): Framework {
|
|
79
|
+
if (deps.one) return 'one'
|
|
80
|
+
if (deps.expo) return 'expo'
|
|
81
|
+
if (deps['react-native']) return 'bare'
|
|
82
|
+
return 'unknown'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function detectProject(dir: string): DetectedProject | null {
|
|
86
|
+
const pkgPath = join(dir, 'package.json')
|
|
87
|
+
if (!existsSync(pkgPath)) return null
|
|
88
|
+
|
|
89
|
+
let pkg: any
|
|
90
|
+
try {
|
|
91
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
92
|
+
} catch {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
97
|
+
const framework = detectFramework(deps)
|
|
98
|
+
|
|
99
|
+
if (framework === 'unknown') return null
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
dir,
|
|
103
|
+
name: pkg.name || basename(dir),
|
|
104
|
+
framework,
|
|
105
|
+
hasViteConfig:
|
|
106
|
+
existsSync(join(dir, 'vite.config.ts')) || existsSync(join(dir, 'vite.config.js')),
|
|
107
|
+
hasMetroConfig:
|
|
108
|
+
existsSync(join(dir, 'metro.config.js')) ||
|
|
109
|
+
existsSync(join(dir, 'metro.config.ts')),
|
|
110
|
+
hasSootsimDependency: Boolean(deps.sootsim),
|
|
111
|
+
devCommand: pkg.scripts?.dev || null,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getWorkspaceDirs(root: string): string[] {
|
|
116
|
+
const dirs: string[] = []
|
|
117
|
+
|
|
118
|
+
const pnpmWs = join(root, 'pnpm-workspace.yaml')
|
|
119
|
+
if (existsSync(pnpmWs)) {
|
|
120
|
+
const content = readFileSync(pnpmWs, 'utf8')
|
|
121
|
+
const match = content.match(/packages:\s*\n((?:\s+-\s+.+\n?)+)/)
|
|
122
|
+
if (match) {
|
|
123
|
+
const lines = match[1].split('\n').filter(Boolean)
|
|
124
|
+
for (const line of lines) {
|
|
125
|
+
const glob = line.replace(/^\s*-\s*['"]?/, '').replace(/['"]?\s*$/, '')
|
|
126
|
+
if (glob) dirs.push(...expandGlob(root, glob))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return dirs
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const pkgPath = join(root, 'package.json')
|
|
133
|
+
if (existsSync(pkgPath)) {
|
|
134
|
+
try {
|
|
135
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
136
|
+
const workspaces = Array.isArray(pkg.workspaces)
|
|
137
|
+
? pkg.workspaces
|
|
138
|
+
: pkg.workspaces?.packages || []
|
|
139
|
+
for (const glob of workspaces) {
|
|
140
|
+
dirs.push(...expandGlob(root, glob))
|
|
141
|
+
}
|
|
142
|
+
} catch {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return dirs
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function expandGlob(root: string, pattern: string): string[] {
|
|
149
|
+
const clean = pattern.replace(/\/\*\*?$/, '').replace(/\*$/, '')
|
|
150
|
+
const baseDir = join(root, clean)
|
|
151
|
+
|
|
152
|
+
if (!existsSync(baseDir)) return []
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const entries = readdirSync(baseDir)
|
|
156
|
+
return entries
|
|
157
|
+
.map((entry) => join(baseDir, entry))
|
|
158
|
+
.filter((path) => {
|
|
159
|
+
try {
|
|
160
|
+
return statSync(path).isDirectory() && existsSync(join(path, 'package.json'))
|
|
161
|
+
} catch {
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
} catch {
|
|
166
|
+
return []
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function scanWorkspace(root: string): DetectedWorkspace {
|
|
171
|
+
const type = detectMonorepoType(root)
|
|
172
|
+
const packageManager = detectPackageManager(root)
|
|
173
|
+
|
|
174
|
+
if (type === 'single') {
|
|
175
|
+
const project = detectProject(root)
|
|
176
|
+
return {
|
|
177
|
+
root,
|
|
178
|
+
type,
|
|
179
|
+
packageManager,
|
|
180
|
+
apps: project ? [project] : [],
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const workspaceDirs = getWorkspaceDirs(root)
|
|
185
|
+
const apps: DetectedProject[] = []
|
|
186
|
+
|
|
187
|
+
const rootProject = detectProject(root)
|
|
188
|
+
if (rootProject) apps.push(rootProject)
|
|
189
|
+
|
|
190
|
+
for (const dir of workspaceDirs) {
|
|
191
|
+
if (dir === root) continue
|
|
192
|
+
const project = detectProject(dir)
|
|
193
|
+
if (project) apps.push(project)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { root, type, packageManager, apps }
|
|
197
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// sootsim detox — run existing detox suites against sootsim
|
|
2
|
+
//
|
|
3
|
+
// the command is a thin orchestration layer over `npx jest`:
|
|
4
|
+
// 1. resolves a test dir (cwd e2e|test/e2e|detox, or the flag)
|
|
5
|
+
// 2. makes sure a sootsim shell is reachable (auto-launches one if not)
|
|
6
|
+
// 3. spawns jest with our preset baked in so `import ... from 'detox'`
|
|
7
|
+
// resolves to the sootsim driver
|
|
8
|
+
//
|
|
9
|
+
// it deliberately shells out to the user's own jest so their jest version,
|
|
10
|
+
// transforms, and setupFiles keep working. the only thing we force is the
|
|
11
|
+
// moduleNameMapper for `^detox$`.
|
|
12
|
+
|
|
13
|
+
import { spawn } from 'child_process'
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs'
|
|
15
|
+
import { tmpdir } from 'os'
|
|
16
|
+
import { dirname, resolve, join, relative } from 'path'
|
|
17
|
+
import { rnxPublicBrand } from '../../src/public-brand'
|
|
18
|
+
import { resolveCliAuth } from '../auth'
|
|
19
|
+
import { registerRun, shouldRegisterRun } from '../run-registry'
|
|
20
|
+
import { discoverSootsimUrl } from './flow'
|
|
21
|
+
import { resolveDefaultUploadOrigin } from './upload'
|
|
22
|
+
|
|
23
|
+
// resolve the detox preset directory via the package's own subpath export.
|
|
24
|
+
function resolveDetoxDir(): string {
|
|
25
|
+
try {
|
|
26
|
+
const entry = import.meta.resolve(`${rnxPublicBrand.packageName}/detox`)
|
|
27
|
+
// import.meta.resolve returns a file:// URL — convert to a path
|
|
28
|
+
return dirname(entry.startsWith('file://') ? entry.slice(7) : entry)
|
|
29
|
+
} catch {
|
|
30
|
+
// fallback for source-layout dev (no compiled package.json exports)
|
|
31
|
+
return resolve(import.meta.dirname!, '..', '..', 'detox')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface RunDetoxOpts {
|
|
36
|
+
port?: number
|
|
37
|
+
verbose?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const HELP = `
|
|
41
|
+
rnxsim detox — run detox-style tests against an rnx shell
|
|
42
|
+
|
|
43
|
+
usage:
|
|
44
|
+
rnxsim detox [testFiles...] run all tests under e2e/, test/e2e/, or detox/
|
|
45
|
+
rnxsim detox --config <path> use a specific jest config
|
|
46
|
+
rnxsim detox init scaffold sootsim-detox.config.cjs + sample test
|
|
47
|
+
rnxsim detox --watch jest watch mode
|
|
48
|
+
rnxsim detox --headed keep the rnx shell window visible
|
|
49
|
+
rnxsim detox -t <pattern> pass a jest --testNamePattern
|
|
50
|
+
rnxsim detox --no-launch skip auto-launching an rnx shell
|
|
51
|
+
|
|
52
|
+
rnxsim detox auto-launches an rnx shell if none is running on the
|
|
53
|
+
expected port. disable with --no-launch if you're managing the shell yourself.
|
|
54
|
+
`
|
|
55
|
+
|
|
56
|
+
export async function runDetox(args: string[], opts: RunDetoxOpts = {}): Promise<void> {
|
|
57
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
58
|
+
console.log(HELP)
|
|
59
|
+
process.exit(0)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (args[0] === 'init') {
|
|
63
|
+
return initDetoxProject()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const getFlag = (name: string) => {
|
|
67
|
+
const i = args.indexOf(name)
|
|
68
|
+
return i >= 0 ? args[i + 1] : undefined
|
|
69
|
+
}
|
|
70
|
+
const hasFlag = (name: string) => args.includes(name)
|
|
71
|
+
|
|
72
|
+
const watch = hasFlag('--watch')
|
|
73
|
+
const headed = hasFlag('--headed')
|
|
74
|
+
const noLaunch = hasFlag('--no-launch')
|
|
75
|
+
const configFlag = getFlag('--config')
|
|
76
|
+
const port = opts.port || Number(process.env.SOOTSIM_PORT) || 5173
|
|
77
|
+
|
|
78
|
+
// positional args: test file paths passed directly to jest
|
|
79
|
+
const positional = args.filter((a, i) => {
|
|
80
|
+
if (a.startsWith('--') || a === '-t' || a === '-h') return false
|
|
81
|
+
const prev = args[i - 1]
|
|
82
|
+
if (
|
|
83
|
+
prev === '--config' ||
|
|
84
|
+
prev === '-t' ||
|
|
85
|
+
prev === '--testNamePattern' ||
|
|
86
|
+
prev === '--grep'
|
|
87
|
+
)
|
|
88
|
+
return false
|
|
89
|
+
return true
|
|
90
|
+
})
|
|
91
|
+
const passthroughJestFlags = args.filter(
|
|
92
|
+
(a) => a === '--runInBand' || a.startsWith('--maxWorkers='),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
// test dir discovery. external test suites set SOOTSIM_TEST_DIR; otherwise
|
|
96
|
+
// we auto-detect the conventional locations.
|
|
97
|
+
const detoxDirs = ['e2e', 'test/e2e', 'detox']
|
|
98
|
+
let testDir: string | null = process.env.SOOTSIM_TEST_DIR
|
|
99
|
+
? resolve(process.cwd(), process.env.SOOTSIM_TEST_DIR)
|
|
100
|
+
: null
|
|
101
|
+
if (!testDir) {
|
|
102
|
+
for (const dir of detoxDirs) {
|
|
103
|
+
const full = resolve(process.cwd(), dir)
|
|
104
|
+
if (existsSync(full)) {
|
|
105
|
+
testDir = full
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!testDir) {
|
|
111
|
+
console.error(
|
|
112
|
+
` error: no detox tests found. expected one of: ${detoxDirs.join(', ')}\n` +
|
|
113
|
+
` run 'rnxsim detox init' to scaffold a starter suite.`,
|
|
114
|
+
)
|
|
115
|
+
process.exit(1)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// auto-launch: make sure a sootsim shell is reachable before running jest.
|
|
119
|
+
// Reuse the Maestro discovery path: scan local dev servers.
|
|
120
|
+
// and `sootsim open` the first match.
|
|
121
|
+
if (!noLaunch) {
|
|
122
|
+
await ensureShellRunning(port)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// build the jest command. prefer the user's config if they passed --config
|
|
126
|
+
// or if a sootsim-detox.config.cjs exists in cwd. otherwise point at our
|
|
127
|
+
// preset directly via --preset-free inline config.
|
|
128
|
+
const jestArgs: string[] = ['jest']
|
|
129
|
+
const localConfig =
|
|
130
|
+
configFlag ||
|
|
131
|
+
firstExisting(['sootsim-detox.config.cjs', 'jest.config.cjs', 'jest.config.js'])
|
|
132
|
+
|
|
133
|
+
let tempConfig: string | null = null
|
|
134
|
+
if (localConfig) {
|
|
135
|
+
jestArgs.push('--config', resolve(process.cwd(), localConfig))
|
|
136
|
+
} else {
|
|
137
|
+
// no local config — write a temp config that pulls in our preset.
|
|
138
|
+
// jest's --preset flag has quirky module resolution that breaks with
|
|
139
|
+
// subpath exports, so we generate a real config file instead.
|
|
140
|
+
// scope roots + testMatch to the discovered test dir so jest doesn't
|
|
141
|
+
// scan the entire repo (which causes haste collisions in monorepos).
|
|
142
|
+
const presetPath = resolve(resolveDetoxDir(), 'jest-preset.cjs')
|
|
143
|
+
tempConfig = join(tmpdir(), `sootsim-detox-${process.pid}.config.cjs`)
|
|
144
|
+
const cfg = {
|
|
145
|
+
rootDir: testDir,
|
|
146
|
+
roots: ['<rootDir>'],
|
|
147
|
+
testMatch: ['<rootDir>/**/*.test.ts', '<rootDir>/**/*.test.js'],
|
|
148
|
+
}
|
|
149
|
+
writeFileSync(
|
|
150
|
+
tempConfig,
|
|
151
|
+
`module.exports = { ...require(${JSON.stringify(presetPath)}), ...${JSON.stringify(cfg)} }\n`,
|
|
152
|
+
)
|
|
153
|
+
jestArgs.push('--config', tempConfig)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (watch) jestArgs.push('--watch')
|
|
157
|
+
|
|
158
|
+
const testNamePattern =
|
|
159
|
+
getFlag('-t') || getFlag('--testNamePattern') || getFlag('--grep')
|
|
160
|
+
if (testNamePattern) jestArgs.push('-t', testNamePattern)
|
|
161
|
+
|
|
162
|
+
// hosted run registration (org dashboard pass/fail row). when it applies,
|
|
163
|
+
// capture jest's structured results via --json --outputFile so the row can
|
|
164
|
+
// carry test counts + the first failure; console output stays untouched.
|
|
165
|
+
const auth = resolveCliAuth()
|
|
166
|
+
const wantsRegistration = !watch && shouldRegisterRun({ uploadedShare: false, auth })
|
|
167
|
+
let resultsFile: string | null = process.env.SOOTSIM_JEST_OUTPUT_FILE || null
|
|
168
|
+
if (process.env.SOOTSIM_JEST_JSON === '1') jestArgs.push('--json')
|
|
169
|
+
if (process.env.SOOTSIM_JEST_OUTPUT_FILE) {
|
|
170
|
+
jestArgs.push('--outputFile', process.env.SOOTSIM_JEST_OUTPUT_FILE)
|
|
171
|
+
} else if (wantsRegistration) {
|
|
172
|
+
resultsFile = join(tmpdir(), `sootsim-detox-results-${process.pid}.json`)
|
|
173
|
+
jestArgs.push('--json', '--outputFile', resultsFile)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
jestArgs.push(...passthroughJestFlags)
|
|
177
|
+
|
|
178
|
+
// any positional test files get passed straight through
|
|
179
|
+
jestArgs.push(...positional)
|
|
180
|
+
|
|
181
|
+
console.log(` rnxsim detox`)
|
|
182
|
+
console.log(` test dir: ${testDir}`)
|
|
183
|
+
console.log(` port: ${port}${headed ? ' (headed)' : ''}`)
|
|
184
|
+
if (localConfig) console.log(` config: ${localConfig}`)
|
|
185
|
+
|
|
186
|
+
const child = spawn('npx', jestArgs, {
|
|
187
|
+
cwd: process.cwd(),
|
|
188
|
+
stdio: 'inherit',
|
|
189
|
+
env: {
|
|
190
|
+
...process.env,
|
|
191
|
+
SOOTSIM_TEST_DIR: testDir,
|
|
192
|
+
SOOTSIM_PORT: String(port),
|
|
193
|
+
SOOTSIM_HEADED: headed ? '1' : '',
|
|
194
|
+
SOOTSIM_URL: process.env.SOOTSIM_URL || `http://localhost:${port}`,
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// jest owns the browsers, and it only reaps them from its own signal and exit
|
|
199
|
+
// handlers. this process is the one a caller has a pid for, so a SIGTERM aimed
|
|
200
|
+
// at `sootsim detox` lands here and jest never hears about it: it is a
|
|
201
|
+
// grandchild (this -> npx -> jest), so it keeps running with no parent and
|
|
202
|
+
// every chrome it launched is orphaned for good. forward the signal instead of
|
|
203
|
+
// exiting out from under it, and let its exit code end this process.
|
|
204
|
+
const forward = (signal: NodeJS.Signals) => () => {
|
|
205
|
+
child.kill(signal)
|
|
206
|
+
}
|
|
207
|
+
const onTerm = forward('SIGTERM')
|
|
208
|
+
const onInt = forward('SIGINT')
|
|
209
|
+
process.on('SIGTERM', onTerm)
|
|
210
|
+
process.on('SIGINT', onInt)
|
|
211
|
+
|
|
212
|
+
const startedAt = Date.now()
|
|
213
|
+
const code = await new Promise<number>((resolveCode, reject) => {
|
|
214
|
+
child.once('error', reject)
|
|
215
|
+
child.once('exit', (exitCode, signal) => {
|
|
216
|
+
if (signal) {
|
|
217
|
+
console.error(` error: jest terminated by ${signal}`)
|
|
218
|
+
resolveCode(1)
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
resolveCode(exitCode ?? 1)
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
process.off('SIGTERM', onTerm)
|
|
225
|
+
process.off('SIGINT', onInt)
|
|
226
|
+
if (tempConfig)
|
|
227
|
+
try {
|
|
228
|
+
unlinkSync(tempConfig)
|
|
229
|
+
} catch {}
|
|
230
|
+
|
|
231
|
+
if (wantsRegistration) {
|
|
232
|
+
const results = resultsFile ? readJestResults(resultsFile) : null
|
|
233
|
+
const origin = await resolveDefaultUploadOrigin()
|
|
234
|
+
const run = await registerRun({
|
|
235
|
+
origin,
|
|
236
|
+
kind: 'detox',
|
|
237
|
+
name: relative(process.cwd(), testDir) || 'e2e',
|
|
238
|
+
status: code === 0 ? 'passed' : 'failed',
|
|
239
|
+
summary: results
|
|
240
|
+
? `${results.numPassedTests}/${results.numTotalTests} tests passed`
|
|
241
|
+
: null,
|
|
242
|
+
failureMessage: code === 0 ? null : (results?.firstFailure ?? null),
|
|
243
|
+
// tests map onto the row's step summary: total count + first failing
|
|
244
|
+
// index, same shape flow lanes fill from their step trace.
|
|
245
|
+
stepCount: results?.numTotalTests ?? null,
|
|
246
|
+
failedStepIndex: results?.firstFailedIndex ?? null,
|
|
247
|
+
durationMs: Date.now() - startedAt,
|
|
248
|
+
auth,
|
|
249
|
+
})
|
|
250
|
+
if (run) console.log(` run: ${run.id}`)
|
|
251
|
+
}
|
|
252
|
+
process.exit(code)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
type JestRunSummary = {
|
|
256
|
+
numTotalTests: number
|
|
257
|
+
numPassedTests: number
|
|
258
|
+
firstFailure: string | null
|
|
259
|
+
firstFailedIndex: number | null
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// parse jest's --json output file down to the counts + first failure the
|
|
263
|
+
// hosted run row carries. tolerant: any parse problem returns null and the
|
|
264
|
+
// run registers with exit-code status only.
|
|
265
|
+
function readJestResults(file: string): JestRunSummary | null {
|
|
266
|
+
try {
|
|
267
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8')) as {
|
|
268
|
+
numTotalTests?: number
|
|
269
|
+
numPassedTests?: number
|
|
270
|
+
testResults?: Array<{
|
|
271
|
+
testResults?: Array<{
|
|
272
|
+
status?: string
|
|
273
|
+
fullName?: string
|
|
274
|
+
failureMessages?: string[]
|
|
275
|
+
}>
|
|
276
|
+
}>
|
|
277
|
+
}
|
|
278
|
+
let firstFailure: string | null = null
|
|
279
|
+
let firstFailedIndex: number | null = null
|
|
280
|
+
let index = 0
|
|
281
|
+
for (const suite of parsed.testResults ?? []) {
|
|
282
|
+
for (const test of suite.testResults ?? []) {
|
|
283
|
+
if (firstFailedIndex == null && test.status === 'failed') {
|
|
284
|
+
firstFailedIndex = index
|
|
285
|
+
const message = test.failureMessages?.[0]?.split('\n')[0]
|
|
286
|
+
firstFailure = [test.fullName, message].filter(Boolean).join(': ') || null
|
|
287
|
+
}
|
|
288
|
+
index += 1
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (typeof parsed.numTotalTests !== 'number') return null
|
|
292
|
+
return {
|
|
293
|
+
numTotalTests: parsed.numTotalTests,
|
|
294
|
+
numPassedTests: parsed.numPassedTests ?? 0,
|
|
295
|
+
firstFailure,
|
|
296
|
+
firstFailedIndex,
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
return null
|
|
300
|
+
} finally {
|
|
301
|
+
if (!process.env.SOOTSIM_JEST_OUTPUT_FILE) {
|
|
302
|
+
try {
|
|
303
|
+
unlinkSync(file)
|
|
304
|
+
} catch {}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function firstExisting(names: string[]): string | null {
|
|
310
|
+
for (const n of names) {
|
|
311
|
+
if (existsSync(resolve(process.cwd(), n))) return n
|
|
312
|
+
}
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function ensureShellRunning(port: number): Promise<void> {
|
|
317
|
+
const shellUrl = await discoverSootsimUrl()
|
|
318
|
+
if (shellUrl) return
|
|
319
|
+
|
|
320
|
+
console.warn(
|
|
321
|
+
` warn: no rnx shell reachable on :${port}. start your app, then run \`rnxsim open <app-port>\`\n` +
|
|
322
|
+
` or pass --no-launch if you're managing the shell yourself.`,
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function initDetoxProject(): void {
|
|
327
|
+
const cwd = process.cwd()
|
|
328
|
+
const configPath = resolve(cwd, 'sootsim-detox.config.cjs')
|
|
329
|
+
if (existsSync(configPath)) {
|
|
330
|
+
console.log(` skip: ${configPath} already exists`)
|
|
331
|
+
} else {
|
|
332
|
+
writeFileSync(
|
|
333
|
+
configPath,
|
|
334
|
+
`// rnxsim detox jest config.
|
|
335
|
+
// extends the sootsim preset, which rewrites \`import ... from 'detox'\`
|
|
336
|
+
// to the sootsim driver and keeps your jest transforms intact.
|
|
337
|
+
|
|
338
|
+
/** @type {import('@jest/types').Config.InitialOptions} */
|
|
339
|
+
module.exports = {
|
|
340
|
+
preset: 'rnxsim/detox/jest-preset',
|
|
341
|
+
rootDir: __dirname,
|
|
342
|
+
testMatch: ['<rootDir>/e2e/**/*.test.ts', '<rootDir>/e2e/**/*.test.js'],
|
|
343
|
+
}
|
|
344
|
+
`,
|
|
345
|
+
)
|
|
346
|
+
console.log(` created ${configPath}`)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const e2eDir = resolve(cwd, 'e2e')
|
|
350
|
+
const samplePath = resolve(e2eDir, 'example.test.ts')
|
|
351
|
+
if (existsSync(samplePath)) {
|
|
352
|
+
console.log(` skip: ${samplePath} already exists`)
|
|
353
|
+
} else {
|
|
354
|
+
mkdirSync(e2eDir, { recursive: true })
|
|
355
|
+
writeFileSync(
|
|
356
|
+
samplePath,
|
|
357
|
+
`// sample rnxsim detox test. resolve \`detox\` via the jest preset in
|
|
358
|
+
// sootsim-detox.config.cjs — no extra imports required.
|
|
359
|
+
|
|
360
|
+
import { by, device, element, expect, waitFor } from 'detox'
|
|
361
|
+
|
|
362
|
+
describe('example', () => {
|
|
363
|
+
beforeAll(async () => {
|
|
364
|
+
await device.launchApp()
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('shows the welcome text', async () => {
|
|
368
|
+
await waitFor(element(by.text('Welcome'))).toBeVisible().withTimeout(5000)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
it('taps a button by id', async () => {
|
|
372
|
+
await element(by.id('start-button')).tap()
|
|
373
|
+
await expect(element(by.id('home-screen'))).toBeVisible()
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
`,
|
|
377
|
+
)
|
|
378
|
+
console.log(` created ${samplePath}`)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
console.log(`\n next:`)
|
|
382
|
+
console.log(` rnxsim detox # run the sample`)
|
|
383
|
+
console.log(` rnxsim detox --watch`)
|
|
384
|
+
process.exit(0)
|
|
385
|
+
}
|