rnwind 0.0.1 → 0.0.2

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 (330) hide show
  1. package/lib/cjs/core/parser/animation.cjs +427 -0
  2. package/lib/cjs/core/parser/animation.cjs.map +1 -0
  3. package/lib/cjs/core/parser/animation.d.ts +126 -0
  4. package/lib/cjs/core/parser/border-dispatcher.cjs +180 -0
  5. package/lib/cjs/core/parser/border-dispatcher.cjs.map +1 -0
  6. package/lib/cjs/core/parser/border-dispatcher.d.ts +15 -0
  7. package/lib/cjs/core/parser/case-convert.cjs +15 -0
  8. package/lib/cjs/core/parser/case-convert.cjs.map +1 -0
  9. package/lib/cjs/core/parser/case-convert.d.ts +6 -0
  10. package/lib/cjs/core/parser/color-properties-dispatcher.cjs +84 -0
  11. package/lib/cjs/core/parser/color-properties-dispatcher.cjs.map +1 -0
  12. package/lib/cjs/core/parser/color-properties-dispatcher.d.ts +19 -0
  13. package/lib/cjs/core/parser/color.cjs +164 -0
  14. package/lib/cjs/core/parser/color.cjs.map +1 -0
  15. package/lib/cjs/core/parser/color.d.ts +12 -0
  16. package/lib/cjs/core/parser/constants.cjs +21 -0
  17. package/lib/cjs/core/parser/constants.cjs.map +1 -0
  18. package/lib/cjs/core/parser/constants.d.ts +8 -0
  19. package/lib/cjs/core/parser/declaration.cjs +347 -0
  20. package/lib/cjs/core/parser/declaration.cjs.map +1 -0
  21. package/lib/cjs/core/parser/declaration.d.ts +15 -0
  22. package/lib/cjs/core/parser/gradient.cjs +132 -0
  23. package/lib/cjs/core/parser/gradient.cjs.map +1 -0
  24. package/lib/cjs/core/parser/gradient.d.ts +59 -0
  25. package/lib/cjs/core/parser/haptics.cjs +73 -0
  26. package/lib/cjs/core/parser/haptics.cjs.map +1 -0
  27. package/lib/cjs/core/parser/haptics.d.ts +47 -0
  28. package/lib/cjs/core/parser/index.d.ts +8 -0
  29. package/lib/cjs/core/parser/keyframes.cjs +95 -0
  30. package/lib/cjs/core/parser/keyframes.cjs.map +1 -0
  31. package/lib/cjs/core/parser/keyframes.d.ts +26 -0
  32. package/lib/cjs/core/parser/layout-dispatcher.cjs +100 -0
  33. package/lib/cjs/core/parser/layout-dispatcher.cjs.map +1 -0
  34. package/lib/cjs/core/parser/layout-dispatcher.d.ts +14 -0
  35. package/lib/cjs/core/parser/length.cjs +96 -0
  36. package/lib/cjs/core/parser/length.cjs.map +1 -0
  37. package/lib/cjs/core/parser/length.d.ts +48 -0
  38. package/lib/cjs/core/parser/motion-dispatcher.cjs +77 -0
  39. package/lib/cjs/core/parser/motion-dispatcher.cjs.map +1 -0
  40. package/lib/cjs/core/parser/motion-dispatcher.d.ts +11 -0
  41. package/lib/cjs/core/parser/property.cjs +22 -0
  42. package/lib/cjs/core/parser/property.cjs.map +1 -0
  43. package/lib/cjs/core/parser/property.d.ts +8 -0
  44. package/lib/cjs/core/parser/safe-area.cjs +404 -0
  45. package/lib/cjs/core/parser/safe-area.cjs.map +1 -0
  46. package/lib/cjs/core/parser/safe-area.d.ts +39 -0
  47. package/lib/cjs/core/parser/selector.cjs +22 -0
  48. package/lib/cjs/core/parser/selector.cjs.map +1 -0
  49. package/lib/cjs/core/parser/selector.d.ts +11 -0
  50. package/lib/cjs/core/parser/shorthand.cjs +156 -0
  51. package/lib/cjs/core/parser/shorthand.cjs.map +1 -0
  52. package/lib/cjs/core/parser/shorthand.d.ts +61 -0
  53. package/lib/cjs/core/parser/text-truncate.cjs +78 -0
  54. package/lib/cjs/core/parser/text-truncate.cjs.map +1 -0
  55. package/lib/cjs/core/parser/text-truncate.d.ts +44 -0
  56. package/lib/cjs/core/parser/theme-vars.cjs +414 -0
  57. package/lib/cjs/core/parser/theme-vars.cjs.map +1 -0
  58. package/lib/cjs/core/parser/theme-vars.d.ts +61 -0
  59. package/lib/cjs/core/parser/tokens.cjs +304 -0
  60. package/lib/cjs/core/parser/tokens.cjs.map +1 -0
  61. package/lib/cjs/core/parser/tokens.d.ts +45 -0
  62. package/lib/cjs/core/parser/transform.cjs +198 -0
  63. package/lib/cjs/core/parser/transform.cjs.map +1 -0
  64. package/lib/cjs/core/parser/transform.d.ts +36 -0
  65. package/lib/cjs/core/parser/tw-parser.cjs +1567 -0
  66. package/lib/cjs/core/parser/tw-parser.cjs.map +1 -0
  67. package/lib/cjs/core/parser/tw-parser.d.ts +194 -0
  68. package/lib/cjs/core/parser/types.d.ts +37 -0
  69. package/lib/cjs/core/parser/typography-dispatcher.cjs +93 -0
  70. package/lib/cjs/core/parser/typography-dispatcher.cjs.map +1 -0
  71. package/lib/cjs/core/parser/typography-dispatcher.d.ts +11 -0
  72. package/lib/cjs/core/parser/typography.cjs +97 -0
  73. package/lib/cjs/core/parser/typography.cjs.map +1 -0
  74. package/lib/cjs/core/parser/typography.d.ts +43 -0
  75. package/lib/cjs/core/style-builder/build-style.cjs +397 -0
  76. package/lib/cjs/core/style-builder/build-style.cjs.map +1 -0
  77. package/lib/cjs/core/style-builder/build-style.d.ts +54 -0
  78. package/lib/cjs/core/style-builder/index.d.ts +3 -0
  79. package/lib/cjs/core/style-builder/union-builder.cjs +326 -0
  80. package/lib/cjs/core/style-builder/union-builder.cjs.map +1 -0
  81. package/lib/cjs/core/style-builder/union-builder.d.ts +128 -0
  82. package/lib/cjs/core/types.d.ts +14 -0
  83. package/lib/cjs/metro/dts.cjs +127 -0
  84. package/lib/cjs/metro/dts.cjs.map +1 -0
  85. package/lib/cjs/metro/dts.d.ts +16 -0
  86. package/lib/cjs/metro/index.cjs +19 -0
  87. package/lib/cjs/metro/index.cjs.map +1 -0
  88. package/lib/cjs/metro/index.d.ts +9 -0
  89. package/lib/cjs/metro/resolver.cjs +47 -0
  90. package/lib/cjs/metro/resolver.cjs.map +1 -0
  91. package/lib/cjs/metro/resolver.d.ts +22 -0
  92. package/lib/cjs/metro/state.cjs +251 -0
  93. package/lib/cjs/metro/state.cjs.map +1 -0
  94. package/lib/cjs/metro/state.d.ts +72 -0
  95. package/lib/cjs/metro/transform-ast.cjs +1255 -0
  96. package/lib/cjs/metro/transform-ast.cjs.map +1 -0
  97. package/lib/cjs/metro/transform-ast.d.ts +73 -0
  98. package/lib/cjs/metro/transformer.cjs +345 -0
  99. package/lib/cjs/metro/transformer.cjs.map +1 -0
  100. package/lib/cjs/metro/transformer.d.ts +47 -0
  101. package/lib/cjs/metro/warn-unknown-classes.cjs +86 -0
  102. package/lib/cjs/metro/warn-unknown-classes.cjs.map +1 -0
  103. package/lib/cjs/metro/warn-unknown-classes.d.ts +21 -0
  104. package/lib/cjs/metro/with-config.cjs +196 -0
  105. package/lib/cjs/metro/with-config.cjs.map +1 -0
  106. package/lib/cjs/metro/with-config.d.ts +57 -0
  107. package/lib/cjs/runtime/chain-handlers.cjs +37 -0
  108. package/lib/cjs/runtime/chain-handlers.cjs.map +1 -0
  109. package/lib/cjs/runtime/chain-handlers.d.ts +33 -0
  110. package/lib/cjs/runtime/components/rnwind-provider.cjs +98 -0
  111. package/lib/cjs/runtime/components/rnwind-provider.cjs.map +1 -0
  112. package/lib/cjs/runtime/components/rnwind-provider.d.ts +84 -0
  113. package/lib/cjs/runtime/gradient-types.d.ts +58 -0
  114. package/lib/cjs/runtime/haptics.cjs +113 -0
  115. package/lib/cjs/runtime/haptics.cjs.map +1 -0
  116. package/lib/cjs/runtime/haptics.d.ts +48 -0
  117. package/lib/cjs/runtime/hooks/use-css.cjs +21 -0
  118. package/lib/cjs/runtime/hooks/use-css.cjs.map +1 -0
  119. package/lib/cjs/runtime/hooks/use-css.d.ts +11 -0
  120. package/lib/cjs/runtime/hooks/use-interact.cjs +46 -0
  121. package/lib/cjs/runtime/hooks/use-interact.cjs.map +1 -0
  122. package/lib/cjs/runtime/hooks/use-interact.d.ts +42 -0
  123. package/lib/cjs/runtime/hooks/use-scheme.cjs +68 -0
  124. package/lib/cjs/runtime/hooks/use-scheme.cjs.map +1 -0
  125. package/lib/cjs/runtime/hooks/use-scheme.d.ts +34 -0
  126. package/lib/cjs/runtime/index.cjs +45 -0
  127. package/lib/cjs/runtime/index.cjs.map +1 -0
  128. package/lib/cjs/runtime/index.d.ts +27 -0
  129. package/lib/cjs/runtime/interactive-box.cjs +35 -0
  130. package/lib/cjs/runtime/interactive-box.cjs.map +1 -0
  131. package/lib/cjs/runtime/interactive-box.d.ts +40 -0
  132. package/lib/cjs/runtime/lookup-css.cjs +542 -0
  133. package/lib/cjs/runtime/lookup-css.cjs.map +1 -0
  134. package/lib/cjs/runtime/lookup-css.d.ts +164 -0
  135. package/lib/cjs/runtime/types.d.ts +29 -0
  136. package/lib/cjs/testing/index.cjs +367 -0
  137. package/lib/cjs/testing/index.cjs.map +1 -0
  138. package/lib/cjs/testing/index.d.ts +145 -0
  139. package/lib/esm/core/parser/animation.d.ts +126 -0
  140. package/lib/esm/core/parser/animation.mjs +408 -0
  141. package/lib/esm/core/parser/animation.mjs.map +1 -0
  142. package/lib/esm/core/parser/border-dispatcher.d.ts +15 -0
  143. package/lib/esm/core/parser/border-dispatcher.mjs +178 -0
  144. package/lib/esm/core/parser/border-dispatcher.mjs.map +1 -0
  145. package/lib/esm/core/parser/case-convert.d.ts +6 -0
  146. package/lib/esm/core/parser/case-convert.mjs +13 -0
  147. package/lib/esm/core/parser/case-convert.mjs.map +1 -0
  148. package/lib/esm/core/parser/color-properties-dispatcher.d.ts +19 -0
  149. package/lib/esm/core/parser/color-properties-dispatcher.mjs +82 -0
  150. package/lib/esm/core/parser/color-properties-dispatcher.mjs.map +1 -0
  151. package/lib/esm/core/parser/color.d.ts +12 -0
  152. package/lib/esm/core/parser/color.mjs +162 -0
  153. package/lib/esm/core/parser/color.mjs.map +1 -0
  154. package/lib/esm/core/parser/constants.d.ts +8 -0
  155. package/lib/esm/core/parser/constants.mjs +13 -0
  156. package/lib/esm/core/parser/constants.mjs.map +1 -0
  157. package/lib/esm/core/parser/declaration.d.ts +15 -0
  158. package/lib/esm/core/parser/declaration.mjs +345 -0
  159. package/lib/esm/core/parser/declaration.mjs.map +1 -0
  160. package/lib/esm/core/parser/gradient.d.ts +59 -0
  161. package/lib/esm/core/parser/gradient.mjs +130 -0
  162. package/lib/esm/core/parser/gradient.mjs.map +1 -0
  163. package/lib/esm/core/parser/haptics.d.ts +47 -0
  164. package/lib/esm/core/parser/haptics.mjs +71 -0
  165. package/lib/esm/core/parser/haptics.mjs.map +1 -0
  166. package/lib/esm/core/parser/index.d.ts +8 -0
  167. package/lib/esm/core/parser/keyframes.d.ts +26 -0
  168. package/lib/esm/core/parser/keyframes.mjs +91 -0
  169. package/lib/esm/core/parser/keyframes.mjs.map +1 -0
  170. package/lib/esm/core/parser/layout-dispatcher.d.ts +14 -0
  171. package/lib/esm/core/parser/layout-dispatcher.mjs +98 -0
  172. package/lib/esm/core/parser/layout-dispatcher.mjs.map +1 -0
  173. package/lib/esm/core/parser/length.d.ts +48 -0
  174. package/lib/esm/core/parser/length.mjs +90 -0
  175. package/lib/esm/core/parser/length.mjs.map +1 -0
  176. package/lib/esm/core/parser/motion-dispatcher.d.ts +11 -0
  177. package/lib/esm/core/parser/motion-dispatcher.mjs +75 -0
  178. package/lib/esm/core/parser/motion-dispatcher.mjs.map +1 -0
  179. package/lib/esm/core/parser/property.d.ts +8 -0
  180. package/lib/esm/core/parser/property.mjs +20 -0
  181. package/lib/esm/core/parser/property.mjs.map +1 -0
  182. package/lib/esm/core/parser/safe-area.d.ts +39 -0
  183. package/lib/esm/core/parser/safe-area.mjs +402 -0
  184. package/lib/esm/core/parser/safe-area.mjs.map +1 -0
  185. package/lib/esm/core/parser/selector.d.ts +11 -0
  186. package/lib/esm/core/parser/selector.mjs +20 -0
  187. package/lib/esm/core/parser/selector.mjs.map +1 -0
  188. package/lib/esm/core/parser/shorthand.d.ts +61 -0
  189. package/lib/esm/core/parser/shorthand.mjs +148 -0
  190. package/lib/esm/core/parser/shorthand.mjs.map +1 -0
  191. package/lib/esm/core/parser/text-truncate.d.ts +44 -0
  192. package/lib/esm/core/parser/text-truncate.mjs +75 -0
  193. package/lib/esm/core/parser/text-truncate.mjs.map +1 -0
  194. package/lib/esm/core/parser/theme-vars.d.ts +61 -0
  195. package/lib/esm/core/parser/theme-vars.mjs +409 -0
  196. package/lib/esm/core/parser/theme-vars.mjs.map +1 -0
  197. package/lib/esm/core/parser/tokens.d.ts +45 -0
  198. package/lib/esm/core/parser/tokens.mjs +298 -0
  199. package/lib/esm/core/parser/tokens.mjs.map +1 -0
  200. package/lib/esm/core/parser/transform.d.ts +36 -0
  201. package/lib/esm/core/parser/transform.mjs +193 -0
  202. package/lib/esm/core/parser/transform.mjs.map +1 -0
  203. package/lib/esm/core/parser/tw-parser.d.ts +194 -0
  204. package/lib/esm/core/parser/tw-parser.mjs +1565 -0
  205. package/lib/esm/core/parser/tw-parser.mjs.map +1 -0
  206. package/lib/esm/core/parser/types.d.ts +37 -0
  207. package/lib/esm/core/parser/typography-dispatcher.d.ts +11 -0
  208. package/lib/esm/core/parser/typography-dispatcher.mjs +91 -0
  209. package/lib/esm/core/parser/typography-dispatcher.mjs.map +1 -0
  210. package/lib/esm/core/parser/typography.d.ts +43 -0
  211. package/lib/esm/core/parser/typography.mjs +91 -0
  212. package/lib/esm/core/parser/typography.mjs.map +1 -0
  213. package/lib/esm/core/style-builder/build-style.d.ts +54 -0
  214. package/lib/esm/core/style-builder/build-style.mjs +395 -0
  215. package/lib/esm/core/style-builder/build-style.mjs.map +1 -0
  216. package/lib/esm/core/style-builder/index.d.ts +3 -0
  217. package/lib/esm/core/style-builder/union-builder.d.ts +128 -0
  218. package/lib/esm/core/style-builder/union-builder.mjs +324 -0
  219. package/lib/esm/core/style-builder/union-builder.mjs.map +1 -0
  220. package/lib/esm/core/types.d.ts +14 -0
  221. package/lib/esm/metro/dts.d.ts +16 -0
  222. package/lib/esm/metro/dts.mjs +125 -0
  223. package/lib/esm/metro/dts.mjs.map +1 -0
  224. package/lib/esm/metro/index.d.ts +9 -0
  225. package/lib/esm/metro/index.mjs +6 -0
  226. package/lib/esm/metro/index.mjs.map +1 -0
  227. package/lib/esm/metro/resolver.d.ts +22 -0
  228. package/lib/esm/metro/resolver.mjs +43 -0
  229. package/lib/esm/metro/resolver.mjs.map +1 -0
  230. package/lib/esm/metro/state.d.ts +72 -0
  231. package/lib/esm/metro/state.mjs +243 -0
  232. package/lib/esm/metro/state.mjs.map +1 -0
  233. package/lib/esm/metro/transform-ast.d.ts +73 -0
  234. package/lib/esm/metro/transform-ast.mjs +1234 -0
  235. package/lib/esm/metro/transform-ast.mjs.map +1 -0
  236. package/lib/esm/metro/transformer.d.ts +47 -0
  237. package/lib/esm/metro/transformer.mjs +322 -0
  238. package/lib/esm/metro/transformer.mjs.map +1 -0
  239. package/lib/esm/metro/warn-unknown-classes.d.ts +21 -0
  240. package/lib/esm/metro/warn-unknown-classes.mjs +84 -0
  241. package/lib/esm/metro/warn-unknown-classes.mjs.map +1 -0
  242. package/lib/esm/metro/with-config.d.ts +57 -0
  243. package/lib/esm/metro/with-config.mjs +194 -0
  244. package/lib/esm/metro/with-config.mjs.map +1 -0
  245. package/lib/esm/runtime/chain-handlers.d.ts +33 -0
  246. package/lib/esm/runtime/chain-handlers.mjs +34 -0
  247. package/lib/esm/runtime/chain-handlers.mjs.map +1 -0
  248. package/lib/esm/runtime/components/rnwind-provider.d.ts +84 -0
  249. package/lib/esm/runtime/components/rnwind-provider.mjs +94 -0
  250. package/lib/esm/runtime/components/rnwind-provider.mjs.map +1 -0
  251. package/lib/esm/runtime/gradient-types.d.ts +58 -0
  252. package/lib/esm/runtime/haptics.d.ts +48 -0
  253. package/lib/esm/runtime/haptics.mjs +110 -0
  254. package/lib/esm/runtime/haptics.mjs.map +1 -0
  255. package/lib/esm/runtime/hooks/use-css.d.ts +11 -0
  256. package/lib/esm/runtime/hooks/use-css.mjs +19 -0
  257. package/lib/esm/runtime/hooks/use-css.mjs.map +1 -0
  258. package/lib/esm/runtime/hooks/use-interact.d.ts +42 -0
  259. package/lib/esm/runtime/hooks/use-interact.mjs +44 -0
  260. package/lib/esm/runtime/hooks/use-interact.mjs.map +1 -0
  261. package/lib/esm/runtime/hooks/use-scheme.d.ts +34 -0
  262. package/lib/esm/runtime/hooks/use-scheme.mjs +63 -0
  263. package/lib/esm/runtime/hooks/use-scheme.mjs.map +1 -0
  264. package/lib/esm/runtime/index.d.ts +27 -0
  265. package/lib/esm/runtime/index.mjs +18 -0
  266. package/lib/esm/runtime/index.mjs.map +1 -0
  267. package/lib/esm/runtime/interactive-box.d.ts +40 -0
  268. package/lib/esm/runtime/interactive-box.mjs +33 -0
  269. package/lib/esm/runtime/interactive-box.mjs.map +1 -0
  270. package/lib/esm/runtime/lookup-css.d.ts +164 -0
  271. package/lib/esm/runtime/lookup-css.mjs +531 -0
  272. package/lib/esm/runtime/lookup-css.mjs.map +1 -0
  273. package/lib/esm/runtime/types.d.ts +29 -0
  274. package/lib/esm/testing/index.d.ts +145 -0
  275. package/lib/esm/testing/index.mjs +344 -0
  276. package/lib/esm/testing/index.mjs.map +1 -0
  277. package/package.json +79 -13
  278. package/preset.css +1171 -0
  279. package/src/core/parser/animation.ts +404 -0
  280. package/src/core/parser/border-dispatcher.ts +176 -0
  281. package/src/core/parser/case-convert.ts +10 -0
  282. package/src/core/parser/color-properties-dispatcher.ts +78 -0
  283. package/src/core/parser/color.ts +157 -0
  284. package/src/core/parser/constants.ts +11 -0
  285. package/src/core/parser/declaration.ts +340 -0
  286. package/src/core/parser/gradient.ts +148 -0
  287. package/src/core/parser/haptics.ts +88 -0
  288. package/src/core/parser/index.ts +8 -0
  289. package/src/core/parser/keyframes.ts +84 -0
  290. package/src/core/parser/layout-dispatcher.ts +92 -0
  291. package/src/core/parser/length.ts +100 -0
  292. package/src/core/parser/motion-dispatcher.ts +89 -0
  293. package/src/core/parser/property.ts +15 -0
  294. package/src/core/parser/safe-area.ts +404 -0
  295. package/src/core/parser/selector.ts +17 -0
  296. package/src/core/parser/shorthand.ts +152 -0
  297. package/src/core/parser/text-truncate.ts +79 -0
  298. package/src/core/parser/theme-vars.ts +412 -0
  299. package/src/core/parser/tokens.ts +286 -0
  300. package/src/core/parser/transform.ts +195 -0
  301. package/src/core/parser/tw-parser.ts +1709 -0
  302. package/src/core/parser/types.ts +45 -0
  303. package/src/core/parser/typography-dispatcher.ts +83 -0
  304. package/src/core/parser/typography.ts +83 -0
  305. package/src/core/style-builder/build-style.ts +442 -0
  306. package/src/core/style-builder/index.ts +3 -0
  307. package/src/core/style-builder/union-builder.ts +328 -0
  308. package/src/core/types.ts +15 -0
  309. package/src/metro/dts.ts +128 -0
  310. package/src/metro/index.ts +9 -0
  311. package/src/metro/resolver.ts +42 -0
  312. package/src/metro/state.ts +257 -0
  313. package/src/metro/transform-ast.ts +1498 -0
  314. package/src/metro/transformer.ts +347 -0
  315. package/src/metro/warn-unknown-classes.ts +79 -0
  316. package/src/metro/with-config.ts +229 -0
  317. package/src/runtime/chain-handlers.ts +47 -0
  318. package/src/runtime/components/rnwind-provider.tsx +144 -0
  319. package/src/runtime/gradient-types.ts +60 -0
  320. package/src/runtime/haptics.ts +120 -0
  321. package/src/runtime/hooks/use-css.ts +16 -0
  322. package/src/runtime/hooks/use-interact.ts +65 -0
  323. package/src/runtime/hooks/use-scheme.ts +63 -0
  324. package/src/runtime/index.ts +54 -0
  325. package/src/runtime/interactive-box.tsx +57 -0
  326. package/src/runtime/lookup-css.ts +628 -0
  327. package/src/runtime/types.ts +32 -0
  328. package/src/testing/index.ts +507 -0
  329. package/src/types/tailwindcss-node.d.ts +33 -0
  330. package/src/index.ts +0 -1
