sootsim 0.1.83 → 0.1.85

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 (209) hide show
  1. package/README.md +0 -1
  2. package/detox/colors.ts +54 -0
  3. package/detox/config-loader.ts +135 -0
  4. package/detox/element-types.ts +36 -0
  5. package/detox/expectations.ts +477 -0
  6. package/detox/gestures.ts +442 -0
  7. package/detox/index.ts +1403 -0
  8. package/detox/jest-environment.ts +86 -0
  9. package/detox/jest-preset.cjs +50 -0
  10. package/detox/matchers.ts +29 -0
  11. package/detox/navigation.ts +43 -0
  12. package/detox/run-test.ts +113 -0
  13. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  14. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  15. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  16. package/detox/screenshots/theme-blue-toggle.png +0 -0
  17. package/detox/screenshots/theme-blue.png +0 -0
  18. package/detox/screenshots/theme-red-toggle.png +0 -0
  19. package/detox/screenshots/theme-red.png +0 -0
  20. package/dist-cli/bin.js +3 -3
  21. package/dist-cli/chunks/{agent-MQ7GLVIB.js → agent-T3DUH5YJ.js} +2 -2
  22. package/dist-cli/chunks/{agent-wrapper-7KAFDQCN.js → agent-wrapper-NSBF4THI.js} +2 -2
  23. package/dist-cli/chunks/{assert-TV46GUNU.js → assert-X3F7TRCZ.js} +2 -2
  24. package/dist-cli/chunks/auto-bootstrap-47RN2V5G.js +2 -0
  25. package/dist-cli/chunks/beta-BRCGAF2N.js +2 -0
  26. package/dist-cli/chunks/chunk-36RPD6JI.js +2 -0
  27. package/dist-cli/chunks/{chunk-PM5NVKLP.js → chunk-3WGHC7JN.js} +2 -2
  28. package/dist-cli/chunks/chunk-4DBPNLGI.js +1 -0
  29. package/dist-cli/chunks/{chunk-J2GYISVJ.js → chunk-4EVSIUNB.js} +2 -2
  30. package/dist-cli/chunks/{chunk-JHJNODXN.js → chunk-4QZHZ6BC.js} +2 -2
  31. package/dist-cli/chunks/{chunk-F3HP444U.js → chunk-5DIGWOY7.js} +1 -1
  32. package/dist-cli/chunks/{chunk-DP7O5MHK.js → chunk-5N3V7OCG.js} +2 -2
  33. package/dist-cli/chunks/{chunk-Y4BUVURT.js → chunk-5S6D7K4L.js} +2 -2
  34. package/dist-cli/chunks/{chunk-ECJBV65H.js → chunk-7LKUN46F.js} +2 -2
  35. package/dist-cli/chunks/{chunk-WTKTOL3C.js → chunk-AC6QGW22.js} +2 -2
  36. package/dist-cli/chunks/{chunk-IBNRRAES.js → chunk-AFNDVS4E.js} +2 -2
  37. package/dist-cli/chunks/{chunk-6TNANCQC.js → chunk-BESAZ2HA.js} +2 -2
  38. package/dist-cli/chunks/{chunk-WN7M3QON.js → chunk-BHZJ6RIH.js} +2 -2
  39. package/dist-cli/chunks/{chunk-277XAALA.js → chunk-BZL6D4TV.js} +3 -3
  40. package/dist-cli/chunks/{chunk-CYV6Y6YV.js → chunk-CF2LPRXD.js} +2 -2
  41. package/dist-cli/chunks/chunk-DWTLRPEN.js +79 -0
  42. package/dist-cli/chunks/{chunk-CJY3AVI7.js → chunk-E2QE5FFP.js} +1 -1
  43. package/dist-cli/chunks/chunk-EBEL6TTJ.js +4 -0
  44. package/dist-cli/chunks/{chunk-DM6WT7QM.js → chunk-EFM53PZ5.js} +1 -1
  45. package/dist-cli/chunks/{chunk-YUELRHGB.js → chunk-EKXK3SWK.js} +2 -2
  46. package/dist-cli/chunks/{chunk-4LS5MZAI.js → chunk-G7CIZ5S3.js} +3 -3
  47. package/dist-cli/chunks/{chunk-6NN2D4EJ.js → chunk-GTAD6IUV.js} +1 -1
  48. package/dist-cli/chunks/{chunk-OYMFNU3M.js → chunk-H44IQHKZ.js} +1 -1
  49. package/dist-cli/chunks/{chunk-IP3QJLRH.js → chunk-HQDJ5BOF.js} +1 -1
  50. package/dist-cli/chunks/{chunk-5DJXZIFZ.js → chunk-KUSQ4NNJ.js} +1 -1
  51. package/dist-cli/chunks/{chunk-HAWOAQAG.js → chunk-MAO7F5PH.js} +3 -3
  52. package/dist-cli/chunks/{chunk-572VSFNP.js → chunk-NVTL3JQG.js} +1 -1
  53. package/dist-cli/chunks/{chunk-6XZOEBTZ.js → chunk-O6N2CEET.js} +2 -2
  54. package/dist-cli/chunks/{chunk-HNWEELAE.js → chunk-OISHLFON.js} +1 -1
  55. package/dist-cli/chunks/{chunk-2PY3UZVO.js → chunk-OUNLJM56.js} +2 -2
  56. package/dist-cli/chunks/chunk-OXOARRKR.js +67 -0
  57. package/dist-cli/chunks/{chunk-NXATOWWF.js → chunk-PHPXGLME.js} +1 -1
  58. package/dist-cli/chunks/{chunk-JQ7ZXOXJ.js → chunk-PQFFUJR6.js} +2 -2
  59. package/dist-cli/chunks/{chunk-KASUZ5XV.js → chunk-QLJNSOS7.js} +1 -1
  60. package/dist-cli/chunks/chunk-QQAECG5B.js +2 -0
  61. package/dist-cli/chunks/{chunk-FJYT7XL2.js → chunk-RZHREO3M.js} +2 -2
  62. package/dist-cli/chunks/{chunk-FRM355UL.js → chunk-SBGOUA6F.js} +2 -2
  63. package/dist-cli/chunks/chunk-SSCA2AEA.js +1 -0
  64. package/dist-cli/chunks/{chunk-Y2VJBRSP.js → chunk-UYRGCJ4N.js} +1 -1
  65. package/dist-cli/chunks/{chunk-2AWQ7OB2.js → chunk-WGDL5V6C.js} +1 -1
  66. package/dist-cli/chunks/{chunk-VMXWC2JO.js → chunk-Y5PLPEEU.js} +2 -2
  67. package/dist-cli/chunks/chunk-ZFAM4N5B.js +1 -0
  68. package/dist-cli/chunks/{chunk-RH4F2TF7.js → chunk-ZO3VHP6W.js} +1 -1
  69. package/dist-cli/chunks/cli-version-WPFDM2A6.js +2 -0
  70. package/dist-cli/chunks/{compat-QLLWBTS3.js → compat-PCXGGZBZ.js} +3 -3
  71. package/dist-cli/chunks/{config-2DSLDCXV.js → config-LULEVEYL.js} +2 -2
  72. package/dist-cli/chunks/control-6P6HY7UF.js +2 -0
  73. package/dist-cli/chunks/{cpu-profile-GEIKHCPC.js → cpu-profile-NOK73ZYW.js} +2 -2
  74. package/dist-cli/chunks/{daemon-4EBUFN4D.js → daemon-4A3DMUYL.js} +2 -2
  75. package/dist-cli/chunks/{debug-WGD6XWOF.js → debug-74BWB2ZG.js} +3 -3
  76. package/dist-cli/chunks/{detox-LNKGRZU6.js → detox-HEOMINSC.js} +2 -2
  77. package/dist-cli/chunks/{device-AYKXKVIQ.js → device-TTXXBJFZ.js} +2 -2
  78. package/dist-cli/chunks/{diagnose-TMXSDOOC.js → diagnose-QZ3GOHSE.js} +2 -2
  79. package/dist-cli/chunks/drivers-QRPWNOIT.js +2 -0
  80. package/dist-cli/chunks/{electron-QFPF7TBY.js → electron-QVOWV44R.js} +3 -3
  81. package/dist-cli/chunks/flow-QMA7GVN6.js +2 -0
  82. package/dist-cli/chunks/{hints-MXKRR4TG.js → hints-YKWRNMJC.js} +2 -2
  83. package/dist-cli/chunks/{home-paths-REMWQDAO.js → home-paths-SFADSTJM.js} +2 -2
  84. package/dist-cli/chunks/{inspect-XGSQNFV7.js → inspect-LEWGQCIU.js} +3 -3
  85. package/dist-cli/chunks/install-7N2N7Q32.js +2 -0
  86. package/dist-cli/chunks/{install-desktop-NQG3RZSA.js → install-desktop-22HYQZ2G.js} +3 -3
  87. package/dist-cli/chunks/{keys-5QZWXL3F.js → keys-3ZT3MICU.js} +2 -2
  88. package/dist-cli/chunks/{launch-SBXOZWKO.js → launch-ZXW2NFLG.js} +3 -3
  89. package/dist-cli/chunks/{login-EACQXE24.js → login-NJKJ7GZO.js} +4 -4
  90. package/dist-cli/chunks/{logout-IBQLMUML.js → logout-VMMQL7CB.js} +2 -2
  91. package/dist-cli/chunks/{maestro-LFYXUX7O.js → maestro-OJY4MTI7.js} +2 -2
  92. package/dist-cli/chunks/{preview-U4SBOEGQ.js → preview-QU2GXTEV.js} +2 -2
  93. package/dist-cli/chunks/{profile-GWS5ECMY.js → profile-7APWK47T.js} +2 -2
  94. package/dist-cli/chunks/{react-QDHLMVYL.js → react-RSVO5JZZ.js} +2 -2
  95. package/dist-cli/chunks/{record-BUEUWPDI.js → record-UWH4MDEO.js} +2 -2
  96. package/dist-cli/chunks/runtime-3FUENRHM.js +2 -0
  97. package/dist-cli/chunks/{runtime-delivery-G7L6RVZ7.js → runtime-delivery-QMKGRV7N.js} +2 -2
  98. package/dist-cli/chunks/{screenshot-T2HBA3VI.js → screenshot-43M27ALE.js} +2 -2
  99. package/dist-cli/chunks/{screenshot-mode-EG5HMIH3.js → screenshot-mode-EBYYN6TY.js} +2 -2
  100. package/dist-cli/chunks/{screenshots-S52AFHTV.js → screenshots-7TQZL6Z6.js} +2 -2
  101. package/dist-cli/chunks/{server-MFFVYUGG.js → server-VCFM25Z6.js} +2 -2
  102. package/dist-cli/chunks/setup-repo-HFH4VKJQ.js +2 -0
  103. package/dist-cli/chunks/{skills-HQGWBS2O.js → skills-RQA6EJQL.js} +2 -2
  104. package/dist-cli/chunks/{start-E3DRYY7W.js → start-ZT6MBYND.js} +4 -4
  105. package/dist-cli/chunks/store-BJBTDSZE.js +2 -0
  106. package/dist-cli/chunks/telemetry-ZZZKTILZ.js +2 -0
  107. package/dist-cli/chunks/{test-ZY3EF62K.js → test-RNRX5SWV.js} +3 -3
  108. package/dist-cli/chunks/{three-mode-WSPKQCJ5.js → three-mode-TQZH25ZO.js} +2 -2
  109. package/dist-cli/chunks/{timeline-3XAB5EWZ.js → timeline-GGN3AY6P.js} +2 -2
  110. package/dist-cli/chunks/{upgrade-WNENPFM5.js → upgrade-XT22D67C.js} +2 -2
  111. package/dist-cli/chunks/upload-NC2AYLC5.js +2 -0
  112. package/dist-cli/chunks/{web-D2AOZY44.js → web-KEHVF5MB.js} +2 -2
  113. package/dist-cli/chunks/{what-happened-F43KNSG6.js → what-happened-PATQRJ5T.js} +2 -2
  114. package/dist-cli/chunks/{whoami-T22VBR7C.js → whoami-CXVY26VV.js} +2 -2
  115. package/dist-lib/agent-daemon-client.cjs +1 -1
  116. package/dist-lib/agent-events.cjs +1 -1
  117. package/dist-lib/agent-sessions.cjs +1 -1
  118. package/dist-lib/attached-projects.cjs +1 -1
  119. package/dist-lib/auth/shared-session.cjs +1 -1
  120. package/dist-lib/backend-origin.cjs +1 -1
  121. package/dist-lib/beta.cjs +44 -0
  122. package/dist-lib/bridge-constants.cjs +1 -1
  123. package/dist-lib/cli-constants.cjs +1 -1
  124. package/dist-lib/config.cjs +1 -1
  125. package/dist-lib/detox/index.cjs +1770 -0
  126. package/dist-lib/detox/jest-preset.cjs +50 -0
  127. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  128. package/dist-lib/home-paths.cjs +1 -1
  129. package/dist-lib/host/bridge-host.cjs +1 -1
  130. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  131. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  132. package/dist-lib/index.cjs +136 -138
  133. package/dist-lib/metro.cjs +31 -26
  134. package/dist-lib/profiles.cjs +1 -1
  135. package/dist-lib/render-mode.cjs +1 -1
  136. package/dist-lib/scripts/demo-app-registry.cjs +809 -0
  137. package/dist-lib/scripts/dev-server-scanner.cjs +1269 -0
  138. package/dist-lib/skills.cjs +17766 -0
  139. package/dist-lib/vite.cjs +129 -39
  140. package/package.json +39 -14
  141. package/scripts/demo-app-registry.ts +989 -0
  142. package/scripts/dev-server-scanner.ts +674 -0
  143. package/src/agent-daemon-client.ts +390 -0
  144. package/src/agent-events.ts +71 -0
  145. package/src/agent-prompt.ts +71 -0
  146. package/src/agent-sessions.ts +572 -0
  147. package/src/attached-projects.ts +536 -0
  148. package/src/auth/shared-session.ts +199 -0
  149. package/src/backend-origin.ts +49 -0
  150. package/src/beta.ts +21 -0
  151. package/src/bridge-constants.ts +10 -0
  152. package/src/cli-constants.ts +1 -0
  153. package/src/cli-version.ts +30 -0
  154. package/src/codex-client.ts +215 -0
  155. package/src/config.ts +110 -0
  156. package/src/dev-bundle-resolution.ts +180 -0
  157. package/src/home-paths.ts +382 -0
  158. package/src/host/agent-host.ts +576 -0
  159. package/src/host/bridge-host.ts +2293 -0
  160. package/src/host/fetch-proxy-handler.ts +288 -0
  161. package/src/host/fetch-proxy-overrides.ts +39 -0
  162. package/src/host/open-url.ts +234 -0
  163. package/src/index.ts +9 -0
  164. package/src/metro-plugin.ts +139 -0
  165. package/src/native-dev-bundle-url.ts +62 -0
  166. package/src/native-seam-manifest.ts +313 -0
  167. package/src/profiles.ts +179 -0
  168. package/src/render-mode.ts +27 -0
  169. package/src/runtime-assets.ts +84 -0
  170. package/src/runtime-delivery.ts +334 -0
  171. package/src/screenshots/compose.ts +422 -0
  172. package/src/screenshots/frame-compose.ts +438 -0
  173. package/src/screenshots/orchestrate.ts +244 -0
  174. package/src/screenshots/registry.ts +58 -0
  175. package/src/screenshots/schema.ts +364 -0
  176. package/src/skills/builtin/a11y-review.ts +126 -0
  177. package/src/skills/builtin/compat-check.ts +104 -0
  178. package/src/skills/builtin/perf-profile.ts +84 -0
  179. package/src/skills/builtin/screenshot-all.ts +46 -0
  180. package/src/skills/builtin/test-flow.ts +118 -0
  181. package/src/skills/builtin/visual-diff.ts +94 -0
  182. package/src/skills/registry.ts +107 -0
  183. package/src/skills/types.ts +41 -0
  184. package/src/vite-plugin-one.ts +189 -0
  185. package/src/vite-plugin.ts +1381 -0
  186. package/src/worklets-babel.ts +132 -0
  187. package/dist-cli/chunks/auto-bootstrap-FQS4ZD2K.js +0 -2
  188. package/dist-cli/chunks/beta-VG7CDY2U.js +0 -2
  189. package/dist-cli/chunks/chunk-2OIBDYHW.js +0 -1
  190. package/dist-cli/chunks/chunk-6BNLVMXA.js +0 -1
  191. package/dist-cli/chunks/chunk-6XD6CBJM.js +0 -2
  192. package/dist-cli/chunks/chunk-CHQTO426.js +0 -1
  193. package/dist-cli/chunks/chunk-FAPYGVIU.js +0 -4
  194. package/dist-cli/chunks/chunk-PEHFE3LG.js +0 -64
  195. package/dist-cli/chunks/chunk-RXH2SLKF.js +0 -2
  196. package/dist-cli/chunks/chunk-UXQWC5ZR.js +0 -79
  197. package/dist-cli/chunks/chunk-XFQL74PF.js +0 -5
  198. package/dist-cli/chunks/cli-version-PWF6I6LY.js +0 -2
  199. package/dist-cli/chunks/control-UIOXGYXU.js +0 -2
  200. package/dist-cli/chunks/demo-app-registry-G3BDOFWC.js +0 -2
  201. package/dist-cli/chunks/drivers-IDQF34HP.js +0 -2
  202. package/dist-cli/chunks/flow-3JN3Y7RF.js +0 -2
  203. package/dist-cli/chunks/install-2N3YOOSN.js +0 -2
  204. package/dist-cli/chunks/runtime-PVB4VGUH.js +0 -2
  205. package/dist-cli/chunks/setup-repo-YOF7NV5D.js +0 -2
  206. package/dist-cli/chunks/store-MAI6D3UO.js +0 -2
  207. package/dist-cli/chunks/telemetry-RCQKCJTH.js +0 -2
  208. package/dist-cli/chunks/upload-YLJ4RA73.js +0 -2
  209. package/dist-lib/vite-base.cjs +0 -6937
