@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,862 +0,0 @@
1
- /**
2
- * project.ts — Multi-file project decompiler.
3
- *
4
- * Takes a full IRWorkflowDefinition and produces a project file tree:
5
- * - main.workflow.tsx (root layout + routing + state machine hooks)
6
- * - pages/*.tsx (separate page components per route/view)
7
- * - models/*.ts (TypeScript interfaces from field definitions)
8
- * - actions/*.server.ts (server action stubs from transition actions)
9
- * - mm.config.ts (blueprint configuration: defineBlueprint)
10
- *
11
- * This is the multi-file counterpart to the single-file decompile() function.
12
- * It splits complex definitions into cohesive, focused files matching the
13
- * envelope fs-tree file role conventions (view-entry, page, model, server-action, config).
14
- */
15
-
16
- import type {
17
- IRWorkflowDefinition,
18
- IRExperienceNode,
19
- IRFieldDefinition,
20
- IRStateDefinition,
21
- IRTransitionDefinition,
22
- IRActionDefinition,
23
- } from '@mindmatrix/player-core';
24
- import { decompile } from './index';
25
- import type { DecompilerInput, DecompileOptions } from './index';
26
- import type { FileRole } from '../envelope/fs-tree';
27
-
28
- // =============================================================================
29
- // Public Types
30
- // =============================================================================
31
-
32
- /** A single file in the decompiled project. */
33
- export interface ProjectFile {
34
- /** Relative path from project root (e.g. "main.workflow.tsx"). */
35
- path: string;
36
- /** File role matching the envelope fs-tree convention. */
37
- role: FileRole;
38
- /** Generated source code. */
39
- content: string;
40
- }
41
-
42
- /** Result of decompiling a definition into a multi-file project. */
43
- export interface DecompileProjectResult {
44
- /** All generated files. */
45
- files: ProjectFile[];
46
- /** The main entry file path. */
47
- entryFile: string;
48
- /** The slug used for naming. */
49
- slug: string;
50
- }
51
-
52
- /** Options for project decompilation. */
53
- export interface DecompileProjectOptions extends DecompileOptions {
54
- /** Skip generating mm.config.ts. Default: false. */
55
- skipConfig?: boolean;
56
- /** Skip generating model files. Default: false. */
57
- skipModels?: boolean;
58
- /** Skip generating server action stubs. Default: false. */
59
- skipActions?: boolean;
60
- /** Skip splitting pages into separate files. Default: false. */
61
- skipPages?: boolean;
62
- /** Skip decompiling child definitions into separate files. Default: false. */
63
- skipChildren?: boolean;
64
- }
65
-
66
- // =============================================================================
67
- // Name Helpers
68
- // =============================================================================
69
-
70
- function pascalCase(slug: string): string {
71
- return slug
72
- .split(/[-_]/)
73
- .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
74
- .join('');
75
- }
76
-
77
- function camelCase(str: string): string {
78
- return str.replace(/[-_]([a-z])/g, (_, c: string) => c.toUpperCase());
79
- }
80
-
81
- /** Map IR field type to TypeScript type string, with union type support. */
82
- function fieldTypeToTS(fieldType: string, field?: IRFieldDefinition): string {
83
- // Check for options → emit union type
84
- const options = getFieldOptions(field);
85
- if (options && options.length > 0 && (fieldType === 'select' || fieldType === 'text')) {
86
- return options.map(o => `'${o.replace(/'/g, "\\'")}'`).join(' | ');
87
- }
88
-
89
- switch (fieldType) {
90
- case 'text':
91
- case 'rich_text':
92
- case 'email':
93
- case 'url':
94
- case 'phone':
95
- case 'color':
96
- case 'select':
97
- return 'string';
98
- case 'number':
99
- case 'currency':
100
- case 'percentage':
101
- case 'rating':
102
- case 'duration':
103
- case 'auto_number':
104
- return 'number';
105
- case 'boolean':
106
- return 'boolean';
107
- case 'date':
108
- case 'datetime':
109
- case 'created_at':
110
- case 'updated_at':
111
- return 'Date';
112
- case 'multi_select':
113
- return 'string[]';
114
- case 'json':
115
- return 'Record<string, unknown>';
116
- case 'file':
117
- case 'image':
118
- return 'string'; // URL
119
- case 'relation':
120
- case 'lookup':
121
- return 'string'; // ID reference
122
- default:
123
- return 'unknown';
124
- }
125
- }
126
-
127
- /** Extracts options from field validation, metadata, or legacy field.options. */
128
- function getFieldOptions(field?: IRFieldDefinition): string[] | null {
129
- if (!field) return null;
130
- const f = field as unknown as Record<string, unknown>;
131
- const validation = f.validation as Record<string, unknown> | undefined;
132
- if (validation?.options && Array.isArray(validation.options)) return validation.options as string[];
133
- const meta = f.metadata as Record<string, unknown> | undefined;
134
- if (meta?.options && Array.isArray(meta.options)) return meta.options as string[];
135
- if (f.options && Array.isArray(f.options)) return f.options as string[];
136
- return null;
137
- }
138
-
139
- // =============================================================================
140
- // Model Generation
141
- // =============================================================================
142
-
143
- /**
144
- * Generates a TypeScript interface file from field definitions.
145
- *
146
- * Output example:
147
- * export interface Invoice {
148
- * amount: number;
149
- * vendor: string;
150
- * date: Date;
151
- * status: string;
152
- * }
153
- */
154
- function generateModelFile(
155
- slug: string,
156
- fields: IRFieldDefinition[],
157
- ): string {
158
- const typeName = pascalCase(slug);
159
- const lines: string[] = [
160
- `/**`,
161
- ` * ${typeName} — data model interface.`,
162
- ` *`,
163
- ` * Auto-generated from workflow definition "${slug}".`,
164
- ` * Fields: ${fields.length}`,
165
- ` */`,
166
- ``,
167
- `export interface ${typeName} {`,
168
- ];
169
-
170
- for (const field of fields) {
171
- const tsType = fieldTypeToTS(field.type, field);
172
- const optional = field.required ? '' : '?';
173
- const comment = field.label ? ` /** ${field.label} */` : '';
174
- if (comment) lines.push(` ${comment}`);
175
- lines.push(` ${camelCase(field.name)}${optional}: ${tsType};`);
176
- }
177
-
178
- lines.push(`}`);
179
- lines.push(``);
180
-
181
- // Export state enum if states exist (added by caller)
182
- return lines.join('\n');
183
- }
184
-
185
- /**
186
- * Generates a state enum from state definitions.
187
- *
188
- * Output:
189
- * export enum InvoiceState {
190
- * Draft = 'draft',
191
- * Submitted = 'submitted',
192
- * }
193
- */
194
- function generateStateEnum(
195
- slug: string,
196
- states: IRStateDefinition[],
197
- ): string {
198
- if (states.length === 0) return '';
199
-
200
- const enumName = `${pascalCase(slug)}State`;
201
- const lines: string[] = [
202
- ``,
203
- `export enum ${enumName} {`,
204
- ];
205
-
206
- for (const state of states) {
207
- const enumKey = pascalCase(state.name);
208
- lines.push(` ${enumKey} = '${state.name}',`);
209
- }
210
-
211
- lines.push(`}`);
212
- lines.push(``);
213
- return lines.join('\n');
214
- }
215
-
216
- // =============================================================================
217
- // Server Action Generation
218
- // =============================================================================
219
-
220
- /** Extracts unique server-side action types from states and transitions. */
221
- function extractServerActions(
222
- states: IRStateDefinition[],
223
- transitions: IRTransitionDefinition[],
224
- ): Array<{ name: string; type: string; config: Record<string, unknown> }> {
225
- const seen = new Set<string>();
226
- const actions: Array<{ name: string; type: string; config: Record<string, unknown> }> = [];
227
-
228
- const SERVER_ACTION_TYPES = new Set([
229
- 'http_request', 'webhook', 'call_webhook',
230
- 'notify', 'send_notification',
231
- 'call_workflow', 'spawn_instance', 'spawn_subworkflow',
232
- 'emit_event',
233
- ]);
234
-
235
- function collectFromActions(defs: IRActionDefinition[]) {
236
- for (const action of defs) {
237
- if (SERVER_ACTION_TYPES.has(action.type) && !seen.has(action.id)) {
238
- seen.add(action.id);
239
- const name = actionToFunctionName(action);
240
- actions.push({ name, type: action.type, config: action.config });
241
- }
242
- }
243
- }
244
-
245
- for (const state of states) {
246
- collectFromActions(state.on_enter);
247
- collectFromActions(state.on_exit);
248
- for (const during of state.during) {
249
- collectFromActions(during.actions);
250
- }
251
- }
252
- for (const trans of transitions) {
253
- collectFromActions(trans.actions);
254
- }
255
-
256
- return actions;
257
- }
258
-
259
- function actionToFunctionName(action: IRActionDefinition): string {
260
- // Use a descriptive name from config or fall back to type + id
261
- const slug = (action.config.name ?? action.config.event ?? action.config.slug ?? action.type) as string;
262
- return camelCase(String(slug).replace(/[^a-zA-Z0-9_-]/g, '-'));
263
- }
264
-
265
- /**
266
- * Generates a *.server.ts file with server action stubs.
267
- *
268
- * Output example:
269
- * import type { TransitionContext } from '@mindmatrix/react';
270
- *
271
- * export async function sendNotification(ctx: TransitionContext) {
272
- * // TODO: Implement notification logic
273
- * }
274
- */
275
- function generateServerActionFile(
276
- actions: Array<{ name: string; type: string; config: Record<string, unknown> }>,
277
- ): string {
278
- const lines: string[] = [
279
- `/**`,
280
- ` * Server actions — backend handlers for workflow transitions.`,
281
- ` *`,
282
- ` * These functions run server-side during state transitions.`,
283
- ` * Each receives a TransitionContext with instance data and utilities.`,
284
- ` */`,
285
- ``,
286
- `import type { TransitionContext } from '@mindmatrix/react';`,
287
- ``,
288
- ];
289
-
290
- for (const action of actions) {
291
- const comment = actionComment(action.type, action.config);
292
- lines.push(`/** ${comment} */`);
293
- lines.push(`export async function ${action.name}(ctx: TransitionContext): Promise<void> {`);
294
- lines.push(` const { instance, env } = ctx;`);
295
-
296
- // Emit a type-specific stub body
297
- switch (action.type) {
298
- case 'http_request':
299
- case 'webhook':
300
- case 'call_webhook': {
301
- const url = String(action.config.url ?? 'https://api.example.com/webhook');
302
- const method = String(action.config.method ?? 'POST');
303
- lines.push(` await fetch('${url}', {`);
304
- lines.push(` method: '${method}',`);
305
- lines.push(` headers: { 'Content-Type': 'application/json' },`);
306
- lines.push(` body: JSON.stringify({ instanceId: instance.id }),`);
307
- lines.push(` });`);
308
- break;
309
- }
310
- case 'notify':
311
- case 'send_notification': {
312
- const msg = String(action.config.message ?? action.config.body ?? 'Notification');
313
- lines.push(` // Send notification: "${msg}"`);
314
- lines.push(` await env.notify({ message: '${msg}', instanceId: instance.id });`);
315
- break;
316
- }
317
- case 'call_workflow':
318
- case 'spawn_instance':
319
- case 'spawn_subworkflow': {
320
- const slug = String(action.config.slug ?? action.config.workflow ?? 'child-workflow');
321
- lines.push(` await env.spawn('${slug}', { parentId: instance.id });`);
322
- break;
323
- }
324
- case 'emit_event': {
325
- const event = String(action.config.event ?? action.config.name ?? 'custom-event');
326
- lines.push(` await env.emit('${event}', { instanceId: instance.id });`);
327
- break;
328
- }
329
- default:
330
- lines.push(` // TODO: Implement ${action.type} logic`);
331
- }
332
-
333
- lines.push(`}`);
334
- lines.push(``);
335
- }
336
-
337
- return lines.join('\n');
338
- }
339
-
340
- function actionComment(type: string, config: Record<string, unknown>): string {
341
- switch (type) {
342
- case 'http_request':
343
- case 'webhook':
344
- case 'call_webhook':
345
- return `HTTP ${config.method ?? 'POST'} to ${config.url ?? 'webhook endpoint'}`;
346
- case 'notify':
347
- case 'send_notification':
348
- return `Send notification: ${config.message ?? config.body ?? ''}`;
349
- case 'call_workflow':
350
- case 'spawn_instance':
351
- case 'spawn_subworkflow':
352
- return `Spawn child workflow: ${config.slug ?? config.workflow ?? ''}`;
353
- case 'emit_event':
354
- return `Emit event: ${config.event ?? config.name ?? ''}`;
355
- default:
356
- return `Server action: ${type}`;
357
- }
358
- }
359
-
360
- // =============================================================================
361
- // Config Generation
362
- // =============================================================================
363
-
364
- /**
365
- * Generates mm.config.ts with defineBlueprint configuration.
366
- */
367
- function generateConfigFile(def: IRWorkflowDefinition): string {
368
- const lines: string[] = [
369
- `/**`,
370
- ` * Blueprint configuration for "${def.name || pascalCase(def.slug)}".`,
371
- ` */`,
372
- ``,
373
- `import { defineBlueprint } from '@mindmatrix/react';`,
374
- ``,
375
- `export default defineBlueprint({`,
376
- ` slug: '${def.slug}',`,
377
- ` name: '${(def.name || pascalCase(def.slug)).replace(/'/g, "\\'")}',`,
378
- ` version: '${def.version}',`,
379
- ` category: '${def.category}',`,
380
- ];
381
-
382
- if (def.description) {
383
- lines.push(` description: '${def.description.replace(/'/g, "\\'")}',`);
384
- }
385
-
386
- // Roles
387
- if (def.roles && def.roles.length > 0) {
388
- lines.push(` roles: [`);
389
- for (const role of def.roles) {
390
- const perms = role.permissions.map(p => `'${p}'`).join(', ');
391
- lines.push(` { name: '${role.name}', permissions: [${perms}] },`);
392
- }
393
- lines.push(` ],`);
394
- }
395
-
396
- lines.push(`});`);
397
- lines.push(``);
398
- return lines.join('\n');
399
- }
400
-
401
- // =============================================================================
402
- // Page Extraction
403
- // =============================================================================
404
-
405
- interface ExtractedPage {
406
- /** Route path or state name this page represents. */
407
- route: string;
408
- /** PascalCase component name for the page. */
409
- componentName: string;
410
- /** The experience subtree for this page. */
411
- tree: IRExperienceNode;
412
- /** File path relative to project root. */
413
- filePath: string;
414
- }
415
-
416
- /**
417
- * Extracts page components from an experience tree.
418
- *
419
- * Heuristics for page detection:
420
- * 1. Top-level children with visible_when conditions referencing route/state
421
- * 2. Children with id containing "page", "route", "view"
422
- * 3. Children wrapped in Show components with route conditions
423
- */
424
- function extractPages(
425
- experience: IRExperienceNode | undefined,
426
- slug: string,
427
- ): ExtractedPage[] {
428
- if (!experience || !experience.children) return [];
429
-
430
- const pages: ExtractedPage[] = [];
431
- const rootChildren = experience.children;
432
-
433
- for (const child of rootChildren) {
434
- // Check for route-conditional sections (visible_when with route/activeRoute)
435
- const isRoutePage = child.visible_when && (
436
- /activeRoute|route|isOn/i.test(child.visible_when)
437
- );
438
-
439
- // Check for page-like IDs
440
- const isPageId = child.id && (
441
- /page|route|view/i.test(child.id)
442
- );
443
-
444
- // Check for Show wrappers with route conditions
445
- const isShowRoute = child.component === 'Show' && child.config?.when &&
446
- /activeRoute|route/i.test(String(child.config.when));
447
-
448
- if (isRoutePage || isPageId || isShowRoute) {
449
- // Extract route name from visible_when or id
450
- const routeName = extractRouteName(child) || child.id || `page-${pages.length}`;
451
- const componentName = pascalCase(routeName.replace(/^\//, '').replace(/\//g, '-') || `${slug}-page`);
452
-
453
- pages.push({
454
- route: routeName,
455
- componentName,
456
- tree: child,
457
- filePath: `pages/${componentName}.tsx`,
458
- });
459
- }
460
- }
461
-
462
- return pages;
463
- }
464
-
465
- function extractRouteName(node: IRExperienceNode): string {
466
- // Try to extract route from visible_when: "activeRoute === '/dashboard'"
467
- if (node.visible_when) {
468
- const match = node.visible_when.match(/['"]\/([^'"]+)['"]/);
469
- if (match) return match[1];
470
- // Try: "isOnDashboard" → "dashboard"
471
- const boolMatch = node.visible_when.match(/isOn([A-Z]\w+)/);
472
- if (boolMatch) return boolMatch[1].toLowerCase();
473
- }
474
- // Try from Show when config
475
- if (node.config?.when) {
476
- const match = String(node.config.when).match(/['"]\/([^'"]+)['"]/);
477
- if (match) return match[1];
478
- }
479
- return '';
480
- }
481
-
482
- // =============================================================================
483
- // Page File Generation
484
- // =============================================================================
485
-
486
- /**
487
- * Generates a page component TSX file from an extracted page.
488
- * Uses the single-file decompiler for the experience subtree.
489
- */
490
- function generatePageFile(page: ExtractedPage, _slug: string): string {
491
- // Create a minimal definition with just the experience subtree
492
- const pageInput: DecompilerInput = {
493
- slug: page.route || 'page',
494
- name: page.componentName,
495
- version: '1.0.0',
496
- category: 'page',
497
- states: [],
498
- transitions: [],
499
- fields: [],
500
- roles: [],
501
- experience: page.tree,
502
- };
503
-
504
- const result = decompile(pageInput, {
505
- componentName: page.componentName,
506
- includeAnnotation: false,
507
- });
508
-
509
- return result.code;
510
- }
511
-
512
- // =============================================================================
513
- // Main Entry File Generation
514
- // =============================================================================
515
-
516
- /**
517
- * Generates the main.workflow.tsx file.
518
- *
519
- * When pages are extracted, this file contains:
520
- * - Field declarations (useState)
521
- * - State effects (useOnEnter/useOnExit)
522
- * - Transition declarations (useTransition)
523
- * - Page imports
524
- * - Root layout with page rendering
525
- */
526
- function generateMainFile(
527
- def: DecompilerInput,
528
- pages: ExtractedPage[],
529
- hasModel: boolean,
530
- serverActionNames: string[],
531
- options?: DecompileOptions,
532
- ): string {
533
- // If no pages to split, use the single-file decompiler directly
534
- if (pages.length === 0) {
535
- const result = decompile(def, options);
536
- return result.code;
537
- }
538
-
539
- // Build main file with page imports and layout
540
- const slug = def.slug;
541
- const componentName = options?.componentName ?? pascalCase(slug);
542
-
543
- const importLines: string[] = [];
544
-
545
- // Collect @mindmatrix/react imports based on what's used
546
- const reactImports = new Set<string>();
547
-
548
- // Fields need useState
549
- const nonComputed = def.fields.filter(f => !f.computed);
550
- if (nonComputed.length > 0) reactImports.add('useState');
551
-
552
- // State effects
553
- for (const state of def.states) {
554
- if (state.on_enter.length > 0) reactImports.add('useOnEnter');
555
- if (state.on_exit.length > 0) reactImports.add('useOnExit');
556
- if (state.during.length > 0) reactImports.add('useWhileIn');
557
- }
558
-
559
- // Transitions
560
- if (def.transitions.length > 0) reactImports.add('useTransition');
561
-
562
- // Layout atoms for the root wrapper
563
- reactImports.add('Stack');
564
-
565
- if (reactImports.size > 0) {
566
- const sorted = [...reactImports].sort();
567
- importLines.push(`import { ${sorted.join(', ')} } from '@mindmatrix/react';`);
568
- }
569
-
570
- // Model import
571
- if (hasModel) {
572
- const typeName = pascalCase(slug);
573
- importLines.push(`import type { ${typeName} } from './models/${slug}';`);
574
- }
575
-
576
- // Server action imports
577
- if (serverActionNames.length > 0) {
578
- const names = serverActionNames.join(', ');
579
- importLines.push(`import { ${names} } from './actions/${slug}.server';`);
580
- }
581
-
582
- // Page imports
583
- for (const page of pages) {
584
- importLines.push(`import { ${page.componentName} } from './${page.filePath.replace(/\.tsx$/, '')}';`);
585
- }
586
-
587
- // Build component body lines
588
- const bodyLines: string[] = [];
589
-
590
- // Field declarations
591
- for (const field of nonComputed) {
592
- const name = camelCase(field.name);
593
- const setter = `set${pascalCase(field.name)}`;
594
- const defaultVal = fieldDefaultLiteral(field);
595
- bodyLines.push(` const [${name}, ${setter}] = useState(${defaultVal});`);
596
- }
597
-
598
- if (nonComputed.length > 0) bodyLines.push(``);
599
-
600
- // State effects
601
- for (const state of def.states) {
602
- if (state.on_enter.length > 0) {
603
- const actions = state.on_enter.map(a => actionToInlineCode(a)).filter(Boolean);
604
- bodyLines.push(` useOnEnter('${state.name}', () => {`);
605
- for (const a of actions) bodyLines.push(` ${a}`);
606
- bodyLines.push(` });`);
607
- }
608
- if (state.on_exit.length > 0) {
609
- const actions = state.on_exit.map(a => actionToInlineCode(a)).filter(Boolean);
610
- bodyLines.push(` useOnExit('${state.name}', () => {`);
611
- for (const a of actions) bodyLines.push(` ${a}`);
612
- bodyLines.push(` });`);
613
- }
614
- }
615
-
616
- // Transitions
617
- for (const trans of def.transitions) {
618
- const varName = camelCase(trans.name);
619
- const from = trans.from.length === 1
620
- ? `'${trans.from[0]}'`
621
- : `[${trans.from.map(f => `'${f}'`).join(', ')}]`;
622
- bodyLines.push(` const ${varName} = useTransition('${trans.name}', { from: ${from}, to: '${trans.to}' });`);
623
- }
624
-
625
- if (def.transitions.length > 0) bodyLines.push(``);
626
-
627
- // Build root layout JSX with page rendering
628
- bodyLines.push(` return (`);
629
- bodyLines.push(` <Stack sx={{ minHeight: '100vh' }}>`);
630
- for (const page of pages) {
631
- bodyLines.push(` <${page.componentName} />`);
632
- }
633
- bodyLines.push(` </Stack>`);
634
- bodyLines.push(` );`);
635
-
636
- // Assemble file
637
- const lines: string[] = [
638
- ...importLines,
639
- ``,
640
- `/**`,
641
- ` * @workflow slug="${slug}" version="${def.version}" category="${def.category}"`,
642
- ...(def.description ? [` * @description ${def.description}`] : []),
643
- ` */`,
644
- `export default function ${componentName}() {`,
645
- ...bodyLines,
646
- `}`,
647
- ``,
648
- ];
649
-
650
- return lines.join('\n');
651
- }
652
-
653
- function fieldDefaultLiteral(field: IRFieldDefinition): string {
654
- if (field.default_value !== undefined) {
655
- if (typeof field.default_value === 'string') return `'${field.default_value}'`;
656
- return JSON.stringify(field.default_value);
657
- }
658
- switch (field.type) {
659
- case 'text': case 'rich_text': case 'email': case 'url': case 'phone':
660
- case 'color': case 'select':
661
- return "''";
662
- case 'number': case 'currency': case 'percentage': case 'rating':
663
- case 'duration': case 'auto_number':
664
- return '0';
665
- case 'boolean':
666
- return 'false';
667
- case 'date': case 'datetime':
668
- return 'null';
669
- case 'multi_select': case 'json':
670
- return '[]';
671
- default:
672
- return 'null';
673
- }
674
- }
675
-
676
- function actionToInlineCode(action: IRActionDefinition): string {
677
- switch (action.type) {
678
- case 'set_field': {
679
- const field = String(action.config.field ?? '');
680
- const setter = `set${pascalCase(field)}`;
681
- const val = action.config.expression
682
- ? String(action.config.expression)
683
- : JSON.stringify(action.config.value ?? null);
684
- return `${setter}(${val});`;
685
- }
686
- case 'log_event':
687
- case 'log':
688
- return `console.log('${action.config.message ?? action.config.event ?? action.type}');`;
689
- case 'noop':
690
- return '';
691
- default:
692
- return `// ${action.type}: ${JSON.stringify(action.config)}`;
693
- }
694
- }
695
-
696
- // =============================================================================
697
- // Main: decompileProject
698
- // =============================================================================
699
-
700
- /**
701
- * Decompiles an IR definition into a multi-file project structure.
702
- *
703
- * Produces files matching the envelope fs-tree file role conventions:
704
- * - main.workflow.tsx → view-entry (root layout + state machine)
705
- * - pages/*.tsx → page (per-route components)
706
- * - models/<slug>.ts → model (TypeScript interfaces)
707
- * - actions/<slug>.server.ts → server-action (backend handlers)
708
- * - mm.config.ts → config (defineBlueprint)
709
- *
710
- * For simple definitions (no states/transitions/models), falls back
711
- * to single-file output in main.workflow.tsx.
712
- */
713
- export function decompileProject(
714
- definition: DecompilerInput,
715
- options?: DecompileProjectOptions,
716
- ): DecompileProjectResult {
717
- const files: ProjectFile[] = [];
718
- const slug = definition.slug;
719
-
720
- // 1. Extract pages from experience tree
721
- const pages = options?.skipPages
722
- ? []
723
- : extractPages(definition.experience, slug);
724
-
725
- // 2. Extract server actions from states + transitions
726
- const serverActions = options?.skipActions
727
- ? []
728
- : extractServerActions(definition.states, definition.transitions);
729
-
730
- // 3. Generate model file
731
- const hasFields = definition.fields.length > 0;
732
- if (hasFields && !options?.skipModels) {
733
- let modelContent = generateModelFile(slug, definition.fields);
734
- if (definition.states.length > 0) {
735
- modelContent += generateStateEnum(slug, definition.states);
736
- }
737
- files.push({
738
- path: `models/${slug}.ts`,
739
- role: 'model',
740
- content: modelContent,
741
- });
742
- }
743
-
744
- // 4. Generate server action file
745
- if (serverActions.length > 0) {
746
- files.push({
747
- path: `actions/${slug}.server.ts`,
748
- role: 'server-action',
749
- content: generateServerActionFile(serverActions),
750
- });
751
- }
752
-
753
- // 5. Generate page files
754
- for (const page of pages) {
755
- files.push({
756
- path: page.filePath,
757
- role: 'page',
758
- content: generatePageFile(page, slug),
759
- });
760
- }
761
-
762
- // 6. Generate config file
763
- if (!options?.skipConfig) {
764
- files.push({
765
- path: 'mm.config.ts',
766
- role: 'config',
767
- content: generateConfigFile(definition),
768
- });
769
- }
770
-
771
- // 7. Decompile child definitions into separate .workflow.tsx files
772
- // Each child becomes its own file in the project (UBER-SCALE support)
773
- if (definition.childDefinitions && definition.childDefinitions.length > 0) {
774
- for (const child of definition.childDefinitions) {
775
- const childSlug = child.slug;
776
- const childFileName = `${childSlug}.workflow.tsx`;
777
-
778
- // Build a DecompilerInput from the child definition
779
- const childInput: DecompilerInput = {
780
- ...child,
781
- experience: (child as any).views?.default as IRExperienceNode | undefined,
782
- };
783
-
784
- // Generate model files for child data definitions
785
- if (child.category === 'data' && child.fields.length > 0 && !options?.skipModels) {
786
- let childModelContent = generateModelFile(childSlug, child.fields);
787
- if (child.states.length > 0) {
788
- childModelContent += generateStateEnum(childSlug, child.states);
789
- }
790
- files.push({
791
- path: `models/${childSlug}.ts`,
792
- role: 'model',
793
- content: childModelContent,
794
- });
795
- }
796
-
797
- // Generate server action files for child workflows with server actions
798
- if (!options?.skipActions) {
799
- const childServerActions = extractServerActions(child.states, child.transitions);
800
- if (childServerActions.length > 0) {
801
- files.push({
802
- path: `actions/${childSlug}.server.ts`,
803
- role: 'server-action',
804
- content: generateServerActionFile(childServerActions),
805
- });
806
- }
807
- }
808
-
809
- // Generate the child workflow file itself
810
- const childResult = decompile(childInput, {
811
- componentName: pascalCase(childSlug),
812
- includeAnnotation: true,
813
- });
814
-
815
- files.push({
816
- path: childFileName,
817
- role: 'view-entry',
818
- content: childResult.code,
819
- });
820
- }
821
- }
822
-
823
- // 8. Generate main entry file
824
- const mainContent = generateMainFile(
825
- definition,
826
- pages,
827
- hasFields && !options?.skipModels,
828
- serverActions.map(a => a.name),
829
- options,
830
- );
831
-
832
- files.push({
833
- path: 'main.workflow.tsx',
834
- role: 'view-entry',
835
- content: mainContent,
836
- });
837
-
838
- return {
839
- files,
840
- entryFile: 'main.workflow.tsx',
841
- slug,
842
- };
843
- }
844
-
845
- /**
846
- * Returns true if a definition is complex enough to warrant multi-file output.
847
- *
848
- * Criteria: has states + transitions + either fields or experience tree.
849
- */
850
- export function shouldDecompileAsProject(def: IRWorkflowDefinition): boolean {
851
- // Check for child definitions in metadata (UBER-SCALE projects)
852
- const meta = def.metadata as Record<string, unknown> | undefined;
853
- const childSlugs = meta?.childSlugs;
854
- const hasChildren = Array.isArray(childSlugs) && childSlugs.length > 0;
855
-
856
- return (
857
- hasChildren ||
858
- (def.states.length > 1 &&
859
- def.transitions.length > 0 &&
860
- (def.fields.length > 0 || def.category === 'blueprint'))
861
- );
862
- }