@@ -0,0 +1,347 @@
1
+ import type { File } from '@babel/types'
2
+ import * as t from '@babel/types'
3
+ import { parse } from '@babel/parser'
4
+ import generate from '@babel/generator'
5
+ import { createHash } from 'node:crypto'
6
+ import { transformAst } from './transform-ast'
7
+ import { getClassNamePrefixes, getRnwindCacheKey, getRnwindState, onThemeChange } from './state'
8
+ import { STYLE_SPECIFIERS, THEME_SIGNATURE_MODULE } from './resolver'
9
+ import { filterUnknownClassCandidates } from './warn-unknown-classes'
10
+
11
+ /** The shape of the upstream module we delegate parsing/babel work to. */
12
+ interface UpstreamTransformer {
13
+ transform: (args: BabelTransformerArgs) => Promise<BabelTransformerResult> | BabelTransformerResult
14
+ }
15
+
16
+ /** Env var that points at the upstream `babelTransformerPath` we override. */
17
+ const UPSTREAM_ENV = 'RNWIND_UPSTREAM_TRANSFORMER'
18
+
19
+ /** Cached upstream module — required once, reused across every transform call. */
20
+ let cachedUpstream: UpstreamTransformer | null = null
21
+
22
+ const generateModule = (generate as unknown as { default?: typeof generate }).default ?? generate
23
+
24
+ /**
25
+ * Parse user source with the broad plugin set (Flow + JSX + TypeScript
26
+ * + class properties). Permissive on purpose so we don't reject any
27
+ * file the upstream could have handled. Returns `null` when parse
28
+ * fails — caller falls back to the raw source string.
29
+ * @param source Source text.
30
+ * @returns Parsed AST, or null on parse failure.
31
+ */
32
+ function parseUserSource(source: string): File | null {
33
+ try {
34
+ return parse(source, {
35
+ sourceType: 'unambiguous',
36
+ allowReturnOutsideFunction: true,
37
+ allowImportExportEverywhere: true,
38
+ plugins: ['typescript', 'jsx'],
39
+ }) as unknown as File
40
+ } catch {
41
+ try {
42
+ return parse(source, {
43
+ sourceType: 'unambiguous',
44
+ allowReturnOutsideFunction: true,
45
+ allowImportExportEverywhere: true,
46
+ plugins: ['flow', 'jsx'],
47
+ }) as unknown as File
48
+ } catch {
49
+ return null
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Print Tailwind-shaped candidates oxide picked up but the parser
56
+ * could NOT compile — typo, missing custom utility, or class not in
57
+ * the user's theme. Filtering by candidates that ALSO appear inside a
58
+ * `className="…"` literal eliminates false positives from imports,
59
+ * comments, and JSX prop values.
60
+ * @param source Original source text — searched for className literals.
61
+ * @param candidates Every candidate oxide surfaced from the source.
62
+ * @param atoms Successfully resolved atoms (keys are class names).
63
+ * @param filename Source path, prefixed onto the warning.
64
+ */
65
+ function warnUnknownClasses(
66
+ source: string,
67
+ candidates: readonly string[],
68
+ atoms: ReadonlyMap<string, unknown>,
69
+ filename: string,
70
+ ): void {
71
+ const atomNames = new Set(atoms.keys())
72
+ const unknown = filterUnknownClassCandidates(source, candidates, atomNames)
73
+ if (unknown.length === 0) return
74
+ // eslint-disable-next-line no-console
75
+ console.warn(`rnwind: unknown class${unknown.length > 1 ? 'es' : ''} in ${filename}: ${unknown.join(', ')}`)
76
+ }
77
+
78
+ /**
79
+ * Extract the bare extension for oxide / internal switches.
80
+ * @param filename Absolute path.
81
+ * @returns Extension without the leading dot (`tsx` / `ts` / `js` / `jsx`).
82
+ */
83
+ function extensionOf(filename: string): string {
84
+ const index = filename.lastIndexOf('.')
85
+ if (index === -1) return 'tsx'
86
+ return filename.slice(index + 1)
87
+ }
88
+
89
+ /**
90
+ * Read the project root Metro hands us per-transform. Falls back to
91
+ * `process.cwd()` only when the upstream harness doesn't set it (unit
92
+ * tests, standalone). Metro's production pipeline always sets it.
93
+ * @param args Metro transformer args.
94
+ * @returns Absolute project root.
95
+ */
96
+ function projectRootOf(args: BabelTransformerArgs): string {
97
+ const fromOptions = args.options?.projectRoot
98
+ if (typeof fromOptions === 'string' && fromOptions.length > 0) return fromOptions
99
+ return process.cwd()
100
+ }
101
+
102
+ /**
103
+ * Whether a `.css` filename is the user's theme entry (the file
104
+ * `withRnwindConfig` pointed us at via `RNWIND_CSS_ENTRY_FILE`).
105
+ * Only the theme CSS should trigger a scheme rebuild — unrelated CSS
106
+ * files in the project stay invisible to rnwind.
107
+ * @param filename Absolute CSS path.
108
+ * @returns Whether the file is the configured theme entry.
109
+ */
110
+ function isThemeCssEntry(filename: string): boolean {
111
+ const cssEntry = process.env.RNWIND_CSS_ENTRY_FILE
112
+ return typeof cssEntry === 'string' && cssEntry.length > 0 && cssEntry === filename
113
+ }
114
+
115
+ /**
116
+ * Parse + run rnwind's JSX rewrite + regenerate source code. When
117
+ * parsing or transformation fails, fall back to the original source —
118
+ * we don't want a transient parse error to crash Metro for a file the
119
+ * upstream might handle fine.
120
+ * @param args Metro args; `src` is the original source text.
121
+ * @returns Rewritten source text (with `className=` rewrites applied).
122
+ */
123
+ async function rewriteSource(args: BabelTransformerArgs): Promise<string> {
124
+ const ast = parseUserSource(args.src)
125
+ if (!ast) return args.src
126
+
127
+ const state = getRnwindState(projectRootOf(args))
128
+ const extension = extensionOf(args.filename)
129
+ const parsed = await state.parser.parseAtoms({ content: args.src, extension })
130
+
131
+ warnUnknownClasses(args.src, parsed.candidates, parsed.atoms, args.filename)
132
+
133
+ const classNamePrefixes = getClassNamePrefixes()
134
+ if (parsed.atoms.size === 0) {
135
+ state.builder.dropFile(args.filename)
136
+ await state.builder.writeSchemes()
137
+ transformAst(ast, {
138
+ styleSpecifiers: [],
139
+ gradientAtoms: parsed.gradientAtoms,
140
+ hapticAtoms: parsed.hapticAtoms,
141
+ classNamePrefixes,
142
+ })
143
+ injectThemeSignatureImport(ast)
144
+ return generateModule(ast).code
145
+ }
146
+
147
+ const { changed } = await state.builder.recordFile(args.filename, parsed.atoms, parsed.keyframes)
148
+ if (changed) await state.builder.writeSchemes()
149
+
150
+ transformAst(ast, {
151
+ styleSpecifiers: STYLE_SPECIFIERS as unknown as readonly string[],
152
+ gradientAtoms: parsed.gradientAtoms,
153
+ hapticAtoms: parsed.hapticAtoms,
154
+ classNamePrefixes,
155
+ })
156
+ injectThemeSignatureImport(ast)
157
+ return generateModule(ast).code
158
+ }
159
+
160
+ /**
161
+ * Prepend `import 'rnwind/__generated/theme-signature'` to every
162
+ * rnwind-transformed file. The resolver maps that specifier to the
163
+ * user's theme CSS so Metro's dependency graph carries a real edge
164
+ * from this JS file to the CSS. When the user edits `global.css`,
165
+ * the CSS module's SHA1 changes, and Metro invalidates every JS file
166
+ * holding this import — forcing them to re-transform with the new
167
+ * theme. The `.css` branch in {@link transform} returns an empty
168
+ * `export {}` module so the runtime cost is one extra `require()`.
169
+ * @param ast Babel File AST to mutate in place.
170
+ */
171
+ function injectThemeSignatureImport(ast: File): void {
172
+ const declaration = t.importDeclaration([], t.stringLiteral(THEME_SIGNATURE_MODULE))
173
+ ast.program.body.unshift(declaration)
174
+ }
175
+
176
+ /**
177
+ * Read the upstream transformer's `getCacheKey()` so our cache-key
178
+ * contribution composes with — rather than replaces — whatever the
179
+ * host framework wants to mix in.
180
+ * @returns Upstream cache key, or `null` when no upstream exposes one.
181
+ */
182
+ function loadUpstreamCacheKey(): string | null {
183
+ const upstream = loadUpstream() as (UpstreamTransformer & { getCacheKey?: () => string }) | null
184
+ if (!upstream) return null
185
+ try {
186
+ return typeof upstream.getCacheKey === 'function' ? upstream.getCacheKey() : null
187
+ } catch {
188
+ return null
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Invoke the upstream `babelTransformerPath` Metro originally had
194
+ * configured. The path is read from `RNWIND_UPSTREAM_TRANSFORMER`,
195
+ * which `withRnwindConfig` sets at Metro startup. When the env var is
196
+ * unset (unit tests, standalone use), fall back to a typescript+jsx
197
+ * parse.
198
+ * @param args Metro's per-file args.
199
+ * @returns Upstream transform result containing the post-babel AST.
200
+ */
201
+ async function runUpstream(args: BabelTransformerArgs): Promise<BabelTransformerResult> {
202
+ if (args.ast && !process.env[UPSTREAM_ENV]) return { ast: args.ast }
203
+ const upstream = loadUpstream()
204
+ if (upstream) return await Promise.resolve(upstream.transform(args))
205
+ if (args.ast) return { ast: args.ast }
206
+ return { ast: parseSource(args.src) }
207
+ }
208
+
209
+ /**
210
+ * Lazily require the upstream transformer module. Cached after first
211
+ * load so per-file overhead is one cache lookup.
212
+ * @returns Upstream module, or null when env is unset.
213
+ */
214
+ function loadUpstream(): UpstreamTransformer | null {
215
+ if (cachedUpstream) return cachedUpstream
216
+ const upstreamPath = process.env[UPSTREAM_ENV]
217
+ if (!upstreamPath || upstreamPath.length === 0) return null
218
+ try {
219
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
220
+ const required = require(upstreamPath) as UpstreamTransformer | { default?: UpstreamTransformer }
221
+ const upstream = (required as { default?: UpstreamTransformer }).default ?? (required as UpstreamTransformer)
222
+ if (typeof upstream.transform !== 'function') return null
223
+ cachedUpstream = upstream
224
+ return upstream
225
+ } catch (error) {
226
+ // eslint-disable-next-line no-console
227
+ if (process.env.RNWIND_DEBUG) console.error('rnwind: failed to load upstream transformer:', error)
228
+ return null
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Cheap guard — the file has to look JS/TS, live outside `node_modules`,
234
+ * and mention `className=` before we spend AST cycles on it.
235
+ * @param args Metro args.
236
+ * @returns Whether the file might need the rnwind pass.
237
+ */
238
+ function isRewriteCandidate(args: BabelTransformerArgs): boolean {
239
+ if (!/\.(?:tsx|ts|jsx|js)$/i.test(args.filename)) return false
240
+ if (args.filename.includes('/node_modules/')) return false
241
+ return args.src.includes('className=')
242
+ }
243
+
244
+ /**
245
+ * Fallback parse when no upstream is configured AND Metro didn't hand
246
+ * us an AST. Used by unit tests and standalone setups.
247
+ * @param source Source text.
248
+ * @returns Parsed Babel File.
249
+ */
250
+ function parseSource(source: string): File {
251
+ return parse(source, { sourceType: 'module', plugins: ['typescript', 'jsx'] }) as unknown as File
252
+ }
253
+
254
+ /** Metro's babel transformer signature. */
255
+ export interface BabelTransformerArgs {
256
+ filename: string
257
+ src: string
258
+ options: { projectRoot?: string; [key: string]: unknown }
259
+ ast?: File
260
+ plugins?: readonly unknown[]
261
+ }
262
+
263
+ /** Return shape Metro expects from a babel transformer. */
264
+ export interface BabelTransformerResult {
265
+ ast: File
266
+ metadata?: unknown
267
+ }
268
+
269
+ /**
270
+ * rnwind's Metro babel transformer. Two phases per source file:
271
+ *
272
+ * 1. **Pre-process the source string before handing it to the upstream
273
+ * babel pipeline.** babel-preset-expo / React's JSX transform run
274
+ * inside the upstream and convert `<View className="..."/>` into
275
+ * `React.createElement(View, {className})`. If we walked the AST
276
+ * AFTER the upstream, there'd be no JSX attributes left to
277
+ * rewrite. So we parse, run our pass, regenerate code, and feed
278
+ * THAT to the upstream as `src`.
279
+ * 2. **Delegate to the upstream `babelTransformerPath`** (Expo's
280
+ * default handles Flow stripping, expo-router macros, etc.).
281
+ *
282
+ * Skip both phases when the file isn't a JS/TS source under user
283
+ * code, or doesn't mention `className=` — hand straight to upstream.
284
+ * @param args Metro's per-file args.
285
+ * @returns Mutated AST + metadata.
286
+ */
287
+ export async function transform(args: BabelTransformerArgs): Promise<BabelTransformerResult> {
288
+ // Short-circuit `.css` inputs: the theme CSS is pulled into the dep
289
+ // graph as a sentinel (see `THEME_SIGNATURE_MODULE` in resolver.ts)
290
+ // so Metro watches it and invalidates importers on edit, but the
291
+ // file's CSS syntax can't go through a JS babel transformer.
292
+ //
293
+ // When the CSS being transformed IS the user's theme entry, we
294
+ // piggyback on Metro's own file-watcher: Metro calls us here on
295
+ // every CSS save; we trigger `onThemeChange` to rebuild parser +
296
+ // rewrite scheme files with the new values. Metro's dep graph then
297
+ // HMRs the regenerated `common.style.js` to the running app.
298
+ //
299
+ // Emitting the CSS content hash in the fake JS output is what makes
300
+ // Metro propagate invalidation to downstream importers — constant
301
+ // `export {}` bytes would never look changed and Metro would skip
302
+ // the chain.
303
+ if (args.filename.endsWith('.css')) {
304
+ if (isThemeCssEntry(args.filename)) {
305
+ try {
306
+ await onThemeChange(projectRootOf(args))
307
+ } catch {
308
+ // CSS edit happened outside a configured project (e.g. tests).
309
+ }
310
+ }
311
+ const themeHash = createHash('sha256').update(args.src).digest('hex').slice(0, 16)
312
+ const stub = `export const __rnwindThemeHash = ${JSON.stringify(themeHash)};\n`
313
+ return { ast: parse(stub, { sourceType: 'module' }) as unknown as File }
314
+ }
315
+ if (!isRewriteCandidate(args)) {
316
+ if (/\.(?:tsx|ts|jsx|js)$/i.test(args.filename) && !args.filename.includes('/node_modules/')) {
317
+ try {
318
+ getRnwindState(projectRootOf(args)).builder.dropFile(args.filename)
319
+ } catch {
320
+ // State not configured (e.g. test). Nothing to drop.
321
+ }
322
+ }
323
+ return runUpstream(args)
324
+ }
325
+
326
+ const rewrittenSource = await rewriteSource(args)
327
+ return runUpstream({ ...args, src: rewrittenSource, ast: undefined })
328
+ }
329
+
330
+ /**
331
+ * Metro's babel-transformer contract: a `getCacheKey()` export is
332
+ * sampled per-file and mixed into the transform cache key. Returning
333
+ * a string that includes the theme CSS content hash invalidates every
334
+ * cached transform on every CSS edit — so the bundle rebuilds with
335
+ * the new theme automatically on the next request.
336
+ * @returns Cache-key segment that includes rnwind's current theme hash.
337
+ */
338
+ export function getCacheKey(): string {
339
+ const upstreamKey = loadUpstreamCacheKey()
340
+ const ownKey = getRnwindCacheKey()
341
+ return upstreamKey ? `${upstreamKey}|${ownKey}` : ownKey
342
+ }
343
+
344
+ /** Test-only — drop the cached upstream so a new env var picks up next call. */
345
+ export function __resetUpstreamCache(): void {
346
+ cachedUpstream = null
347
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Yield every literal-text segment found at a `className=` site.
3
+ * @param source Raw source text.
4
+ * @yields Each literal's inner text.
5
+ */
6
+ function* iterateClassNameLiterals(source: string): Iterable<string> {
7
+ // className="..." / className='...'
8
+ for (const match of source.matchAll(/className\s*=\s*"([^"]*)"/g)) yield match[1] ?? ''
9
+ for (const match of source.matchAll(/className\s*=\s*'([^']*)'/g)) yield match[1] ?? ''
10
+ // className={"..."} / className={'...'}
11
+ for (const match of source.matchAll(/className\s*=\s*\{\s*"([^"]*)"\s*\}/g)) yield match[1] ?? ''
12
+ for (const match of source.matchAll(/className\s*=\s*\{\s*'([^']*)'\s*\}/g)) yield match[1] ?? ''
13
+ // className={`...`} — yield each static quasi between `${...}` substitutions.
14
+ for (const match of source.matchAll(/className\s*=\s*\{\s*`([^`]*)`\s*\}/g)) {
15
+ const body = match[1] ?? ''
16
+ for (const part of body.split(/\$\{[^}]*\}/)) yield part
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Pull every `className="…"`, `className={'…'}`, `className={"…"}`, and
22
+ * `className={\`…\`}` literal out of the source and union their
23
+ * whitespace-separated tokens. A regex-based scan is enough — the
24
+ * warning is best-effort, not load-bearing, and a regex sidesteps
25
+ * having to re-parse the file.
26
+ *
27
+ * Skipped on purpose:
28
+ * - `className={someExpression}` with no inline literal (we can't
29
+ * introspect the runtime value at build time).
30
+ * - Template literals with substitutions (`` `text-${size}` ``) — we
31
+ * only union the static-quasi parts, which is fine because the
32
+ * warning fires only on candidates that ARE in the static parts.
33
+ * @param source Raw source text.
34
+ * @returns Set of whitespace-separated tokens drawn from every literal.
35
+ */
36
+ function collectClassNameTokens(source: string): Set<string> {
37
+ const out = new Set<string>()
38
+ for (const literal of iterateClassNameLiterals(source)) {
39
+ for (const token of literal.split(/\s+/)) {
40
+ if (token.length > 0) out.add(token)
41
+ }
42
+ }
43
+ return out
44
+ }
45
+
46
+ /**
47
+ * Filter Tailwind candidate strings down to ones that actually appear
48
+ * as a token inside a `className="…"` literal in the source. Oxide
49
+ * scans the entire file and surfaces anything Tailwind-shaped — that
50
+ * includes import specifiers (`'expo-router'`), comment markers
51
+ * (`/* @rnwind-theme=… *\/`), JSX prop values (`keyboardType="email-
52
+ * address"`), and bare suffixes Tailwind splits out from compound
53
+ * utilities (`bg-sky-500` also produces `sky-500`). None of those are
54
+ * "unknown classes" worth nagging the user about; the genuine signal
55
+ * is a typo in a real `className`, e.g. `bg-srface` for `bg-surface`.
56
+ *
57
+ * The filter walks every `className="…" / {'…'} / {`…`}` literal in the
58
+ * source, splits each on whitespace, and unions the tokens. Only
59
+ * candidates in that token set survive. Then known-good atoms are
60
+ * subtracted, leaving genuine typos.
61
+ * @param source Raw source text the transformer received from Metro.
62
+ * @param candidates Every candidate oxide picked up.
63
+ * @param atoms Set of atom names the parser successfully resolved.
64
+ * @returns Candidates that look like real "unknown classes" the user typed.
65
+ */
66
+ export function filterUnknownClassCandidates(
67
+ source: string,
68
+ candidates: readonly string[],
69
+ atoms: ReadonlySet<string>,
70
+ ): string[] {
71
+ const literalTokens = collectClassNameTokens(source)
72
+ const out: string[] = []
73
+ for (const candidate of candidates) {
74
+ if (atoms.has(candidate)) continue
75
+ if (!literalTokens.has(candidate)) continue
76
+ out.push(candidate)
77
+ }
78
+ return out
79
+ }
@@ -0,0 +1,229 @@
1
+ import { existsSync, mkdirSync, utimesSync, watch as watchFile } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { writeDtsFile } from './dts'
4
+ import { createRnwindResolver, type ResolveRequestFn } from './resolver'
5
+ import { configureRnwindState, getRnwindState, onThemeChange } from './state'
6
+
7
+ /** Default cache directory at the project root. Visible for debugging. */
8
+ const DEFAULT_CACHE_DIR = '.rnwind'
9
+
10
+ /**
11
+ * Active CSS watcher — replaced (and the prior one closed) when
12
+ * `withRnwindConfig` is called again (Metro restart, repeated init).
13
+ * Only one watcher per process; no stacking.
14
+ */
15
+ let activeCssWatcher: { cssPath: string; close: () => void } | null = null
16
+
17
+ /**
18
+ * Watch the theme CSS for edits. On change, rewrite the per-scheme
19
+ * files with the fresh theme AND bump `mtime` on every source file
20
+ * rnwind has transformed so far — Metro's own watcher sees those
21
+ * mtime changes, invalidates the modules, and re-transforms them
22
+ * against the new CSS on the next request. `getCacheKey()` alone is
23
+ * NOT enough: Metro samples the cache key once per worker lifetime,
24
+ * so edits during an already-running dev server don't propagate
25
+ * without this explicit nudge.
26
+ * @param cssPath Absolute path to the theme CSS to watch.
27
+ * @param projectRoot Metro's project root (for `getRnwindState`).
28
+ */
29
+ function watchThemeCss(cssPath: string, projectRoot: string): void {
30
+ if (activeCssWatcher?.cssPath === cssPath) return
31
+ activeCssWatcher?.close()
32
+ if (!existsSync(cssPath)) return
33
+ let pending = false
34
+ const watcher = watchFile(cssPath, { persistent: false }, () => {
35
+ // Debounce: editors often emit 2-3 change events per save (atomic
36
+ // rename dance, tmp files). Coalesce to ONE rebuild per microtask.
37
+ if (pending) return
38
+ pending = true
39
+ queueMicrotask(async () => {
40
+ pending = false
41
+ try {
42
+ await onThemeChange(projectRoot)
43
+ touchRecordedFiles(projectRoot)
44
+ } catch {
45
+ // Invalidation is best-effort — never crash the dev server.
46
+ }
47
+ })
48
+ })
49
+ activeCssWatcher = { cssPath, close: () => watcher.close() }
50
+ }
51
+
52
+ /**
53
+ * Bump the mtime on every file the builder has transformed. Metro's
54
+ * file watcher keys on `mtime`, so this is what makes it invalidate
55
+ * those modules and re-transform them.
56
+ * @param projectRoot Metro's project root.
57
+ */
58
+ function touchRecordedFiles(projectRoot: string): void {
59
+ const state = getRnwindState(projectRoot)
60
+ const files = state.builder.recordedFiles()
61
+ const now = new Date()
62
+ for (const file of files) {
63
+ try {
64
+ if (existsSync(file)) utimesSync(file, now, now)
65
+ } catch {
66
+ // One file's stat failure shouldn't stop the others.
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Where the rnwind babel transformer lives — resolved relative to this
73
+ * module so the path works from both the `src/` tree (tests) and the
74
+ * built `lib/` output. Tries a few extensions because `require.resolve`
75
+ * doesn't auto-find `.cjs` / `.mjs` from a bare specifier.
76
+ * @returns Absolute path to the rnwind transformer module.
77
+ */
78
+ function transformerPath(): string {
79
+ for (const candidate of ['./transformer.cjs', './transformer.mjs', './transformer.js', './transformer.ts', './transformer']) {
80
+ try {
81
+ return require.resolve(candidate)
82
+ } catch {
83
+ // try the next extension
84
+ }
85
+ }
86
+ throw new Error('rnwind: could not resolve the metro transformer path')
87
+ }
88
+
89
+ /**
90
+ * Resolve the effective cache directory, honoring a user override and
91
+ * falling back to `<projectRoot>/.rnwind`.
92
+ * @param projectRoot Anchor for relative paths.
93
+ * @param override User-supplied option.
94
+ * @returns Absolute cache directory.
95
+ */
96
+ function resolveCacheDir(projectRoot: string, override: string | undefined): string {
97
+ if (!override || override.length === 0) return path.resolve(projectRoot, DEFAULT_CACHE_DIR)
98
+ return path.isAbsolute(override) ? override : path.resolve(projectRoot, override)
99
+ }
100
+
101
+ /**
102
+ * Read the theme CSS and extract `@variant <name>` blocks for the .d.ts
103
+ * generator. Forces construction of `getRnwindState`, then reads
104
+ * `parser.declaredSchemes` (populated synchronously at construction).
105
+ * @param cssEntry Absolute path to theme CSS.
106
+ * @param projectRoot
107
+ * @returns Scheme names (empty when the theme has no variants; `'base'` is filtered).
108
+ */
109
+ function discoverSchemes(cssEntry: string, projectRoot: string): readonly string[] {
110
+ if (!existsSync(cssEntry)) return []
111
+ try {
112
+ const { parser } = getRnwindState(projectRoot)
113
+ return parser.declaredSchemes.filter((name) => name !== 'base')
114
+ } catch {
115
+ return []
116
+ }
117
+ }
118
+
119
+ /** User-facing options for `withRnwindConfig`. */
120
+ export interface RnwindMetroOptions {
121
+ /** Path to the theme CSS (absolute or relative to `projectRoot`). Required. */
122
+ cssEntryFile: string
123
+ /** Where rnwind writes the `.d.ts` file. Set `false` to disable. Defaults to `<projectRoot>/rnwind-types.d.ts`. */
124
+ dtsFile?: string | false
125
+ /** Optional project-root override — defaults to `metroConfig.projectRoot` then `process.cwd()`. */
126
+ projectRoot?: string
127
+ /** Cache directory. Absolute, or relative to `projectRoot`. Default: `.rnwind` at project root. */
128
+ cacheDir?: string
129
+ /**
130
+ * Extra JSX prop-name prefixes that rnwind should rewrite. Each
131
+ * prefix `P` turns `<Tag PClassName="…">` into `<Tag
132
+ * PStyle={lookupCss(…)}>`. The built-in `'contentContainer'` prefix
133
+ * is always on (covers ScrollView / FlatList / SectionList); user
134
+ * entries merge on top.
135
+ */
136
+ classNamePrefixes?: readonly string[]
137
+ }
138
+
139
+ /** Shape we mutate on Metro's config. Loose so we don't pin Metro's internal types. */
140
+ export interface MetroConfigLike {
141
+ projectRoot?: string
142
+ watchFolders?: string[]
143
+ transformer?: {
144
+ babelTransformerPath?: string
145
+ [key: string]: unknown
146
+ }
147
+ resolver?: {
148
+ resolveRequest?: ResolveRequestFn | null
149
+ [key: string]: unknown
150
+ }
151
+ [key: string]: unknown
152
+ }
153
+
154
+ /**
155
+ * Wrap a Metro config with rnwind's pipeline:
156
+ * - Install the rnwind babel transformer.
157
+ * - Chain a `resolveRequest` hook that serves
158
+ * `rnwind/__generated/schemes` from `<cacheDir>/schemes.js`.
159
+ * - Write the `.d.ts` so TypeScript accepts `className=` on RN components.
160
+ * - Publish the theme CSS path + cache dir via env so Metro workers
161
+ * can rebuild their local state.
162
+ * - Ensure the cache dir is a watched folder Metro's haste-map indexes.
163
+ *
164
+ * Theme-edit hot reload happens implicitly: every transformed file
165
+ * imports `rnwind/__generated/schemes`, and that module eager-imports
166
+ * `common.style.js`. When the theme changes, the per-scheme files
167
+ * regenerate with new bytes; Metro's content SHA1 dedup detects the
168
+ * change and invalidates every importer automatically.
169
+ * `getCacheKey()` on the transformer covers the per-file transform
170
+ * cache. No file watcher / source-padding hack needed — the dep graph
171
+ * carries the signal.
172
+ * @param metroConfig Config from `getDefaultConfig(__dirname)` or equivalent.
173
+ * @param options rnwind options.
174
+ * @returns The same config, mutated.
175
+ */
176
+ export function withRnwindConfig<C extends MetroConfigLike>(metroConfig: C, options: RnwindMetroOptions): C {
177
+ const projectRoot = options.projectRoot ?? metroConfig.projectRoot ?? process.cwd()
178
+ const cacheDir = resolveCacheDir(projectRoot, options.cacheDir)
179
+ const cssEntry = path.isAbsolute(options.cssEntryFile) ? options.cssEntryFile : path.resolve(projectRoot, options.cssEntryFile)
180
+
181
+ mkdirSync(cacheDir, { recursive: true })
182
+ const watchFolders = (metroConfig.watchFolders ?? []).filter((p) => typeof p === 'string' && p.length > 0)
183
+ configureRnwindState(cssEntry, cacheDir, watchFolders, options.classNamePrefixes)
184
+
185
+ // Warm the state eagerly (in the Metro master process) so oxide's
186
+ // Scanner walks every project source (and every monorepo
187
+ // watch-folder) ONCE and the manifest + scheme files hold the
188
+ // complete union before Metro's resolver tries to SHA1 them on the
189
+ // first transform. Each worker lazy-repeats this scan on its first
190
+ // transform to converge on identical state.
191
+ try {
192
+ void getRnwindState(projectRoot).builder.ensureFilesExist()
193
+ } catch {
194
+ // Any init error surfaces again at the first transform; don't crash Metro boot.
195
+ }
196
+
197
+ // Install transformer + resolver. Capture the existing
198
+ // babelTransformerPath BEFORE we override it — our worker chains to
199
+ // it (env-passed) so Flow / expo-router / babel-preset-expo etc. all
200
+ // continue to run.
201
+ const existingTransformerPath = metroConfig.transformer?.babelTransformerPath
202
+ if (typeof existingTransformerPath === 'string' && existingTransformerPath.length > 0) {
203
+ process.env.RNWIND_UPSTREAM_TRANSFORMER = existingTransformerPath
204
+ }
205
+ const upstream = metroConfig.resolver?.resolveRequest ?? null
206
+ metroConfig.transformer = { ...metroConfig.transformer, babelTransformerPath: transformerPath() }
207
+ metroConfig.resolver = { ...metroConfig.resolver, resolveRequest: createRnwindResolver(upstream) }
208
+
209
+ // Metro's haste-map indexes `watchFolders` at startup. Adding the
210
+ // cache dir guarantees scheme style files + manifest get SHA1'd
211
+ // without a "Failed to get the SHA-1" race when the first transform
212
+ // writes them.
213
+ const existingWatch = metroConfig.watchFolders ?? []
214
+ metroConfig.watchFolders = existingWatch.includes(cacheDir) ? existingWatch : [...existingWatch, cacheDir]
215
+
216
+ if (options.dtsFile !== false) {
217
+ const dtsPath = options.dtsFile ?? path.resolve(projectRoot, 'rnwind-types.d.ts')
218
+ const schemes = discoverSchemes(cssEntry, projectRoot)
219
+ writeDtsFile(dtsPath, schemes, options.classNamePrefixes)
220
+ }
221
+
222
+ // Watch the theme CSS. On edit, we rewrite scheme files AND touch
223
+ // mtime on every transformed source file so Metro invalidates them
224
+ // and re-transforms — the only reliable way to propagate theme
225
+ // changes to an already-running dev server.
226
+ watchThemeCss(cssEntry, projectRoot)
227
+
228
+ return metroConfig
229
+ }