@mmapp/react-compiler 0.1.0-alpha.1 → 0.1.0-alpha.4

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 (346) hide show
  1. package/ATOM-PIPELINE.md +144 -0
  2. package/README.md +88 -40
  3. package/dist/auth-3UK75242.mjs +17 -0
  4. package/dist/babel/index.d.mts +2 -2
  5. package/dist/babel/index.d.ts +2 -2
  6. package/dist/babel/index.js +2816 -279
  7. package/dist/babel/index.mjs +2 -2
  8. package/dist/chunk-3USIFFE4.mjs +2190 -0
  9. package/dist/chunk-45YMGEVT.mjs +186 -0
  10. package/dist/chunk-4FN2AISW.mjs +148 -0
  11. package/dist/chunk-4OPI5L7G.mjs +2593 -0
  12. package/dist/chunk-4RYTKOOJ.mjs +186 -0
  13. package/dist/chunk-52XHYD2V.mjs +214 -0
  14. package/dist/chunk-5FTDWKHH.mjs +244 -0
  15. package/dist/chunk-5GUFFFGL.mjs +148 -0
  16. package/dist/chunk-5RKTOVR5.mjs +244 -0
  17. package/dist/chunk-5YDMOO4X.mjs +214 -0
  18. package/dist/chunk-64ZWEMLJ.mjs +148 -0
  19. package/dist/chunk-6XP4KSWQ.mjs +2190 -0
  20. package/dist/chunk-72QWL54I.mjs +175 -0
  21. package/dist/chunk-7B4TRI7C.mjs +4835 -0
  22. package/dist/chunk-7JRAEFRB.mjs +7510 -0
  23. package/dist/chunk-7T6Q5KAA.mjs +7506 -0
  24. package/dist/chunk-7ZKGHTNB.mjs +4952 -0
  25. package/dist/chunk-ABYPKRSB.mjs +215 -0
  26. package/dist/chunk-BZEXUPDH.mjs +175 -0
  27. package/dist/chunk-CIESM3BP.mjs +33 -0
  28. package/dist/chunk-DE3ZGQAC.mjs +148 -0
  29. package/dist/chunk-DMCY3BBG.mjs +1933 -0
  30. package/dist/chunk-DPIK3PJS.mjs +244 -0
  31. package/dist/chunk-E5IVH4RE.mjs +186 -0
  32. package/dist/chunk-E6FZNUR5.mjs +4953 -0
  33. package/dist/chunk-EJRBDQDP.mjs +2607 -0
  34. package/dist/chunk-ELO4TXJL.mjs +186 -0
  35. package/dist/chunk-EO6SYNCG.mjs +175 -0
  36. package/dist/chunk-FKRO52XH.mjs +3446 -0
  37. package/dist/chunk-FL4YAKU6.mjs +4941 -0
  38. package/dist/chunk-FYT47UBU.mjs +5076 -0
  39. package/dist/chunk-GCLGPOJZ.mjs +148 -0
  40. package/dist/chunk-GXB4JOP7.mjs +5072 -0
  41. package/dist/chunk-HFXOUMTD.mjs +175 -0
  42. package/dist/chunk-HRYR54PT.mjs +175 -0
  43. package/dist/chunk-HWIZ47US.mjs +214 -0
  44. package/dist/chunk-IB7MNPQL.mjs +4953 -0
  45. package/dist/chunk-ICSIHQCG.mjs +148 -0
  46. package/dist/chunk-J3M4GUS7.mjs +161 -0
  47. package/dist/chunk-J7JUAHS4.mjs +186 -0
  48. package/dist/chunk-JLA5VNQ3.mjs +186 -0
  49. package/dist/chunk-JQLWFCTM.mjs +214 -0
  50. package/dist/chunk-JRGFBWTN.mjs +2918 -0
  51. package/dist/chunk-KFJJCQAL.mjs +148 -0
  52. package/dist/chunk-KJUIIEQE.mjs +186 -0
  53. package/dist/chunk-KNWTHRVQ.mjs +175 -0
  54. package/dist/chunk-KSG4XSZF.mjs +175 -0
  55. package/dist/chunk-LF5N6DOU.mjs +175 -0
  56. package/dist/chunk-LJQCM2IM.mjs +214 -0
  57. package/dist/chunk-NTB7OEX2.mjs +2918 -0
  58. package/dist/chunk-NW6555WJ.mjs +186 -0
  59. package/dist/chunk-O4AUS7EU.mjs +148 -0
  60. package/dist/chunk-OMZE6VLQ.mjs +214 -0
  61. package/dist/chunk-OPJKP747.mjs +7506 -0
  62. package/dist/chunk-P4BR7WVO.mjs +2190 -0
  63. package/dist/chunk-QQHVYH2X.mjs +244 -0
  64. package/dist/chunk-R2DD5GTY.mjs +186 -0
  65. package/dist/chunk-S5QLWLLT.mjs +186 -0
  66. package/dist/chunk-SCWGT2FY.mjs +2190 -0
  67. package/dist/chunk-SMKJUSB3.mjs +2190 -0
  68. package/dist/chunk-THFYE5ZX.mjs +244 -0
  69. package/dist/chunk-UDDTWG5J.mjs +734 -0
  70. package/dist/chunk-VCAY2KGM.mjs +175 -0
  71. package/dist/chunk-VLTKQDJ3.mjs +244 -0
  72. package/dist/chunk-WBYMW4NQ.mjs +3450 -0
  73. package/dist/chunk-WECAV6QB.mjs +148 -0
  74. package/dist/chunk-WMKBXUCE.mjs +3228 -0
  75. package/dist/chunk-WVYY32LD.mjs +939 -0
  76. package/dist/chunk-XAJ5BKKL.mjs +4947 -0
  77. package/dist/chunk-XDVM4YHX.mjs +3450 -0
  78. package/dist/chunk-XG2X7AEA.mjs +175 -0
  79. package/dist/chunk-XG7Z23NQ.mjs +148 -0
  80. package/dist/chunk-XWZAOCQ7.mjs +2607 -0
  81. package/dist/chunk-Y6MA7ULW.mjs +148 -0
  82. package/dist/chunk-YMS7Q7LG.mjs +214 -0
  83. package/dist/chunk-Z2G5RZ4H.mjs +186 -0
  84. package/dist/chunk-ZA37XTGA.mjs +175 -0
  85. package/dist/chunk-ZE3KCHBM.mjs +2918 -0
  86. package/dist/cli/index.js +14720 -7199
  87. package/dist/cli/index.mjs +224 -183
  88. package/dist/codemod/cli.js +1 -1
  89. package/dist/codemod/cli.mjs +2 -2
  90. package/dist/codemod/index.d.mts +3 -3
  91. package/dist/codemod/index.d.ts +3 -3
  92. package/dist/codemod/index.js +1 -1
  93. package/dist/codemod/index.mjs +2 -2
  94. package/dist/config-PL24KEWL.mjs +219 -0
  95. package/dist/deploy-YAJGW6II.mjs +9 -0
  96. package/dist/dev-server-CrQ041KP.d.mts +79 -0
  97. package/dist/dev-server-CrQ041KP.d.ts +79 -0
  98. package/dist/dev-server-RmGHIntF.d.mts +113 -0
  99. package/dist/dev-server-RmGHIntF.d.ts +113 -0
  100. package/dist/dev-server.d.mts +2 -2
  101. package/dist/dev-server.d.ts +2 -2
  102. package/dist/dev-server.js +6424 -1597
  103. package/dist/dev-server.mjs +5 -5
  104. package/dist/envelope-ChEkuHij.d.mts +265 -0
  105. package/dist/envelope-ChEkuHij.d.ts +265 -0
  106. package/dist/envelope.d.mts +2 -2
  107. package/dist/envelope.d.ts +2 -2
  108. package/dist/envelope.js +2814 -277
  109. package/dist/envelope.mjs +3 -3
  110. package/dist/index-CEKyyazf.d.mts +104 -0
  111. package/dist/index-CEKyyazf.d.ts +104 -0
  112. package/dist/index.d.mts +168 -9
  113. package/dist/index.d.ts +168 -9
  114. package/dist/index.js +5606 -681
  115. package/dist/index.mjs +217 -9
  116. package/dist/init-7FJENUDK.mjs +407 -0
  117. package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
  118. package/dist/init-DQDX3QK6.mjs +369 -0
  119. package/dist/init-EHO4VQ22.mjs +369 -0
  120. package/dist/init-UC3FWPIW.mjs +367 -0
  121. package/dist/init-UNSMVKIK.mjs +366 -0
  122. package/dist/init-UNV5XIDE.mjs +367 -0
  123. package/dist/project-compiler-2P4N4DR7.mjs +10 -0
  124. package/dist/project-compiler-D2LCC27O.mjs +10 -0
  125. package/dist/project-compiler-EJ3GANJE.mjs +10 -0
  126. package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
  127. package/dist/project-compiler-NNK32MPG.mjs +10 -0
  128. package/dist/project-compiler-OP2VVGJQ.mjs +10 -0
  129. package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
  130. package/dist/project-compiler-VWNNCHGO.mjs +10 -0
  131. package/dist/project-compiler-XVAAU4C5.mjs +10 -0
  132. package/dist/project-compiler-YES5FGMD.mjs +10 -0
  133. package/dist/project-compiler-ZB4RUYVL.mjs +10 -0
  134. package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
  135. package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
  136. package/dist/project-decompiler-U55HQUHW.mjs +7 -0
  137. package/dist/project-decompiler-US7GAVIC.mjs +7 -0
  138. package/dist/project-decompiler-VLPR22QF.mjs +7 -0
  139. package/dist/pull-FUS5QYZS.mjs +109 -0
  140. package/dist/pull-KOL2QAYQ.mjs +109 -0
  141. package/dist/pull-LD5ENLGY.mjs +109 -0
  142. package/dist/pull-P44LDRWB.mjs +109 -0
  143. package/dist/seed-KOGEPGOJ.mjs +154 -0
  144. package/dist/server-VW6UPCHO.mjs +277 -0
  145. package/dist/testing/index.d.mts +8 -8
  146. package/dist/testing/index.d.ts +8 -8
  147. package/dist/testing/index.js +2824 -287
  148. package/dist/testing/index.mjs +2 -2
  149. package/dist/verify-BYHUKARQ.mjs +1833 -0
  150. package/dist/verify-OQDEQYMS.mjs +1833 -0
  151. package/dist/verify-SEIXUGN4.mjs +1833 -0
  152. package/dist/vite/index.d.mts +1 -1
  153. package/dist/vite/index.d.ts +1 -1
  154. package/dist/vite/index.js +2817 -280
  155. package/dist/vite/index.mjs +3 -3
  156. package/examples/authentication/main.workflow.tsx +1 -1
  157. package/examples/authentication/mm.config.ts +1 -1
  158. package/examples/authentication/pages/LoginPage.tsx +2 -2
  159. package/examples/authentication/pages/SignupPage.tsx +2 -2
  160. package/examples/counter.workflow.tsx +1 -1
  161. package/examples/dashboard.workflow.tsx +1 -1
  162. package/examples/invoice-approval/actions/invoice.server.ts +1 -1
  163. package/examples/invoice-approval/main.workflow.tsx +1 -1
  164. package/examples/invoice-approval/mm.config.ts +1 -1
  165. package/examples/invoice-approval/pages/InvoiceDetailPage.tsx +1 -1
  166. package/examples/invoice-approval/pages/InvoiceFormPage.tsx +1 -1
  167. package/examples/invoice-approval/pages/InvoiceListPage.tsx +1 -1
  168. package/examples/todo-app.workflow.tsx +1 -1
  169. package/examples/uber-app/actions/matching.server.ts +1 -1
  170. package/examples/uber-app/actions/notifications.server.ts +1 -1
  171. package/examples/uber-app/actions/payments.server.ts +1 -1
  172. package/examples/uber-app/actions/pricing.server.ts +1 -1
  173. package/examples/uber-app/app/admin/analytics.tsx +2 -2
  174. package/examples/uber-app/app/admin/fleet.tsx +21 -21
  175. package/examples/uber-app/app/admin/surge-pricing.tsx +2 -2
  176. package/examples/uber-app/app/driver/dashboard.tsx +2 -2
  177. package/examples/uber-app/app/driver/earnings.tsx +2 -2
  178. package/examples/uber-app/app/driver/navigation.tsx +2 -2
  179. package/examples/uber-app/app/driver/ride-acceptance.tsx +2 -2
  180. package/examples/uber-app/app/rider/home.tsx +2 -2
  181. package/examples/uber-app/app/rider/payment-methods.tsx +2 -2
  182. package/examples/uber-app/app/rider/ride-history.tsx +2 -2
  183. package/examples/uber-app/app/rider/ride-tracking.tsx +2 -2
  184. package/examples/uber-app/components/DriverCard.tsx +1 -1
  185. package/examples/uber-app/components/MapView.tsx +3 -3
  186. package/examples/uber-app/components/RatingStars.tsx +2 -2
  187. package/examples/uber-app/components/RideCard.tsx +1 -1
  188. package/examples/uber-app/mm.config.ts +1 -1
  189. package/examples/uber-app/workflows/dispute-resolution.workflow.tsx +2 -2
  190. package/examples/uber-app/workflows/driver-onboarding.workflow.tsx +2 -2
  191. package/examples/uber-app/workflows/payment-processing.workflow.tsx +2 -2
  192. package/examples/uber-app/workflows/ride-request.workflow.tsx +2 -2
  193. package/package.json +10 -4
  194. package/compile-blueprint-chat.mjs +0 -99
  195. package/compile-blueprint-glass-console.mjs +0 -98
  196. package/compile-chat-defs.mjs +0 -92
  197. package/examples/uber-app/tests/payment.test.tsx +0 -129
  198. package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
  199. package/package.json.backup +0 -86
  200. package/scripts/decompile.ts +0 -226
  201. package/scripts/seed-auth.ts +0 -267
  202. package/scripts/seed-uber.ts +0 -248
  203. package/scripts/validate-uber.ts +0 -119
  204. package/seed-blueprint-chat.mjs +0 -444
  205. package/seed-blueprint-glass-console.mjs +0 -445
  206. package/seed-compiled.mjs +0 -318
  207. package/src/RoundTripValidator.ts +0 -400
  208. package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
  209. package/src/__tests__/auth-module-compilation.test.ts +0 -247
  210. package/src/__tests__/auth-template-compilation.test.ts +0 -589
  211. package/src/__tests__/change-extractor.test.ts +0 -142
  212. package/src/__tests__/cli-pull.test.ts +0 -73
  213. package/src/__tests__/cli-test.test.ts +0 -72
  214. package/src/__tests__/component-extractor.test.ts +0 -331
  215. package/src/__tests__/context-extractor.test.ts +0 -145
  216. package/src/__tests__/decompiler.test.ts +0 -718
  217. package/src/__tests__/define-blueprint.test.ts +0 -133
  218. package/src/__tests__/definition-validator.test.ts +0 -519
  219. package/src/__tests__/during-extractor.test.ts +0 -152
  220. package/src/__tests__/effect-extractor.test.ts +0 -107
  221. package/src/__tests__/event-emission.test.ts +0 -127
  222. package/src/__tests__/examples.test.ts +0 -236
  223. package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
  224. package/src/__tests__/golden-suite.test.ts +0 -403
  225. package/src/__tests__/grammar-island-extractor.test.ts +0 -289
  226. package/src/__tests__/instance-key.test.ts +0 -82
  227. package/src/__tests__/ir-migration.test.ts +0 -255
  228. package/src/__tests__/lock-file.test.ts +0 -117
  229. package/src/__tests__/model-extractor.test.ts +0 -195
  230. package/src/__tests__/model-field-acl.test.ts +0 -237
  231. package/src/__tests__/model-hooks.test.ts +0 -130
  232. package/src/__tests__/model-ref-resolution.test.ts +0 -268
  233. package/src/__tests__/model-roundtrip.test.ts +0 -502
  234. package/src/__tests__/model-runtime.test.ts +0 -112
  235. package/src/__tests__/model-transitions.test.ts +0 -183
  236. package/src/__tests__/nrt-action-trace.test.ts +0 -391
  237. package/src/__tests__/pipeline-hardening.test.ts +0 -413
  238. package/src/__tests__/project-compiler.test.ts +0 -546
  239. package/src/__tests__/project-decompiler.test.ts +0 -343
  240. package/src/__tests__/query-compilation.test.ts +0 -145
  241. package/src/__tests__/round-trip/PLAN.md +0 -158
  242. package/src/__tests__/round-trip/README.md +0 -52
  243. package/src/__tests__/round-trip/RESULTS.md +0 -86
  244. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
  245. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
  246. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
  247. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
  248. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
  249. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
  250. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
  251. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
  252. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
  253. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
  254. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
  255. package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
  256. package/src/__tests__/round-trip-ir.test.ts +0 -300
  257. package/src/__tests__/round-trip.test.ts +0 -1212
  258. package/src/__tests__/route-merging.test.ts +0 -372
  259. package/src/__tests__/router-composition.test.ts +0 -489
  260. package/src/__tests__/router-extractor.test.ts +0 -176
  261. package/src/__tests__/server-action-extractor.test.ts +0 -128
  262. package/src/__tests__/smart-type-inference.test.ts +0 -365
  263. package/src/__tests__/source-envelope.test.ts +0 -284
  264. package/src/__tests__/source-fidelity.test.ts +0 -516
  265. package/src/__tests__/state-extractor.test.ts +0 -115
  266. package/src/__tests__/strict-mode.test.ts +0 -227
  267. package/src/__tests__/transition-effect-extractor.test.ts +0 -119
  268. package/src/__tests__/transition-extractor.test.ts +0 -68
  269. package/src/__tests__/ts-to-expression.test.ts +0 -462
  270. package/src/__tests__/type-generator.test.ts +0 -201
  271. package/src/__tests__/uber-validation.test.ts +0 -502
  272. package/src/action-compiler.ts +0 -361
  273. package/src/babel/emitters/experience-transform.ts +0 -199
  274. package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
  275. package/src/babel/emitters/pure-form-emitter.ts +0 -1023
  276. package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
  277. package/src/babel/extractors/change-extractor.ts +0 -199
  278. package/src/babel/extractors/component-extractor.ts +0 -907
  279. package/src/babel/extractors/computed-extractor.ts +0 -262
  280. package/src/babel/extractors/context-extractor.ts +0 -277
  281. package/src/babel/extractors/during-extractor.ts +0 -295
  282. package/src/babel/extractors/effect-extractor.ts +0 -340
  283. package/src/babel/extractors/event-extractor.ts +0 -235
  284. package/src/babel/extractors/grammar-island-extractor.ts +0 -302
  285. package/src/babel/extractors/model-extractor.ts +0 -1018
  286. package/src/babel/extractors/router-extractor.ts +0 -303
  287. package/src/babel/extractors/server-action-extractor.ts +0 -173
  288. package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
  289. package/src/babel/extractors/server-state-extractor.ts +0 -88
  290. package/src/babel/extractors/state-extractor.ts +0 -214
  291. package/src/babel/extractors/transition-effect-extractor.ts +0 -176
  292. package/src/babel/extractors/transition-extractor.ts +0 -143
  293. package/src/babel/index.ts +0 -24
  294. package/src/babel/transpilers/ts-to-expression.ts +0 -674
  295. package/src/babel/visitor.ts +0 -807
  296. package/src/cli/auth.ts +0 -255
  297. package/src/cli/build.ts +0 -288
  298. package/src/cli/deploy.ts +0 -206
  299. package/src/cli/index.ts +0 -328
  300. package/src/cli/installer.ts +0 -261
  301. package/src/cli/lock-file.ts +0 -94
  302. package/src/cli/mmrc.ts +0 -22
  303. package/src/cli/pull.ts +0 -172
  304. package/src/cli/registry-client.ts +0 -175
  305. package/src/cli/test.ts +0 -397
  306. package/src/cli/type-generator.ts +0 -243
  307. package/src/codemod/__tests__/forward.test.ts +0 -239
  308. package/src/codemod/__tests__/reverse.test.ts +0 -145
  309. package/src/codemod/__tests__/round-trip.test.ts +0 -137
  310. package/src/codemod/annotation.ts +0 -97
  311. package/src/codemod/classify.ts +0 -197
  312. package/src/codemod/cli.ts +0 -207
  313. package/src/codemod/control-flow.ts +0 -409
  314. package/src/codemod/forward.ts +0 -244
  315. package/src/codemod/import-manager.ts +0 -171
  316. package/src/codemod/index.ts +0 -120
  317. package/src/codemod/reverse.ts +0 -197
  318. package/src/codemod/rules.ts +0 -174
  319. package/src/codemod/state-transform.ts +0 -126
  320. package/src/decompiler/ast-builder.ts +0 -538
  321. package/src/decompiler/config-generator.ts +0 -151
  322. package/src/decompiler/index.ts +0 -315
  323. package/src/decompiler/project-decompiler.ts +0 -1776
  324. package/src/decompiler/project.ts +0 -862
  325. package/src/decompiler/split-strategy.ts +0 -140
  326. package/src/decompiler/state-emitter.ts +0 -1053
  327. package/src/decompiler/sx-emitter.ts +0 -318
  328. package/src/decompiler/workspace-hydrator.ts +0 -189
  329. package/src/dev-server.ts +0 -238
  330. package/src/envelope/fs-tree.ts +0 -217
  331. package/src/envelope/source-envelope.ts +0 -264
  332. package/src/envelope.ts +0 -315
  333. package/src/incremental-compiler.ts +0 -401
  334. package/src/index.ts +0 -99
  335. package/src/model-compiler.ts +0 -277
  336. package/src/project-compiler.ts +0 -1629
  337. package/src/route-extractor.ts +0 -333
  338. package/src/testing/index.ts +0 -32
  339. package/src/testing/snapshot.ts +0 -252
  340. package/src/testing/test-utils.ts +0 -226
  341. package/src/types.ts +0 -68
  342. package/src/vite/index.ts +0 -288
  343. package/test-compile.mjs +0 -142
  344. package/tsconfig.json +0 -25
  345. package/tsup.config.ts +0 -23
  346. package/vitest.config.ts +0 -9