@@ -0,0 +1,139 @@
1
+ // sootsim metro plugin — serves the sootsim shell at /__soot/ on metro's server.
2
+ // shell assets come from the active engine runtime under ~/.sootsim/runtimes/,
3
+ // which is fetched by `sootsim runtime install` / `sootsim setup-repo`. resolved
4
+ // per-request so swapping runtimes (`sootsim runtime use <v>`) takes effect
5
+ // without restarting metro.
6
+ //
7
+ // usage in metro.config.js:
8
+ // const { withSootsim } = require('sootsim/metro')
9
+ // module.exports = withSootsim(getDefaultConfig(__dirname))
10
+
11
+ import fs from 'fs'
12
+ import path from 'path'
13
+ import {
14
+ isRootRuntimeAssetPath,
15
+ resolveActiveRuntimeRoot,
16
+ resolveRuntimeFilePath,
17
+ serveRuntimeFile,
18
+ SOOTSIM_RUNTIME_MISSING_MESSAGE,
19
+ } from './runtime-assets'
20
+
21
+ const prefix = '/__soot'
22
+
23
+ export interface SootsimMetroOptions {
24
+ bundleUrl?: string
25
+ enabled?: boolean
26
+ }
27
+
28
+ // the slice of metro's config we touch — keeps `T` open for whatever
29
+ // shape consumers pass while letting us extend `server.enhanceMiddleware`.
30
+ type MetroConfigWithServer = {
31
+ server?: {
32
+ enhanceMiddleware?: (middleware: unknown, server: unknown) => unknown
33
+ [key: string]: unknown
34
+ }
35
+ }
36
+
37
+ export function withSootsim<T extends Record<string, unknown>>(
38
+ config: T,
39
+ options: SootsimMetroOptions = {},
40
+ ): T {
41
+ if (options.enabled === false) return config
42
+
43
+ const bundleUrl =
44
+ options.bundleUrl || '/index.bundle?platform=ios&dev=true&hot=true&minify=false'
45
+
46
+ const cfgServer = (config as MetroConfigWithServer).server
47
+ const existingEnhance = cfgServer?.enhanceMiddleware
48
+
49
+ const serverConfig = {
50
+ ...cfgServer,
51
+ enhanceMiddleware: (metroMiddleware: any, metroServer: any) => {
52
+ let middleware = metroMiddleware
53
+ if (existingEnhance) {
54
+ middleware = existingEnhance(metroMiddleware, metroServer)
55
+ }
56
+
57
+ let connect: any
58
+ try {
59
+ connect = require('connect')
60
+ } catch {
61
+ console.warn('[sootsim] connect not available')
62
+ return middleware
63
+ }
64
+
65
+ const server = connect()
66
+
67
+ server.use((req: any, res: any, next: any) => {
68
+ const url = req.url || ''
69
+ const pathname = url.split('?')[0]
70
+ const runtimeRoot = resolveActiveRuntimeRoot()
71
+
72
+ if (!runtimeRoot) {
73
+ if (pathname === prefix || pathname === prefix + '/') {
74
+ res.statusCode = 500
75
+ res.end(SOOTSIM_RUNTIME_MISSING_MESSAGE)
76
+ return
77
+ }
78
+ next()
79
+ return
80
+ }
81
+
82
+ // root-relative runtime assets emitted by the shell and engine vite
83
+ // builds. if a user app has its own /assets/foo request, we only
84
+ // intercept when that exact file exists in the runtime; otherwise
85
+ // metro handles it normally.
86
+ if (isRootRuntimeAssetPath(pathname)) {
87
+ const fullPath = resolveRuntimeFilePath(runtimeRoot, pathname)
88
+ if (fullPath) {
89
+ serveRuntimeFile(res, fullPath)
90
+ return
91
+ }
92
+ }
93
+
94
+ // /__soot/* — serve runtime assets
95
+ if (pathname.startsWith(prefix + '/')) {
96
+ const fullPath = resolveRuntimeFilePath(
97
+ runtimeRoot,
98
+ pathname.slice(prefix.length),
99
+ )
100
+ if (fullPath) {
101
+ serveRuntimeFile(res, fullPath)
102
+ return
103
+ }
104
+ }
105
+
106
+ // serve HTML shell for /__soot and extensionless in-shell routes.
107
+ if (
108
+ pathname === prefix ||
109
+ pathname === prefix + '/' ||
110
+ pathname.startsWith(prefix + '/')
111
+ ) {
112
+ const ext = path.extname(pathname)
113
+ if (!ext) {
114
+ const htmlPath = path.join(runtimeRoot, 'index.html')
115
+ let html = fs.readFileSync(htmlPath, 'utf8')
116
+ html = html.replace(
117
+ '</head>',
118
+ `<script>history.replaceState(null,'','${prefix}/?bundle=${encodeURIComponent(bundleUrl)}')</script></head>`,
119
+ )
120
+ res.setHeader('content-type', 'text/html')
121
+ res.end(html)
122
+ return
123
+ }
124
+ }
125
+
126
+ next()
127
+ })
128
+
129
+ server.use(middleware)
130
+
131
+ console.log(`[sootsim] serving at ${prefix}/`)
132
+ return server
133
+ },
134
+ }
135
+
136
+ return { ...config, server: serverConfig } as T
137
+ }
138
+
139
+ export default withSootsim
@@ -0,0 +1,62 @@
1
+ // canonical entry path for a plain Metro packager that serves no Expo
2
+ // manifest. only used when `/status` confirms `packager-status:running` and
3
+ // the server reports nothing else — never as a guess for a manifest-capable
4
+ // dev server, whose `launchAsset.url` is always the source of truth.
5
+ export const METRO_BUNDLE_PATH =
6
+ '/index.bundle?platform=ios&dev=true&hot=true&minify=false'
7
+
8
+ function isAbsoluteHttpUrl(url: string): boolean {
9
+ return /^https?:\/\//i.test(url)
10
+ }
11
+
12
+ function isNativeDevBundlePath(pathname: string): boolean {
13
+ return pathname.endsWith('.bundle')
14
+ }
15
+
16
+ // opt-in (sootbot preview-recording context only): serve a production
17
+ // minified bundle instead of the default dev=true unminified one. a
18
+ // preview run just records the app — it never needs fast-refresh or dev
19
+ // warnings — and the ~28MB unminified dev bundle cold-(re)boots
20
+ // unreliably on small CI runners (tens of seconds when metro is warm,
21
+ // minutes cold, sometimes past any wait-ready budget). a minified bundle
22
+ // parses/executes far faster and reboots reliably. gated on
23
+ // SOOTSIM_PREVIEW_PROD_BUNDLE so normal interactive `sootsim open` keeps
24
+ // dev=true. app-agnostic: rewrites whatever metro/launchAsset advertised.
25
+ function applyPreviewProdBundle(parsed: URL): void {
26
+ if (!process.env.SOOTSIM_PREVIEW_PROD_BUNDLE) return
27
+ parsed.searchParams.set('dev', 'false')
28
+ parsed.searchParams.set('minify', 'true')
29
+ // prod bundles have no HMR; keep metro's cache key consistent.
30
+ if (parsed.searchParams.has('hot')) parsed.searchParams.set('hot', 'false')
31
+ }
32
+
33
+ export function normalizeNativeDevBundleUrl(bundleUrl: string): string {
34
+ try {
35
+ const isAbsolute = isAbsoluteHttpUrl(bundleUrl)
36
+ const parsed = new URL(bundleUrl, 'http://soot.local')
37
+ parsed.pathname = parsed.pathname.replace(/\.\.bundle$/, '.bundle')
38
+ if (!isNativeDevBundlePath(parsed.pathname)) return bundleUrl
39
+ // do NOT force dev=true or minify=false — metro's dev server already
40
+ // defaults to dev mode + unminified, and iOS sim's launchAsset URL
41
+ // omits both params. forcing them gives metro a different cache key
42
+ // for the same bundle. only the prod-bundle override path
43
+ // (demo-upload-specs) sets dev=false&minify=true on purpose.
44
+ // do NOT touch `hot` either. real RN iOS clients load launchAsset.url
45
+ // verbatim — expo's createBundleUrlSearchParams hardcodes hot=false
46
+ // (with a `// TODO: Is this still needed?` comment) for every manifest
47
+ // it emits, and HMR still works on iOS because RN's HMRClient.js
48
+ // polyfill connects to /hot independently. sootsim's HmrClient is the
49
+ // same — connection happens via /hot regardless of this query param —
50
+ // so matching the iOS sim's URL exactly lets metro share its bundle
51
+ // cache between the two clients (otherwise the 28MB+ bundle rebuilds
52
+ // twice on first cold metro request).
53
+ // strip transform.bytecode — metro would return hermes bytecode and our
54
+ // bundle-loader can only execute plain JS.
55
+ parsed.searchParams.delete('transform.bytecode')
56
+ applyPreviewProdBundle(parsed)
57
+ if (isAbsolute) return parsed.toString()
58
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`
59
+ } catch {
60
+ return bundleUrl
61
+ }
62
+ }
@@ -0,0 +1,313 @@
1
+ // native-seam manifest — the canonical registry of upstream packages whose
2
+ // native-module spec files we override.
3
+ //
4
+ // the rule: when a guest bundle imports from one of these packages, vite lets
5
+ // the package's JS resolve from node_modules normally. only the listed
6
+ // `seamBasenames` are redirected to our stub. this keeps the upstream JS layer
7
+ // (hooks, helpers, transforms, listeners) running unchanged.
8
+ //
9
+ // adding a new entry:
10
+ // 1. identify upstream's native-module file (look for files calling
11
+ // `requireNativeModule`, `TurboModuleRegistry.getEnforced`,
12
+ // `requireNativeComponent`, or in `src/specs/Native*Module.ts`)
13
+ // 2. write a thin stub at `packages/compat/src/stubs/native-seams/<pkg>.ts`
14
+ // that exports the same default + named exports upstream's seam file
15
+ // exports — except the implementation calls into sootsim's engine.
16
+ // 3. append an entry below. the plugin handles the rest.
17
+ //
18
+ // canonical example: react-native-reanimated. see
19
+ // - `packages/compat/src/stubs/react-native-reanimated.ts` (the seam stub)
20
+ // - `reanimatedNativeSeamRedirect` in vite-plugin.ts (still hard-coded
21
+ // because it has package-specific edge cases — fabricUtils,
22
+ // validate-worklets-version. new entries should use this manifest path
23
+ // for the simple basename-match case.)
24
+
25
+ export interface NativeSeamEntry {
26
+ /**
27
+ * upstream npm package name. all imports whose importer path includes
28
+ * `/<pkg>/` are candidates for redirect.
29
+ */
30
+ pkg: string
31
+ /**
32
+ * basenames of upstream files to redirect. each entry matches the file
33
+ * name (no extension, no platform suffix like `.ios` / `.android`) of any
34
+ * upstream module imported transitively from within `pkg`.
35
+ *
36
+ * example: if upstream has
37
+ * `node_modules/expo-notifications/src/NotificationsHandler.ts`, list
38
+ * `'NotificationsHandler'` here and we'll redirect any import resolving
39
+ * to that file to our seam stub.
40
+ */
41
+ seamBasenames: string[]
42
+ /**
43
+ * absolute path to the seam stub. typically
44
+ * `packages/compat/src/stubs/native-seams/<pkg>.ts` — but a single stub
45
+ * file may export every native module the package exposes, so the same
46
+ * `target` can appear in multiple entries if a package has multiple native
47
+ * modules that share an implementation file.
48
+ *
49
+ * authors set this via the `seamPath(<pkg>)` helper below.
50
+ */
51
+ target: string
52
+ /**
53
+ * notes — short reason this entry exists. shown by the debug logger when
54
+ * `SOOTSIM_NATIVE_SEAM_DEBUG=1` is set.
55
+ */
56
+ notes?: string
57
+ }
58
+
59
+ import path from 'node:path'
60
+ import { fileURLToPath } from 'node:url'
61
+
62
+ const HERE = path.dirname(fileURLToPath(import.meta.url))
63
+ const NATIVE_SEAMS_DIR = path.resolve(HERE, '../../compat/src/stubs/native-seams')
64
+
65
+ /** resolve a stub path under `packages/compat/src/stubs/native-seams/`. */
66
+ export function seamPath(basename: string): string {
67
+ return path.resolve(NATIVE_SEAMS_DIR, `${basename}.ts`)
68
+ }
69
+
70
+ /**
71
+ * the manifest. agents append entries here during the native-seam migration
72
+ * (plans/compat-stubs-native-seam-audit.md). keep alphabetical by `pkg` to
73
+ * minimise merge conflicts.
74
+ */
75
+ export const NATIVE_SEAM_MANIFEST: NativeSeamEntry[] = [
76
+ // entries land here during the Phase 1 migration. example shape:
77
+ //
78
+ // {
79
+ // pkg: 'expo-notifications',
80
+ // seamBasenames: [
81
+ // 'NotificationsHandler',
82
+ // 'NotificationsEmitter',
83
+ // 'NotificationScheduler',
84
+ // 'NotificationCategoriesService',
85
+ // 'ServerRegistrationModule',
86
+ // ],
87
+ // target: seamPath('expo-notifications'),
88
+ // notes: 'route through engine NotificationBus',
89
+ // },
90
+ {
91
+ pkg: '@metamask/react-native-search-api',
92
+ // upstream is single-file `index.js`; no sub-imports inside the package
93
+ // to redirect at the basename layer. listed here for documentation parity
94
+ // — the bundle-loader's stub-registry routes the package-entry directly to
95
+ // `stubs/metamask-react-native-search-api.ts`, which pairs the upstream
96
+ // JS wrapper with the native seam below.
97
+ seamBasenames: ['index'],
98
+ target: seamPath('metamask-react-native-search-api'),
99
+ notes:
100
+ 'iOS CoreSpotlight + NSUserActivity native seam; pure-JS wrapper class lives at stubs/metamask-react-native-search-api.ts',
101
+ },
102
+ {
103
+ pkg: '@ua/react-native-airship',
104
+ seamBasenames: [
105
+ // src/NativeRNAirship.ts — the RNAirship TurboModule
106
+ 'NativeRNAirship',
107
+ // codegen host components (no native backing in sootsim)
108
+ 'RNAirshipEmbeddedViewNativeComponent',
109
+ 'RNAirshipMessageViewNativeComponent',
110
+ ],
111
+ target: seamPath('react-native-airship'),
112
+ notes:
113
+ 'push-notification SAAS — noop the SAAS layer, route banners through NotificationBus',
114
+ },
115
+ {
116
+ pkg: 'react-native-background-fetch',
117
+ // src/NativeBackgroundFetch.ts — TurboModuleRegistry.getEnforcing('RNBackgroundFetch')
118
+ seamBasenames: ['NativeBackgroundFetch'],
119
+ target: seamPath('react-native-background-fetch'),
120
+ notes:
121
+ 'iOS BGTaskScheduler / Android WorkManager TurboModule — no real scheduler in browser; acknowledge registrations, never fire periodic events',
122
+ },
123
+ {
124
+ pkg: 'expo-asset',
125
+ // src/ExpoAsset.ts — `requireNativeModule('ExpoAsset')`; only
126
+ // `downloadAsync` is on the native surface. all the rest (`Asset`,
127
+ // `AssetHooks`, `AssetSourceResolver`, ...) is pure JS that composes on
128
+ // top.
129
+ seamBasenames: ['ExpoAsset'],
130
+ target: seamPath('expo-asset'),
131
+ notes:
132
+ 'expo-modules-core requireNativeModule(ExpoAsset) — only downloadAsync; mirror upstream web build (return remote URL)',
133
+ },
134
+ {
135
+ pkg: '@bam.tech/react-native-image-resizer',
136
+ // upstream is not shipped to most guest bundles via node_modules — it
137
+ // arrives through the bundle-loader stub-registry which routes the
138
+ // package entry to `stubs/bam-react-native-image-resizer.ts`. listed here
139
+ // for documentation parity; vite-path redirect would key on
140
+ // `NativeImageResizer`.
141
+ seamBasenames: ['NativeImageResizer'],
142
+ target: seamPath('bam-react-native-image-resizer'),
143
+ notes:
144
+ 'ImageResizer TurboModule — browser-side canvas resize implementing the iOS contain/cover/stretch + onlyScaleDown semantics from ios/ImageResizer.mm',
145
+ },
146
+ {
147
+ pkg: 'react-native-adjust',
148
+ // upstream's `index.js` reads `NativeModules.Adjust` and
149
+ // `NativeModules.AdjustEventEmitter` directly — there is no NativeAdjust
150
+ // spec file. listed for documentation parity; the actual seam delivery is
151
+ // through the bundle-loader stub-registry routing the package entry to
152
+ // `stubs/react-native-adjust.ts`, which imports the native seam from this
153
+ // target.
154
+ seamBasenames: ['index'],
155
+ target: seamPath('react-native-adjust'),
156
+ notes:
157
+ 'Adjust attribution SAAS — noop native side (Adjust + AdjustEventEmitter NativeModules), pure-JS classes (AdjustConfig/AdjustEvent/...) live in stubs/react-native-adjust.ts',
158
+ },
159
+ {
160
+ pkg: 'expo-clipboard',
161
+ // src/ExpoClipboard.ts — `requireNativeModule<ExpoClipboardModule>('ExpoClipboard')`.
162
+ // upstream also ships `ExpoClipboard.web.ts` (which we mirror); the
163
+ // redirect catches both `.ts` and `.web.ts` since basenames match.
164
+ seamBasenames: ['ExpoClipboard'],
165
+ target: seamPath('expo-clipboard'),
166
+ notes:
167
+ 'expo-modules-core requireNativeModule(ExpoClipboard) — real navigator.clipboard text + ClipboardItem image; mirrors upstream ExpoClipboard.web.ts',
168
+ },
169
+ {
170
+ pkg: 'expo-font',
171
+ // src/ExpoFontLoader.ts — `requireNativeModule<ExpoFontLoaderModule>('ExpoFontLoader')`.
172
+ // we mirror upstream's web variant `ExpoFontLoader.web.ts` (FontFace API
173
+ // + managed <style id="expo-generated-fonts">).
174
+ seamBasenames: ['ExpoFontLoader'],
175
+ target: seamPath('expo-font'),
176
+ notes:
177
+ 'expo-modules-core requireNativeModule(ExpoFontLoader) — FontFace API registration; mirrors upstream ExpoFontLoader.web.ts',
178
+ },
179
+ {
180
+ pkg: '@react-native-async-storage/async-storage',
181
+ // src/NativeAsyncStorageModule.ts — TurboModule spec for `RNCAsyncStorage`.
182
+ // src/RCTAsyncStorage.ts — legacy `NativeModules['RNCAsyncStorage']` resolver.
183
+ // both basenames redirect to the same seam; upstream's AsyncStorage.ts
184
+ // imports default from either depending on bridgeless vs paper.
185
+ seamBasenames: ['NativeAsyncStorageModule', 'RCTAsyncStorage'],
186
+ target: seamPath('async-storage'),
187
+ notes:
188
+ 'RNCAsyncStorage TurboModule — localStorage-backed; preserves multiMerge JSON deep-merge semantics from upstream iOS module',
189
+ },
190
+ {
191
+ pkg: 'expo-contacts',
192
+ // src/ExpoContacts.ts — `requireNativeModule('ExpoContacts')`. seam
193
+ // exposes the native iOS module shape through sootsim's in-memory store.
194
+ seamBasenames: ['ExpoContacts'],
195
+ target: seamPath('expo-contacts'),
196
+ notes:
197
+ 'expo-modules-core requireNativeModule(ExpoContacts) — granted native iOS-style contacts module backed by an in-memory store',
198
+ },
199
+ {
200
+ pkg: 'expo-calendar',
201
+ // src/ExpoCalendar.ts — `requireNativeModule('ExpoCalendar')`. seam
202
+ // exposes the native iOS module shape through sootsim's in-memory store.
203
+ seamBasenames: ['ExpoCalendar'],
204
+ target: seamPath('expo-calendar'),
205
+ notes:
206
+ 'expo-modules-core requireNativeModule(ExpoCalendar) — granted native iOS-style calendar/reminders module backed by an in-memory store',
207
+ },
208
+ {
209
+ pkg: 'expo-media-library',
210
+ // src/ExpoMediaLibrary.ts — `requireNativeModule('ExpoMediaLibrary')`.
211
+ // seam mirrors upstream's `ExpoMediaLibrary.web.ts` (refuse permissions
212
+ // + expose MediaType/SortBy constants).
213
+ seamBasenames: ['ExpoMediaLibrary'],
214
+ target: seamPath('expo-media-library'),
215
+ notes:
216
+ 'expo-modules-core requireNativeModule(ExpoMediaLibrary) — refuses permissions + exposes MediaType/SortBy enums per upstream ExpoMediaLibrary.web.ts',
217
+ },
218
+ {
219
+ pkg: 'expo-file-system',
220
+ // src/ExpoFileSystem.ts — `requireNativeModule('ExpoFileSystem')`. seam
221
+ // mirrors upstream's `ExpoFileSystem.web.ts` (warn-not-supported stubs).
222
+ seamBasenames: ['ExpoFileSystem'],
223
+ target: seamPath('expo-file-system'),
224
+ notes:
225
+ 'expo-modules-core requireNativeModule(ExpoFileSystem) — warn-not-supported stubs per upstream ExpoFileSystem.web.ts; no general browser fs access',
226
+ },
227
+ {
228
+ pkg: 'expo-image-picker',
229
+ // src/ExponentImagePicker.ts — `requireNativeModule('ExponentImagePicker')`.
230
+ // seam routes selection requests through sootsim's engine-owned image
231
+ // picker (`SootSim.bridges.imagePicker.pickFromLibrary`) so the same
232
+ // canvas-rendered iOS picker handles every image-source library.
233
+ seamBasenames: ['ExponentImagePicker'],
234
+ target: seamPath('expo-image-picker'),
235
+ notes:
236
+ "expo-modules-core requireNativeModule(ExponentImagePicker) — routes through engine SootSim.bridges.imagePicker (shell-worker ImagePickerView) instead of upstream web build's DOM <input type=file>",
237
+ },
238
+ {
239
+ pkg: 'expo-camera',
240
+ // upstream splits the native surface across two files:
241
+ // - src/ExpoCamera.ts → requireNativeViewManager('ExpoCamera')
242
+ // - src/ExpoCameraManager.ts → requireNativeModule('ExpoCamera')
243
+ // each basename redirects to a different seam — the view component vs.
244
+ // the imperative module. live preview + capture route through the same
245
+ // engine `camera-stream` module the built-in sootsim Camera app uses,
246
+ // so guest <CameraView> sees the same feed as the OS camera app.
247
+ seamBasenames: ['ExpoCamera'],
248
+ target: seamPath('expo-camera'),
249
+ notes:
250
+ 'expo-camera native view manager — host component backed by engine camera-stream (same getUserMedia pipeline as the built-in CameraApp); exposes CameraViewRef methods (takePicture/...) via useImperativeHandle',
251
+ },
252
+ {
253
+ pkg: 'expo-camera',
254
+ seamBasenames: ['ExpoCameraManager'],
255
+ target: seamPath('expo-camera-manager'),
256
+ notes:
257
+ 'expo-camera native module — permissions/feature-gates/codecs/Type/FlashMode constants; barcode scanner is noop (no engine scanner integration)',
258
+ },
259
+ {
260
+ pkg: 'expo-image',
261
+ // upstream splits the native surface across two files:
262
+ // - src/ExpoImage.tsx → requireNativeViewManager('ExpoImage')
263
+ // - src/ImageModule.ts → requireNativeModule('ExpoImage')
264
+ // the view-component seam translates expo-image's prop shape
265
+ // (contentFit, tintColor, source) onto sootsim's existing
266
+ // <sootsim-image> host. no new canvas component is needed — sootsim's
267
+ // image-loader + canvaskit-renderer already handle resizeMode,
268
+ // tintColor, and onLoad/onError lifecycle.
269
+ seamBasenames: ['ExpoImage'],
270
+ target: seamPath('expo-image'),
271
+ notes:
272
+ 'expo-image native view — thin prop translator onto sootsim-image host (contentFit→resizeMode, tintColor passthrough); upstream Image.tsx + ImageBackground + useImage resolve from node_modules unchanged',
273
+ },
274
+ {
275
+ pkg: 'expo-image',
276
+ seamBasenames: ['ImageModule'],
277
+ target: seamPath('expo-image-module'),
278
+ notes:
279
+ 'expo-image native module — prefetch/loadAsync route through sootsim image-loader; cache controls noop (browser owns HTTP cache); blurhash encode not yet implemented',
280
+ },
281
+ {
282
+ pkg: '@notifee/react-native',
283
+ // src/NotifeeNativeModule.ts — the base class NotifeeApiModule extends.
284
+ // upstream also ships .web.ts (returns `{}` from `native`); both
285
+ // basenames redirect to the same seam — a real implementation that
286
+ // routes `this.native.*` calls through the engine NotificationBus.
287
+ seamBasenames: ['NotifeeNativeModule'],
288
+ target: seamPath('notifee'),
289
+ notes:
290
+ 'Notifee NotifeeApiModule base class — routes display/cancel/channel/badge calls through engine NotificationBus; tap events fan out via the shared NotifeeJSEventEmitter so onForegroundEvent listeners fire',
291
+ },
292
+ {
293
+ pkg: 'expo-notifications',
294
+ // five native module spec files, each `requireNativeModule(...)`-shaped.
295
+ // every one redirects to the same `expo-notifications` seam — see
296
+ // `stubs/native-seams/expo-notifications.ts` for the union surface.
297
+ seamBasenames: [
298
+ // src/NotificationsHandlerModule.ts — presentation policy
299
+ 'NotificationsHandlerModule',
300
+ // src/NotificationsEmitterModule.ts — last-tap response + listener
301
+ 'NotificationsEmitterModule',
302
+ // src/NotificationScheduler.ts — schedule / cancel
303
+ 'NotificationScheduler',
304
+ // src/BadgeModule.ts — app badge count
305
+ 'BadgeModule',
306
+ // src/ServerRegistrationModule.ts — expo push registration
307
+ 'ServerRegistrationModule',
308
+ ],
309
+ target: seamPath('expo-notifications'),
310
+ notes:
311
+ 'expo-notifications native modules — routes scheduling/badge/installation-id through engine NotificationBus; tap callbacks fan out via the bus event emitters',
312
+ },
313
+ ]