rnxsim 0.1.344 → 0.1.346

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 (211) hide show
  1. package/README.md +59 -31
  2. package/cli/app-fonts.ts +8 -28
  3. package/cli/app-project.ts +12 -184
  4. package/cli/app-splash.ts +3 -17
  5. package/cli/bin.ts +15 -28
  6. package/cli/commands/control.ts +20 -30
  7. package/cli/commands/daemon.ts +19 -44
  8. package/cli/commands/flow.ts +0 -27
  9. package/cli/commands/install-desktop.ts +2 -16
  10. package/cli/commands/login.ts +3 -43
  11. package/cli/commands/logout.ts +0 -7
  12. package/cli/commands/serve.ts +2 -32
  13. package/cli/commands/setup.ts +20 -194
  14. package/cli/commands/telemetry.ts +7 -5
  15. package/cli/commands/upload.ts +1 -48
  16. package/cli/commands/welcome.ts +64 -0
  17. package/cli/outbound-endpoints.ts +586 -0
  18. package/cli/privacy.ts +53 -0
  19. package/cli/runtime-notes.ts +2 -2
  20. package/cli/telemetry.ts +46 -149
  21. package/dist-cli/bin.js +6 -6
  22. package/dist-cli/chunks/{agent-HTSE5FA4.js → agent-U6HZUHAQ.js} +2 -2
  23. package/dist-cli/chunks/{agent-wrapper-L6X2AEZL.js → agent-wrapper-IM2VL6XP.js} +2 -2
  24. package/dist-cli/chunks/{app-fonts-FGF3ETYH.js → app-fonts-LHVTPWCN.js} +2 -2
  25. package/dist-cli/chunks/{assert-HA2ZYVDN.js → assert-UMGCZXWN.js} +2 -2
  26. package/dist-cli/chunks/{auth-HONDAV75.js → auth-PQY57FPY.js} +2 -2
  27. package/dist-cli/chunks/{beta-U7EE4GMR.js → beta-3MMNRHL7.js} +2 -2
  28. package/dist-cli/chunks/{camera-RTKNUTIX.js → camera-UKISEHMH.js} +2 -2
  29. package/dist-cli/chunks/{chunk-CNLPLCO2.js → chunk-36P343FE.js} +2 -2
  30. package/dist-cli/chunks/{chunk-IWR4HFC4.js → chunk-3KPJVR63.js} +2 -2
  31. package/dist-cli/chunks/chunk-54Y5IXWQ.js +15 -0
  32. package/dist-cli/chunks/{chunk-EPEZZ6YS.js → chunk-5KMV7NAJ.js} +2 -2
  33. package/dist-cli/chunks/{chunk-NDGAF5OF.js → chunk-5WIYZI5H.js} +2 -2
  34. package/dist-cli/chunks/{chunk-KBSVSAAE.js → chunk-6GXUQ3DM.js} +2 -2
  35. package/dist-cli/chunks/{chunk-KT6WMCUS.js → chunk-6MUEKKJ4.js} +1 -1
  36. package/dist-cli/chunks/{chunk-TOHQI2II.js → chunk-6Q6S53R7.js} +1 -1
  37. package/dist-cli/chunks/{chunk-63NZGJ4A.js → chunk-6SQCDLTJ.js} +1 -1
  38. package/dist-cli/chunks/{chunk-Q2B6EQPB.js → chunk-6UW6O575.js} +2 -2
  39. package/dist-cli/chunks/chunk-6VH3UUYQ.js +4 -0
  40. package/dist-cli/chunks/{chunk-BGLWR43R.js → chunk-6XJGGFMD.js} +1 -1
  41. package/dist-cli/chunks/{chunk-TZNRKIS3.js → chunk-75LMZXF5.js} +2 -2
  42. package/dist-cli/chunks/{chunk-FZ5DRZJE.js → chunk-7CJNUNQA.js} +3 -3
  43. package/dist-cli/chunks/{chunk-UCVQOQCY.js → chunk-BQDGFR4B.js} +2 -2
  44. package/dist-cli/chunks/{chunk-H4J7EWZC.js → chunk-BSIVS2SQ.js} +1 -1
  45. package/dist-cli/chunks/{chunk-6E6SBYIO.js → chunk-CD2LITQX.js} +1 -1
  46. package/dist-cli/chunks/{chunk-PNMCALX5.js → chunk-DGH3D6WS.js} +2 -2
  47. package/dist-cli/chunks/chunk-EP6XBKPM.js +11 -0
  48. package/dist-cli/chunks/{chunk-OCUXAA6O.js → chunk-EVX6Y7ZM.js} +2 -2
  49. package/dist-cli/chunks/chunk-EYRKIWHE.js +4 -0
  50. package/dist-cli/chunks/{chunk-N5PFDTFL.js → chunk-FSYB2S6Q.js} +17 -17
  51. package/dist-cli/chunks/{chunk-6PEZSVIG.js → chunk-HPCTBEAP.js} +1 -1
  52. package/dist-cli/chunks/chunk-HS2BME6V.js +6 -0
  53. package/dist-cli/chunks/{chunk-G6IPT2PS.js → chunk-I3GUDHG2.js} +2 -2
  54. package/dist-cli/chunks/{chunk-MWUXQIA3.js → chunk-I4HVFKLM.js} +2 -2
  55. package/dist-cli/chunks/{chunk-ZPTFL5FQ.js → chunk-IBNK7SWL.js} +2 -2
  56. package/dist-cli/chunks/chunk-IUG4GWQC.js +25 -0
  57. package/dist-cli/chunks/{chunk-XQFDEY3F.js → chunk-IUIKD2YI.js} +1 -1
  58. package/dist-cli/chunks/{chunk-JFUVWIJT.js → chunk-JPVXLY7X.js} +1 -1
  59. package/dist-cli/chunks/{chunk-JLUEQPEF.js → chunk-K4PZLT2K.js} +1 -1
  60. package/dist-cli/chunks/{chunk-CRWYKW67.js → chunk-KNIKUJPD.js} +1 -1
  61. package/dist-cli/chunks/{chunk-EZLLZDYX.js → chunk-L6GWDWGZ.js} +2 -2
  62. package/dist-cli/chunks/chunk-LAVDDUQR.js +4 -0
  63. package/dist-cli/chunks/{chunk-22WJPMTQ.js → chunk-LPXKPZ4B.js} +1 -1
  64. package/dist-cli/chunks/{chunk-IPC45HTT.js → chunk-LYLK5VRP.js} +2 -2
  65. package/dist-cli/chunks/{chunk-S6LBAUWH.js → chunk-ME46FC4L.js} +1 -1
  66. package/dist-cli/chunks/chunk-NNXSLD76.js +99 -0
  67. package/dist-cli/chunks/{chunk-7V4ULF3U.js → chunk-PNH3YBZ4.js} +2 -2
  68. package/dist-cli/chunks/{chunk-GRX6NSBG.js → chunk-PPEJMNRO.js} +2 -2
  69. package/dist-cli/chunks/{chunk-K3VNCCEJ.js → chunk-SEWGXJG6.js} +3 -3
  70. package/dist-cli/chunks/{chunk-R3UYHE6G.js → chunk-T4JPZI32.js} +1 -1
  71. package/dist-cli/chunks/{chunk-NRTHFEPM.js → chunk-TAQYETRH.js} +2 -2
  72. package/dist-cli/chunks/{chunk-7JEEO33Y.js → chunk-TBVBWJJL.js} +2 -2
  73. package/dist-cli/chunks/{chunk-HRBMD2FS.js → chunk-UA54YLWR.js} +26 -26
  74. package/dist-cli/chunks/{chunk-G3UAFZE2.js → chunk-UV3TXNMG.js} +1 -1
  75. package/dist-cli/chunks/{chunk-EH35QQG4.js → chunk-VZ6YFPIN.js} +45 -45
  76. package/dist-cli/chunks/chunk-W54SRZ5K.js +14 -0
  77. package/dist-cli/chunks/chunk-WAC75WN6.js +4 -0
  78. package/dist-cli/chunks/{chunk-S7CSBQ6G.js → chunk-X2WIEZ7Q.js} +1 -1
  79. package/dist-cli/chunks/{chunk-EN32LN2B.js → chunk-XWTYFWZ3.js} +2 -2
  80. package/dist-cli/chunks/chunk-Y2C3CZVD.js +4 -0
  81. package/dist-cli/chunks/chunk-YM4L2CFB.js +4 -0
  82. package/dist-cli/chunks/{chunk-ZZSBCGHC.js → chunk-YXDR4CAQ.js} +1 -1
  83. package/dist-cli/chunks/{chunk-6NLWKL7Y.js → chunk-Z6P427WC.js} +2 -2
  84. package/dist-cli/chunks/{cleanup-EMYD5WZG.js → cleanup-KXPS5VSK.js} +2 -2
  85. package/dist-cli/chunks/cli-version-LJ6NFLBP.js +4 -0
  86. package/dist-cli/chunks/{compat-W2N5QTVG.js → compat-JWF7N3VO.js} +2 -2
  87. package/dist-cli/chunks/config-M42VJXNK.js +13 -0
  88. package/dist-cli/chunks/{control-4SFBROUE.js → control-HMZDIS6P.js} +2 -2
  89. package/dist-cli/chunks/daemon-MQ4UYKNQ.js +4 -0
  90. package/dist-cli/chunks/{debug-HCR2KFUA.js → debug-F44GWYMF.js} +3 -3
  91. package/dist-cli/chunks/{desktop-T2KS5PBJ.js → desktop-YJMWW6MW.js} +3 -3
  92. package/dist-cli/chunks/{detox-5S7EHINH.js → detox-5B4ASTJO.js} +2 -2
  93. package/dist-cli/chunks/{device-LVM3MZRJ.js → device-2544YZNY.js} +2 -2
  94. package/dist-cli/chunks/{diagnose-WK37XKLI.js → diagnose-LS4245JR.js} +2 -2
  95. package/dist-cli/chunks/{disk-cleanup-X7VJOXMS.js → disk-cleanup-XYL2HQQY.js} +2 -2
  96. package/dist-cli/chunks/drivers-P5CRSKH6.js +4 -0
  97. package/dist-cli/chunks/{film-7KLTI3ER.js → film-LS3TIZRM.js} +3 -3
  98. package/dist-cli/chunks/flow-7V77GXAB.js +4 -0
  99. package/dist-cli/chunks/help-HRESIFZX.js +4 -0
  100. package/dist-cli/chunks/{hidden-runtime-alias-4RVGUVFO.js → hidden-runtime-alias-HGN2EEQR.js} +2 -2
  101. package/dist-cli/chunks/{home-paths-5DHSAEGZ.js → home-paths-T3NTXE4U.js} +2 -2
  102. package/dist-cli/chunks/inspect-UC44UH4M.js +4 -0
  103. package/dist-cli/chunks/install-desktop-RDV66JHG.js +4 -0
  104. package/dist-cli/chunks/login-WVP5H5NQ.js +26 -0
  105. package/dist-cli/chunks/logout-GXX5OY6X.js +4 -0
  106. package/dist-cli/chunks/{maestro-HTRLQDJE.js → maestro-MQMGBNOB.js} +3 -3
  107. package/dist-cli/chunks/{maestro-generate-GX2QNA3F.js → maestro-generate-CO355QV2.js} +3 -3
  108. package/dist-cli/chunks/{mode-GM6KZHQP.js → mode-QFB3M2AA.js} +2 -2
  109. package/dist-cli/chunks/{optional-demo-registry-B4TD7JQP.js → optional-demo-registry-UIPVSBHV.js} +2 -2
  110. package/dist-cli/chunks/{perf-3PRXJQIC.js → perf-TDOSW3LD.js} +2 -2
  111. package/dist-cli/chunks/{permissions-TJZIRF2S.js → permissions-TEVQLCGJ.js} +2 -2
  112. package/dist-cli/chunks/privacy-DVHAMHR6.js +4 -0
  113. package/dist-cli/chunks/{record-LI55DTPE.js → record-OFEQLSWA.js} +3 -3
  114. package/dist-cli/chunks/{report-issue-TBGJVQIQ.js → report-issue-B2UPOERJ.js} +2 -2
  115. package/dist-cli/chunks/reset-XQQYM55B.js +4 -0
  116. package/dist-cli/chunks/runtime-GN6HITVB.js +4 -0
  117. package/dist-cli/chunks/{screenshot-command-BEN7YYIF.js → screenshot-command-NTLAY7QN.js} +3 -3
  118. package/dist-cli/chunks/{screenshot-layers-3IU4KYNO.js → screenshot-layers-BBLKNRQU.js} +2 -2
  119. package/dist-cli/chunks/{screenshots-capture-ZRC54ST6.js → screenshots-capture-6GHIRCTG.js} +2 -2
  120. package/dist-cli/chunks/serve-H6GJ4OHW.js +118 -0
  121. package/dist-cli/chunks/setup-6YLKSMWS.js +30 -0
  122. package/dist-cli/chunks/{skills-GKBR2HQB.js → skills-FUL7D6C7.js} +2 -2
  123. package/dist-cli/chunks/{state-LIHZJSL7.js → state-O2HE7XSR.js} +2 -2
  124. package/dist-cli/chunks/{storage-4TPN3NFB.js → storage-WIZTF7RN.js} +4 -4
  125. package/dist-cli/chunks/store-NJ2MBZC2.js +4 -0
  126. package/dist-cli/chunks/telemetry-BEC6W5WP.js +4 -0
  127. package/dist-cli/chunks/{timeline-EX63UNWU.js → timeline-MA4JT7UJ.js} +2 -2
  128. package/dist-cli/chunks/{upgrade-O6Y6EINE.js → upgrade-H5LSDXBH.js} +2 -2
  129. package/dist-cli/chunks/upload-Q4DWKIA4.js +4 -0
  130. package/dist-cli/chunks/{version-HLNL3NFB.js → version-4UWEEDVP.js} +2 -2
  131. package/dist-cli/chunks/{web-4EDPYPW7.js → web-F3LXRN3K.js} +2 -2
  132. package/dist-cli/chunks/welcome-AZBOSIG2.js +5 -0
  133. package/dist-cli/chunks/{what-happened-77VNUEPK.js → what-happened-6OSNLOAY.js} +2 -2
  134. package/dist-lib/agent-daemon-client.cjs +1 -1
  135. package/dist-lib/agent-events.cjs +1 -1
  136. package/dist-lib/agent-identity.cjs +1 -1
  137. package/dist-lib/agent-sessions.cjs +1 -1
  138. package/dist-lib/attached-projects.cjs +1 -1
  139. package/dist-lib/auth/shared-session.cjs +1 -1
  140. package/dist-lib/backend-origin.cjs +13 -1
  141. package/dist-lib/beta.cjs +1 -1
  142. package/dist-lib/beta.mjs +1 -1
  143. package/dist-lib/bridge-constants.cjs +1 -1
  144. package/dist-lib/bridge-contract.cjs +1 -1
  145. package/dist-lib/cli-constants.cjs +1 -1
  146. package/dist-lib/config.cjs +1 -1
  147. package/dist-lib/detox/index.cjs +1 -1
  148. package/dist-lib/dev-bundle-resolution.cjs +43 -13
  149. package/dist-lib/home-paths.cjs +145 -11
  150. package/dist-lib/host/bridge-host.cjs +326 -78
  151. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  152. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  153. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  154. package/dist-lib/host/replacement-module-handler.cjs +71 -4
  155. package/dist-lib/host/websocket-proxy.cjs +1 -1
  156. package/dist-lib/index.cjs +337 -37
  157. package/dist-lib/jump-to-source-babel.cjs +1 -1
  158. package/dist-lib/menu.cjs +1 -1
  159. package/dist-lib/menu.mjs +1 -1
  160. package/dist-lib/metro-production-bundle.cjs +1 -1
  161. package/dist-lib/metro-production-bundle.mjs +1 -1
  162. package/dist-lib/metro.cjs +484 -14
  163. package/dist-lib/profiles.cjs +1 -1
  164. package/dist-lib/public-brand.cjs +1 -1
  165. package/dist-lib/react-native-host-modules.cjs +1 -1
  166. package/dist-lib/react-native-host-modules.mjs +1 -1
  167. package/dist-lib/render-mode.cjs +1 -1
  168. package/dist-lib/scripts/dev-server-scanner.cjs +120 -50
  169. package/dist-lib/sdk.cjs +1 -1
  170. package/dist-lib/sdk.mjs +1 -1
  171. package/dist-lib/skills.cjs +493 -439
  172. package/dist-lib/vite.cjs +193 -37
  173. package/package.json +2 -1
  174. package/scripts/dev-server-scanner.ts +112 -51
  175. package/skills/rnx-test/SKILL.md +4 -0
  176. package/src/backend-origin.ts +15 -1
  177. package/src/dev-bundle-resolution.ts +42 -18
  178. package/src/dev-server-open.ts +197 -0
  179. package/src/home-paths.ts +168 -15
  180. package/src/host/bridge-host.ts +2 -2
  181. package/src/host/replacement-module-handler.ts +68 -2
  182. package/src/metro-plugin.ts +83 -5
  183. package/src/native-dev-bundle-url.ts +22 -0
  184. package/src/vite-plugin-one.ts +42 -28
  185. package/dist-cli/chunks/chunk-3JC7H4PR.js +0 -25
  186. package/dist-cli/chunks/chunk-6DLIC7BZ.js +0 -4
  187. package/dist-cli/chunks/chunk-7TNPO2OJ.js +0 -9
  188. package/dist-cli/chunks/chunk-AMF5KTOI.js +0 -4
  189. package/dist-cli/chunks/chunk-BX5VXTOQ.js +0 -101
  190. package/dist-cli/chunks/chunk-D73H7ACZ.js +0 -4
  191. package/dist-cli/chunks/chunk-DY4O7KYI.js +0 -4
  192. package/dist-cli/chunks/chunk-GU2V4JDG.js +0 -15
  193. package/dist-cli/chunks/chunk-POOCKX5B.js +0 -14
  194. package/dist-cli/chunks/chunk-Z2HMSVSN.js +0 -4
  195. package/dist-cli/chunks/cli-version-CN3NTKLH.js +0 -4
  196. package/dist-cli/chunks/config-SELKGT4F.js +0 -13
  197. package/dist-cli/chunks/daemon-V2SFCU2E.js +0 -4
  198. package/dist-cli/chunks/drivers-3YFTJ5WA.js +0 -4
  199. package/dist-cli/chunks/flow-36ANTMJU.js +0 -4
  200. package/dist-cli/chunks/help-ZGL26SPK.js +0 -4
  201. package/dist-cli/chunks/inspect-R3B55RTS.js +0 -4
  202. package/dist-cli/chunks/install-desktop-TZ3H3VWK.js +0 -4
  203. package/dist-cli/chunks/login-4DFULI7S.js +0 -28
  204. package/dist-cli/chunks/logout-QMCVYMBB.js +0 -4
  205. package/dist-cli/chunks/reset-O6MW6X7X.js +0 -4
  206. package/dist-cli/chunks/runtime-JESXBHRQ.js +0 -4
  207. package/dist-cli/chunks/serve-QGKTWPNF.js +0 -118
  208. package/dist-cli/chunks/setup-3WPWATCX.js +0 -32
  209. package/dist-cli/chunks/store-VBX3L46S.js +0 -4
  210. package/dist-cli/chunks/telemetry-ACNNIQIG.js +0 -4
  211. package/dist-cli/chunks/upload-OD2UQHMM.js +0 -4
