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,541 @@
|
|
|
1
|
+
// `sootsim assert <verb> <verb-args...> <assertion-flags...>`
|
|
2
|
+
//
|
|
3
|
+
// thin wrapper that runs any read verb with --json forced on, captures the
|
|
4
|
+
// JSON payload, and converts predicate flags into a bisect-friendly exit
|
|
5
|
+
// code. see docs for the full flag surface — this file intentionally stays
|
|
6
|
+
// small: every assertion is a pure function over the parsed JSON, and the
|
|
7
|
+
// only runtime work is the retry loop for --within and the optional shell
|
|
8
|
+
// out to `jq` for --jq.
|
|
9
|
+
//
|
|
10
|
+
// exit codes:
|
|
11
|
+
// 0 assertion passed (bisect: good)
|
|
12
|
+
// 1 assertion evaluated and failed (bisect: bad)
|
|
13
|
+
// 125 could not evaluate (bisect: skip)
|
|
14
|
+
// 2 misuse — bad flags, unknown verb (treated as bad by bisect)
|
|
15
|
+
|
|
16
|
+
import { spawn } from 'child_process'
|
|
17
|
+
|
|
18
|
+
// READ_VERBS lists every command whose stdout we know is valid JSON under
|
|
19
|
+
// --json. keeping this explicit means `sootsim assert tap 100 200` fails
|
|
20
|
+
// fast with a clear message instead of silently running a write verb and
|
|
21
|
+
// trying to parse " tapped 100,200" as JSON.
|
|
22
|
+
const READ_VERBS = new Set([
|
|
23
|
+
// top-level
|
|
24
|
+
'describe',
|
|
25
|
+
'find',
|
|
26
|
+
'list',
|
|
27
|
+
'network',
|
|
28
|
+
'logs',
|
|
29
|
+
// get <noun>
|
|
30
|
+
'count',
|
|
31
|
+
'tree',
|
|
32
|
+
'url',
|
|
33
|
+
'a11y',
|
|
34
|
+
'node',
|
|
35
|
+
'layout',
|
|
36
|
+
'keyboard',
|
|
37
|
+
'errors',
|
|
38
|
+
'warnings',
|
|
39
|
+
'requests',
|
|
40
|
+
'animations',
|
|
41
|
+
'animation',
|
|
42
|
+
'state',
|
|
43
|
+
'sample-color',
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
// assertion flags that assert recognizes. everything else is passed through
|
|
47
|
+
// to the inner verb untouched. keeping this as a set (rather than positional
|
|
48
|
+
// parsing) means `sootsim assert find --testid X --count 4` reads naturally.
|
|
49
|
+
const BOOL_ASSERT_FLAGS = new Set([
|
|
50
|
+
'--exists',
|
|
51
|
+
'--empty',
|
|
52
|
+
'--negate',
|
|
53
|
+
'--quiet',
|
|
54
|
+
'--allow-timeout',
|
|
55
|
+
'!',
|
|
56
|
+
])
|
|
57
|
+
const VALUE_ASSERT_FLAGS = new Set([
|
|
58
|
+
'--count',
|
|
59
|
+
'--count-at-least',
|
|
60
|
+
'--count-at-most',
|
|
61
|
+
'--contains',
|
|
62
|
+
'--not-contains',
|
|
63
|
+
'--matches',
|
|
64
|
+
'--not-matches',
|
|
65
|
+
'--equals',
|
|
66
|
+
'--jq',
|
|
67
|
+
'--has-path',
|
|
68
|
+
'--within',
|
|
69
|
+
])
|
|
70
|
+
// --path-equals takes TWO values: <dotted.path> <value>
|
|
71
|
+
const DUAL_VALUE_ASSERT_FLAGS = new Set(['--path-equals'])
|
|
72
|
+
|
|
73
|
+
interface Predicate {
|
|
74
|
+
kind: string
|
|
75
|
+
args: string[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface AssertArgs {
|
|
79
|
+
verbArgs: string[]
|
|
80
|
+
predicates: Predicate[]
|
|
81
|
+
withinMs: number
|
|
82
|
+
negate: boolean
|
|
83
|
+
quiet: boolean
|
|
84
|
+
allowTimeout: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseAssertArgs(raw: string[]): AssertArgs | { error: string } {
|
|
88
|
+
const verbArgs: string[] = []
|
|
89
|
+
const predicates: Predicate[] = []
|
|
90
|
+
let withinMs = 0
|
|
91
|
+
let negate = false
|
|
92
|
+
let quiet = false
|
|
93
|
+
let allowTimeout = false
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < raw.length; i++) {
|
|
96
|
+
const a = raw[i]
|
|
97
|
+
if (a === '--within') {
|
|
98
|
+
const v = Number(raw[i + 1])
|
|
99
|
+
if (!Number.isFinite(v) || v < 0) {
|
|
100
|
+
return { error: `--within requires a non-negative number, got: ${raw[i + 1]}` }
|
|
101
|
+
}
|
|
102
|
+
withinMs = v
|
|
103
|
+
i++
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
if (a === '--negate' || a === '!') {
|
|
107
|
+
negate = true
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
if (a === '--quiet') {
|
|
111
|
+
quiet = true
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (a === '--allow-timeout') {
|
|
115
|
+
allowTimeout = true
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
if (BOOL_ASSERT_FLAGS.has(a)) {
|
|
119
|
+
predicates.push({ kind: a, args: [] })
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
if (VALUE_ASSERT_FLAGS.has(a)) {
|
|
123
|
+
const v = raw[i + 1]
|
|
124
|
+
if (v == null) return { error: `${a} requires a value` }
|
|
125
|
+
predicates.push({ kind: a, args: [v] })
|
|
126
|
+
i++
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
if (DUAL_VALUE_ASSERT_FLAGS.has(a)) {
|
|
130
|
+
const v1 = raw[i + 1]
|
|
131
|
+
const v2 = raw[i + 2]
|
|
132
|
+
if (v1 == null || v2 == null) return { error: `${a} requires <path> <value>` }
|
|
133
|
+
predicates.push({ kind: a, args: [v1, v2] })
|
|
134
|
+
i += 2
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
verbArgs.push(a)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (verbArgs.length === 0)
|
|
141
|
+
return { error: 'assert requires a verb (e.g. find, describe, get errors)' }
|
|
142
|
+
return { verbArgs, predicates, withinMs, negate, quiet, allowTimeout }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// resolve the verb name the user typed. handles `get <noun>`, `do <verb>`,
|
|
146
|
+
// and plain top-level verbs. returns null if the verb is unknown or is a
|
|
147
|
+
// known write verb (which would never produce parseable json).
|
|
148
|
+
function resolveReadVerb(verbArgs: string[]): string | null {
|
|
149
|
+
if (verbArgs[0] === 'get' || verbArgs[0] === 'debug') {
|
|
150
|
+
return READ_VERBS.has(verbArgs[1]) ? verbArgs[1] : null
|
|
151
|
+
}
|
|
152
|
+
if (verbArgs[0] === 'do' || verbArgs[0] === 'wait') return null
|
|
153
|
+
return READ_VERBS.has(verbArgs[0]) ? verbArgs[0] : null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
interface VerbResult {
|
|
157
|
+
ok: boolean
|
|
158
|
+
payload: unknown
|
|
159
|
+
reason?: string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function runVerbOnce(verbArgs: string[]): Promise<VerbResult> {
|
|
163
|
+
// re-invoke self using whichever entrypoint is actually running (bin.ts
|
|
164
|
+
// in dev, dist-cli/bin.js when installed). running the real bin keeps us
|
|
165
|
+
// honest — every code path the user's verb would take, assert takes too.
|
|
166
|
+
const binPath = process.argv[1]
|
|
167
|
+
const args = [binPath, ...verbArgs, '--json']
|
|
168
|
+
|
|
169
|
+
return new Promise<VerbResult>((resolve) => {
|
|
170
|
+
const proc = spawn(process.execPath, args, {
|
|
171
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
172
|
+
env: process.env,
|
|
173
|
+
})
|
|
174
|
+
let stdout = ''
|
|
175
|
+
let stderr = ''
|
|
176
|
+
proc.stdout.on('data', (d) => (stdout += d.toString()))
|
|
177
|
+
proc.stderr.on('data', (d) => (stderr += d.toString()))
|
|
178
|
+
proc.on('error', (err) => {
|
|
179
|
+
resolve({ ok: false, payload: null, reason: `spawn failed: ${err.message}` })
|
|
180
|
+
})
|
|
181
|
+
proc.on('close', (code) => {
|
|
182
|
+
if (code !== 0) {
|
|
183
|
+
// stderr tail usually has the bridge diagnostic — include a short
|
|
184
|
+
// slice so the skip message is actionable.
|
|
185
|
+
const tail = stderr.trim().split('\n').slice(-2).join(' | ').slice(0, 200)
|
|
186
|
+
resolve({
|
|
187
|
+
ok: false,
|
|
188
|
+
payload: null,
|
|
189
|
+
reason: `verb exited ${code}${tail ? `: ${tail}` : ''}`,
|
|
190
|
+
})
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
if (!stdout.trim()) {
|
|
194
|
+
resolve({ ok: false, payload: null, reason: 'verb produced no output' })
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const payload = JSON.parse(stdout)
|
|
199
|
+
resolve({ ok: true, payload })
|
|
200
|
+
} catch {
|
|
201
|
+
resolve({
|
|
202
|
+
ok: false,
|
|
203
|
+
payload: null,
|
|
204
|
+
reason: `verb output was not valid json: ${stdout.slice(0, 120)}`,
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// read a dotted path against a JSON value. numeric segments index arrays,
|
|
212
|
+
// anything else reads object keys. returns { found: false } for any missing
|
|
213
|
+
// segment so callers can distinguish "path exists, value is null" from
|
|
214
|
+
// "path doesn't exist."
|
|
215
|
+
function readPath(
|
|
216
|
+
value: unknown,
|
|
217
|
+
path: string,
|
|
218
|
+
): { found: true; value: unknown } | { found: false } {
|
|
219
|
+
const parts = path.split('.').filter((s) => s.length > 0)
|
|
220
|
+
let cur: unknown = value
|
|
221
|
+
for (const part of parts) {
|
|
222
|
+
if (cur == null) return { found: false }
|
|
223
|
+
if (Array.isArray(cur)) {
|
|
224
|
+
const idx = Number(part)
|
|
225
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= cur.length) return { found: false }
|
|
226
|
+
cur = cur[idx]
|
|
227
|
+
continue
|
|
228
|
+
}
|
|
229
|
+
if (typeof cur === 'object') {
|
|
230
|
+
const obj = cur as Record<string, unknown>
|
|
231
|
+
if (!(part in obj)) return { found: false }
|
|
232
|
+
cur = obj[part]
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
return { found: false }
|
|
236
|
+
}
|
|
237
|
+
return { found: true, value: cur }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
interface PredicateResult {
|
|
241
|
+
ok: boolean
|
|
242
|
+
reason: string
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function lengthOf(payload: unknown): number | null {
|
|
246
|
+
if (Array.isArray(payload)) return payload.length
|
|
247
|
+
if (payload && typeof payload === 'object') return Object.keys(payload).length
|
|
248
|
+
return null
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function evalPredicate(
|
|
252
|
+
pred: Predicate,
|
|
253
|
+
payload: unknown,
|
|
254
|
+
): Promise<PredicateResult> {
|
|
255
|
+
const { kind, args } = pred
|
|
256
|
+
switch (kind) {
|
|
257
|
+
case '--exists': {
|
|
258
|
+
if (payload == null) return { ok: false, reason: 'payload is null' }
|
|
259
|
+
const len = lengthOf(payload)
|
|
260
|
+
if (len === 0) return { ok: false, reason: 'payload is empty' }
|
|
261
|
+
return { ok: true, reason: 'exists' }
|
|
262
|
+
}
|
|
263
|
+
case '--empty': {
|
|
264
|
+
if (payload == null) return { ok: true, reason: 'payload is null' }
|
|
265
|
+
const len = lengthOf(payload)
|
|
266
|
+
if (len === 0) return { ok: true, reason: 'payload is empty' }
|
|
267
|
+
return { ok: false, reason: `payload has ${len} item(s)` }
|
|
268
|
+
}
|
|
269
|
+
case '--count': {
|
|
270
|
+
const want = Number(args[0])
|
|
271
|
+
const got = lengthOf(payload)
|
|
272
|
+
if (got == null)
|
|
273
|
+
return { ok: false, reason: `payload not countable (${typeof payload})` }
|
|
274
|
+
return {
|
|
275
|
+
ok: got === want,
|
|
276
|
+
reason: `count=${got}, want=${want}`,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
case '--count-at-least': {
|
|
280
|
+
const want = Number(args[0])
|
|
281
|
+
const got = lengthOf(payload)
|
|
282
|
+
if (got == null) return { ok: false, reason: `payload not countable` }
|
|
283
|
+
return { ok: got >= want, reason: `count=${got}, want>=${want}` }
|
|
284
|
+
}
|
|
285
|
+
case '--count-at-most': {
|
|
286
|
+
const want = Number(args[0])
|
|
287
|
+
const got = lengthOf(payload)
|
|
288
|
+
if (got == null) return { ok: false, reason: `payload not countable` }
|
|
289
|
+
return { ok: got <= want, reason: `count=${got}, want<=${want}` }
|
|
290
|
+
}
|
|
291
|
+
case '--contains': {
|
|
292
|
+
const needle = args[0]
|
|
293
|
+
const hay = JSON.stringify(payload)
|
|
294
|
+
return {
|
|
295
|
+
ok: hay.includes(needle),
|
|
296
|
+
reason: hay.includes(needle) ? `contains "${needle}"` : `missing "${needle}"`,
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
case '--not-contains': {
|
|
300
|
+
const needle = args[0]
|
|
301
|
+
const hay = JSON.stringify(payload)
|
|
302
|
+
return {
|
|
303
|
+
ok: !hay.includes(needle),
|
|
304
|
+
reason: !hay.includes(needle)
|
|
305
|
+
? `not contains "${needle}"`
|
|
306
|
+
: `unexpected "${needle}"`,
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
case '--matches': {
|
|
310
|
+
const re = new RegExp(args[0])
|
|
311
|
+
const hay = JSON.stringify(payload)
|
|
312
|
+
const ok = re.test(hay)
|
|
313
|
+
return {
|
|
314
|
+
ok,
|
|
315
|
+
reason: ok ? `matches /${args[0]}/` : `no match for /${args[0]}/`,
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
case '--not-matches': {
|
|
319
|
+
const re = new RegExp(args[0])
|
|
320
|
+
const hay = JSON.stringify(payload)
|
|
321
|
+
const ok = !re.test(hay)
|
|
322
|
+
return {
|
|
323
|
+
ok,
|
|
324
|
+
reason: ok ? `not matches /${args[0]}/` : `unexpected /${args[0]}/`,
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
case '--equals': {
|
|
328
|
+
const want = args[0]
|
|
329
|
+
const got =
|
|
330
|
+
typeof payload === 'string'
|
|
331
|
+
? payload
|
|
332
|
+
: typeof payload === 'number' || typeof payload === 'boolean'
|
|
333
|
+
? String(payload)
|
|
334
|
+
: JSON.stringify(payload)
|
|
335
|
+
return {
|
|
336
|
+
ok: got === want,
|
|
337
|
+
reason: `got=${JSON.stringify(got)}, want=${JSON.stringify(want)}`,
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
case '--has-path': {
|
|
341
|
+
const r = readPath(payload, args[0])
|
|
342
|
+
return {
|
|
343
|
+
ok: r.found,
|
|
344
|
+
reason: r.found ? `path ${args[0]} present` : `path ${args[0]} missing`,
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
case '--path-equals': {
|
|
348
|
+
const [path, want] = args
|
|
349
|
+
const r = readPath(payload, path)
|
|
350
|
+
if (!r.found) return { ok: false, reason: `path ${path} missing` }
|
|
351
|
+
const got =
|
|
352
|
+
typeof r.value === 'string' ||
|
|
353
|
+
typeof r.value === 'number' ||
|
|
354
|
+
typeof r.value === 'boolean'
|
|
355
|
+
? String(r.value)
|
|
356
|
+
: JSON.stringify(r.value)
|
|
357
|
+
return { ok: got === want, reason: `${path}=${got}, want=${want}` }
|
|
358
|
+
}
|
|
359
|
+
case '--jq': {
|
|
360
|
+
// shell out to `jq`. if jq isn't installed we can't evaluate, so we
|
|
361
|
+
// bubble that up as a 125-skip rather than treating it as a failure.
|
|
362
|
+
const expr = args[0]
|
|
363
|
+
try {
|
|
364
|
+
const out = await runJq(expr, payload)
|
|
365
|
+
const trimmed = out.trim()
|
|
366
|
+
const truthy =
|
|
367
|
+
trimmed.length > 0 &&
|
|
368
|
+
trimmed !== 'null' &&
|
|
369
|
+
trimmed !== 'false' &&
|
|
370
|
+
trimmed !== '""'
|
|
371
|
+
return { ok: truthy, reason: `jq: ${trimmed.slice(0, 120)}` }
|
|
372
|
+
} catch (err: any) {
|
|
373
|
+
// throw so the caller can turn this into a 125 — consistent with how
|
|
374
|
+
// "verb crashed" is treated. we don't want bisect marking commits bad
|
|
375
|
+
// because jq isn't installed.
|
|
376
|
+
throw new Error(`jq evaluation failed: ${err.message}`)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
default:
|
|
380
|
+
return { ok: false, reason: `unknown predicate ${kind}` }
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function runJq(expr: string, payload: unknown): Promise<string> {
|
|
385
|
+
return new Promise((resolve, reject) => {
|
|
386
|
+
const proc = spawn('jq', [expr], { stdio: ['pipe', 'pipe', 'pipe'] })
|
|
387
|
+
let out = ''
|
|
388
|
+
let err = ''
|
|
389
|
+
proc.stdout.on('data', (d) => (out += d.toString()))
|
|
390
|
+
proc.stderr.on('data', (d) => (err += d.toString()))
|
|
391
|
+
proc.on('error', reject)
|
|
392
|
+
proc.on('close', (code) => {
|
|
393
|
+
if (code !== 0) {
|
|
394
|
+
reject(new Error(err.trim() || `jq exited ${code}`))
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
resolve(out)
|
|
398
|
+
})
|
|
399
|
+
proc.stdin.end(JSON.stringify(payload))
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export async function runAssert(rawArgs: string[]): Promise<void> {
|
|
404
|
+
if (rawArgs[0] === '--help' || rawArgs[0] === '-h' || rawArgs.length === 0) {
|
|
405
|
+
printHelp()
|
|
406
|
+
process.exit(rawArgs.length === 0 ? 2 : 0)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const parsed = parseAssertArgs(rawArgs)
|
|
410
|
+
if ('error' in parsed) {
|
|
411
|
+
process.stderr.write(` assert: ${parsed.error}\n`)
|
|
412
|
+
process.exit(2)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const { verbArgs, predicates, withinMs, negate, quiet, allowTimeout } = parsed
|
|
416
|
+
|
|
417
|
+
const verb = resolveReadVerb(verbArgs)
|
|
418
|
+
if (!verb) {
|
|
419
|
+
process.stderr.write(
|
|
420
|
+
` assert: unknown or non-read verb: ${verbArgs.join(' ')}\n` +
|
|
421
|
+
` supported: ${Array.from(READ_VERBS).sort().join(', ')}\n`,
|
|
422
|
+
)
|
|
423
|
+
process.exit(2)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// default assertion when none given is --exists. matches the common
|
|
427
|
+
// "does this node / error / entry even show up?" check.
|
|
428
|
+
const effectivePredicates =
|
|
429
|
+
predicates.length > 0 ? predicates : [{ kind: '--exists', args: [] }]
|
|
430
|
+
|
|
431
|
+
const deadline = withinMs > 0 ? Date.now() + withinMs : 0
|
|
432
|
+
let attempts = 0
|
|
433
|
+
let lastFailReason = ''
|
|
434
|
+
let lastVerbSkipReason = ''
|
|
435
|
+
|
|
436
|
+
while (true) {
|
|
437
|
+
attempts++
|
|
438
|
+
const verbResult = await runVerbOnce(verbArgs)
|
|
439
|
+
if (!verbResult.ok) {
|
|
440
|
+
lastVerbSkipReason = verbResult.reason || 'verb failed'
|
|
441
|
+
if (Date.now() < deadline) {
|
|
442
|
+
await sleep(150)
|
|
443
|
+
continue
|
|
444
|
+
}
|
|
445
|
+
// out of retry budget. default is skip (125) because "verb couldn't
|
|
446
|
+
// evaluate" is distinct from "assertion evaluated and failed".
|
|
447
|
+
// --allow-timeout flips this to fail (1) — used when an auto-wait
|
|
448
|
+
// timeout inside the verb is a meaningful negative signal, not a
|
|
449
|
+
// "please bisect-skip this commit".
|
|
450
|
+
const exitCode = allowTimeout ? 1 : 125
|
|
451
|
+
if (!quiet) {
|
|
452
|
+
const label = exitCode === 1 ? 'fail' : 'skip'
|
|
453
|
+
process.stderr.write(` assert: ${label} — ${lastVerbSkipReason}\n`)
|
|
454
|
+
}
|
|
455
|
+
process.exit(exitCode)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
let allPass = true
|
|
459
|
+
const reasons: string[] = []
|
|
460
|
+
try {
|
|
461
|
+
for (const pred of effectivePredicates) {
|
|
462
|
+
const r = await evalPredicate(pred, verbResult.payload)
|
|
463
|
+
reasons.push(`${pred.kind}: ${r.reason}`)
|
|
464
|
+
if (!r.ok) {
|
|
465
|
+
allPass = false
|
|
466
|
+
break
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
} catch (err: any) {
|
|
470
|
+
if (!quiet) process.stderr.write(` assert: skip — ${err.message}\n`)
|
|
471
|
+
process.exit(125)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const pass = negate ? !allPass : allPass
|
|
475
|
+
if (pass) {
|
|
476
|
+
if (!quiet) {
|
|
477
|
+
process.stderr.write(
|
|
478
|
+
` assert: pass${attempts > 1 ? ` (attempt ${attempts})` : ''} — ${reasons.join('; ')}\n`,
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
process.exit(0)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
lastFailReason = reasons.join('; ')
|
|
485
|
+
if (Date.now() < deadline) {
|
|
486
|
+
await sleep(150)
|
|
487
|
+
continue
|
|
488
|
+
}
|
|
489
|
+
if (!quiet) {
|
|
490
|
+
process.stderr.write(
|
|
491
|
+
` assert: fail${attempts > 1 ? ` after ${attempts} attempt(s)` : ''} — ${lastFailReason}\n`,
|
|
492
|
+
)
|
|
493
|
+
}
|
|
494
|
+
process.exit(1)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function sleep(ms: number): Promise<void> {
|
|
499
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function printHelp(): void {
|
|
503
|
+
process.stdout.write(`
|
|
504
|
+
rnxsim assert — run any read verb and convert its output into a bisect-friendly exit code.
|
|
505
|
+
|
|
506
|
+
usage:
|
|
507
|
+
rnxsim assert <verb> <verb-args...> <assertion-flags...>
|
|
508
|
+
|
|
509
|
+
exit codes:
|
|
510
|
+
0 assertion passed (git bisect: good)
|
|
511
|
+
1 assertion failed (git bisect: bad)
|
|
512
|
+
125 could not evaluate (git bisect: skip — app not loaded, verb crashed)
|
|
513
|
+
2 misuse (bad flags/verb)
|
|
514
|
+
|
|
515
|
+
assertion flags (AND'd when multiple are given):
|
|
516
|
+
--count <n> result array has exactly n items
|
|
517
|
+
--count-at-least <n> length >= n
|
|
518
|
+
--count-at-most <n> length <= n
|
|
519
|
+
--exists non-null, non-empty (default if no flag given)
|
|
520
|
+
--empty null or zero length
|
|
521
|
+
--contains <str> JSON.stringify(result) contains str (repeatable)
|
|
522
|
+
--not-contains <str> opposite
|
|
523
|
+
--matches <regex> result (stringified) matches regex
|
|
524
|
+
--not-matches <regex> opposite
|
|
525
|
+
--equals <literal> scalar equality
|
|
526
|
+
--has-path <dotted.path> nested path exists
|
|
527
|
+
--path-equals <path> <value> nested path equals value
|
|
528
|
+
--jq <expr> jq expression is truthy (requires jq on PATH)
|
|
529
|
+
--within <ms> retry verb + predicates until pass or deadline
|
|
530
|
+
--allow-timeout treat verb auto-wait timeout as a fail, not a skip
|
|
531
|
+
--negate, ! flip pass/fail (does not flip skip)
|
|
532
|
+
--quiet suppress the one-line pass/fail summary
|
|
533
|
+
|
|
534
|
+
examples:
|
|
535
|
+
rnxsim assert find --testid set-max-input --count 4
|
|
536
|
+
rnxsim assert describe --contains "swap-form-header"
|
|
537
|
+
rnxsim assert get errors --count 0
|
|
538
|
+
rnxsim assert find --testid submit --exists --within 3000
|
|
539
|
+
rnxsim assert describe --jq '.shell.state == "app"'
|
|
540
|
+
`)
|
|
541
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { refreshSharedDesktopAuthSession } from '../../src/auth/shared-session'
|
|
2
|
+
import { resolveCliAuth } from '../auth'
|
|
3
|
+
|
|
4
|
+
function printHelp(): void {
|
|
5
|
+
console.log(`
|
|
6
|
+
rnxsim auth — inspect CLI auth
|
|
7
|
+
|
|
8
|
+
usage:
|
|
9
|
+
rnxsim auth
|
|
10
|
+
|
|
11
|
+
API keys are read from RNX_API_KEY, or SOOTSIM_API_KEY if that is unset.
|
|
12
|
+
Use \`rnxsim login\` to sign in and \`rnxsim logout\` to clear the desktop session.
|
|
13
|
+
`)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function printStatus(): Promise<number> {
|
|
17
|
+
const auth = resolveCliAuth()
|
|
18
|
+
if (!auth) {
|
|
19
|
+
console.log(' not signed in')
|
|
20
|
+
console.log(' set RNX_API_KEY=sk_rnx_... or run `rnxsim login`')
|
|
21
|
+
return 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (auth.kind === 'api-key') {
|
|
25
|
+
console.log(` api key ${auth.secret.slice(0, 14)}...`)
|
|
26
|
+
console.log(` source: ${auth.envName} env var`)
|
|
27
|
+
return 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (auth.kind === 'github') {
|
|
31
|
+
console.log(` github token (${auth.source})`)
|
|
32
|
+
console.log(` repo: ${auth.repoId}`)
|
|
33
|
+
return 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const current = await refreshSharedDesktopAuthSession(auth.origin)
|
|
37
|
+
const user = current?.user
|
|
38
|
+
console.log(` ${user?.email || user?.name || user?.id || 'signed in'}`)
|
|
39
|
+
console.log(` origin: ${current?.origin ?? auth.origin}`)
|
|
40
|
+
if (current?.updatedAt) console.log(` updated: ${current.updatedAt}`)
|
|
41
|
+
return 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function runAuth(args: string[]): Promise<void> {
|
|
45
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
46
|
+
printHelp()
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const sub = args.find((arg) => !arg.startsWith('-'))
|
|
51
|
+
if (!sub || sub === 'status') {
|
|
52
|
+
process.exitCode = await printStatus()
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.error(` unknown auth command: ${sub}`)
|
|
57
|
+
printHelp()
|
|
58
|
+
process.exit(1)
|
|
59
|
+
}
|