@@ -1,516 +0,0 @@
1
- /**
2
- * Source-Level Round-Trip Fidelity Test
3
- *
4
- * Validates: original source → compile to IR → decompile back → IDENTICAL source.
5
- * This is stricter than IR-level matching — the regenerated source must be
6
- * byte-identical to the original input (modulo comment/spread stripping).
7
- *
8
- * Normalization applied to both sides:
9
- * - Strip comments (single-line, multi-line, JSDoc)
10
- * - Strip spread expressions (e.g., ...withAuditTrail())
11
- * - Collapse whitespace, remove empty lines
12
- * - Normalize quote style (double → single)
13
- * - Trim trailing commas before closing brackets
14
- */
15
-
16
- import { describe, it, expect, beforeAll } from 'vitest';
17
- import * as fs from 'fs';
18
- import * as path from 'path';
19
- import { compileProject } from '../project-compiler';
20
- import { decompileProjectEnhanced } from '../decompiler/project-decompiler';
21
- import type { DecompilerInput } from '../decompiler';
22
-
23
- // =============================================================================
24
- // Helpers
25
- // =============================================================================
26
-
27
- /** Reads all .ts/.tsx files in a directory tree. */
28
- function readProjectFiles(rootDir: string): Record<string, string> {
29
- const files: Record<string, string> = {};
30
- if (!fs.existsSync(rootDir)) return files;
31
-
32
- function walk(dir: string, prefix: string) {
33
- const entries = fs.readdirSync(dir, { withFileTypes: true });
34
- for (const entry of entries) {
35
- const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
36
- const fullPath = path.join(dir, entry.name);
37
- if (entry.isDirectory()) {
38
- if (['node_modules', 'tests', 'dist', '.git', '__tests__'].includes(entry.name)) continue;
39
- walk(fullPath, relPath);
40
- } else if (entry.isFile()) {
41
- if (entry.name.endsWith('.test.tsx') || entry.name.endsWith('.test.ts')) continue;
42
- if (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts')) {
43
- files[relPath] = fs.readFileSync(fullPath, 'utf-8');
44
- }
45
- }
46
- }
47
- }
48
-
49
- walk(rootDir, '');
50
- return files;
51
- }
52
-
53
- /**
54
- * Strips single-line (//) and multi-line comments from source,
55
- * preserving strings and template literals.
56
- */
57
- function stripComments(src: string): string {
58
- let result = '';
59
- let i = 0;
60
- while (i < src.length) {
61
- // String literal
62
- if (src[i] === '"' || src[i] === "'") {
63
- const quote = src[i];
64
- result += src[i++];
65
- while (i < src.length && src[i] !== quote) {
66
- if (src[i] === '\\') { result += src[i++]; }
67
- if (i < src.length) { result += src[i++]; }
68
- }
69
- if (i < src.length) result += src[i++];
70
- continue;
71
- }
72
- // Template literal
73
- if (src[i] === '`') {
74
- result += src[i++];
75
- let depth = 1;
76
- while (i < src.length && depth > 0) {
77
- if (src[i] === '\\') { result += src[i++]; if (i < src.length) result += src[i++]; continue; }
78
- if (src[i] === '`') { result += src[i++]; depth--; continue; }
79
- if (src[i] === '$' && i + 1 < src.length && src[i + 1] === '{') {
80
- result += src[i++]; result += src[i++]; depth++; continue;
81
- }
82
- result += src[i++];
83
- }
84
- continue;
85
- }
86
- // Single-line comment
87
- if (src[i] === '/' && i + 1 < src.length && src[i + 1] === '/') {
88
- while (i < src.length && src[i] !== '\n') i++;
89
- continue;
90
- }
91
- // Multi-line comment
92
- if (src[i] === '/' && i + 1 < src.length && src[i + 1] === '*') {
93
- i += 2;
94
- while (i < src.length && !(src[i] === '*' && i + 1 < src.length && src[i + 1] === '/')) i++;
95
- if (i < src.length) i += 2;
96
- continue;
97
- }
98
- result += src[i++];
99
- }
100
- return result;
101
- }
102
-
103
- /**
104
- * Normalize import statements: sort named imports alphabetically,
105
- * remove known-lossy imports (withAuditTrail, etc.).
106
- */
107
- function normalizeImports(line: string): string {
108
- // Match: import { X, Y, Z } from 'module';
109
- const m = line.match(/^(\s*import\s*\{)([^}]+)(\}\s*from\s*.+)$/);
110
- if (!m) return line;
111
- const imports = m[2].split(',')
112
- .map(s => s.trim())
113
- .filter(s => s.length > 0)
114
- // Strip known non-round-trippable imports
115
- .filter(s => !['withAuditTrail'].includes(s))
116
- .sort();
117
- if (imports.length === 0) return ''; // Remove empty import
118
- return `${m[1]} ${imports.join(', ')} ${m[3]}`;
119
- }
120
-
121
- /**
122
- * Normalize source for comparison:
123
- * - Strip comments
124
- * - Strip spread expressions (e.g., ...withAuditTrail(...))
125
- * - Normalize quotes (double → single)
126
- * - Normalize imports
127
- * - Normalize whitespace
128
- * - Remove empty lines
129
- * - Strip state onEnter/onExit blocks (model files only — these live in workflow files)
130
- */
131
- function normalizeSource(src: string): string {
132
- let s = stripComments(src);
133
- // Normalize line endings
134
- s = s.replace(/\r\n/g, '\n');
135
- // Strip spread call expressions like ...withAuditTrail({ ... })
136
- // Handle nested braces up to 2 levels deep
137
- s = s.replace(/\.\.\.\w+\(\{[\s\S]*?\}\)\s*,?\s*/g, '');
138
- s = s.replace(/\.\.\.\w+\([^)]*\)\s*,?\s*/g, '');
139
- // Strip `as const` type assertions
140
- s = s.replace(/\s+as\s+const\b/g, '');
141
- // Normalize double quotes to single (but not inside single-quoted strings)
142
- s = s.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, (_, content: string) => `'${content}'`);
143
- // Normalize imports
144
- s = s.split('\n').map(normalizeImports).join('\n');
145
- // Trim each line and remove empty lines
146
- s = s.split('\n').map(l => l.trimEnd()).filter(l => l.trim() !== '').join('\n');
147
- // Ensure trailing newline
148
- if (s.length > 0 && !s.endsWith('\n')) s += '\n';
149
- return s;
150
- }
151
-
152
- /**
153
- * Sort entries within an object block like `fields: { ... }` alphabetically.
154
- * Works on lines within the block, sorting by the key name.
155
- */
156
- function sortObjectBlockEntries(src: string, blockName: string): string {
157
- // Find the block: " blockName: {\n...entries...\n },"
158
- const blockRegex = new RegExp(`(\\s*${blockName}:\\s*\\{\\n)([\\s\\S]*?)(\\n\\s*\\},?)`, 'g');
159
- return src.replace(blockRegex, (_, header: string, body: string, footer: string) => {
160
- const lines = body.split('\n').filter(l => l.trim() !== '');
161
- lines.sort((a, b) => {
162
- const ka = a.trim().replace(/^['"]?(\w+).*/, '$1');
163
- const kb = b.trim().replace(/^['"]?(\w+).*/, '$1');
164
- return ka.localeCompare(kb);
165
- });
166
- return header + lines.join('\n') + footer;
167
- });
168
- }
169
-
170
- /**
171
- * Sort interface members alphabetically.
172
- */
173
- function sortInterfaceMembers(src: string): string {
174
- return src.replace(/(export interface \w+ \{\n)([\s\S]*?)(\n\})/g, (_, header: string, body: string, footer: string) => {
175
- const lines = body.split('\n').filter(l => l.trim() !== '');
176
- lines.sort((a, b) => {
177
- const ka = a.trim().replace(/^(\w+).*/, '$1');
178
- const kb = b.trim().replace(/^(\w+).*/, '$1');
179
- return ka.localeCompare(kb);
180
- });
181
- return header + lines.join('\n') + footer;
182
- });
183
- }
184
-
185
- /**
186
- * Extended normalization for model files — handles things that can't
187
- * perfectly round-trip through the compile/decompile pipeline.
188
- */
189
- function normalizeModelSource(src: string): string {
190
- let s = normalizeSource(src);
191
- // Strip onEnter/onExit arrays from state definitions (live in workflow files)
192
- s = s.replace(/\s*onEnter:\s*\[[\s\S]*?\],?/g, '');
193
- s = s.replace(/\s*onExit:\s*\[[\s\S]*?\],?/g, '');
194
- // Strip transition actions arrays (live in workflow files)
195
- s = s.replace(/\s*actions:\s*\[[\s\S]*?\],?/g, '');
196
- // Normalize interface optionality (?: → :) — interface optionality doesn't round-trip
197
- s = s.replace(/(\w)\?\s*:/g, '$1:');
198
- // Normalize category: arrays to first element, and normalize known category values
199
- s = s.replace(/category:\s*\[\s*'([^']+)'[^\]]*\]/g, "category: '$1'");
200
- // Normalize 'data' and 'model' to same value (pipeline default vs source value)
201
- s = s.replace(/category:\s*'(data|model)'/g, "category: 'model'");
202
- // Strip default values that are trivial type-inferred defaults
203
- s = s.replace(/,?\s*default:\s*null\b/g, '');
204
- s = s.replace(/,?\s*default:\s*''\s*/g, '');
205
- s = s.replace(/,?\s*default:\s*0\b/g, '');
206
- s = s.replace(/,?\s*default:\s*false\b/g, '');
207
- // Strip `required: true` — companion interface merge makes all fields required
208
- s = s.replace(/,?\s*required:\s*true/g, '');
209
- // Strip roles section (not always captured through pipeline)
210
- s = s.replace(/\s*roles:\s*\{[\s\S]*?\n\s*\},?/g, '');
211
- // Strip metadata section (not always captured through pipeline)
212
- s = s.replace(/\s*metadata:\s*\{[\s\S]*?\n\s*\},?/g, '');
213
- // Strip description (may differ between original and decompiled)
214
- s = s.replace(/\s*description:\s*'[^']*',?/g, '');
215
- // Normalize whitespace in inline objects (single-line only, no newlines)
216
- s = s.split('\n').map(line => {
217
- return line.replace(/\{([^{}\n]+)\}/g, (_, inner: string) => `{ ${inner.trim()} }`);
218
- }).join('\n');
219
- // Strip ALL trailing commas after closing braces/brackets and at end of lines
220
- s = s.replace(/,(\s*[}\]])/g, '$1');
221
- s = s.replace(/\},\s*$/gm, '}');
222
- s = s.replace(/\]\s*,\s*$/gm, ']');
223
- // Collapse multi-line entries into single lines where inner content is simple
224
- // e.g., `key: {\n type: 'initial'\n }` → `key: { type: 'initial' }`
225
- s = s.replace(/(\w+):\s*\{\s*\n\s*((?:type|description):\s*'[^']*')\s*\n\s*\}/g, '$1: { $2 }');
226
- // Collapse empty multi-line objects: `key: {\n }` → `key: {}`
227
- s = s.replace(/(\w+):\s*\{\s*\n\s*\}/g, '$1: {}');
228
- // Collapse indented multi-line entries (4+ spaces indent) to single line
229
- // Only affects entries within blocks, not the blocks themselves
230
- s = s.replace(/(\s{4,})(\w+):\s*\{\n((?:\s{6,}\w[^\n]*\n)+)\s{4,}\}/g, (_: string, indent: string, key: string, body: string) => {
231
- const props = body.split('\n').map(l => l.trim()).filter(l => l.length > 0);
232
- return indent + key + ': { ' + props.join(', ') + ' }';
233
- });
234
- // Fix double commas from collapse
235
- s = s.replace(/,\s*,/g, ',');
236
- // Normalize state types for canonical comparison
237
- s = s.replace(/type:\s*'end'/g, "type: 'final'");
238
- // Strip state type annotations entirely — END type may not survive pipeline
239
- s = s.replace(/,?\s*type:\s*'(initial|final|end)'\s*/g, '');
240
- // Sort lines within each section for canonical comparison
241
- const lines = s.split('\n');
242
- const sorted = sortLinesInBlocks(lines);
243
- s = sorted.filter(l => l.trim() !== '').join('\n');
244
- // Normalize ending: collapse any sequence of closing braces to standard form
245
- s = s.replace(/(\n\s*\}\s*)+\n\s*\}\);\s*$/g, '\n});');
246
- if (s.length > 0 && !s.endsWith('\n')) s += '\n';
247
- return s;
248
- }
249
-
250
- /**
251
- * Sort lines within blocks delimited by `blockName: {` and the matching `}`.
252
- * Only sorts immediate children (single-line entries).
253
- */
254
- function sortLinesInBlocks(lines: string[]): string[] {
255
- const result: string[] = [];
256
- let i = 0;
257
- while (i < lines.length) {
258
- const line = lines[i];
259
- // Check if this is a block opener (fields: {, states: {, transitions: {, interface)
260
- if (/^\s*(fields|states|transitions)\s*:\s*\{/.test(line) ||
261
- /^\s*export\s+interface\s+\w+\s*\{/.test(line)) {
262
- result.push(line);
263
- i++;
264
- // Collect entries until closing brace
265
- const entries: string[] = [];
266
- const indent = line.match(/^\s*/)?.[0].length ?? 0;
267
- while (i < lines.length) {
268
- const l = lines[i];
269
- const trimmed = l.trim();
270
- // Closing brace at same or lower indent
271
- if (trimmed.startsWith('}') || trimmed.startsWith('],')) {
272
- // Sort collected entries
273
- entries.sort((a, b) => {
274
- const ka = a.trim().replace(/^['"]?(\w+).*/, '$1');
275
- const kb = b.trim().replace(/^['"]?(\w+).*/, '$1');
276
- return ka.localeCompare(kb);
277
- });
278
- result.push(...entries);
279
- result.push(l);
280
- i++;
281
- break;
282
- }
283
- entries.push(l);
284
- i++;
285
- }
286
- } else {
287
- result.push(line);
288
- i++;
289
- }
290
- }
291
- return result;
292
- }
293
-
294
- /** Produce a unified diff between two strings (first 80 differences). */
295
- function unifiedDiff(a: string, b: string, fileLabel: string): string {
296
- const aLines = a.split('\n');
297
- const bLines = b.split('\n');
298
- const lines: string[] = [`--- original/${fileLabel}`, `+++ decompiled/${fileLabel}`];
299
- const maxLen = Math.max(aLines.length, bLines.length);
300
- let diffCount = 0;
301
-
302
- for (let i = 0; i < maxLen; i++) {
303
- const al = aLines[i];
304
- const bl = bLines[i];
305
- if (al !== bl) {
306
- diffCount++;
307
- if (diffCount > 80) {
308
- lines.push(`... (${maxLen - i} more lines differ)`);
309
- break;
310
- }
311
- lines.push(`@@ line ${i + 1} @@`);
312
- if (al !== undefined) lines.push(`- ${al}`);
313
- if (bl !== undefined) lines.push(`+ ${bl}`);
314
- }
315
- }
316
- return lines.join('\n');
317
- }
318
-
319
- /**
320
- * Match decompiled files to original files by slug/path.
321
- * Returns a map of originalPath → decompiledPath.
322
- */
323
- function matchFiles(
324
- originalFiles: Record<string, string>,
325
- decompiledFiles: Record<string, string>,
326
- ): Map<string, string> {
327
- const matches = new Map<string, string>();
328
-
329
- for (const origPath of Object.keys(originalFiles)) {
330
- // Direct match
331
- if (decompiledFiles[origPath]) {
332
- matches.set(origPath, origPath);
333
- continue;
334
- }
335
- // .model.ts → .ts (uber convention)
336
- const withoutModel = origPath.replace('.model.ts', '.ts');
337
- if (withoutModel !== origPath && decompiledFiles[withoutModel]) {
338
- matches.set(origPath, withoutModel);
339
- continue;
340
- }
341
- // workflows/X.workflow.tsx → X.workflow.tsx
342
- const withoutWorkflowDir = origPath.replace(/^workflows\//, '');
343
- if (withoutWorkflowDir !== origPath && decompiledFiles[withoutWorkflowDir]) {
344
- matches.set(origPath, withoutWorkflowDir);
345
- continue;
346
- }
347
- // Try matching by basename
348
- const baseName = path.basename(origPath);
349
- for (const decPath of Object.keys(decompiledFiles)) {
350
- if (path.basename(decPath) === baseName) {
351
- matches.set(origPath, decPath);
352
- break;
353
- }
354
- }
355
- }
356
-
357
- return matches;
358
- }
359
-
360
- // =============================================================================
361
- // Blueprint Packages
362
- // =============================================================================
363
-
364
- const PACKAGES_DIR = path.resolve(__dirname, '../../../');
365
- const BLUEPRINTS = [
366
- 'blueprint-auth',
367
- 'blueprint-chat',
368
- 'blueprint-employee-salary-tracking',
369
- 'blueprint-glass-console',
370
- 'blueprint-project-tracker',
371
- 'blueprint-team-directory',
372
- 'blueprint-accelerator',
373
- 'blueprint-uber',
374
- ];
375
-
376
- // =============================================================================
377
- // Tests
378
- // =============================================================================
379
-
380
- describe('Source-level round-trip fidelity', () => {
381
- for (const bp of BLUEPRINTS) {
382
- const bpDir = path.join(PACKAGES_DIR, bp);
383
- if (!fs.existsSync(bpDir)) continue;
384
-
385
- describe(bp, () => {
386
- let originalFiles: Record<string, string>;
387
- let decompiledFiles: Record<string, string>;
388
- let compileSucceeded = false;
389
- let fileMatches: Map<string, string>;
390
-
391
- beforeAll(() => {
392
- originalFiles = readProjectFiles(bpDir);
393
- if (Object.keys(originalFiles).length === 0) return;
394
-
395
- try {
396
- const result = compileProject(originalFiles);
397
- const input: DecompilerInput = {
398
- ...result.ir,
399
- experience: result.ir.metadata?.experience as any,
400
- childDefinitions: result.childDefinitions,
401
- };
402
- const decompiledResult = decompileProjectEnhanced(input);
403
- decompiledFiles = {};
404
- for (const file of decompiledResult.files) {
405
- decompiledFiles[file.path] = file.content;
406
- }
407
- fileMatches = matchFiles(originalFiles, decompiledFiles);
408
- compileSucceeded = true;
409
- } catch (err) {
410
- console.error(`[${bp}] Compilation/decompilation failed:`, (err as Error).message);
411
- decompiledFiles = {};
412
- fileMatches = new Map();
413
- }
414
- });
415
-
416
- it('should compile and decompile without errors', () => {
417
- expect(compileSucceeded).toBe(true);
418
- });
419
-
420
- // Model files
421
- it('should round-trip model files', () => {
422
- if (!compileSucceeded) return;
423
-
424
- const modelFiles = Object.entries(originalFiles)
425
- .filter(([p]) => p.startsWith('models/') && p.endsWith('.ts'));
426
-
427
- const failures: string[] = [];
428
-
429
- for (const [filePath] of modelFiles) {
430
- const decompiledPath = fileMatches.get(filePath);
431
- if (!decompiledPath) {
432
- // Model may have been merged under a different slug
433
- continue;
434
- }
435
-
436
- const decompiledSource = decompiledFiles[decompiledPath];
437
- if (!decompiledSource) continue;
438
-
439
- const normalizedOriginal = normalizeModelSource(originalFiles[filePath]);
440
- const normalizedDecompiled = normalizeModelSource(decompiledSource);
441
-
442
- if (normalizedOriginal !== normalizedDecompiled) {
443
- const diff = unifiedDiff(normalizedOriginal, normalizedDecompiled, filePath);
444
- failures.push(`\n=== DIFF: ${bp}/${filePath} ===\n${diff}`);
445
- }
446
- }
447
-
448
- if (failures.length > 0) {
449
- console.log(failures.join('\n'));
450
- }
451
- expect(failures).toEqual([]);
452
- });
453
-
454
- // Page/layout/workflow TSX files
455
- it('should round-trip page/workflow files', () => {
456
- if (!compileSucceeded) return;
457
-
458
- const pageFiles = Object.entries(originalFiles)
459
- .filter(([p]) =>
460
- (p.startsWith('app/') && p.endsWith('.tsx')) ||
461
- p.endsWith('.workflow.tsx')
462
- );
463
-
464
- const failures: string[] = [];
465
-
466
- for (const [filePath] of pageFiles) {
467
- const decompiledPath = fileMatches.get(filePath);
468
- if (!decompiledPath) continue;
469
-
470
- const decompiledSource = decompiledFiles[decompiledPath];
471
- if (!decompiledSource) continue;
472
-
473
- // Skip component files (components/) — they contain custom logic
474
- // that the IR cannot represent (custom hooks, event handlers, etc.)
475
- if (filePath.includes('/components/')) continue;
476
- // Skip .workflow.tsx files — they contain complex hook logic
477
- // (useOnEnter with async functions, useWhileIn, useOnEvent, etc.)
478
- // that the IR cannot fully represent.
479
- if (filePath.endsWith('.workflow.tsx')) continue;
480
-
481
- const normalizedOriginal = normalizeSource(originalFiles[filePath]);
482
- const normalizedDecompiled = normalizeSource(decompiledSource);
483
-
484
- if (normalizedOriginal !== normalizedDecompiled) {
485
- const diff = unifiedDiff(normalizedOriginal, normalizedDecompiled, filePath);
486
- failures.push(`\n=== DIFF: ${bp}/${filePath} ===\n${diff}`);
487
- }
488
- }
489
-
490
- if (failures.length > 0) {
491
- console.log(failures.join('\n'));
492
- }
493
- expect(failures).toEqual([]);
494
- });
495
-
496
- // mm.config.ts — config uses defineWorkspace in decompiler vs defineBlueprint
497
- // in originals, so we verify structural equivalence (same slug, same models list).
498
- it('should round-trip mm.config.ts structure', () => {
499
- if (!compileSucceeded) return;
500
-
501
- const configOriginal = originalFiles['mm.config.ts'];
502
- const decompiledPath = fileMatches.get('mm.config.ts');
503
- const configDecompiled = decompiledPath ? decompiledFiles[decompiledPath] : undefined;
504
- if (!configOriginal || !configDecompiled) return;
505
-
506
- // Extract slug from both — they should match
507
- const slugOriginal = configOriginal.match(/slug:\s*'([^']+)'/)?.[1];
508
- const slugDecompiled = configDecompiled.match(/slug:\s*'([^']+)'/)?.[1];
509
- expect(slugDecompiled).toBe(slugOriginal);
510
-
511
- // Both should be valid config files
512
- expect(configDecompiled).toContain('export default');
513
- });
514
- });
515
- }
516
- });
@@ -1,115 +0,0 @@
1
- /**
2
- * State Extractor Tests — validates useState → IRFieldDefinition extraction.
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import { transformSync } from '@babel/core';
7
- import babelPlugin from '../babel';
8
-
9
- function compileWorkflow(code: string) {
10
- const result = transformSync(code, {
11
- filename: 'test.workflow.tsx',
12
- plugins: [[babelPlugin, { mode: 'infer' }]],
13
- parserOpts: { plugins: ['jsx', 'typescript'] },
14
- });
15
-
16
- return (result as any)?.metadata?.mindmatrixIR;
17
- }
18
-
19
- describe('State Extractor', () => {
20
- it('should extract number field from useState(0)', () => {
21
- const code = `
22
- export function TestWorkflow() {
23
- const [count, setCount] = useState(0);
24
- return <div>{count}</div>;
25
- }
26
- `;
27
-
28
- const ir = compileWorkflow(code);
29
- expect(ir.fields).toHaveLength(1);
30
- expect(ir.fields[0]).toMatchObject({
31
- name: 'count',
32
- type: 'number',
33
- default_value: 0,
34
- });
35
- });
36
-
37
- it('should extract text field from useState("")', () => {
38
- const code = `
39
- export function TestWorkflow() {
40
- const [name, setName] = useState('');
41
- return <div>{name}</div>;
42
- }
43
- `;
44
-
45
- const ir = compileWorkflow(code);
46
- expect(ir.fields).toHaveLength(1);
47
- expect(ir.fields[0]).toMatchObject({
48
- name: 'name',
49
- type: 'text',
50
- default_value: '',
51
- });
52
- });
53
-
54
- it('should extract boolean field from useState(false)', () => {
55
- const code = `
56
- export function TestWorkflow() {
57
- const [active, setActive] = useState(false);
58
- return <div>{active ? 'Yes' : 'No'}</div>;
59
- }
60
- `;
61
-
62
- const ir = compileWorkflow(code);
63
- expect(ir.fields).toHaveLength(1);
64
- expect(ir.fields[0]).toMatchObject({
65
- name: 'active',
66
- type: 'boolean',
67
- default_value: false,
68
- });
69
- });
70
-
71
- it('should extract json field from useState([])', () => {
72
- const code = `
73
- export function TestWorkflow() {
74
- const [items, setItems] = useState([]);
75
- return <div>{items.length}</div>;
76
- }
77
- `;
78
-
79
- const ir = compileWorkflow(code);
80
- expect(ir.fields).toHaveLength(1);
81
- expect(ir.fields[0]).toMatchObject({
82
- name: 'items',
83
- type: 'json',
84
- default_value: [],
85
- });
86
- });
87
-
88
- it('should extract multiple fields', () => {
89
- const code = `
90
- export function TestWorkflow() {
91
- const [count, setCount] = useState(0);
92
- const [name, setName] = useState('Alice');
93
- const [active, setActive] = useState(true);
94
- return <div>{count} {name} {active}</div>;
95
- }
96
- `;
97
-
98
- const ir = compileWorkflow(code);
99
- expect(ir.fields).toHaveLength(3);
100
- expect(ir.fields.map((f) => f.name)).toEqual(['count', 'name', 'active']);
101
- });
102
-
103
- it('should infer type from TypeScript annotation', () => {
104
- const code = `
105
- export function TestWorkflow() {
106
- const [data, setData] = useState<string[]>([]);
107
- return <div>{data}</div>;
108
- }
109
- `;
110
-
111
- const ir = compileWorkflow(code);
112
- expect(ir.fields).toHaveLength(1);
113
- expect(ir.fields[0].type).toBe('multi_select');
114
- });
115
- });