@@ -0,0 +1,586 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import ts from 'typescript'
4
+
5
+ export type OutboundCategory =
6
+ | 'runtime-delivery'
7
+ | 'bounded-diagnostics'
8
+ | 'explicit-user-action'
9
+ | 'local-development'
10
+ | 'same-origin-runtime'
11
+ | 'guest-app-network'
12
+
13
+ export interface OutboundEndpointDeclaration {
14
+ category: OutboundCategory
15
+ destination: string
16
+ preConsent: boolean
17
+ payload: string
18
+ }
19
+
20
+ export const OUTBOUND_ENDPOINTS: Record<string, OutboundEndpointDeclaration> = {
21
+ runtime_delivery: {
22
+ category: 'runtime-delivery',
23
+ destination:
24
+ 'https://contrast.dev/runtimes/manifest.json and selected runtime tarball',
25
+ preConsent: true,
26
+ payload: 'HTTP runtime version request; no project or app data',
27
+ },
28
+ bounded_diagnostics: {
29
+ category: 'bounded-diagnostics',
30
+ destination: 'configured PostHog ingestion host',
31
+ preConsent: false,
32
+ payload: 'package name/version/unsupported API or error class only',
33
+ },
34
+ contrast_user_action: {
35
+ category: 'explicit-user-action',
36
+ destination: 'configured Contrast origin',
37
+ preConsent: false,
38
+ payload:
39
+ 'explicit auth, billing, preview, issue report, test, or generated-flow request; a stored login may refresh auth/subscription state',
40
+ },
41
+ npm_update_check: {
42
+ category: 'explicit-user-action',
43
+ destination: 'https://registry.npmjs.org/rnxsim/latest',
44
+ preConsent: false,
45
+ payload: 'package update request only',
46
+ },
47
+ release_notes: {
48
+ category: 'explicit-user-action',
49
+ destination: 'https://contrast.dev/api/changelog/sootsim',
50
+ preConsent: false,
51
+ payload: 'requested runtime version only',
52
+ },
53
+ electron_download: {
54
+ category: 'explicit-user-action',
55
+ destination: 'https://contrast.dev/api/electron-release/',
56
+ preConsent: false,
57
+ payload: 'platform, architecture, and requested desktop release',
58
+ },
59
+ local_development: {
60
+ category: 'local-development',
61
+ destination:
62
+ 'loopback development server, bridge, browser debugging, or app asset URL',
63
+ preConsent: true,
64
+ payload:
65
+ 'local protocol traffic needed to discover, load, and drive the selected app',
66
+ },
67
+ same_origin_runtime: {
68
+ category: 'same-origin-runtime',
69
+ destination: 'the origin already serving the rnx runtime',
70
+ preConsent: true,
71
+ payload: 'runtime assets and shared local settings',
72
+ },
73
+ guest_app_network: {
74
+ category: 'guest-app-network',
75
+ destination: 'URLs requested by the user app or supplied explicitly by the user',
76
+ preConsent: true,
77
+ payload: 'the app request itself; rnx does not add telemetry fields',
78
+ },
79
+ presigned_upload: {
80
+ category: 'explicit-user-action',
81
+ destination: 'server-issued presigned object upload URL',
82
+ preConsent: false,
83
+ payload: 'preview objects selected by the user for upload',
84
+ },
85
+ }
86
+
87
+ export interface OutboundCallDeclaration {
88
+ category: keyof typeof OUTBOUND_ENDPOINTS
89
+ count: number
90
+ }
91
+
92
+ // generated from the AST-level check below, then reviewed and classified.
93
+ // a new fetch, websocket, node HTTP request, or TCP connection has no entry and
94
+ // fails the package test until its destination and consent behavior are declared.
95
+ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> = {
96
+ 'packages/contrast-runtime-delivery/src/index.ts :: fetch :: url': {
97
+ category: 'runtime_delivery',
98
+ count: 2,
99
+ },
100
+ "packages/sootsim/scripts/demo-app-registry.ts :: fetch :: 'http://localhost:1986/'": {
101
+ category: 'local_development',
102
+ count: 1,
103
+ },
104
+ 'packages/sootsim/scripts/dev-server-scanner.ts :: http.request :: { hostname, port, path, method, timeout, headers }':
105
+ {
106
+ category: 'local_development',
107
+ count: 1,
108
+ },
109
+ 'packages/sootsim/scripts/dev-server-scanner.ts :: sock.connect :: port': {
110
+ category: 'local_development',
111
+ count: 1,
112
+ },
113
+ 'packages/sootsim-engine/src/auth/openLogin.ts :: fetch :: `${CONTRAST_ORIGIN}/api/dev-login`':
114
+ { category: 'contrast_user_action', count: 1 },
115
+ 'packages/sootsim-engine/src/auth/shared-session.ts :: fetch :: `${CONTRAST_ORIGIN}/api/auth/me`':
116
+ { category: 'contrast_user_action', count: 1 },
117
+ 'packages/sootsim-engine/src/auth/shared-session.ts :: fetch :: `${CONTRAST_ORIGIN}/api/auth/sign-out`':
118
+ { category: 'contrast_user_action', count: 1 },
119
+ 'packages/sootsim-engine/src/auth/UploadsMenu.tsx :: fetch :: `${CONTRAST_ORIGIN}/api/preview/uploads?limit=20`':
120
+ { category: 'contrast_user_action', count: 1 },
121
+ "packages/sootsim-engine/src/billing/client.ts :: fetch :: apiUrl('/api/sootsim/billing/checkout')":
122
+ { category: 'contrast_user_action', count: 1 },
123
+ 'packages/sootsim-engine/src/billing/client.ts :: fetch :: apiUrl(path)': {
124
+ category: 'contrast_user_action',
125
+ count: 3,
126
+ },
127
+ 'packages/sootsim-engine/src/components/native-ui/LottieHost.tsx :: fetch :: descriptor.value':
128
+ { category: 'guest_app_network', count: 1 },
129
+ 'packages/sootsim-engine/src/demo/demo-share-register.ts :: fetch :: shareUrl': {
130
+ category: 'guest_app_network',
131
+ count: 1,
132
+ },
133
+ 'packages/sootsim-engine/src/engine/android-emoji-font.ts :: fetch :: url': {
134
+ category: 'same_origin_runtime',
135
+ count: 1,
136
+ },
137
+ 'packages/sootsim-engine/src/engine/app-font-loader.ts :: fetch :: manifestFetchUrl(origin)':
138
+ { category: 'local_development', count: 1 },
139
+ 'packages/sootsim-engine/src/engine/app-font-loader.ts :: fetch :: resolvedUrl': {
140
+ category: 'guest_app_network',
141
+ count: 1,
142
+ },
143
+ 'packages/sootsim-engine/src/engine/config-plugin-splash-loader.ts :: fetch :: manifestFetchUrl(origin)':
144
+ { category: 'local_development', count: 1 },
145
+ 'packages/sootsim-engine/src/engine/emoji-metrics-font-loader.ts :: fetch :: attemptedBundledUrl':
146
+ { category: 'same_origin_runtime', count: 1 },
147
+ 'packages/sootsim-engine/src/engine/expo-app-config.ts :: fetch :: manifestFetchUrl(origin)':
148
+ { category: 'local_development', count: 1 },
149
+ 'packages/sootsim-engine/src/engine/image-loader.ts :: fetch :: resolveImageFetchUri(uri)':
150
+ { category: 'guest_app_network', count: 1 },
151
+ 'packages/sootsim-engine/src/engine/language-font-loader.ts :: fetch :: url': {
152
+ category: 'same_origin_runtime',
153
+ count: 1,
154
+ },
155
+ 'packages/sootsim-engine/src/engine/sf-symbols-font.ts :: fetch :: url': {
156
+ category: 'same_origin_runtime',
157
+ count: 1,
158
+ },
159
+ 'packages/sootsim-engine/src/ExternalApp.tsx :: fetch :: getBundleProxyUrl(devServerUrl)':
160
+ { category: 'guest_app_network', count: 1 },
161
+ 'packages/sootsim-engine/src/ExternalApp.tsx :: fetch :: requestUrl': {
162
+ category: 'guest_app_network',
163
+ count: 1,
164
+ },
165
+ 'packages/sootsim-engine/src-electron/tray.ts :: client.get :: parsed': {
166
+ category: 'guest_app_network',
167
+ count: 1,
168
+ },
169
+ 'packages/sootsim-engine/src/mcp/ws-bridge.ts :: fetch :: bridgeHealthUrl': {
170
+ category: 'local_development',
171
+ count: 1,
172
+ },
173
+ 'packages/sootsim-engine/src/mcp/ws-bridge.ts :: websocket :: resolveSootsimBridgeWebSocketUrl({ bridgePort, href: window.location.href, })':
174
+ { category: 'local_development', count: 1 },
175
+ 'packages/sootsim-engine/src/menu/menuActions.ts :: fetch :: screenshot': {
176
+ category: 'guest_app_network',
177
+ count: 1,
178
+ },
179
+ 'packages/sootsim-engine/src/preview/fetch-recorder.ts :: fetch :: absolute': {
180
+ category: 'guest_app_network',
181
+ count: 2,
182
+ },
183
+ 'packages/sootsim-engine/src/preview/presigned-upload.ts :: fetch :: `${origin}/api/preview/upload/finalize`':
184
+ { category: 'contrast_user_action', count: 1 },
185
+ 'packages/sootsim-engine/src/preview/presigned-upload.ts :: fetch :: `${origin}/api/preview/upload/init`':
186
+ { category: 'contrast_user_action', count: 1 },
187
+ 'packages/sootsim-engine/src/preview/presigned-upload.ts :: fetch :: job.url': {
188
+ category: 'presigned_upload',
189
+ count: 1,
190
+ },
191
+ 'packages/sootsim-engine/src/preview/warm-bundle-cache.ts :: fetch :: url': {
192
+ category: 'guest_app_network',
193
+ count: 1,
194
+ },
195
+ "packages/sootsim-engine/src/projects/projects-persistence.ts :: fetch :: '/__sootsim/shared-config'":
196
+ { category: 'same_origin_runtime', count: 1 },
197
+ 'packages/sootsim-engine/src/react-native/index.ts :: fetch :: resource': {
198
+ category: 'guest_app_network',
199
+ count: 1,
200
+ },
201
+ 'packages/sootsim-engine/src/react-native/Keyboard.ts :: fetch :: url': {
202
+ category: 'guest_app_network',
203
+ count: 1,
204
+ },
205
+ 'packages/sootsim-engine/src/render-worker/canvaskit-shared.ts :: fetch :: fontUrl': {
206
+ category: 'same_origin_runtime',
207
+ count: 1,
208
+ },
209
+ 'packages/sootsim-engine/src/render-worker/canvaskit-shared.ts :: fetch :: wasmUrl': {
210
+ category: 'same_origin_runtime',
211
+ count: 1,
212
+ },
213
+ 'packages/sootsim-engine/src/render-worker/compositor-worker.ts :: fetch :: `${engineDistBase}canvaskit-graphite-pipelines.bin`':
214
+ { category: 'same_origin_runtime', count: 1 },
215
+ 'packages/sootsim-engine/src/render-worker/host.ts :: fetch :: msg.url': {
216
+ category: 'guest_app_network',
217
+ count: 1,
218
+ },
219
+ 'packages/sootsim-engine/src/render-worker/video-host.ts :: fetch :: uri': {
220
+ category: 'guest_app_network',
221
+ count: 1,
222
+ },
223
+ 'packages/sootsim-engine/src/render-worker/worker-font-loader.ts :: fetch :: url': {
224
+ category: 'same_origin_runtime',
225
+ count: 1,
226
+ },
227
+ 'packages/sootsim-engine/src/render-worker/worker-handlers/lifecycle.ts :: fetch :: url':
228
+ { category: 'guest_app_network', count: 1 },
229
+ 'packages/sootsim-engine/src/screenshots/capture-store.ts :: fetch :: cap.dataUrl': {
230
+ category: 'same_origin_runtime',
231
+ count: 1,
232
+ },
233
+ "packages/sootsim-engine/src/screenshots/capture-store.ts :: fetch :: captureApiUrl(ref, 'grid')":
234
+ { category: 'contrast_user_action', count: 1 },
235
+ 'packages/sootsim-engine/src/screenshots/capture-store.ts :: fetch :: captureApiUrl(ref, variant)':
236
+ { category: 'contrast_user_action', count: 1 },
237
+ 'packages/sootsim-engine/src/screenshots/capture-store.ts :: fetch :: captureApiUrl(ref)':
238
+ { category: 'contrast_user_action', count: 1 },
239
+ "packages/sootsim-engine/src/server-discovery.ts :: fetch :: '/__server-scan'": {
240
+ category: 'local_development',
241
+ count: 1,
242
+ },
243
+ 'packages/sootsim-engine/src/server-discovery.ts :: fetch :: `${gatewayOrigin}/__scan`':
244
+ { category: 'local_development', count: 1 },
245
+ "packages/sootsim-engine/src/settings/persistence.ts :: fetch :: '/__sootsim/shared-config'":
246
+ { category: 'same_origin_runtime', count: 2 },
247
+ 'packages/sootsim/cli/app-fonts.ts :: fetch :: `${origin}/`': {
248
+ category: 'local_development',
249
+ count: 1,
250
+ },
251
+ 'packages/sootsim/cli/app-fonts.ts :: fetch :: url': {
252
+ category: 'local_development',
253
+ count: 1,
254
+ },
255
+ 'packages/sootsim/cli/app-splash.ts :: fetch :: url': {
256
+ category: 'local_development',
257
+ count: 1,
258
+ },
259
+ 'packages/sootsim/cli/commands/control.ts :: fetch :: imageUrl': {
260
+ category: 'local_development',
261
+ count: 1,
262
+ },
263
+ 'packages/sootsim/cli/commands/control.ts :: fetch :: spec.url': {
264
+ category: 'local_development',
265
+ count: 1,
266
+ },
267
+ 'packages/sootsim/cli/commands/control.ts :: websocket :: `ws://127.0.0.1:${port}`': {
268
+ category: 'local_development',
269
+ count: 1,
270
+ },
271
+ 'packages/sootsim/cli/commands/cpu-profile.ts :: fetch :: `http://localhost:${cdpPort}/json/list`':
272
+ { category: 'local_development', count: 1 },
273
+ 'packages/sootsim/cli/commands/cpu-profile.ts :: websocket :: wsUrl': {
274
+ category: 'local_development',
275
+ count: 1,
276
+ },
277
+ 'packages/sootsim/cli/commands/install-desktop.ts :: fetch :: url': {
278
+ category: 'electron_download',
279
+ count: 1,
280
+ },
281
+ "packages/sootsim/cli/commands/login.ts :: fetch :: `${origin.replace(/\\/$/, '')}/api/dev-login`":
282
+ { category: 'local_development', count: 1 },
283
+ "packages/sootsim/cli/commands/logout.ts :: fetch :: `${current.origin.replace(/\\/$/, '')}/api/auth/sign-out`":
284
+ { category: 'contrast_user_action', count: 1 },
285
+ "packages/sootsim/cli/commands/maestro-generate.ts :: fetch :: `${args.origin.replace(/\\/$/, '')}/api/llm`":
286
+ { category: 'contrast_user_action', count: 1 },
287
+ 'packages/sootsim/cli/commands/report-issue.ts :: fetch :: url': {
288
+ category: 'contrast_user_action',
289
+ count: 1,
290
+ },
291
+ 'packages/sootsim/cli/commands/runtime.ts :: websocket :: `ws://127.0.0.1:${port}`': {
292
+ category: 'local_development',
293
+ count: 1,
294
+ },
295
+ 'packages/sootsim/cli/commands/screenshots-capture.ts :: fetch :: `${apiOrigin}/api/sootsim/screenshot-capture?ref=${encodeURIComponent(ref)}&org=${encodeURIComponent(org)}`':
296
+ { category: 'contrast_user_action', count: 1 },
297
+ 'packages/sootsim/cli/commands/screenshots-capture.ts :: fetch :: `${apiOrigin}/api/sootsim/screenshot-decks?org=${encodeURIComponent(org)}&repo=${encodeURIComponent(repo)}`':
298
+ { category: 'contrast_user_action', count: 1 },
299
+ 'packages/sootsim/cli/commands/upgrade.ts :: fetch :: REGISTRY_LATEST_URL': {
300
+ category: 'npm_update_check',
301
+ count: 1,
302
+ },
303
+ 'packages/sootsim/cli/commands/upload.ts :: fetch :: url': {
304
+ category: 'presigned_upload',
305
+ count: 1,
306
+ },
307
+ 'packages/sootsim/cli/recording-access.ts :: fetch :: `${billingOrigin}/api/sootsim/billing/subscription`':
308
+ { category: 'contrast_user_action', count: 1 },
309
+ "packages/sootsim/cli/run-registry.ts :: fetch :: `${args.origin.replace(/\\/$/, '')}/api/sootsim/test-runs`":
310
+ { category: 'contrast_user_action', count: 1 },
311
+ 'packages/sootsim/cli/runtime-notes.ts :: fetch-alias :: url': {
312
+ category: 'release_notes',
313
+ count: 1,
314
+ },
315
+ 'packages/sootsim/cli/telemetry.ts :: fetch :: `${config.host}/batch/`': {
316
+ category: 'bounded_diagnostics',
317
+ count: 1,
318
+ },
319
+ 'packages/sootsim/cli/ws-bridge.ts :: websocket :: `ws://localhost:${wsPort}`': {
320
+ category: 'local_development',
321
+ count: 1,
322
+ },
323
+ 'packages/sootsim/src/agent-daemon-client.ts :: socket.connect :: port': {
324
+ category: 'local_development',
325
+ count: 1,
326
+ },
327
+ 'packages/sootsim/src/agent-daemon-client.ts :: websocket :: `ws://localhost:${this.port}`':
328
+ { category: 'local_development', count: 1 },
329
+ "packages/sootsim/src/auth/shared-session.ts :: fetch :: `${origin.replace(/\\/$/, '')}/api/auth/me`":
330
+ { category: 'contrast_user_action', count: 1 },
331
+ "packages/sootsim/src/dev-bundle-resolution.ts :: fetch :: '/__server-scan'": {
332
+ category: 'local_development',
333
+ count: 1,
334
+ },
335
+ 'packages/sootsim/src/dev-bundle-resolution.ts :: fetch :: `/__fetch-proxy?url=${encodeURIComponent(targetUrl)}`':
336
+ { category: 'guest_app_network', count: 1 },
337
+ 'packages/sootsim/src/dev-bundle-resolution.ts :: fetch :: targetUrl': {
338
+ category: 'guest_app_network',
339
+ count: 1,
340
+ },
341
+ 'packages/sootsim/src/dev-server-open.ts :: http.get :: `http://127.0.0.1:${port}/__server-scan`':
342
+ { category: 'local_development', count: 1 },
343
+ 'packages/sootsim/src/host/bridge-host.ts :: fetch :: parsedTarget.toString()': {
344
+ category: 'guest_app_network',
345
+ count: 1,
346
+ },
347
+ "packages/sootsim/src/host/fetch-proxy-handler.ts :: transport.request :: { hostname: targetUrl.hostname, port: targetUrl.port || (targetUrl.protocol === 'https:' ? 443 : 80), path: targetUrl.pathname + targetUrl.search, method, headers: targetHeaders, ...loopbackTlsOptions(targetUrl), }":
348
+ { category: 'guest_app_network', count: 1 },
349
+ "packages/sootsim/src/host/fetch-proxy-handler.ts :: transport.request :: { hostname: targetUrl.hostname, port: targetUrl.port || (targetUrl.protocol === 'https:' ? 443 : 80), path: targetUrl.pathname + targetUrl.search, method: req.method, headers: fwdHeaders, ...loopbackTlsOptions(targetUrl), }":
350
+ { category: 'guest_app_network', count: 1 },
351
+ 'packages/sootsim/src/host/websocket-proxy.ts :: websocket :: targetUrl.href': {
352
+ category: 'guest_app_network',
353
+ count: 2,
354
+ },
355
+ 'packages/sootsim/src/screenshots/org-deck.ts :: fetch :: url': {
356
+ category: 'contrast_user_action',
357
+ count: 1,
358
+ },
359
+ }
360
+
361
+ interface DetectedOutboundCall {
362
+ key: string
363
+ count: number
364
+ }
365
+
366
+ function sourceFilesUnder(root: string): string[] {
367
+ const files: string[] = []
368
+ const pending = [root]
369
+ while (pending.length > 0) {
370
+ const current = pending.pop()
371
+ if (!current) continue
372
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
373
+ const fullPath = path.join(current, entry.name)
374
+ if (entry.isDirectory()) {
375
+ if (entry.name !== 'vendor' && entry.name !== 'test-fixtures')
376
+ pending.push(fullPath)
377
+ continue
378
+ }
379
+ if (/\.(?:ts|tsx)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
380
+ files.push(fullPath)
381
+ }
382
+ }
383
+ }
384
+ return files.sort()
385
+ }
386
+
387
+ interface NetworkNames {
388
+ fetch: Set<string>
389
+ httpClient: Set<string>
390
+ netClient: Set<string>
391
+ socket: Set<string>
392
+ nodeRequest: Set<string>
393
+ nodeGet: Set<string>
394
+ }
395
+
396
+ function networkNames(source: ts.SourceFile): NetworkNames {
397
+ const names: NetworkNames = {
398
+ fetch: new Set(['fetch']),
399
+ httpClient: new Set(['http', 'https']),
400
+ netClient: new Set(['net']),
401
+ socket: new Set(),
402
+ nodeRequest: new Set(),
403
+ nodeGet: new Set(),
404
+ }
405
+ const isClientExpression = (node: ts.Expression, clients: Set<string>): boolean => {
406
+ if (ts.isIdentifier(node)) return clients.has(node.text)
407
+ if (ts.isParenthesizedExpression(node))
408
+ return isClientExpression(node.expression, clients)
409
+ return (
410
+ ts.isConditionalExpression(node) &&
411
+ isClientExpression(node.whenTrue, clients) &&
412
+ isClientExpression(node.whenFalse, clients)
413
+ )
414
+ }
415
+ const visit = (node: ts.Node): void => {
416
+ if (
417
+ (ts.isParameter(node) || ts.isVariableDeclaration(node)) &&
418
+ ts.isIdentifier(node.name) &&
419
+ node.initializer &&
420
+ ts.isIdentifier(node.initializer) &&
421
+ names.fetch.has(node.initializer.text)
422
+ ) {
423
+ names.fetch.add(node.name.text)
424
+ }
425
+ if (
426
+ ts.isImportDeclaration(node) &&
427
+ ts.isStringLiteral(node.moduleSpecifier) &&
428
+ /^(?:node:)?https?$/.test(node.moduleSpecifier.text)
429
+ ) {
430
+ const bindings = node.importClause?.namedBindings
431
+ if (bindings && ts.isNamedImports(bindings)) {
432
+ for (const element of bindings.elements) {
433
+ const imported = (element.propertyName ?? element.name).text
434
+ if (imported === 'request') {
435
+ names.nodeRequest.add(element.name.text)
436
+ }
437
+ if (imported === 'get') names.nodeGet.add(element.name.text)
438
+ }
439
+ }
440
+ }
441
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
442
+ const moduleName = node.moduleSpecifier.text.replace(/^node:/, '')
443
+ const local =
444
+ node.importClause?.name ??
445
+ (node.importClause?.namedBindings &&
446
+ ts.isNamespaceImport(node.importClause.namedBindings)
447
+ ? node.importClause.namedBindings.name
448
+ : undefined)
449
+ if (local && (moduleName === 'http' || moduleName === 'https')) {
450
+ names.httpClient.add(local.text)
451
+ }
452
+ if (local && moduleName === 'net') names.netClient.add(local.text)
453
+ }
454
+ if (
455
+ ts.isVariableDeclaration(node) &&
456
+ ts.isIdentifier(node.name) &&
457
+ node.initializer
458
+ ) {
459
+ if (isClientExpression(node.initializer, names.httpClient)) {
460
+ names.httpClient.add(node.name.text)
461
+ }
462
+ if (isClientExpression(node.initializer, names.netClient)) {
463
+ names.netClient.add(node.name.text)
464
+ }
465
+ if (
466
+ ts.isNewExpression(node.initializer) &&
467
+ ts.isPropertyAccessExpression(node.initializer.expression) &&
468
+ node.initializer.expression.name.text === 'Socket' &&
469
+ isClientExpression(node.initializer.expression.expression, names.netClient)
470
+ ) {
471
+ names.socket.add(node.name.text)
472
+ }
473
+ }
474
+ ts.forEachChild(node, visit)
475
+ }
476
+ visit(source)
477
+ return names
478
+ }
479
+
480
+ function callKind(node: ts.Node, names: NetworkNames): string | null {
481
+ if (ts.isNewExpression(node) && ts.isIdentifier(node.expression)) {
482
+ if (node.expression.text === 'WebSocket') return 'websocket'
483
+ if (node.expression.text === 'EventSource') return 'event-source'
484
+ if (node.expression.text === 'XMLHttpRequest') return 'xml-http-request'
485
+ return null
486
+ }
487
+ if (!ts.isCallExpression(node)) return null
488
+ const expression = node.expression
489
+ if (ts.isIdentifier(expression)) {
490
+ if (names.fetch.has(expression.text)) {
491
+ return expression.text === 'fetch' ? 'fetch' : 'fetch-alias'
492
+ }
493
+ if (names.nodeRequest.has(expression.text)) return 'node-request'
494
+ if (names.nodeGet.has(expression.text)) return 'node-get'
495
+ return null
496
+ }
497
+ if (!ts.isPropertyAccessExpression(expression)) return null
498
+ const method = expression.name.text
499
+ if (method === 'fetch') return 'fetch'
500
+ if (method === 'sendBeacon') return 'send-beacon'
501
+ const ownerExpression = expression.expression
502
+ const owner = ownerExpression.getText().replace(/\s+/g, ' ')
503
+ const knownHttpClient =
504
+ ts.isIdentifier(ownerExpression) && names.httpClient.has(ownerExpression.text)
505
+ const knownNetClient =
506
+ ts.isIdentifier(ownerExpression) && names.netClient.has(ownerExpression.text)
507
+ const knownSocket =
508
+ ts.isIdentifier(ownerExpression) && names.socket.has(ownerExpression.text)
509
+ if (method === 'request' && (knownHttpClient || owner === 'transport')) {
510
+ return `${owner}.request`
511
+ }
512
+ if (method === 'get' && knownHttpClient) return `${owner}.get`
513
+ if (
514
+ (method === 'connect' || method === 'createConnection') &&
515
+ (knownNetClient || knownSocket)
516
+ ) {
517
+ return `${owner}.${method}`
518
+ }
519
+ return null
520
+ }
521
+
522
+ function firstArgument(node: ts.Node): string {
523
+ const args =
524
+ ts.isCallExpression(node) || ts.isNewExpression(node) ? node.arguments : null
525
+ const first = args?.[0]
526
+ return first ? first.getText().replace(/\s+/g, ' ').trim() : '<none>'
527
+ }
528
+
529
+ export function detectOutboundCalls(repositoryRoot: string): DetectedOutboundCall[] {
530
+ const roots = [
531
+ path.join(repositoryRoot, 'packages/sootsim/cli'),
532
+ path.join(repositoryRoot, 'packages/sootsim/scripts'),
533
+ path.join(repositoryRoot, 'packages/sootsim/src'),
534
+ path.join(repositoryRoot, 'packages/sootsim-engine/src'),
535
+ path.join(repositoryRoot, 'packages/sootsim-engine/src-electron'),
536
+ path.join(repositoryRoot, 'packages/contrast-runtime-delivery/src'),
537
+ ]
538
+ const counts = new Map<string, number>()
539
+ for (const file of roots.flatMap(sourceFilesUnder)) {
540
+ if (file.endsWith('/outbound-endpoints.ts')) continue
541
+ const source = ts.createSourceFile(
542
+ file,
543
+ fs.readFileSync(file, 'utf8'),
544
+ ts.ScriptTarget.Latest,
545
+ true,
546
+ file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
547
+ )
548
+ const names = networkNames(source)
549
+ const relative = path.relative(repositoryRoot, file).split(path.sep).join('/')
550
+ const visit = (node: ts.Node): void => {
551
+ const kind = callKind(node, names)
552
+ if (kind) {
553
+ const key = `${relative} :: ${kind} :: ${firstArgument(node)}`
554
+ counts.set(key, (counts.get(key) ?? 0) + 1)
555
+ }
556
+ ts.forEachChild(node, visit)
557
+ }
558
+ visit(source)
559
+ }
560
+ return [...counts.entries()]
561
+ .map(([key, count]) => ({ key, count }))
562
+ .sort((a, b) => a.key.localeCompare(b.key))
563
+ }
564
+
565
+ export function undeclaredOutboundCalls(repositoryRoot: string): string[] {
566
+ const detected = detectOutboundCalls(repositoryRoot)
567
+ const failures: string[] = []
568
+ const seen = new Set<string>()
569
+ for (const call of detected) {
570
+ seen.add(call.key)
571
+ const declaration = DECLARED_OUTBOUND_CALLS[call.key]
572
+ if (!declaration) {
573
+ failures.push(`undeclared: ${call.key} (count ${call.count})`)
574
+ continue
575
+ }
576
+ if (declaration.count !== call.count) {
577
+ failures.push(
578
+ `count changed: ${call.key} (declared ${declaration.count}, found ${call.count})`,
579
+ )
580
+ }
581
+ }
582
+ for (const key of Object.keys(DECLARED_OUTBOUND_CALLS)) {
583
+ if (!seen.has(key)) failures.push(`stale declaration: ${key}`)
584
+ }
585
+ return failures
586
+ }
package/cli/privacy.ts ADDED
@@ -0,0 +1,53 @@
1
+ import {
2
+ readPrivacyPreferences,
3
+ writePrivacyPreferences,
4
+ type PrivacyPreferences,
5
+ } from '../src/home-paths'
6
+ import { isAgentEnv } from './commands/inspect/env'
7
+ import { confirm } from './prompt'
8
+
9
+ function isCi(): boolean {
10
+ return (
11
+ process.env.CI === '1' ||
12
+ process.env.CI === 'true' ||
13
+ process.env.GITHUB_ACTIONS === 'true'
14
+ )
15
+ }
16
+
17
+ export function canPromptForPrivacyChoice(): boolean {
18
+ return process.stdin.isTTY === true && !isCi() && !isAgentEnv()
19
+ }
20
+
21
+ export function diagnosticsAllowedForCurrentProcess(
22
+ preferences: PrivacyPreferences = readPrivacyPreferences(),
23
+ ): boolean {
24
+ const doNotTrack = process.env.DO_NOT_TRACK
25
+ if (doNotTrack === '1' || doNotTrack === 'true') return false
26
+ if (!canPromptForPrivacyChoice()) return false
27
+ return preferences.configured && preferences.productAnalytics
28
+ }
29
+
30
+ export async function ensurePrivacyChoice(options: {
31
+ prompt: boolean
32
+ }): Promise<PrivacyPreferences> {
33
+ const current = readPrivacyPreferences()
34
+ if (current.configured) return current
35
+
36
+ if (!options.prompt || !canPromptForPrivacyChoice()) {
37
+ return {
38
+ productAnalytics: false,
39
+ crashReports: false,
40
+ configured: false,
41
+ }
42
+ }
43
+
44
+ const allowed = await confirm(
45
+ 'Share anonymous package incompatibilities and error classes to improve rnx?',
46
+ false,
47
+ )
48
+ // the engine's historical analytics and crash transports carry broader
49
+ // payloads, so crashReports stays off. productAnalytics is now consent for
50
+ // the bounded CLI diagnostic transport only.
51
+ writePrivacyPreferences({ productAnalytics: allowed, crashReports: false })
52
+ return readPrivacyPreferences()
53
+ }
@@ -26,12 +26,12 @@ function isChangelogEntry(value: unknown): value is ChangelogEntry {
26
26
 
27
27
  export async function fetchRuntimeReleaseNotes(
28
28
  version: string,
29
- request: typeof fetch = fetch,
29
+ fetchImpl: typeof fetch = fetch,
30
30
  ): Promise<ChangelogEntry | null> {
31
31
  try {
32
32
  const url = new URL(SOOTSIM_CHANGELOG_API_URL)
33
33
  url.searchParams.set('version', version)
34
- const response = await request(url, {
34
+ const response = await fetchImpl(url, {
35
35
  headers: { accept: 'application/json' },
36
36
  signal: AbortSignal.timeout(5000),
37
37
  })