rnxsim 0.0.0 → 0.1.312

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +512 -0
  3. package/detox/colors.ts +54 -0
  4. package/detox/config-loader.ts +135 -0
  5. package/detox/element-types.ts +39 -0
  6. package/detox/expectations.ts +537 -0
  7. package/detox/gestures.ts +480 -0
  8. package/detox/index.ts +2118 -0
  9. package/detox/jest-preset.cjs +51 -0
  10. package/detox/jest-setup-after-env.cjs +18 -0
  11. package/detox/matchers.ts +30 -0
  12. package/detox/motion-change.cjs +109 -0
  13. package/detox/motion-change.d.cts +35 -0
  14. package/detox/navigation.ts +43 -0
  15. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  16. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  17. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  18. package/detox/screenshots/theme-blue-toggle.png +0 -0
  19. package/detox/screenshots/theme-blue.png +0 -0
  20. package/detox/screenshots/theme-red-toggle.png +0 -0
  21. package/detox/screenshots/theme-red.png +0 -0
  22. package/dist-cli/bin.js +15 -0
  23. package/dist-cli/chunks/agent-NXVDDCFG.js +48 -0
  24. package/dist-cli/chunks/agent-wrapper-DXJCFKL3.js +17 -0
  25. package/dist-cli/chunks/app-fonts-W5AJZQJF.js +4 -0
  26. package/dist-cli/chunks/assert-3LOJEUDT.js +49 -0
  27. package/dist-cli/chunks/auth-MFTYYPNX.js +12 -0
  28. package/dist-cli/chunks/beta-GT3NN67B.js +4 -0
  29. package/dist-cli/chunks/camera-6EAK7CUL.js +22 -0
  30. package/dist-cli/chunks/chunk-274UD5FD.js +4 -0
  31. package/dist-cli/chunks/chunk-2SA2VTGP.js +5 -0
  32. package/dist-cli/chunks/chunk-33FKQH54.js +5 -0
  33. package/dist-cli/chunks/chunk-37I5AIS7.js +3 -0
  34. package/dist-cli/chunks/chunk-3D57IBPC.js +4 -0
  35. package/dist-cli/chunks/chunk-4BXCZHAU.js +4 -0
  36. package/dist-cli/chunks/chunk-5OX7ANRA.js +5 -0
  37. package/dist-cli/chunks/chunk-66NF2KNG.js +9 -0
  38. package/dist-cli/chunks/chunk-6UKJB2VC.js +125 -0
  39. package/dist-cli/chunks/chunk-7INBNA3U.js +26 -0
  40. package/dist-cli/chunks/chunk-7VWOSTD5.js +5 -0
  41. package/dist-cli/chunks/chunk-AY47WKS5.js +23 -0
  42. package/dist-cli/chunks/chunk-B7KMNHX7.js +4 -0
  43. package/dist-cli/chunks/chunk-BKNOLP3J.js +4 -0
  44. package/dist-cli/chunks/chunk-BRIZOR4K.js +28 -0
  45. package/dist-cli/chunks/chunk-DM6KUAOZ.js +4 -0
  46. package/dist-cli/chunks/chunk-EUAVBTKU.js +4 -0
  47. package/dist-cli/chunks/chunk-FTC4M26Q.js +4 -0
  48. package/dist-cli/chunks/chunk-FWYYCUAZ.js +4 -0
  49. package/dist-cli/chunks/chunk-G74YIEVS.js +7 -0
  50. package/dist-cli/chunks/chunk-GZDHEONW.js +507 -0
  51. package/dist-cli/chunks/chunk-HG7ORJMT.js +5 -0
  52. package/dist-cli/chunks/chunk-HMDOLCKG.js +15 -0
  53. package/dist-cli/chunks/chunk-JG6S4RSH.js +4 -0
  54. package/dist-cli/chunks/chunk-KR5BGMBZ.js +4 -0
  55. package/dist-cli/chunks/chunk-KZW5WBGU.js +983 -0
  56. package/dist-cli/chunks/chunk-L24Z3CRS.js +30 -0
  57. package/dist-cli/chunks/chunk-LDQ7HVCW.js +4 -0
  58. package/dist-cli/chunks/chunk-MENV7VLD.js +4 -0
  59. package/dist-cli/chunks/chunk-MNGQ42QE.js +568 -0
  60. package/dist-cli/chunks/chunk-N6A3SXMR.js +4 -0
  61. package/dist-cli/chunks/chunk-NLEMQ4PY.js +6 -0
  62. package/dist-cli/chunks/chunk-NPVGMH2N.js +101 -0
  63. package/dist-cli/chunks/chunk-OR3XTXEB.js +107 -0
  64. package/dist-cli/chunks/chunk-PEPUGJVI.js +4 -0
  65. package/dist-cli/chunks/chunk-Q3RGLERJ.js +142 -0
  66. package/dist-cli/chunks/chunk-SI3BUTQR.js +4 -0
  67. package/dist-cli/chunks/chunk-T4FT6CM7.js +4 -0
  68. package/dist-cli/chunks/chunk-TBMVBI26.js +7 -0
  69. package/dist-cli/chunks/chunk-TCWQJILS.js +5 -0
  70. package/dist-cli/chunks/chunk-TGQNQ6XT.js +766 -0
  71. package/dist-cli/chunks/chunk-TYE3WBSR.js +5 -0
  72. package/dist-cli/chunks/chunk-U4PN6JOT.js +6 -0
  73. package/dist-cli/chunks/chunk-VUFRB347.js +4 -0
  74. package/dist-cli/chunks/chunk-VZBLFMYC.js +10 -0
  75. package/dist-cli/chunks/chunk-WBT32R6N.js +25 -0
  76. package/dist-cli/chunks/chunk-XRGEEPJ5.js +38 -0
  77. package/dist-cli/chunks/chunk-Y2FSZSO7.js +71 -0
  78. package/dist-cli/chunks/chunk-YRV7HR3P.js +74 -0
  79. package/dist-cli/chunks/chunk-YZRFF4YA.js +14 -0
  80. package/dist-cli/chunks/chunk-ZMZTHU7V.js +4 -0
  81. package/dist-cli/chunks/cleanup-H3GWQB63.js +25 -0
  82. package/dist-cli/chunks/cli-version-3QPAJ3LC.js +4 -0
  83. package/dist-cli/chunks/compat-5WG2XJV7.js +20 -0
  84. package/dist-cli/chunks/config-ANMBVVLL.js +13 -0
  85. package/dist-cli/chunks/control-NSGNQFJG.js +4 -0
  86. package/dist-cli/chunks/daemon-JHF4BJ3U.js +4 -0
  87. package/dist-cli/chunks/debug-CHV2O2KF.js +200 -0
  88. package/dist-cli/chunks/desktop-FIPIK4CH.js +31 -0
  89. package/dist-cli/chunks/detox-CVUJP7K6.js +52 -0
  90. package/dist-cli/chunks/device-6WCUM4W6.js +18 -0
  91. package/dist-cli/chunks/diagnose-I6EMWEII.js +43 -0
  92. package/dist-cli/chunks/disk-cleanup-P27CYFTN.js +4 -0
  93. package/dist-cli/chunks/drivers-HQ6AKZT6.js +4 -0
  94. package/dist-cli/chunks/film-UOQFFYNF.js +74 -0
  95. package/dist-cli/chunks/flow-N27MRRNL.js +4 -0
  96. package/dist-cli/chunks/help-SF6FEGEE.js +4 -0
  97. package/dist-cli/chunks/hidden-runtime-alias-NJYFSJKZ.js +4 -0
  98. package/dist-cli/chunks/home-paths-XOV44JEF.js +4 -0
  99. package/dist-cli/chunks/inspect-CWFRFQTY.js +4 -0
  100. package/dist-cli/chunks/install-desktop-KLQDNXZQ.js +4 -0
  101. package/dist-cli/chunks/login-BPFQENCX.js +28 -0
  102. package/dist-cli/chunks/logout-5A45ZGY7.js +4 -0
  103. package/dist-cli/chunks/maestro-E4CSDMLM.js +104 -0
  104. package/dist-cli/chunks/maestro-generate-CKMOJQX2.js +36 -0
  105. package/dist-cli/chunks/mode-SZYNMIQF.js +26 -0
  106. package/dist-cli/chunks/optional-demo-registry-FRH4SFVG.js +4 -0
  107. package/dist-cli/chunks/perf-VXU2XJAX.js +48 -0
  108. package/dist-cli/chunks/permissions-IRIAOVND.js +13 -0
  109. package/dist-cli/chunks/record-RY7FPLLH.js +72 -0
  110. package/dist-cli/chunks/report-issue-KNWXOE53.js +29 -0
  111. package/dist-cli/chunks/runtime-AR3GJTGK.js +4 -0
  112. package/dist-cli/chunks/screenshot-command-QLALR6GY.js +159 -0
  113. package/dist-cli/chunks/screenshot-layers-OQFMFVRL.js +20 -0
  114. package/dist-cli/chunks/screenshots-capture-6TSRJFGB.js +42 -0
  115. package/dist-cli/chunks/serve-F54J2NQO.js +44 -0
  116. package/dist-cli/chunks/setup-7V6UKX4U.js +32 -0
  117. package/dist-cli/chunks/skills-QQBOSETH.js +50 -0
  118. package/dist-cli/chunks/storage-TAPMKK5O.js +37 -0
  119. package/dist-cli/chunks/store-7NX3S43H.js +4 -0
  120. package/dist-cli/chunks/telemetry-E7BUK6RA.js +4 -0
  121. package/dist-cli/chunks/timeline-IAM564NA.js +24 -0
  122. package/dist-cli/chunks/upgrade-CD55QXIX.js +7 -0
  123. package/dist-cli/chunks/upload-GZ6LIMYM.js +4 -0
  124. package/dist-cli/chunks/version-3DYJ5AS3.js +6 -0
  125. package/dist-cli/chunks/web-6NW6YLPI.js +4 -0
  126. package/dist-cli/chunks/what-happened-CZZDOOVG.js +25 -0
  127. package/dist-lib/agent-daemon-client.cjs +468 -0
  128. package/dist-lib/agent-events.cjs +48 -0
  129. package/dist-lib/agent-identity.cjs +53 -0
  130. package/dist-lib/agent-sessions.cjs +725 -0
  131. package/dist-lib/attached-projects.cjs +526 -0
  132. package/dist-lib/auth/shared-session.cjs +195 -0
  133. package/dist-lib/backend-origin.cjs +70 -0
  134. package/dist-lib/beta.cjs +41 -0
  135. package/dist-lib/beta.mjs +13 -0
  136. package/dist-lib/bridge-constants.cjs +161 -0
  137. package/dist-lib/cli-constants.cjs +32 -0
  138. package/dist-lib/config.cjs +96 -0
  139. package/dist-lib/detox/index.cjs +2863 -0
  140. package/dist-lib/detox/jest-preset.cjs +51 -0
  141. package/dist-lib/dev-bundle-resolution.cjs +223 -0
  142. package/dist-lib/home-paths.cjs +421 -0
  143. package/dist-lib/host/bridge-host.cjs +5600 -0
  144. package/dist-lib/host/fetch-proxy-handler.cjs +373 -0
  145. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  146. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  147. package/dist-lib/host/websocket-proxy.cjs +214 -0
  148. package/dist-lib/index.cjs +513 -0
  149. package/dist-lib/jump-to-source-babel.cjs +93 -0
  150. package/dist-lib/menu.cjs +620 -0
  151. package/dist-lib/menu.mjs +581 -0
  152. package/dist-lib/metro.cjs +297 -0
  153. package/dist-lib/profiles.cjs +246 -0
  154. package/dist-lib/public-brand.cjs +39 -0
  155. package/dist-lib/render-mode.cjs +54 -0
  156. package/dist-lib/scripts/dev-server-scanner.cjs +1034 -0
  157. package/dist-lib/sdk.cjs +2333 -0
  158. package/dist-lib/sdk.mjs +2240 -0
  159. package/dist-lib/skills.cjs +23911 -0
  160. package/dist-lib/vite.cjs +358 -0
  161. package/package.json +189 -18
  162. package/scripts/dev-server-scanner.ts +941 -0
  163. package/scripts/optional-demo-registry.ts +113 -0
  164. package/scripts/postinstall.cjs +3 -0
  165. package/scripts/preuninstall.cjs +118 -0
  166. package/skills/contrast/SKILL.md +171 -0
  167. package/skills/rnx-debug/SKILL.md +425 -0
  168. package/skills/rnx-perf/SKILL.md +295 -0
  169. package/skills/rnx-setup/SKILL.md +160 -0
  170. package/skills/rnx-test/SKILL.md +292 -0
  171. package/skills/rnx-visual/SKILL.md +122 -0
  172. package/src/agent-daemon-client.ts +410 -0
  173. package/src/agent-events.ts +71 -0
  174. package/src/agent-identity.ts +23 -0
  175. package/src/agent-prompt.ts +71 -0
  176. package/src/agent-sessions.ts +573 -0
  177. package/src/attached-projects.ts +539 -0
  178. package/src/auth/shared-session.ts +204 -0
  179. package/src/backend-origin.ts +49 -0
  180. package/src/beta.ts +19 -0
  181. package/src/bridge-constants.ts +171 -0
  182. package/src/browser-cache.ts +16 -0
  183. package/src/cli-constants.ts +1 -0
  184. package/src/cli-version.ts +32 -0
  185. package/src/codex-client.ts +215 -0
  186. package/src/config.ts +134 -0
  187. package/src/dev-bundle-resolution.ts +236 -0
  188. package/src/disk-cleanup.ts +466 -0
  189. package/src/home-paths.ts +552 -0
  190. package/src/host/agent-host.ts +666 -0
  191. package/src/host/bridge-host.ts +2626 -0
  192. package/src/host/fetch-proxy-handler.ts +397 -0
  193. package/src/host/fetch-proxy-overrides.ts +51 -0
  194. package/src/host/open-url.ts +89 -0
  195. package/src/host/websocket-proxy.ts +210 -0
  196. package/src/index.ts +12 -705
  197. package/src/jump-to-source-babel.ts +135 -0
  198. package/src/menu.ts +734 -0
  199. package/src/metro-plugin.ts +139 -0
  200. package/src/native-dev-bundle-url.ts +96 -0
  201. package/src/native-seam-manifest.ts +341 -0
  202. package/src/plist.ts +27 -0
  203. package/src/poll-until.ts +92 -0
  204. package/src/profiles.ts +179 -0
  205. package/src/public-brand.ts +9 -0
  206. package/src/render-mode.ts +27 -0
  207. package/src/runtime-assets.ts +228 -0
  208. package/src/runtime-delivery.ts +54 -0
  209. package/src/screenshots/compose.ts +422 -0
  210. package/src/screenshots/frame-compose.ts +454 -0
  211. package/src/screenshots/orchestrate.ts +256 -0
  212. package/src/screenshots/org-deck.ts +91 -0
  213. package/src/screenshots/registry.ts +58 -0
  214. package/src/screenshots/schema.ts +364 -0
  215. package/src/sdk.ts +7 -0
  216. package/src/settings-bundle.ts +418 -0
  217. package/src/skills/builtin/a11y-review.ts +132 -0
  218. package/src/skills/builtin/compat-check.ts +71 -0
  219. package/src/skills/builtin/maestro-test.ts +121 -0
  220. package/src/skills/builtin/perf-profile.ts +90 -0
  221. package/src/skills/builtin/screenshot-all.ts +52 -0
  222. package/src/skills/builtin/visual-diff.ts +100 -0
  223. package/src/skills/registry.ts +107 -0
  224. package/src/skills/types.ts +41 -0
  225. package/src/team-machine-prompt.ts +73 -0
  226. package/src/vite-plugin-one.ts +190 -0
  227. package/src/vite-plugin.ts +1750 -0
  228. package/src/worklets-babel.ts +134 -0
