peachsim 0.1.527

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.
Files changed (324) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +640 -0
  3. package/cli/app-config.ts +82 -0
  4. package/cli/app-fonts.ts +388 -0
  5. package/cli/app-project.ts +59 -0
  6. package/cli/app-splash.ts +171 -0
  7. package/cli/app-state-reset.ts +26 -0
  8. package/cli/auth.ts +184 -0
  9. package/cli/bin.ts +59 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2806 -0
  12. package/cli/browser-backend.ts +44 -0
  13. package/cli/browser-evals.ts +96 -0
  14. package/cli/cli-update.ts +261 -0
  15. package/cli/cloud-client.ts +1530 -0
  16. package/cli/cloud-dispatch.ts +206 -0
  17. package/cli/cloud-session.ts +227 -0
  18. package/cli/command-contract.ts +221 -0
  19. package/cli/command-registry.ts +304 -0
  20. package/cli/commands/agent-wrapper.ts +1048 -0
  21. package/cli/commands/agent.ts +439 -0
  22. package/cli/commands/app-fonts.ts +99 -0
  23. package/cli/commands/assert.ts +542 -0
  24. package/cli/commands/auth.ts +71 -0
  25. package/cli/commands/box/bundle-plane.ts +71 -0
  26. package/cli/commands/box/checkout-plane.ts +256 -0
  27. package/cli/commands/box/plane-share.ts +208 -0
  28. package/cli/commands/box/rnx-command.ts +60 -0
  29. package/cli/commands/box/search-command.ts +157 -0
  30. package/cli/commands/box.ts +925 -0
  31. package/cli/commands/camera.ts +266 -0
  32. package/cli/commands/cleanup.ts +169 -0
  33. package/cli/commands/compat.ts +176 -0
  34. package/cli/commands/config.ts +34 -0
  35. package/cli/commands/control.ts +2154 -0
  36. package/cli/commands/cpu-profile.ts +941 -0
  37. package/cli/commands/daemon-mac-app.ts +165 -0
  38. package/cli/commands/daemon.ts +800 -0
  39. package/cli/commands/debug.ts +751 -0
  40. package/cli/commands/desktop.ts +39 -0
  41. package/cli/commands/detect.ts +201 -0
  42. package/cli/commands/detox.ts +481 -0
  43. package/cli/commands/device.ts +134 -0
  44. package/cli/commands/diagnose.ts +589 -0
  45. package/cli/commands/do-chain.ts +408 -0
  46. package/cli/commands/electron.ts +94 -0
  47. package/cli/commands/film.ts +379 -0
  48. package/cli/commands/flow.ts +1240 -0
  49. package/cli/commands/inspect/actions.ts +516 -0
  50. package/cli/commands/inspect/core.ts +2086 -0
  51. package/cli/commands/inspect/count.ts +17 -0
  52. package/cli/commands/inspect/describe.ts +119 -0
  53. package/cli/commands/inspect/env.ts +25 -0
  54. package/cli/commands/inspect/find.ts +169 -0
  55. package/cli/commands/inspect/get-layout.ts +53 -0
  56. package/cli/commands/inspect/keyboard.ts +54 -0
  57. package/cli/commands/inspect/list.ts +175 -0
  58. package/cli/commands/inspect/memory.ts +215 -0
  59. package/cli/commands/inspect/redaction.ts +39 -0
  60. package/cli/commands/inspect/resolve-target.ts +77 -0
  61. package/cli/commands/inspect/screens.ts +78 -0
  62. package/cli/commands/inspect/settle.ts +32 -0
  63. package/cli/commands/inspect/settling.ts +197 -0
  64. package/cli/commands/inspect/shared.ts +259 -0
  65. package/cli/commands/inspect/sleep.ts +15 -0
  66. package/cli/commands/inspect/tree.ts +23 -0
  67. package/cli/commands/inspect/url.ts +17 -0
  68. package/cli/commands/inspect/wait-event.ts +211 -0
  69. package/cli/commands/inspect/wait-idle.ts +25 -0
  70. package/cli/commands/inspect/wait-ready.ts +87 -0
  71. package/cli/commands/inspect/wait-selector.ts +59 -0
  72. package/cli/commands/inspect/wait.ts +32 -0
  73. package/cli/commands/inspect.ts +4738 -0
  74. package/cli/commands/install-cli.ts +275 -0
  75. package/cli/commands/install-desktop.ts +385 -0
  76. package/cli/commands/login.ts +357 -0
  77. package/cli/commands/logout.ts +24 -0
  78. package/cli/commands/maestro-generate.ts +375 -0
  79. package/cli/commands/maestro.ts +536 -0
  80. package/cli/commands/mode.ts +58 -0
  81. package/cli/commands/no-bridge-hint.ts +80 -0
  82. package/cli/commands/perf.ts +67 -0
  83. package/cli/commands/permissions.ts +203 -0
  84. package/cli/commands/platform.ts +410 -0
  85. package/cli/commands/preview.ts +103 -0
  86. package/cli/commands/profile.ts +106 -0
  87. package/cli/commands/react.ts +353 -0
  88. package/cli/commands/record.ts +1435 -0
  89. package/cli/commands/report-issue.ts +305 -0
  90. package/cli/commands/reset.ts +89 -0
  91. package/cli/commands/runtime.ts +348 -0
  92. package/cli/commands/screenshot-command.ts +106 -0
  93. package/cli/commands/screenshot-layers.ts +144 -0
  94. package/cli/commands/screenshot-mode.ts +38 -0
  95. package/cli/commands/screenshot.ts +492 -0
  96. package/cli/commands/screenshots-capture.ts +608 -0
  97. package/cli/commands/screenshots.ts +127 -0
  98. package/cli/commands/serve.ts +139 -0
  99. package/cli/commands/setup.ts +416 -0
  100. package/cli/commands/shell-boolean-mode.ts +82 -0
  101. package/cli/commands/skills.ts +468 -0
  102. package/cli/commands/slides.ts +357 -0
  103. package/cli/commands/state.ts +94 -0
  104. package/cli/commands/storage.ts +58 -0
  105. package/cli/commands/telemetry.ts +55 -0
  106. package/cli/commands/test.ts +535 -0
  107. package/cli/commands/three-mode.ts +771 -0
  108. package/cli/commands/timeline.ts +121 -0
  109. package/cli/commands/upgrade.ts +167 -0
  110. package/cli/commands/upload.ts +1203 -0
  111. package/cli/commands/version.ts +63 -0
  112. package/cli/commands/welcome.ts +17 -0
  113. package/cli/commands/what-happened.ts +328 -0
  114. package/cli/current-sim.ts +217 -0
  115. package/cli/desktop-companion.ts +315 -0
  116. package/cli/drivers/electron.ts +70 -0
  117. package/cli/drivers/index.ts +20 -0
  118. package/cli/drivers/playwright-provisioning.ts +184 -0
  119. package/cli/drivers/playwright-sim-host.ts +537 -0
  120. package/cli/drivers/playwright.ts +209 -0
  121. package/cli/drivers/registry.ts +65 -0
  122. package/cli/drivers/types.ts +102 -0
  123. package/cli/flow-export.ts +55 -0
  124. package/cli/flow-file.ts +142 -0
  125. package/cli/flow-live-status.ts +126 -0
  126. package/cli/flow-session.ts +140 -0
  127. package/cli/help-core.ts +143 -0
  128. package/cli/help.ts +44 -0
  129. package/cli/hidden-runtime-alias.ts +19 -0
  130. package/cli/hints.ts +214 -0
  131. package/cli/inspect-notice-state.ts +114 -0
  132. package/cli/internal-child.ts +176 -0
  133. package/cli/maestro-js.ts +323 -0
  134. package/cli/main.ts +704 -0
  135. package/cli/open-url.ts +8 -0
  136. package/cli/outbound-endpoints.ts +830 -0
  137. package/cli/parent-pid.ts +204 -0
  138. package/cli/parse-args.ts +189 -0
  139. package/cli/privacy.ts +53 -0
  140. package/cli/prompt.ts +56 -0
  141. package/cli/recording-access.ts +108 -0
  142. package/cli/registry.ts +2 -0
  143. package/cli/resolve-assets.ts +63 -0
  144. package/cli/rnx-in-process.ts +98 -0
  145. package/cli/run-registry.ts +226 -0
  146. package/cli/run-rnx-core.ts +148 -0
  147. package/cli/run-rnx.ts +137 -0
  148. package/cli/runtime-notes.ts +66 -0
  149. package/cli/runtime-summary.ts +25 -0
  150. package/cli/secure-file.ts +31 -0
  151. package/cli/self-invocation.ts +58 -0
  152. package/cli/send-to-box.ts +109 -0
  153. package/cli/setup-repository.ts +191 -0
  154. package/cli/shell-init.ts +98 -0
  155. package/cli/standalone.ts +4 -0
  156. package/cli/telemetry.ts +84 -0
  157. package/cli/ui.ts +68 -0
  158. package/cli/ws-bridge.ts +1020 -0
  159. package/detox/attempt-events.cjs +23 -0
  160. package/detox/colors.ts +54 -0
  161. package/detox/config-loader.ts +135 -0
  162. package/detox/element-types.ts +48 -0
  163. package/detox/expectations.ts +534 -0
  164. package/detox/gestures.ts +501 -0
  165. package/detox/index.ts +2694 -0
  166. package/detox/jest-preset.cjs +29 -0
  167. package/detox/jest-setup-after-env.cjs +22 -0
  168. package/detox/matchers.ts +30 -0
  169. package/detox/motion-change.cjs +109 -0
  170. package/detox/motion-change.d.cts +35 -0
  171. package/detox/navigation-lifecycle.ts +290 -0
  172. package/detox/navigation.ts +43 -0
  173. package/detox/proof-frame.cjs +114 -0
  174. package/detox/record-block-execution.cjs +78 -0
  175. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  176. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  177. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  178. package/detox/screenshots/theme-blue-toggle.png +0 -0
  179. package/detox/screenshots/theme-blue.png +0 -0
  180. package/detox/screenshots/theme-red-toggle.png +0 -0
  181. package/detox/screenshots/theme-red.png +0 -0
  182. package/dist-lib/agent-daemon-client.cjs +502 -0
  183. package/dist-lib/agent-events.cjs +48 -0
  184. package/dist-lib/agent-identity.cjs +53 -0
  185. package/dist-lib/agent-sessions.cjs +759 -0
  186. package/dist-lib/attached-projects.cjs +526 -0
  187. package/dist-lib/auth/shared-session.cjs +208 -0
  188. package/dist-lib/backend-origin.cjs +88 -0
  189. package/dist-lib/beta.cjs +41 -0
  190. package/dist-lib/beta.mjs +13 -0
  191. package/dist-lib/bridge-constants.cjs +161 -0
  192. package/dist-lib/bridge-contract-input.cjs +451 -0
  193. package/dist-lib/bridge-contract-input.mjs +414 -0
  194. package/dist-lib/bridge-contract.cjs +130 -0
  195. package/dist-lib/bridge-contract.mjs +94 -0
  196. package/dist-lib/capture-contract.cjs +220 -0
  197. package/dist-lib/capture-contract.mjs +190 -0
  198. package/dist-lib/cli-constants.cjs +32 -0
  199. package/dist-lib/cloud-contract.cjs +194 -0
  200. package/dist-lib/cloud-contract.mjs +145 -0
  201. package/dist-lib/cloud.cjs +670 -0
  202. package/dist-lib/cloud.mjs +639 -0
  203. package/dist-lib/config.cjs +96 -0
  204. package/dist-lib/detox/index.cjs +3789 -0
  205. package/dist-lib/detox/jest-preset.cjs +29 -0
  206. package/dist-lib/dev-bundle-resolution.cjs +290 -0
  207. package/dist-lib/home-paths.cjs +638 -0
  208. package/dist-lib/host/bridge-host.cjs +10903 -0
  209. package/dist-lib/host/fetch-proxy-handler.cjs +421 -0
  210. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  211. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  212. package/dist-lib/host/replacement-module-handler.cjs +469 -0
  213. package/dist-lib/host/websocket-proxy.cjs +298 -0
  214. package/dist-lib/index.cjs +4268 -0
  215. package/dist-lib/jump-to-source-babel.cjs +93 -0
  216. package/dist-lib/jump-to-source-native.cjs +112 -0
  217. package/dist-lib/menu.cjs +646 -0
  218. package/dist-lib/menu.mjs +604 -0
  219. package/dist-lib/metro-fingerprint-registry.cjs +186 -0
  220. package/dist-lib/metro-fingerprint-registry.mjs +156 -0
  221. package/dist-lib/metro-production-bundle.cjs +329 -0
  222. package/dist-lib/metro-production-bundle.mjs +295 -0
  223. package/dist-lib/metro.cjs +904 -0
  224. package/dist-lib/profiles.cjs +246 -0
  225. package/dist-lib/public-brand.cjs +39 -0
  226. package/dist-lib/react-native-host-modules.cjs +51 -0
  227. package/dist-lib/react-native-host-modules.mjs +25 -0
  228. package/dist-lib/render-mode.cjs +54 -0
  229. package/dist-lib/scripts/dev-server-scanner.cjs +1232 -0
  230. package/dist-lib/sdk.cjs +2433 -0
  231. package/dist-lib/sdk.mjs +2336 -0
  232. package/dist-lib/skills.cjs +39132 -0
  233. package/dist-lib/swift-submit.cjs +92 -0
  234. package/dist-lib/swift-submit.mjs +65 -0
  235. package/dist-lib/swift.cjs +1646 -0
  236. package/dist-lib/vite.cjs +1591 -0
  237. package/package.json +290 -0
  238. package/scripts/dev-server-scanner.ts +1291 -0
  239. package/scripts/optional-demo-registry.ts +113 -0
  240. package/skills/contrast/SKILL.md +165 -0
  241. package/skills/rnx-debug/SKILL.md +433 -0
  242. package/skills/rnx-perf/SKILL.md +399 -0
  243. package/skills/rnx-setup/SKILL.md +172 -0
  244. package/skills/rnx-test/SKILL.md +321 -0
  245. package/skills/rnx-visual/SKILL.md +126 -0
  246. package/src/agent-daemon-client.ts +410 -0
  247. package/src/agent-events.ts +71 -0
  248. package/src/agent-identity.ts +23 -0
  249. package/src/agent-prompt.ts +71 -0
  250. package/src/agent-sessions.ts +580 -0
  251. package/src/app-url.ts +39 -0
  252. package/src/attached-projects.ts +539 -0
  253. package/src/auth/shared-session.ts +235 -0
  254. package/src/backend-origin.ts +70 -0
  255. package/src/beta.ts +19 -0
  256. package/src/bridge-constants.ts +175 -0
  257. package/src/bridge-contract-input.ts +502 -0
  258. package/src/bridge-contract.ts +558 -0
  259. package/src/browser-cache.ts +16 -0
  260. package/src/capture-contract.ts +587 -0
  261. package/src/cli-constants.ts +1 -0
  262. package/src/cli-version.ts +40 -0
  263. package/src/cloud-contract.ts +274 -0
  264. package/src/cloud.ts +762 -0
  265. package/src/codex-client.ts +234 -0
  266. package/src/config.ts +132 -0
  267. package/src/connect.ts +101 -0
  268. package/src/dev-bundle-resolution.ts +346 -0
  269. package/src/dev-server-open.ts +198 -0
  270. package/src/disk-cleanup.ts +540 -0
  271. package/src/flow-timeout-scale.ts +15 -0
  272. package/src/home-paths.ts +806 -0
  273. package/src/host/agent-host.ts +666 -0
  274. package/src/host/bridge-host.ts +2788 -0
  275. package/src/host/fetch-proxy-handler.ts +449 -0
  276. package/src/host/fetch-proxy-overrides.ts +51 -0
  277. package/src/host/open-url.ts +89 -0
  278. package/src/host/plane-host.ts +203 -0
  279. package/src/host/replacement-module-handler.ts +158 -0
  280. package/src/host/websocket-proxy.ts +315 -0
  281. package/src/index.ts +71 -0
  282. package/src/jump-to-source-babel.ts +135 -0
  283. package/src/jump-to-source-native.ts +102 -0
  284. package/src/menu.ts +777 -0
  285. package/src/metro-fingerprint-registry.ts +302 -0
  286. package/src/metro-plugin.ts +341 -0
  287. package/src/metro-production-bundle.ts +471 -0
  288. package/src/native-dev-bundle-url.ts +118 -0
  289. package/src/native-seam-manifest.ts +341 -0
  290. package/src/playwright-host-log.ts +16 -0
  291. package/src/plist.ts +27 -0
  292. package/src/poll-until.ts +123 -0
  293. package/src/profiles.ts +179 -0
  294. package/src/public-brand.ts +9 -0
  295. package/src/react-native-host-modules.ts +26 -0
  296. package/src/render-mode.ts +27 -0
  297. package/src/rolldown-plugin-swift.ts +240 -0
  298. package/src/runtime-assets.ts +233 -0
  299. package/src/runtime-delivery.ts +54 -0
  300. package/src/screenshots/compose.ts +422 -0
  301. package/src/screenshots/frame-compose.ts +514 -0
  302. package/src/screenshots/orchestrate.ts +256 -0
  303. package/src/screenshots/org-deck.ts +91 -0
  304. package/src/screenshots/registry.ts +58 -0
  305. package/src/screenshots/schema.ts +364 -0
  306. package/src/sdk.ts +15 -0
  307. package/src/settings-bundle.ts +418 -0
  308. package/src/shell-bridge-identity.ts +15 -0
  309. package/src/sim-client.ts +697 -0
  310. package/src/skills/builtin/a11y-review.ts +132 -0
  311. package/src/skills/builtin/compat-check.ts +71 -0
  312. package/src/skills/builtin/maestro-test.ts +121 -0
  313. package/src/skills/builtin/perf-profile.ts +90 -0
  314. package/src/skills/builtin/screenshot-all.ts +52 -0
  315. package/src/skills/builtin/visual-diff.ts +100 -0
  316. package/src/skills/registry.ts +107 -0
  317. package/src/skills/types.ts +41 -0
  318. package/src/swift-submit.ts +185 -0
  319. package/src/swift.ts +7 -0
  320. package/src/team-machine-prompt.ts +73 -0
  321. package/src/vite-plugin-one.ts +264 -0
  322. package/src/vite-plugin-swift.ts +620 -0
  323. package/src/vite-plugin.ts +1946 -0
  324. package/src/worklets-babel.ts +250 -0
