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

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 (265) hide show
  1. package/ATOM-PIPELINE.md +144 -0
  2. package/README.md +88 -40
  3. package/dist/babel/index.js +2814 -277
  4. package/dist/babel/index.mjs +2 -2
  5. package/dist/chunk-3USIFFE4.mjs +2190 -0
  6. package/dist/chunk-45YMGEVT.mjs +186 -0
  7. package/dist/chunk-4FN2AISW.mjs +148 -0
  8. package/dist/chunk-4OPI5L7G.mjs +2593 -0
  9. package/dist/chunk-4RYTKOOJ.mjs +186 -0
  10. package/dist/chunk-52XHYD2V.mjs +214 -0
  11. package/dist/chunk-5GUFFFGL.mjs +148 -0
  12. package/dist/chunk-5RKTOVR5.mjs +244 -0
  13. package/dist/chunk-5YDMOO4X.mjs +214 -0
  14. package/dist/chunk-64ZWEMLJ.mjs +148 -0
  15. package/dist/chunk-6XP4KSWQ.mjs +2190 -0
  16. package/dist/chunk-72QWL54I.mjs +175 -0
  17. package/dist/chunk-7B4TRI7C.mjs +4835 -0
  18. package/dist/chunk-7ZKGHTNB.mjs +4952 -0
  19. package/dist/chunk-CIESM3BP.mjs +33 -0
  20. package/dist/chunk-DE3ZGQAC.mjs +148 -0
  21. package/dist/chunk-DMCY3BBG.mjs +1933 -0
  22. package/dist/chunk-DPIK3PJS.mjs +244 -0
  23. package/dist/chunk-E5IVH4RE.mjs +186 -0
  24. package/dist/chunk-E6FZNUR5.mjs +4953 -0
  25. package/dist/chunk-EJRBDQDP.mjs +2607 -0
  26. package/dist/chunk-ELO4TXJL.mjs +186 -0
  27. package/dist/chunk-EO6SYNCG.mjs +175 -0
  28. package/dist/chunk-FKRO52XH.mjs +3446 -0
  29. package/dist/chunk-FL4YAKU6.mjs +4941 -0
  30. package/dist/chunk-FYT47UBU.mjs +5076 -0
  31. package/dist/chunk-GCLGPOJZ.mjs +148 -0
  32. package/dist/chunk-GXB4JOP7.mjs +5072 -0
  33. package/dist/chunk-HFXOUMTD.mjs +175 -0
  34. package/dist/chunk-HWIZ47US.mjs +214 -0
  35. package/dist/chunk-IB7MNPQL.mjs +4953 -0
  36. package/dist/chunk-ICSIHQCG.mjs +148 -0
  37. package/dist/chunk-J7JUAHS4.mjs +186 -0
  38. package/dist/chunk-JLA5VNQ3.mjs +186 -0
  39. package/dist/chunk-JQLWFCTM.mjs +214 -0
  40. package/dist/chunk-KFJJCQAL.mjs +148 -0
  41. package/dist/chunk-KJUIIEQE.mjs +186 -0
  42. package/dist/chunk-KNWTHRVQ.mjs +175 -0
  43. package/dist/chunk-KSG4XSZF.mjs +175 -0
  44. package/dist/chunk-LF5N6DOU.mjs +175 -0
  45. package/dist/chunk-LJQCM2IM.mjs +214 -0
  46. package/dist/chunk-NTB7OEX2.mjs +2918 -0
  47. package/dist/chunk-NW6555WJ.mjs +186 -0
  48. package/dist/chunk-OMZE6VLQ.mjs +214 -0
  49. package/dist/chunk-OPJKP747.mjs +7506 -0
  50. package/dist/chunk-P4BR7WVO.mjs +2190 -0
  51. package/dist/chunk-QQHVYH2X.mjs +244 -0
  52. package/dist/chunk-S5QLWLLT.mjs +186 -0
  53. package/dist/chunk-SCWGT2FY.mjs +2190 -0
  54. package/dist/chunk-SMKJUSB3.mjs +2190 -0
  55. package/dist/chunk-THFYE5ZX.mjs +244 -0
  56. package/dist/chunk-VCAY2KGM.mjs +175 -0
  57. package/dist/chunk-WBYMW4NQ.mjs +3450 -0
  58. package/dist/chunk-WECAV6QB.mjs +148 -0
  59. package/dist/chunk-WMKBXUCE.mjs +3228 -0
  60. package/dist/chunk-XAJ5BKKL.mjs +4947 -0
  61. package/dist/chunk-XG2X7AEA.mjs +175 -0
  62. package/dist/chunk-XG7Z23NQ.mjs +148 -0
  63. package/dist/chunk-XWZAOCQ7.mjs +2607 -0
  64. package/dist/chunk-Y6MA7ULW.mjs +148 -0
  65. package/dist/chunk-YMS7Q7LG.mjs +214 -0
  66. package/dist/chunk-ZA37XTGA.mjs +175 -0
  67. package/dist/cli/index.js +13189 -6838
  68. package/dist/cli/index.mjs +140 -22
  69. package/dist/codemod/cli.mjs +1 -1
  70. package/dist/codemod/index.mjs +1 -1
  71. package/dist/config-PL24KEWL.mjs +219 -0
  72. package/dist/dev-server-RmGHIntF.d.mts +113 -0
  73. package/dist/dev-server-RmGHIntF.d.ts +113 -0
  74. package/dist/dev-server.d.mts +1 -1
  75. package/dist/dev-server.d.ts +1 -1
  76. package/dist/dev-server.js +4135 -440
  77. package/dist/dev-server.mjs +5 -5
  78. package/dist/envelope.js +2812 -275
  79. package/dist/envelope.mjs +3 -3
  80. package/dist/index.d.mts +161 -2
  81. package/dist/index.d.ts +161 -2
  82. package/dist/index.js +4429 -428
  83. package/dist/index.mjs +217 -9
  84. package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
  85. package/dist/init-DQDX3QK6.mjs +369 -0
  86. package/dist/init-EHO4VQ22.mjs +369 -0
  87. package/dist/init-UC3FWPIW.mjs +367 -0
  88. package/dist/init-UNSMVKIK.mjs +366 -0
  89. package/dist/init-UNV5XIDE.mjs +367 -0
  90. package/dist/project-compiler-2P4N4DR7.mjs +10 -0
  91. package/dist/project-compiler-D2LCC27O.mjs +10 -0
  92. package/dist/project-compiler-EJ3GANJE.mjs +10 -0
  93. package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
  94. package/dist/project-compiler-OP2VVGJQ.mjs +10 -0
  95. package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
  96. package/dist/project-compiler-VWNNCHGO.mjs +10 -0
  97. package/dist/project-compiler-XVAAU4C5.mjs +10 -0
  98. package/dist/project-compiler-YES5FGMD.mjs +10 -0
  99. package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
  100. package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
  101. package/dist/project-decompiler-US7GAVIC.mjs +7 -0
  102. package/dist/project-decompiler-VLPR22QF.mjs +7 -0
  103. package/dist/pull-FUS5QYZS.mjs +109 -0
  104. package/dist/pull-LD5ENLGY.mjs +109 -0
  105. package/dist/pull-P44LDRWB.mjs +109 -0
  106. package/dist/testing/index.js +2822 -285
  107. package/dist/testing/index.mjs +2 -2
  108. package/dist/verify-SEIXUGN4.mjs +1833 -0
  109. package/dist/vite/index.js +2815 -278
  110. package/dist/vite/index.mjs +3 -3
  111. package/examples/uber-app/app/admin/fleet.tsx +19 -19
  112. package/package.json +16 -6
  113. package/compile-blueprint-chat.mjs +0 -99
  114. package/compile-blueprint-glass-console.mjs +0 -98
  115. package/compile-chat-defs.mjs +0 -92
  116. package/examples/uber-app/tests/payment.test.tsx +0 -129
  117. package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
  118. package/package.json.backup +0 -86
  119. package/scripts/decompile.ts +0 -226
  120. package/scripts/seed-auth.ts +0 -267
  121. package/scripts/seed-uber.ts +0 -248
  122. package/scripts/validate-uber.ts +0 -119
  123. package/seed-blueprint-chat.mjs +0 -444
  124. package/seed-blueprint-glass-console.mjs +0 -445
  125. package/seed-compiled.mjs +0 -318
  126. package/src/RoundTripValidator.ts +0 -400
  127. package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
  128. package/src/__tests__/auth-module-compilation.test.ts +0 -247
  129. package/src/__tests__/auth-template-compilation.test.ts +0 -589
  130. package/src/__tests__/change-extractor.test.ts +0 -142
  131. package/src/__tests__/cli-pull.test.ts +0 -73
  132. package/src/__tests__/cli-test.test.ts +0 -72
  133. package/src/__tests__/component-extractor.test.ts +0 -331
  134. package/src/__tests__/context-extractor.test.ts +0 -145
  135. package/src/__tests__/decompiler.test.ts +0 -718
  136. package/src/__tests__/define-blueprint.test.ts +0 -133
  137. package/src/__tests__/definition-validator.test.ts +0 -519
  138. package/src/__tests__/during-extractor.test.ts +0 -152
  139. package/src/__tests__/effect-extractor.test.ts +0 -107
  140. package/src/__tests__/event-emission.test.ts +0 -127
  141. package/src/__tests__/examples.test.ts +0 -236
  142. package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
  143. package/src/__tests__/golden-suite.test.ts +0 -403
  144. package/src/__tests__/grammar-island-extractor.test.ts +0 -289
  145. package/src/__tests__/instance-key.test.ts +0 -82
  146. package/src/__tests__/ir-migration.test.ts +0 -255
  147. package/src/__tests__/lock-file.test.ts +0 -117
  148. package/src/__tests__/model-extractor.test.ts +0 -195
  149. package/src/__tests__/model-field-acl.test.ts +0 -237
  150. package/src/__tests__/model-hooks.test.ts +0 -130
  151. package/src/__tests__/model-ref-resolution.test.ts +0 -268
  152. package/src/__tests__/model-roundtrip.test.ts +0 -502
  153. package/src/__tests__/model-runtime.test.ts +0 -112
  154. package/src/__tests__/model-transitions.test.ts +0 -183
  155. package/src/__tests__/nrt-action-trace.test.ts +0 -391
  156. package/src/__tests__/pipeline-hardening.test.ts +0 -413
  157. package/src/__tests__/project-compiler.test.ts +0 -546
  158. package/src/__tests__/project-decompiler.test.ts +0 -343
  159. package/src/__tests__/query-compilation.test.ts +0 -145
  160. package/src/__tests__/round-trip/PLAN.md +0 -158
  161. package/src/__tests__/round-trip/README.md +0 -52
  162. package/src/__tests__/round-trip/RESULTS.md +0 -86
  163. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
  164. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
  165. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
  166. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
  167. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
  168. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
  169. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
  170. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
  171. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
  172. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
  173. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
  174. package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
  175. package/src/__tests__/round-trip-ir.test.ts +0 -300
  176. package/src/__tests__/round-trip.test.ts +0 -1212
  177. package/src/__tests__/route-merging.test.ts +0 -372
  178. package/src/__tests__/router-composition.test.ts +0 -489
  179. package/src/__tests__/router-extractor.test.ts +0 -176
  180. package/src/__tests__/server-action-extractor.test.ts +0 -128
  181. package/src/__tests__/smart-type-inference.test.ts +0 -365
  182. package/src/__tests__/source-envelope.test.ts +0 -284
  183. package/src/__tests__/source-fidelity.test.ts +0 -516
  184. package/src/__tests__/state-extractor.test.ts +0 -115
  185. package/src/__tests__/strict-mode.test.ts +0 -227
  186. package/src/__tests__/transition-effect-extractor.test.ts +0 -119
  187. package/src/__tests__/transition-extractor.test.ts +0 -68
  188. package/src/__tests__/ts-to-expression.test.ts +0 -462
  189. package/src/__tests__/type-generator.test.ts +0 -201
  190. package/src/__tests__/uber-validation.test.ts +0 -502
  191. package/src/action-compiler.ts +0 -361
  192. package/src/babel/emitters/experience-transform.ts +0 -199
  193. package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
  194. package/src/babel/emitters/pure-form-emitter.ts +0 -1023
  195. package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
  196. package/src/babel/extractors/change-extractor.ts +0 -199
  197. package/src/babel/extractors/component-extractor.ts +0 -907
  198. package/src/babel/extractors/computed-extractor.ts +0 -262
  199. package/src/babel/extractors/context-extractor.ts +0 -277
  200. package/src/babel/extractors/during-extractor.ts +0 -295
  201. package/src/babel/extractors/effect-extractor.ts +0 -340
  202. package/src/babel/extractors/event-extractor.ts +0 -235
  203. package/src/babel/extractors/grammar-island-extractor.ts +0 -302
  204. package/src/babel/extractors/model-extractor.ts +0 -1018
  205. package/src/babel/extractors/router-extractor.ts +0 -303
  206. package/src/babel/extractors/server-action-extractor.ts +0 -173
  207. package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
  208. package/src/babel/extractors/server-state-extractor.ts +0 -88
  209. package/src/babel/extractors/state-extractor.ts +0 -214
  210. package/src/babel/extractors/transition-effect-extractor.ts +0 -176
  211. package/src/babel/extractors/transition-extractor.ts +0 -143
  212. package/src/babel/index.ts +0 -24
  213. package/src/babel/transpilers/ts-to-expression.ts +0 -674
  214. package/src/babel/visitor.ts +0 -807
  215. package/src/cli/auth.ts +0 -255
  216. package/src/cli/build.ts +0 -288
  217. package/src/cli/deploy.ts +0 -206
  218. package/src/cli/index.ts +0 -328
  219. package/src/cli/installer.ts +0 -261
  220. package/src/cli/lock-file.ts +0 -94
  221. package/src/cli/mmrc.ts +0 -22
  222. package/src/cli/pull.ts +0 -172
  223. package/src/cli/registry-client.ts +0 -175
  224. package/src/cli/test.ts +0 -397
  225. package/src/cli/type-generator.ts +0 -243
  226. package/src/codemod/__tests__/forward.test.ts +0 -239
  227. package/src/codemod/__tests__/reverse.test.ts +0 -145
  228. package/src/codemod/__tests__/round-trip.test.ts +0 -137
  229. package/src/codemod/annotation.ts +0 -97
  230. package/src/codemod/classify.ts +0 -197
  231. package/src/codemod/cli.ts +0 -207
  232. package/src/codemod/control-flow.ts +0 -409
  233. package/src/codemod/forward.ts +0 -244
  234. package/src/codemod/import-manager.ts +0 -171
  235. package/src/codemod/index.ts +0 -120
  236. package/src/codemod/reverse.ts +0 -197
  237. package/src/codemod/rules.ts +0 -174
  238. package/src/codemod/state-transform.ts +0 -126
  239. package/src/decompiler/ast-builder.ts +0 -538
  240. package/src/decompiler/config-generator.ts +0 -151
  241. package/src/decompiler/index.ts +0 -315
  242. package/src/decompiler/project-decompiler.ts +0 -1776
  243. package/src/decompiler/project.ts +0 -862
  244. package/src/decompiler/split-strategy.ts +0 -140
  245. package/src/decompiler/state-emitter.ts +0 -1053
  246. package/src/decompiler/sx-emitter.ts +0 -318
  247. package/src/decompiler/workspace-hydrator.ts +0 -189
  248. package/src/dev-server.ts +0 -238
  249. package/src/envelope/fs-tree.ts +0 -217
  250. package/src/envelope/source-envelope.ts +0 -264
  251. package/src/envelope.ts +0 -315
  252. package/src/incremental-compiler.ts +0 -401
  253. package/src/index.ts +0 -99
  254. package/src/model-compiler.ts +0 -277
  255. package/src/project-compiler.ts +0 -1629
  256. package/src/route-extractor.ts +0 -333
  257. package/src/testing/index.ts +0 -32
  258. package/src/testing/snapshot.ts +0 -252
  259. package/src/testing/test-utils.ts +0 -226
  260. package/src/types.ts +0 -68
  261. package/src/vite/index.ts +0 -288
  262. package/test-compile.mjs +0 -142
  263. package/tsconfig.json +0 -25
  264. package/tsup.config.ts +0 -23
  265. 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
- });