@@ -0,0 +1,204 @@
1
+ import {
2
+ chmodSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from 'node:fs'
9
+ import { dirname, join, resolve } from 'node:path'
10
+ import { electronUserDataDir } from '../home-paths'
11
+
12
+ export type SharedDesktopAuthTeamSummary = {
13
+ id: string
14
+ name: string
15
+ role: 'owner' | 'member'
16
+ githubOrg: string | null
17
+ }
18
+
19
+ export type SharedDesktopAuthUser = {
20
+ id: string
21
+ name?: string
22
+ email?: string
23
+ image?: string
24
+ githubUsername?: string
25
+ teams?: SharedDesktopAuthTeamSummary[]
26
+ isAdmin?: boolean
27
+ }
28
+
29
+ export type SharedDesktopAuthSession = {
30
+ version: 1
31
+ token: string
32
+ user: SharedDesktopAuthUser | null
33
+ origin: string
34
+ source: 'cli' | 'electron' | 'browser' | 'unknown'
35
+ updatedAt: string
36
+ validatedAt?: string
37
+ }
38
+
39
+ const SESSION_VERSION = 1 as const
40
+ const SESSION_FILE_ENV = 'SOOTSIM_SHARED_AUTH_FILE'
41
+ const DEFAULT_ORIGIN = 'https://contrast.dev'
42
+
43
+ function getBaseDir() {
44
+ const explicit = process.env[SESSION_FILE_ENV]
45
+ if (explicit?.trim()) {
46
+ return dirname(resolve(explicit))
47
+ }
48
+ return electronUserDataDir()
49
+ }
50
+
51
+ export function getSharedDesktopAuthFilePath() {
52
+ const explicit = process.env[SESSION_FILE_ENV]
53
+ if (explicit?.trim()) return resolve(explicit)
54
+ return join(getBaseDir(), 'desktop-auth.json')
55
+ }
56
+
57
+ function normalizeUser(input: unknown): SharedDesktopAuthUser | null {
58
+ if (!input || typeof input !== 'object') return null
59
+ const value = input as Record<string, unknown>
60
+ if (typeof value.id !== 'string' || !value.id.trim()) return null
61
+ return {
62
+ id: value.id.trim(),
63
+ name: typeof value.name === 'string' ? value.name : undefined,
64
+ email: typeof value.email === 'string' ? value.email : undefined,
65
+ image: typeof value.image === 'string' ? value.image : undefined,
66
+ githubUsername:
67
+ typeof value.githubUsername === 'string' ? value.githubUsername : undefined,
68
+ isAdmin: value.isAdmin === true,
69
+ teams: Array.isArray(value.teams)
70
+ ? value.teams
71
+ .map((team): SharedDesktopAuthTeamSummary | null => {
72
+ if (!team || typeof team !== 'object') return null
73
+ const t = team as Record<string, unknown>
74
+ if (typeof t.id !== 'string' || typeof t.name !== 'string') return null
75
+ return {
76
+ id: t.id,
77
+ name: t.name,
78
+ role: t.role === 'owner' ? 'owner' : 'member',
79
+ githubOrg: typeof t.githubOrg === 'string' ? t.githubOrg : null,
80
+ }
81
+ })
82
+ .filter((team): team is SharedDesktopAuthTeamSummary => !!team)
83
+ : undefined,
84
+ }
85
+ }
86
+
87
+ function normalizeSession(input: unknown): SharedDesktopAuthSession | null {
88
+ if (!input || typeof input !== 'object') return null
89
+ const value = input as Record<string, unknown>
90
+ if (value.version !== SESSION_VERSION) return null
91
+ if (typeof value.token !== 'string' || !value.token.trim()) return null
92
+ const origin =
93
+ typeof value.origin === 'string' && value.origin.trim()
94
+ ? value.origin.trim()
95
+ : DEFAULT_ORIGIN
96
+ const source =
97
+ value.source === 'cli' ||
98
+ value.source === 'electron' ||
99
+ value.source === 'browser' ||
100
+ value.source === 'unknown'
101
+ ? value.source
102
+ : 'unknown'
103
+ const updatedAt =
104
+ typeof value.updatedAt === 'string' && value.updatedAt
105
+ ? value.updatedAt
106
+ : new Date().toISOString()
107
+ const validatedAt =
108
+ typeof value.validatedAt === 'string' && value.validatedAt
109
+ ? value.validatedAt
110
+ : undefined
111
+
112
+ return {
113
+ version: SESSION_VERSION,
114
+ token: value.token.trim(),
115
+ user: normalizeUser(value.user),
116
+ origin,
117
+ source,
118
+ updatedAt,
119
+ validatedAt,
120
+ }
121
+ }
122
+
123
+ export function readSharedDesktopAuthSession(): SharedDesktopAuthSession | null {
124
+ const filepath = getSharedDesktopAuthFilePath()
125
+ if (!existsSync(filepath)) return null
126
+
127
+ try {
128
+ const parsed = JSON.parse(readFileSync(filepath, 'utf8')) as unknown
129
+ const normalized = normalizeSession(parsed)
130
+ if (!normalized) {
131
+ rmSync(filepath, { force: true })
132
+ return null
133
+ }
134
+ return normalized
135
+ } catch {
136
+ rmSync(filepath, { force: true })
137
+ return null
138
+ }
139
+ }
140
+
141
+ export function writeSharedDesktopAuthSession(
142
+ session: Omit<SharedDesktopAuthSession, 'version' | 'updatedAt'> & {
143
+ updatedAt?: string
144
+ },
145
+ ) {
146
+ const filepath = getSharedDesktopAuthFilePath()
147
+ mkdirSync(dirname(filepath), { recursive: true })
148
+ const normalized: SharedDesktopAuthSession = {
149
+ version: SESSION_VERSION,
150
+ token: session.token.trim(),
151
+ user: session.user ? normalizeUser(session.user) : null,
152
+ origin: session.origin?.trim() || DEFAULT_ORIGIN,
153
+ source: session.source,
154
+ updatedAt: session.updatedAt || new Date().toISOString(),
155
+ validatedAt: session.validatedAt,
156
+ }
157
+ writeFileSync(filepath, JSON.stringify(normalized, null, 2) + '\n')
158
+ try {
159
+ chmodSync(filepath, 0o600)
160
+ } catch {
161
+ // best-effort only
162
+ }
163
+ return normalized
164
+ }
165
+
166
+ export function clearSharedDesktopAuthSession() {
167
+ rmSync(getSharedDesktopAuthFilePath(), { force: true })
168
+ }
169
+
170
+ export async function refreshSharedDesktopAuthSession(
171
+ originOverride?: string,
172
+ ): Promise<SharedDesktopAuthSession | null> {
173
+ const current = readSharedDesktopAuthSession()
174
+ if (!current?.token) return null
175
+
176
+ const origin = originOverride || current.origin || DEFAULT_ORIGIN
177
+
178
+ try {
179
+ const res = await fetch(`${origin.replace(/\/$/, '')}/api/auth/me`, {
180
+ headers: { authorization: `Bearer ${current.token}` },
181
+ })
182
+ if (res.status === 401) {
183
+ clearSharedDesktopAuthSession()
184
+ return null
185
+ }
186
+ if (!res.ok) return current
187
+
188
+ const data = (await res.json()) as { user?: SharedDesktopAuthUser | null }
189
+ if (!data.user?.id) {
190
+ clearSharedDesktopAuthSession()
191
+ return null
192
+ }
193
+
194
+ return writeSharedDesktopAuthSession({
195
+ token: current.token,
196
+ user: data.user,
197
+ origin,
198
+ source: current.source,
199
+ validatedAt: new Date().toISOString(),
200
+ })
201
+ } catch {
202
+ return current
203
+ }
204
+ }
@@ -0,0 +1,49 @@
1
+ const DEFAULT_CONTRAST_BACKEND_ORIGIN = 'https://contrast.dev'
2
+
3
+ function normalizeHostname(hostname: string): string {
4
+ return hostname.replace(/^\[|\]$/g, '').toLowerCase()
5
+ }
6
+
7
+ export function isLoopbackHost(hostname: string): boolean {
8
+ const normalized = normalizeHostname(hostname)
9
+ return (
10
+ normalized === 'localhost' ||
11
+ normalized.endsWith('.localhost') ||
12
+ normalized === '0.0.0.0' ||
13
+ normalized === '::1' ||
14
+ /^127(?:\.\d{1,3}){3}$/.test(normalized)
15
+ )
16
+ }
17
+
18
+ export function isContrastBackendHost(hostname: string): boolean {
19
+ const normalized = normalizeHostname(hostname)
20
+ return (
21
+ isLoopbackHost(normalized) ||
22
+ normalized === 'contrast.dev' ||
23
+ normalized.endsWith('.contrast.dev')
24
+ )
25
+ }
26
+
27
+ export function isContrastBackendOrigin(origin: string): boolean {
28
+ try {
29
+ return isContrastBackendHost(new URL(origin).hostname)
30
+ } catch {
31
+ return false
32
+ }
33
+ }
34
+
35
+ export function resolveContrastBackendOrigin(currentOrigin?: string | null): string {
36
+ const origin = currentOrigin?.trim()
37
+ if (!origin) return DEFAULT_CONTRAST_BACKEND_ORIGIN
38
+
39
+ try {
40
+ const parsed = new URL(origin)
41
+ if (isLoopbackHost(parsed.hostname) || isContrastBackendHost(parsed.hostname)) {
42
+ return parsed.origin
43
+ }
44
+ } catch {}
45
+
46
+ return DEFAULT_CONTRAST_BACKEND_ORIGIN
47
+ }
48
+
49
+ export { DEFAULT_CONTRAST_BACKEND_ORIGIN }
package/src/beta.ts ADDED
@@ -0,0 +1,19 @@
1
+ // public beta flag + copy. single source of truth for the badge that
2
+ // appears on the CLI banner, electron title, shell rail pill, and the
3
+ // download page. flip IS_BETA to false when 1.0 ships and the badge
4
+ // disappears everywhere automatically.
5
+ //
6
+ // see plans/sootsim-public-beta.md for the rollout plan.
7
+
8
+ export const IS_BETA = true
9
+
10
+ // short label — used on the rail pill, electron title, etc.
11
+ export const BETA_LABEL = 'public beta'
12
+
13
+ // one-line tagline — used in the CLI banner.
14
+ export const BETA_TAGLINE = BETA_LABEL
15
+
16
+ // what we ask for in return — kept here so the onboarding flow, the
17
+ // privacy page, and any future surface render the same words.
18
+ export const BETA_ASK_HEADLINE =
19
+ 'we send anonymous error and dependency data while you use it'
@@ -0,0 +1,171 @@
1
+ // canonical bridge port for sootsim's WebSocket connection between the CLI
2
+ // and the running engine. matches main — kept in a single module so CLI,
3
+ // dev plugin, and runtime agree on the same value.
4
+ export const DEFAULT_SOOTSIM_BRIDGE_PORT = 7668
5
+ export const DEFAULT_SOOTSIM_BRIDGE_URL = `http://localhost:${DEFAULT_SOOTSIM_BRIDGE_PORT}/`
6
+
7
+ function parsePort(value: unknown): number | null {
8
+ if (typeof value === 'number') {
9
+ return Number.isInteger(value) && value > 0 ? value : null
10
+ }
11
+ if (typeof value !== 'string') return null
12
+ const trimmed = value.trim()
13
+ if (!trimmed) return null
14
+ const parsed = Number(trimmed)
15
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null
16
+ }
17
+
18
+ function parsePortOffset(value: unknown): number {
19
+ const parsed = parsePort(value)
20
+ return parsed === null ? 0 : parsed
21
+ }
22
+
23
+ export function resolveSootsimBridgePort(
24
+ input: {
25
+ explicitPort?: unknown
26
+ portOffset?: unknown
27
+ } = {},
28
+ ): number {
29
+ const explicitPort = parsePort(input.explicitPort)
30
+ if (explicitPort !== null) return explicitPort
31
+ return DEFAULT_SOOTSIM_BRIDGE_PORT + parsePortOffset(input.portOffset)
32
+ }
33
+
34
+ export function resolveSootsimBridgePortForHttpPort(input: {
35
+ explicitPort?: unknown
36
+ httpPort?: unknown
37
+ baseHttpPort: number
38
+ }): number {
39
+ const explicitPort = parsePort(input.explicitPort)
40
+ if (explicitPort !== null) return explicitPort
41
+ const httpPort = parsePort(input.httpPort)
42
+ const portOffset = httpPort === null ? 0 : Math.max(0, httpPort - input.baseHttpPort)
43
+ return resolveSootsimBridgePort({ portOffset })
44
+ }
45
+
46
+ const CONTRAST_DEV_HTTP_BASE_PORT = 3000
47
+ const SOOTSIM_SITE_DEV_HTTP_BASE_PORT = 3001
48
+ const SOOTSIM_SHELL_DEV_HTTP_BASE_PORT = 5173
49
+ const LOCAL_DEV_HTTP_BASE_PORTS = [
50
+ CONTRAST_DEV_HTTP_BASE_PORT,
51
+ SOOTSIM_SHELL_DEV_HTTP_BASE_PORT,
52
+ ]
53
+ const MAX_LOCAL_DEV_PORT_OFFSET = 2000
54
+
55
+ function parseHrefUrl(value: unknown): URL | null {
56
+ if (typeof value !== 'string' || !value.trim()) return null
57
+ try {
58
+ return new URL(value)
59
+ } catch {
60
+ return null
61
+ }
62
+ }
63
+
64
+ function closestLocalDevBaseHttpPort(httpPort: number): number | null {
65
+ let closest: { basePort: number; offset: number } | null = null
66
+ for (const basePort of LOCAL_DEV_HTTP_BASE_PORTS) {
67
+ const offset = httpPort - basePort
68
+ if (offset < 0 || offset > MAX_LOCAL_DEV_PORT_OFFSET) continue
69
+ if (!closest || offset < closest.offset) closest = { basePort, offset }
70
+ }
71
+ return closest?.basePort ?? null
72
+ }
73
+
74
+ /** the vite dev shell that shares a world with a bridge port. the two are one
75
+ * offset apart by construction (bridge 7668 + N, shell 5173 + N), so a caller
76
+ * that pinned an explicit bridge port can name its own shell directly. that
77
+ * matters because the dev-bridge lockfile records only the stack that
78
+ * heartbeated last: on a machine running several worktree stacks it routinely
79
+ * names someone else's shell, and a page opened there while we poll our own
80
+ * bridge never connects. null when the port is not a local dev bridge port. */
81
+ export function resolveSootsimShellBaseUrlForBridgePort(
82
+ bridgePort: number,
83
+ ): string | null {
84
+ const offset = bridgePort - DEFAULT_SOOTSIM_BRIDGE_PORT
85
+ if (offset < 0 || offset > MAX_LOCAL_DEV_PORT_OFFSET) return null
86
+ return `http://localhost:${SOOTSIM_SHELL_DEV_HTTP_BASE_PORT + offset}/`
87
+ }
88
+
89
+ export function resolveSootsimBridgePortForRuntimeLocation(
90
+ input: {
91
+ explicitPort?: unknown
92
+ href?: unknown
93
+ httpPort?: unknown
94
+ } = {},
95
+ ): number {
96
+ const explicitPort = parsePort(input.explicitPort)
97
+ if (explicitPort !== null) return explicitPort
98
+ const hrefUrl = parseHrefUrl(input.href)
99
+ const httpPort = parsePort(input.httpPort) ?? parsePort(hrefUrl?.port)
100
+ if (httpPort === null) return DEFAULT_SOOTSIM_BRIDGE_PORT
101
+ if (
102
+ hrefUrl?.pathname.startsWith('/sootsim') &&
103
+ (hrefUrl.searchParams.has('embed') || hrefUrl.searchParams.has('nativePreview'))
104
+ ) {
105
+ return resolveSootsimBridgePortForHttpPort({
106
+ httpPort,
107
+ baseHttpPort: CONTRAST_DEV_HTTP_BASE_PORT,
108
+ })
109
+ }
110
+ if (
111
+ hrefUrl?.pathname.startsWith('/sootsim') &&
112
+ httpPort >= SOOTSIM_SITE_DEV_HTTP_BASE_PORT &&
113
+ httpPort <= SOOTSIM_SITE_DEV_HTTP_BASE_PORT + MAX_LOCAL_DEV_PORT_OFFSET
114
+ ) {
115
+ return resolveSootsimBridgePortForHttpPort({
116
+ httpPort,
117
+ baseHttpPort: SOOTSIM_SITE_DEV_HTTP_BASE_PORT,
118
+ })
119
+ }
120
+ const baseHttpPort = closestLocalDevBaseHttpPort(httpPort)
121
+ if (baseHttpPort === null) return DEFAULT_SOOTSIM_BRIDGE_PORT
122
+ return resolveSootsimBridgePortForHttpPort({ httpPort, baseHttpPort })
123
+ }
124
+
125
+ export function resolveSootsimWebSocketProxyUrl(input: {
126
+ targetUrl: string
127
+ href: string
128
+ }): string {
129
+ const pageUrl = parseHrefUrl(input.href)
130
+ if (!pageUrl || (pageUrl.protocol !== 'http:' && pageUrl.protocol !== 'https:')) {
131
+ throw new Error(`invalid rnx websocket proxy page URL: ${input.href}`)
132
+ }
133
+ let targetUrl: URL
134
+ try {
135
+ targetUrl = new URL(input.targetUrl)
136
+ } catch {
137
+ throw new Error(`invalid rnx websocket proxy target URL: ${input.targetUrl}`)
138
+ }
139
+ if (targetUrl.protocol !== 'ws:' && targetUrl.protocol !== 'wss:') {
140
+ throw new Error(`invalid rnx websocket proxy target URL: ${input.targetUrl}`)
141
+ }
142
+ const proxyUrl = new URL('/__websocket-proxy', pageUrl)
143
+ proxyUrl.protocol = pageUrl.protocol === 'https:' ? 'wss:' : 'ws:'
144
+ proxyUrl.searchParams.set('url', input.targetUrl)
145
+ return proxyUrl.href
146
+ }
147
+
148
+ export function resolveSootsimBridgeWebSocketUrl(input: {
149
+ bridgePort: number
150
+ href: string
151
+ }): string {
152
+ return resolveSootsimWebSocketProxyUrl({
153
+ targetUrl: `ws://localhost:${input.bridgePort}`,
154
+ href: input.href,
155
+ })
156
+ }
157
+
158
+ export function isSootsimBridgeHealth(value: unknown, expectedPort: number): boolean {
159
+ return (
160
+ typeof value === 'object' &&
161
+ value !== null &&
162
+ Reflect.get(value, 'ok') === true &&
163
+ Reflect.get(value, 'bridgePort') === expectedPort
164
+ )
165
+ }
166
+
167
+ // private websocket close code used when the daemon intentionally closes a
168
+ // sim. browser clients treat it as terminal and skip their normal reconnect
169
+ // loop; playwright-owned sims use it to close their hosting context.
170
+ export const SOOTSIM_BRIDGE_SIM_CLOSE_CODE = 4001
171
+ export const SOOTSIM_BRIDGE_SIM_CLOSE_REASON = 'sootsim close'
@@ -0,0 +1,16 @@
1
+ export const DISPOSABLE_BROWSER_CACHE_DIR_NAMES = [
2
+ 'Cache',
3
+ 'Code Cache',
4
+ 'DawnCache',
5
+ 'DawnGraphiteCache',
6
+ 'DawnWebGPUCache',
7
+ 'GPUCache',
8
+ 'GrShaderCache',
9
+ 'ShaderCache',
10
+ ] as const
11
+
12
+ const disposableBrowserCacheDirs = new Set<string>(DISPOSABLE_BROWSER_CACHE_DIR_NAMES)
13
+
14
+ export function isDisposableBrowserCacheDir(name: string): boolean {
15
+ return disposableBrowserCacheDirs.has(name)
16
+ }
@@ -0,0 +1 @@
1
+ export const DEFAULT_SOOTSIM_SHELL_URL = 'http://localhost:5173/'
@@ -0,0 +1,32 @@
1
+ // sootsim CLI version — the `sootsim` npm package version. resolved once
2
+ // and cached. shared by `--version`, `--help`, the `upgrade` command, the
3
+ // startup flow, and the bridge-host (which injects it into the served
4
+ // runtime html as window.__sootsimCliVersion).
5
+
6
+ import { readFileSync } from 'node:fs'
7
+ import { fileURLToPath } from 'node:url'
8
+ import { rnxPublicBrand } from './public-brand.ts'
9
+
10
+ let cached: string | null = null
11
+
12
+ export function getCliVersion(): string {
13
+ if (cached != null) return cached
14
+ // prefer package resolution (works for npm/global installs); fall back to
15
+ // the package.json next to this module's source for repo / bundled runs.
16
+ const candidates: Array<() => string> = [
17
+ () =>
18
+ fileURLToPath(import.meta.resolve(`${rnxPublicBrand.packageName}/package.json`)),
19
+ () => fileURLToPath(new URL('../package.json', import.meta.url)),
20
+ ]
21
+ for (const resolve of candidates) {
22
+ try {
23
+ const version = JSON.parse(readFileSync(resolve(), 'utf8')).version
24
+ if (typeof version === 'string' && version) {
25
+ cached = version
26
+ return cached
27
+ }
28
+ } catch {}
29
+ }
30
+ cached = '0.0.0'
31
+ return cached
32
+ }