@@ -0,0 +1,98 @@
1
+ // running an rnx command inside the calling process instead of spawning the CLI.
2
+ //
3
+ // the parse and the dispatch here are the CLI's own: parseRnxArgs, the command
4
+ // registry behind it, and the same per-command handlers main.ts reaches. what
5
+ // changes is the transport and the boundary. the caller hands in the WsBridge,
6
+ // so one vocabulary and one parser run against three of them: the local ws
7
+ // daemon, a cloud box's commands route, and, inside a box shell in workerd, the
8
+ // Box Durable Object's own command router with no network hop.
9
+ //
10
+ // the boundary is run-rnx.ts: output comes back as a value rather than reaching
11
+ // a terminal, and a command that would have called process.exit throws instead,
12
+ // which is what makes this safe to call in workerd, where process.exit cancels
13
+ // the whole request.
14
+
15
+ import { parseRnxArgs, TOP_LEVEL_RUNTIME_COMMANDS } from './parse-args'
16
+ import { type RnxRunResult, runRnx } from './run-rnx'
17
+ import type { ParsedBridgeCliArgs, WsBridge } from './ws-bridge'
18
+
19
+ // the verbs that reach a simulator through the bridge and nothing else. every
20
+ // other registered command needs the machine rnx is installed on: a device to
21
+ // boot, a browser to open, a credential store, the bridge daemon, or the local
22
+ // filesystem. `state` and `reset` sit outside runInspect but take the same
23
+ // bridge, so they belong here too.
24
+ const IN_PROCESS_COMMANDS: ReadonlySet<string> = new Set([
25
+ ...TOP_LEVEL_RUNTIME_COMMANDS,
26
+ 'state',
27
+ 'reset',
28
+ ])
29
+
30
+ const RUNNABLE = [...IN_PROCESS_COMMANDS].sort().join(', ')
31
+
32
+ export interface RnxInProcessOptions {
33
+ /** the command and its arguments, with no node/script prefix. */
34
+ argv: string[]
35
+ /**
36
+ * the transport the command runs against. it takes the parsed bridge args
37
+ * because the local host builds its bridge out of `--sim`, `--port`, and
38
+ * `--timeout`, while a box ignores all three and answers on its own socket.
39
+ */
40
+ createBridge: (parsed: ParsedBridgeCliArgs) => WsBridge
41
+ }
42
+
43
+ export async function runRnxInProcess(
44
+ options: RnxInProcessOptions,
45
+ ): Promise<RnxRunResult> {
46
+ return runRnx(options.argv, async (argv) => {
47
+ const parsed = parseRnxArgs(argv)
48
+ for (const warning of parsed.warnings) console.error(warning)
49
+ const command = parsed.command
50
+ if (!command || !IN_PROCESS_COMMANDS.has(command)) {
51
+ const named = command ? `rnx ${command}` : 'rnx'
52
+ console.error(
53
+ ` ${named} is not available here; this rnx drives a simulator and has no machine to run on. runs here: ${RUNNABLE}`,
54
+ )
55
+ return 1
56
+ }
57
+
58
+ const simTarget = parsed.globalFlags['sim'] ?? parsed.globalFlags['session']
59
+ const commandArgs =
60
+ typeof simTarget === 'string'
61
+ ? ['--sim', simTarget, ...parsed.commandArgs]
62
+ : parsed.commandArgs
63
+ if (commandArgs.includes('--then')) {
64
+ console.error(
65
+ ' rnx do --then is not available here; a chain runs against a sim connection rather than the bridge. run the actions one at a time.',
66
+ )
67
+ return 1
68
+ }
69
+
70
+ const port = parsed.globalFlags['port']
71
+ const opts = {
72
+ ...(typeof port === 'number' ? { port } : {}),
73
+ createBridge: options.createBridge,
74
+ }
75
+ if (command === 'state') {
76
+ const { runState } = await import('./commands/state')
77
+ return runState(commandArgs, opts)
78
+ }
79
+ if (command === 'reset') {
80
+ const { runReset } = await import('./commands/reset')
81
+ return runReset(commandArgs, opts)
82
+ }
83
+ const { runInspect } = await import('./commands/inspect')
84
+ await runInspect([command, ...commandArgs], { ...opts, verbose: parsed.verbose })
85
+ return 0
86
+ })
87
+ }
88
+
89
+ // the transport contract and the command's own name, re-exported so a host
90
+ // that only wants to register this entry and supply a bridge (a Box Durable
91
+ // Object, say) has one module to import.
92
+ export { rnxPublicBrand } from '../src/public-brand'
93
+ export type {
94
+ BridgeClaimResult,
95
+ BridgeSimInfo,
96
+ ParsedBridgeCliArgs,
97
+ WsBridge,
98
+ } from './ws-bridge'
@@ -0,0 +1,226 @@
1
+ // shared test-run registration for both runners (Maestro / Detox). posts a
2
+ // `sootRun` row to /api/sootsim/test-runs so the org
3
+ // dashboard and PR comments get a pass/fail list with git provenance and a
4
+ // link into the hosted replay (the uploaded preview share).
5
+ //
6
+ // registration policy: a run registers when it produced a hosted share
7
+ // (--preview) or when it runs in CI (github-token / actions env) — local
8
+ // iteration without --preview stays local. registration failures warn and
9
+ // never change the run's exit code; the test result is the playback result.
10
+
11
+ import { execSync } from 'child_process'
12
+ import { authHeaderValue, resolveCliAuth, type CliAuth } from './auth'
13
+ import type { SootSimFlowTraceStep } from './bridge-flow-runner'
14
+
15
+ export type RunKind = 'maestro' | 'detox'
16
+
17
+ export type RunProvenance = {
18
+ owner: string | null
19
+ repo: string | null
20
+ branch: string | null
21
+ commitSha: string | null
22
+ githubUsername: string | null
23
+ pullRequestNumber: number | null
24
+ pullRequestTitle: string | null
25
+ githubRunId: string | null
26
+ source: 'cli' | 'ci'
27
+ }
28
+
29
+ function gitOutput(command: string): string | null {
30
+ try {
31
+ const out = execSync(command, {
32
+ stdio: ['ignore', 'pipe', 'ignore'],
33
+ timeout: 3000,
34
+ encoding: 'utf8',
35
+ }).trim()
36
+ return out || null
37
+ } catch {
38
+ return null
39
+ }
40
+ }
41
+
42
+ function parsePositiveInt(value: string | undefined): number | null {
43
+ const trimmed = value?.trim()
44
+ if (!trimmed || !/^\d+$/.test(trimmed)) return null
45
+ return Number(trimmed)
46
+ }
47
+
48
+ // owner/repo from the checkout's github remote, for local runs with no
49
+ // CONTRAST_REPO/GITHUB_REPOSITORY env. runs are repo-scoped on the dashboard
50
+ // (/org/<owner>/<repo>/runs), so a repo-less registration has no home — the
51
+ // server 400s it. handles ssh (git@github.com:o/r.git) and https
52
+ // (https://github.com/o/r(.git)) remote shapes.
53
+ function ownerRepoFromGitRemote(): { owner: string; repo: string } | null {
54
+ const url = gitOutput('git remote get-url origin')
55
+ if (!url) return null
56
+ const match = url.match(/github\.com[/:]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i)
57
+ if (!match) return null
58
+ return { owner: match[1], repo: match[2] }
59
+ }
60
+
61
+ // git provenance for the current run. in GitHub Actions the CONTRAST_*/GITHUB_*
62
+ // env is authoritative (the checkout can be a detached merge ref); locally
63
+ // we ask git itself.
64
+ export function resolveRunProvenance(): RunProvenance {
65
+ const isActions = process.env.GITHUB_ACTIONS === 'true'
66
+ const contrastBranch = process.env.CONTRAST_BRANCH?.trim() || null
67
+ const contrastSha = process.env.CONTRAST_SHA?.trim() || null
68
+ const pullRequestTitle = process.env.CONTRAST_PR_TITLE?.trim() || null
69
+ const repoSlug = (
70
+ process.env.CONTRAST_REPO ||
71
+ process.env.GITHUB_REPOSITORY ||
72
+ ''
73
+ ).trim()
74
+ const [envOwner, envRepo] = repoSlug.includes('/')
75
+ ? repoSlug.split('/', 2)
76
+ : [null, null]
77
+
78
+ if (isActions) {
79
+ // pull_request events: GITHUB_HEAD_REF is the PR head branch and
80
+ // GITHUB_REF looks like refs/pull/<n>/merge. push events: GITHUB_REF_NAME.
81
+ const prFromRef = process.env.GITHUB_REF?.match(/^refs\/pull\/(\d+)\//)?.[1]
82
+ return {
83
+ owner: envOwner,
84
+ repo: envRepo,
85
+ branch:
86
+ contrastBranch ||
87
+ process.env.GITHUB_HEAD_REF?.trim() ||
88
+ process.env.GITHUB_REF_NAME?.trim() ||
89
+ null,
90
+ commitSha: contrastSha || process.env.GITHUB_SHA?.trim() || null,
91
+ githubUsername: process.env.GITHUB_ACTOR?.trim() || null,
92
+ pullRequestNumber:
93
+ parsePositiveInt(process.env.CONTRAST_PR_NUMBER) ?? parsePositiveInt(prFromRef),
94
+ pullRequestTitle,
95
+ githubRunId: process.env.GITHUB_RUN_ID?.trim() || null,
96
+ source: 'ci',
97
+ }
98
+ }
99
+
100
+ const branch = contrastBranch ?? gitOutput('git rev-parse --abbrev-ref HEAD')
101
+ // env slug wins when present; otherwise infer from the github remote so a
102
+ // a plain local `rnx maestro test --preview` still lands on its repo's dashboard.
103
+ const remote = envOwner ? null : ownerRepoFromGitRemote()
104
+ return {
105
+ owner: envOwner ?? remote?.owner ?? null,
106
+ repo: envRepo ?? remote?.repo ?? null,
107
+ branch: branch === 'HEAD' ? null : branch,
108
+ commitSha: contrastSha ?? gitOutput('git rev-parse HEAD'),
109
+ githubUsername: null,
110
+ pullRequestNumber: parsePositiveInt(process.env.CONTRAST_PR_NUMBER),
111
+ pullRequestTitle,
112
+ githubRunId: null,
113
+ source: 'cli',
114
+ }
115
+ }
116
+
117
+ export function stepSummaryFromTrace(steps: SootSimFlowTraceStep[]): {
118
+ stepCount: number | null
119
+ failedStepIndex: number | null
120
+ } {
121
+ if (steps.length === 0) return { stepCount: null, failedStepIndex: null }
122
+ const failed = steps.find((step) => step.status === 'failure')
123
+ return {
124
+ stepCount: steps.length,
125
+ failedStepIndex: failed ? failed.stepIndex : null,
126
+ }
127
+ }
128
+
129
+ // whether this run should register at all — see the policy note at the top.
130
+ export function shouldRegisterRun(args: {
131
+ uploadedShare: boolean
132
+ auth: CliAuth | null
133
+ }): boolean {
134
+ if (!args.auth) return false
135
+ if (args.uploadedShare) return true
136
+ return args.auth.kind === 'github' || process.env.GITHUB_ACTIONS === 'true'
137
+ }
138
+
139
+ export type RegisterRunArgs = {
140
+ origin: string
141
+ kind: RunKind
142
+ status: 'passed' | 'failed'
143
+ name?: string | null
144
+ prompt?: string | null
145
+ summary?: string | null
146
+ failureMessage?: string | null
147
+ previewShareId?: string | null
148
+ // explicit --owner/--repo flags override env/git detection
149
+ owner?: string | null
150
+ repo?: string | null
151
+ durationMs?: number | null
152
+ flowYamlSizeBytes?: number | null
153
+ stepCount?: number | null
154
+ failedStepIndex?: number | null
155
+ auth?: CliAuth | null
156
+ }
157
+
158
+ export type RegisteredRun = {
159
+ id: string
160
+ previewUrl: string | null
161
+ traceUrl: string | null
162
+ }
163
+
164
+ export async function registerRun(args: RegisterRunArgs): Promise<RegisteredRun | null> {
165
+ const auth = args.auth ?? resolveCliAuth()
166
+ if (!auth) return null
167
+ const provenance = resolveRunProvenance()
168
+ const owner =
169
+ args.owner ?? (auth.kind === 'github' ? auth.owner : null) ?? provenance.owner
170
+ const repo = args.repo ?? (auth.kind === 'github' ? auth.repo : null) ?? provenance.repo
171
+
172
+ try {
173
+ const res = await fetch(`${args.origin.replace(/\/$/, '')}/api/sootsim/test-runs`, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'content-type': 'application/json',
177
+ authorization: authHeaderValue(auth),
178
+ },
179
+ body: JSON.stringify({
180
+ kind: args.kind,
181
+ source: provenance.source,
182
+ name: args.name ?? undefined,
183
+ prompt: args.prompt ?? undefined,
184
+ summary: args.summary ?? undefined,
185
+ status: args.status,
186
+ failureMessage: args.failureMessage ?? undefined,
187
+ previewShareId: args.previewShareId ?? undefined,
188
+ owner: owner ?? undefined,
189
+ repo: repo ?? undefined,
190
+ branch: provenance.branch ?? undefined,
191
+ commitSha: provenance.commitSha ?? undefined,
192
+ githubUsername: provenance.githubUsername ?? undefined,
193
+ pullRequestNumber: provenance.pullRequestNumber ?? undefined,
194
+ githubRunId: provenance.githubRunId ?? undefined,
195
+ stepCount: args.stepCount ?? undefined,
196
+ failedStepIndex: args.failedStepIndex ?? undefined,
197
+ durationMs: args.durationMs ?? undefined,
198
+ flowYamlSizeBytes: args.flowYamlSizeBytes ?? undefined,
199
+ }),
200
+ })
201
+ if (!res.ok) {
202
+ const text = await res.text().catch(() => '')
203
+ console.warn(
204
+ ` warn: run registration failed: /api/sootsim/test-runs ${res.status}${text ? `: ${text.slice(0, 200)}` : ''}`,
205
+ )
206
+ return null
207
+ }
208
+ const body = (await res.json()) as {
209
+ run?: { id?: string; traceUrl?: string | null; previewUrl?: string | null }
210
+ }
211
+ if (!body.run?.id) {
212
+ console.warn(' warn: run registration returned no id')
213
+ return null
214
+ }
215
+ return {
216
+ id: body.run.id,
217
+ previewUrl: body.run.previewUrl ?? null,
218
+ traceUrl: body.run.traceUrl ?? null,
219
+ }
220
+ } catch (err) {
221
+ console.warn(
222
+ ` warn: run registration failed: ${err instanceof Error ? err.message : String(err)}`,
223
+ )
224
+ return null
225
+ }
226
+ }
@@ -0,0 +1,148 @@
1
+ import {
2
+ createRnxCommandInvocation,
3
+ getRnxCommandAvailability,
4
+ getRnxFirstPositional,
5
+ } from './command-registry'
6
+ import { renderRnxHelp } from './help-core'
7
+ import { resolveHiddenRuntimeAlias } from './hidden-runtime-alias'
8
+ import { parseRnxArgs } from './parse-args'
9
+ import type {
10
+ CommandArtifact,
11
+ CommandExecutionResult,
12
+ CommandWriter,
13
+ RunRnxCoreOptions,
14
+ SemanticInvocation,
15
+ } from './command-contract'
16
+
17
+ interface CapturedCommandWriter {
18
+ writer: CommandWriter
19
+ stdout: string[]
20
+ stderr: string[]
21
+ }
22
+
23
+ function createCapturedWriter(writer?: CommandWriter): CapturedCommandWriter {
24
+ const stdout: string[] = []
25
+ const stderr: string[] = []
26
+ return {
27
+ stdout,
28
+ stderr,
29
+ writer: {
30
+ stdout(text) {
31
+ stdout.push(text)
32
+ writer?.stdout(text)
33
+ },
34
+ stderr(text) {
35
+ stderr.push(text)
36
+ writer?.stderr(text)
37
+ },
38
+ },
39
+ }
40
+ }
41
+
42
+ function result(
43
+ captured: CapturedCommandWriter,
44
+ exitCode: number,
45
+ invocations: SemanticInvocation[] = [],
46
+ artifacts: CommandArtifact[] = [],
47
+ ): CommandExecutionResult {
48
+ return {
49
+ exitCode,
50
+ stdout: captured.stdout.join(''),
51
+ stderr: captured.stderr.join(''),
52
+ invocations,
53
+ artifacts,
54
+ }
55
+ }
56
+
57
+ export async function runRnxCore(
58
+ argv: string[],
59
+ options: RunRnxCoreOptions,
60
+ ): Promise<CommandExecutionResult> {
61
+ const captured = createCapturedWriter(options.writer)
62
+ const signal = options.signal ?? new AbortController().signal
63
+ let parsed = parseRnxArgs(argv)
64
+ for (const warning of parsed.warnings) captured.writer.stderr(`${warning}\n`)
65
+
66
+ if (!parsed.command && parsed.commandArgs.length > 0) {
67
+ parsed = resolveHiddenRuntimeAlias(parsed)
68
+ }
69
+
70
+ if (parsed.version) {
71
+ captured.writer.stderr(
72
+ 'rnx --version is unavailable in the browser backend; use the node backend.\n',
73
+ )
74
+ return result(captured, 1)
75
+ }
76
+
77
+ if (!parsed.command) {
78
+ if (parsed.commandArgs.length > 0) {
79
+ const attempted = getRnxFirstPositional(parsed.commandArgs) ?? parsed.commandArgs[0]
80
+ captured.writer.stderr(`unknown command: ${attempted}\n`)
81
+ return result(captured, 1)
82
+ }
83
+ captured.writer.stdout(`${renderRnxHelp({ backend: options.backend.kind })}\n`)
84
+ return result(captured, 0)
85
+ }
86
+
87
+ const command = parsed.command
88
+ const helpRequested =
89
+ parsed.help ||
90
+ parsed.commandArgs.includes('--help') ||
91
+ parsed.commandArgs.includes('-h')
92
+ if (helpRequested) {
93
+ const grouping = new Set(['do', 'get', 'debug', 'wait'])
94
+ const subVerb = getRnxFirstPositional(parsed.commandArgs)
95
+ captured.writer.stdout(
96
+ `${renderRnxHelp({
97
+ backend: options.backend.kind,
98
+ command,
99
+ commandArgs: parsed.commandArgs,
100
+ prefer: grouping.has(command) && subVerb ? 'verb' : undefined,
101
+ group: grouping.has(command) ? command : undefined,
102
+ })}\n`,
103
+ )
104
+ return result(captured, 0)
105
+ }
106
+
107
+ const availability = getRnxCommandAvailability(
108
+ command,
109
+ parsed.commandArgs,
110
+ options.backend.kind,
111
+ )
112
+ if (!availability.available) {
113
+ captured.writer.stderr(`${availability.reason ?? `rnx ${command} is unavailable`}\n`)
114
+ return result(captured, 1)
115
+ }
116
+
117
+ if (signal.aborted) {
118
+ captured.writer.stderr('rnx: command cancelled\n')
119
+ return result(captured, 1)
120
+ }
121
+
122
+ const invocation = createRnxCommandInvocation({
123
+ command,
124
+ args: parsed.commandArgs,
125
+ globalFlags: parsed.globalFlags,
126
+ })
127
+
128
+ try {
129
+ const execution = await options.backend.execute(invocation, {
130
+ writer: captured.writer,
131
+ signal,
132
+ })
133
+ return result(
134
+ captured,
135
+ execution.exitCode,
136
+ execution.invocations,
137
+ execution.artifacts,
138
+ )
139
+ } catch (error) {
140
+ const message = signal.aborted
141
+ ? 'command cancelled'
142
+ : error instanceof Error
143
+ ? error.message
144
+ : String(error)
145
+ captured.writer.stderr(`rnx: ${message}\n`)
146
+ return result(captured, 1)
147
+ }
148
+ }
package/cli/run-rnx.ts ADDED
@@ -0,0 +1,137 @@
1
+ // node/workerd compatibility for command modules that still write through
2
+ // process and console. browser-safe commands use run-rnx-core.ts, whose
3
+ // explicit context carries its writer, cancellation signal, semantic
4
+ // invocations, and artifacts without touching ambient process state.
5
+ //
6
+ // two things stand between the command modules and an in-process call, and
7
+ // this module owns both:
8
+ //
9
+ // output — the modules write to console.* and process.std*.write, ~1,777
10
+ // sites. they are not rewritten to take a writer. instead the sinks are
11
+ // resolved from async context, so two commands running concurrently in one
12
+ // isolate keep their output separate. RAN: a global current-sink instead of
13
+ // AsyncLocalStorage interleaves them, which is what makes the context
14
+ // version necessary rather than merely tidy.
15
+ //
16
+ // exit — a command that calls process.exit ends the host process instead of
17
+ // returning a code to its caller. `rnxExit` throws instead, and `runRnx`
18
+ // turns it back into a code at the boundary.
19
+ //
20
+ // AsyncLocalStorage keeps this compatibility module node/workerd only. migrate
21
+ // a command to the explicit core before exposing it through a browser backend.
22
+
23
+ import { AsyncLocalStorage } from 'node:async_hooks'
24
+
25
+ export interface RnxRunResult {
26
+ stdout: string
27
+ stderr: string
28
+ exitCode: number
29
+ }
30
+
31
+ interface RnxSinks {
32
+ stdout: string[]
33
+ stderr: string[]
34
+ }
35
+
36
+ // thrown by `rnxExit` in place of process.exit. every catch that reports an
37
+ // error must re-throw this before reporting, or it turns a deliberate exit
38
+ // into a spurious "command failed" line with the sentinel's own text.
39
+ export class RnxExit extends Error {
40
+ readonly code: number
41
+
42
+ constructor(code: number) {
43
+ super(`rnx exited with code ${code}`)
44
+ this.name = 'RnxExit'
45
+ this.code = code
46
+ }
47
+ }
48
+
49
+ const sinks = new AsyncLocalStorage<RnxSinks>()
50
+
51
+ // stop a command's deliberate exit from being reported as a failure. the
52
+ // guard every reporting catch needs, in one place so the call sites stay one
53
+ // line.
54
+ export function rethrowIfExit(error: unknown): void {
55
+ if (error instanceof RnxExit) throw error
56
+ }
57
+
58
+ // the in-process replacement for process.exit. returns `never` exactly as
59
+ // process.exit does, so it substitutes without changing any call site's types.
60
+ export function rnxExit(code: number): never {
61
+ throw new RnxExit(code)
62
+ }
63
+
64
+ function write(stream: 'stdout' | 'stderr', text: string): boolean {
65
+ const current = sinks.getStore()
66
+ if (!current) return false
67
+ current[stream].push(text)
68
+ return true
69
+ }
70
+
71
+ let installed = false
72
+
73
+ // replace the process-wide writers once. each resolves its destination from
74
+ // async context, so a call made outside any `runRnx` still reaches the real
75
+ // terminal and the host CLI keeps behaving exactly as it did.
76
+ function installCapture(): void {
77
+ if (installed) return
78
+ installed = true
79
+
80
+ const realLog = console.log.bind(console)
81
+ const realError = console.error.bind(console)
82
+ const realWarn = console.warn.bind(console)
83
+ const realStdout = process.stdout.write.bind(process.stdout)
84
+ const realStderr = process.stderr.write.bind(process.stderr)
85
+
86
+ const line = (args: unknown[]): string => `${args.map(String).join(' ')}\n`
87
+
88
+ console.log = (...args: unknown[]) => {
89
+ if (!write('stdout', line(args))) realLog(...args)
90
+ }
91
+ console.error = (...args: unknown[]) => {
92
+ if (!write('stderr', line(args))) realError(...args)
93
+ }
94
+ console.warn = (...args: unknown[]) => {
95
+ if (!write('stderr', line(args))) realWarn(...args)
96
+ }
97
+ process.stdout.write = (chunk: string | Uint8Array, ...rest: unknown[]) => {
98
+ if (
99
+ write('stdout', typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
100
+ ) {
101
+ return true
102
+ }
103
+ return Reflect.apply(realStdout, process.stdout, [chunk, ...rest])
104
+ }
105
+ process.stderr.write = (chunk: string | Uint8Array, ...rest: unknown[]) => {
106
+ if (
107
+ write('stderr', typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk))
108
+ ) {
109
+ return true
110
+ }
111
+ return Reflect.apply(realStderr, process.stderr, [chunk, ...rest])
112
+ }
113
+ }
114
+
115
+ // run one rnx invocation and return what it wrote plus its exit code, instead
116
+ // of writing to the terminal and ending the process. `argv` is the command and
117
+ // its arguments with no node/script prefix, e.g. ['describe', '--json'].
118
+ export async function runRnx(
119
+ argv: string[],
120
+ dispatch: (argv: string[]) => Promise<number>,
121
+ ): Promise<RnxRunResult> {
122
+ installCapture()
123
+ const current: RnxSinks = { stdout: [], stderr: [] }
124
+ const exitCode = await sinks.run(current, async () => {
125
+ try {
126
+ return await dispatch(argv)
127
+ } catch (error) {
128
+ if (error instanceof RnxExit) return error.code
129
+ throw error
130
+ }
131
+ })
132
+ return {
133
+ stdout: current.stdout.join(''),
134
+ stderr: current.stderr.join(''),
135
+ exitCode,
136
+ }
137
+ }
@@ -0,0 +1,66 @@
1
+ import { RNX_CHANGELOG_API_URL, RNX_CHANGELOG_URL } from '../src/runtime-delivery'
2
+ import type { ChangelogEntry } from '../../../scripts/changelog/types'
3
+
4
+ function isChangelogEntry(value: unknown): value is ChangelogEntry {
5
+ if (!value || typeof value !== 'object') return false
6
+ const sections = Reflect.get(value, 'sections')
7
+ return (
8
+ typeof Reflect.get(value, 'title') === 'string' &&
9
+ Array.isArray(sections) &&
10
+ sections.every(
11
+ (section) =>
12
+ section &&
13
+ typeof section === 'object' &&
14
+ typeof Reflect.get(section, 'title') === 'string' &&
15
+ Array.isArray(Reflect.get(section, 'items')) &&
16
+ Reflect.get(section, 'items').every(
17
+ (item: unknown) =>
18
+ item &&
19
+ typeof item === 'object' &&
20
+ typeof Reflect.get(item, 'summary') === 'string' &&
21
+ typeof Reflect.get(item, 'hash') === 'string',
22
+ ),
23
+ )
24
+ )
25
+ }
26
+
27
+ export async function fetchRuntimeReleaseNotes(
28
+ version: string,
29
+ fetchImpl: typeof fetch = fetch,
30
+ ): Promise<ChangelogEntry | null> {
31
+ try {
32
+ const url = new URL(RNX_CHANGELOG_API_URL)
33
+ url.searchParams.set('version', version)
34
+ const response = await fetchImpl(url, {
35
+ headers: { accept: 'application/json' },
36
+ signal: AbortSignal.timeout(5000),
37
+ })
38
+ if (!response.ok) return null
39
+ const payload: unknown = await response.json()
40
+ if (!payload || typeof payload !== 'object') return null
41
+ const entries = Reflect.get(payload, 'entries')
42
+ if (!Array.isArray(entries) || !isChangelogEntry(entries[0])) return null
43
+ return entries[0]
44
+ } catch {
45
+ return null
46
+ }
47
+ }
48
+
49
+ export function formatRuntimeReleaseNotes(
50
+ version: string,
51
+ entry: ChangelogEntry | null,
52
+ ): string {
53
+ const lines = [`what's new in rnx engine v${version}:`]
54
+ if (!entry || entry.sections.length === 0) {
55
+ lines.push(` release notes are not available yet`)
56
+ } else {
57
+ for (const section of entry.sections) {
58
+ lines.push(` ${section.title}:`)
59
+ for (const item of section.items) {
60
+ lines.push(` • ${item.summary}`)
61
+ }
62
+ }
63
+ }
64
+ lines.push(` full changelog: ${RNX_CHANGELOG_URL}`)
65
+ return lines.join('\n')
66
+ }
@@ -0,0 +1,25 @@
1
+ import { readActiveRuntime, shouldSkipPersistentDaemon } from '../src/home-paths'
2
+ import { resolveBridgeWorld } from './ws-bridge'
3
+
4
+ export type RuntimeSummary = {
5
+ primary: string
6
+ installedRuntime: string | null
7
+ isDevBridge: boolean
8
+ }
9
+
10
+ export function getRuntimeSummary(): RuntimeSummary {
11
+ const active = readActiveRuntime()
12
+ const dev = shouldSkipPersistentDaemon() ? resolveBridgeWorld() : null
13
+ if (dev?.source === 'dev-bridge') {
14
+ return {
15
+ primary: `engine dev source (:${dev.bridgePort})`,
16
+ installedRuntime: active ? `installed runtime v${active}` : null,
17
+ isDevBridge: true,
18
+ }
19
+ }
20
+ return {
21
+ primary: active ? `runtime v${active}` : 'runtime not installed',
22
+ installedRuntime: null,
23
+ isDevBridge: false,
24
+ }
25
+ }