@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,175 +0,0 @@
1
- /**
2
- * Registry client — fetches workflow definitions from the MindMatrix API.
3
- * Uses native fetch (Node 18+). No external dependencies.
4
- */
5
-
6
- export interface RegistryConfig {
7
- apiUrl: string;
8
- authToken?: string;
9
- }
10
-
11
- export interface WorkflowDefinitionResponse {
12
- id?: string;
13
- slug: string;
14
- name: string;
15
- version: string;
16
- description?: string;
17
- category: string;
18
- fields: Array<{
19
- name: string;
20
- type: string;
21
- typeVersion?: string;
22
- baseType?: string;
23
- label?: string;
24
- required?: boolean;
25
- default_value?: unknown;
26
- computed?: string;
27
- computed_deps?: string[];
28
- validation?: {
29
- enum?: string[];
30
- min?: number;
31
- max?: number;
32
- pattern?: string;
33
- };
34
- }>;
35
- states: Array<{
36
- name: string;
37
- type: string;
38
- description?: string;
39
- }>;
40
- transitions: Array<{
41
- name: string;
42
- from: string[];
43
- to: string;
44
- description?: string;
45
- roles?: string[];
46
- auto?: boolean;
47
- required_fields?: string[];
48
- }>;
49
- roles?: Array<{ name: string; description?: string }>;
50
- metadata?: Record<string, unknown>;
51
- experience?: unknown;
52
- view?: unknown;
53
- }
54
-
55
- function buildHeaders(config: RegistryConfig): Record<string, string> {
56
- const headers: Record<string, string> = {
57
- 'Content-Type': 'application/json',
58
- };
59
- if (config.authToken) {
60
- headers['Authorization'] = `Bearer ${config.authToken}`;
61
- }
62
- return headers;
63
- }
64
-
65
- function extractDefinition(data: unknown): WorkflowDefinitionResponse | null {
66
- if (!data || typeof data !== 'object') return null;
67
-
68
- // Handle array responses
69
- if (Array.isArray(data)) {
70
- return data.length > 0 ? (data[0] as WorkflowDefinitionResponse) : null;
71
- }
72
-
73
- const obj = data as Record<string, unknown>;
74
-
75
- // Handle paginated responses
76
- const items = obj.items ?? obj.data;
77
- if (items && Array.isArray(items)) {
78
- return items.length > 0 ? (items[0] as WorkflowDefinitionResponse) : null;
79
- }
80
-
81
- // Direct object — must have slug
82
- if ('slug' in obj) {
83
- return obj as unknown as WorkflowDefinitionResponse;
84
- }
85
-
86
- return null;
87
- }
88
-
89
- function extractDefinitions(data: unknown): WorkflowDefinitionResponse[] {
90
- if (!data || typeof data !== 'object') return [];
91
-
92
- if (Array.isArray(data)) {
93
- return data as WorkflowDefinitionResponse[];
94
- }
95
-
96
- const obj = data as Record<string, unknown>;
97
- const items = obj.items ?? obj.data;
98
- if (items && Array.isArray(items)) {
99
- return items as WorkflowDefinitionResponse[];
100
- }
101
-
102
- if ('slug' in obj) {
103
- return [obj as unknown as WorkflowDefinitionResponse];
104
- }
105
-
106
- return [];
107
- }
108
-
109
- /**
110
- * Fetch a single workflow definition by slug.
111
- */
112
- export async function fetchDefinition(
113
- slug: string,
114
- config: RegistryConfig,
115
- ): Promise<WorkflowDefinitionResponse> {
116
- const url = `${config.apiUrl}/workflow/definitions?slug=${encodeURIComponent(slug)}`;
117
-
118
- let res: Response;
119
- try {
120
- res = await fetch(url, { headers: buildHeaders(config) });
121
- } catch (err) {
122
- throw new Error(
123
- `Failed to connect to registry at ${config.apiUrl}. ` +
124
- `Is the API server running? (${err instanceof Error ? err.message : String(err)})`,
125
- );
126
- }
127
-
128
- if (!res.ok) {
129
- if (res.status === 401) {
130
- throw new Error('Authentication required. Provide --token or set MM_AUTH_TOKEN.');
131
- }
132
- if (res.status === 404) {
133
- throw new Error(`Definition "${slug}" not found in registry.`);
134
- }
135
- throw new Error(`Registry returned ${res.status}: ${res.statusText}`);
136
- }
137
-
138
- const data = await res.json();
139
- const def = extractDefinition(data);
140
- if (!def) {
141
- throw new Error(`Definition "${slug}" not found in registry.`);
142
- }
143
-
144
- return def;
145
- }
146
-
147
- /**
148
- * Search definitions by query string.
149
- */
150
- export async function searchDefinitions(
151
- query: string,
152
- config: RegistryConfig,
153
- ): Promise<WorkflowDefinitionResponse[]> {
154
- const url = `${config.apiUrl}/workflow/catalog/definitions?search=${encodeURIComponent(query)}`;
155
-
156
- let res: Response;
157
- try {
158
- res = await fetch(url, { headers: buildHeaders(config) });
159
- } catch (err) {
160
- throw new Error(
161
- `Failed to connect to registry at ${config.apiUrl}. ` +
162
- `Is the API server running? (${err instanceof Error ? err.message : String(err)})`,
163
- );
164
- }
165
-
166
- if (!res.ok) {
167
- if (res.status === 401) {
168
- throw new Error('Authentication required. Provide --token or set MM_AUTH_TOKEN.');
169
- }
170
- throw new Error(`Registry returned ${res.status}: ${res.statusText}`);
171
- }
172
-
173
- const data = await res.json();
174
- return extractDefinitions(data);
175
- }
package/src/cli/test.ts DELETED
@@ -1,397 +0,0 @@
1
- /**
2
- * CLI Test Command — round-trip fidelity verification.
3
- *
4
- * Compiles project sources → decompiles IR → recompiles → compares.
5
- * Validates that the compiler↔decompiler round trip preserves all structural data.
6
- *
7
- * Usage: mmrc test [--src ./src]
8
- */
9
-
10
- import { glob } from 'glob';
11
- import { readFileSync, existsSync } from 'fs';
12
- import { resolve } from 'path';
13
- import type { IRWorkflowDefinition } from '@mindmatrix/player-core';
14
-
15
- // =============================================================================
16
- // Types
17
- // =============================================================================
18
-
19
- export interface TestOptions {
20
- /** Source directory containing workflow project files. Default: '.' */
21
- src?: string;
22
- }
23
-
24
- export interface TestResult {
25
- success: boolean;
26
- fieldCount: number;
27
- fieldMatch: number;
28
- stateCount: number;
29
- stateMatch: number;
30
- transitionCount: number;
31
- transitionMatch: number;
32
- diffs: TestDiff[];
33
- timings: { compile: number; decompile: number; recompile: number; compare: number };
34
- }
35
-
36
- interface TestDiff {
37
- path: string;
38
- expected: unknown;
39
- actual: unknown;
40
- severity: 'error' | 'warning';
41
- }
42
-
43
- // =============================================================================
44
- // Main
45
- // =============================================================================
46
-
47
- export async function test(options: TestOptions = {}): Promise<TestResult> {
48
- const srcDir = options.src ?? '.';
49
- const label = '[mindmatrix-react]';
50
-
51
- console.log(`${label} Round-trip fidelity test\n`);
52
-
53
- // ─── Step 1: Read source files ─────────────────────────────────────────────
54
- const configPath = resolve(srcDir, 'mm.config.ts');
55
- const isProject = existsSync(configPath);
56
-
57
- if (!isProject) {
58
- console.error(`${label} Error: No mm.config.ts found in ${srcDir}`);
59
- console.error(` Round-trip test requires a project with mm.config.ts`);
60
- process.exit(1);
61
- }
62
-
63
- const allFiles = await glob(`${srcDir}/**/*.{ts,tsx}`, {
64
- ignore: ['**/node_modules/**', '**/dist/**', '**/*.test.*'],
65
- });
66
-
67
- if (allFiles.length === 0) {
68
- console.error(`${label} Error: No source files found in ${srcDir}`);
69
- process.exit(1);
70
- }
71
-
72
- const fileMap: Record<string, string> = {};
73
- for (const f of allFiles) {
74
- const rel = f.startsWith(srcDir + '/') ? f.slice(srcDir.length + 1) : f;
75
- fileMap[rel] = readFileSync(f, 'utf-8');
76
- }
77
-
78
- // ─── Step 2: Compile ──────────────────────────────────────────────────────
79
- const { compileProject } = await import('../project-compiler');
80
- const { decompileProjectEnhanced } = await import('../decompiler/project-decompiler');
81
- type DecompilerInput = import('../decompiler').DecompilerInput;
82
-
83
- process.stdout.write(` Compiling project...`);
84
- const t0 = performance.now();
85
-
86
- let ir1: IRWorkflowDefinition;
87
- let childDefs1: IRWorkflowDefinition[];
88
- try {
89
- const result = compileProject(fileMap);
90
- ir1 = result.ir;
91
- childDefs1 = result.childDefinitions;
92
- const compileErrors = result.errors.filter((e: { severity: string }) => e.severity === 'error');
93
- if (compileErrors.length > 0) {
94
- console.log(` FAIL`);
95
- for (const err of compileErrors) {
96
- console.error(` ${err.file}: ${err.message}`);
97
- }
98
- process.exit(1);
99
- }
100
- } catch (err) {
101
- console.log(` FAIL`);
102
- console.error(` ${(err as Error).message}`);
103
- process.exit(1);
104
- }
105
-
106
- const t1 = performance.now();
107
- console.log(` \u2713 (${((t1 - t0) / 1000).toFixed(1)}s)`);
108
-
109
- // ─── Step 3: Decompile ────────────────────────────────────────────────────
110
- process.stdout.write(` Decompiling IR...`);
111
- const t2 = performance.now();
112
-
113
- let decompFiles: Record<string, string>;
114
- try {
115
- const decompInput: DecompilerInput = {
116
- ...ir1,
117
- childDefinitions: childDefs1,
118
- experience: ir1.experience as any,
119
- };
120
- const decompResult = decompileProjectEnhanced(decompInput);
121
- decompFiles = {};
122
- for (const file of decompResult.files) {
123
- decompFiles[file.path] = file.content;
124
- }
125
- } catch (err) {
126
- console.log(` FAIL`);
127
- console.error(` ${(err as Error).message}`);
128
- process.exit(1);
129
- }
130
-
131
- const t3 = performance.now();
132
- console.log(` \u2713 (${((t3 - t2) / 1000).toFixed(1)}s)`);
133
-
134
- // ─── Step 4: Recompile ────────────────────────────────────────────────────
135
- process.stdout.write(` Recompiling...`);
136
- const t4 = performance.now();
137
-
138
- let ir2: IRWorkflowDefinition;
139
- try {
140
- const result2 = compileProject(decompFiles);
141
- ir2 = result2.ir;
142
- const recompileErrors = result2.errors.filter((e: { severity: string }) => e.severity === 'error');
143
- if (recompileErrors.length > 0) {
144
- console.log(` FAIL`);
145
- for (const err of recompileErrors) {
146
- console.error(` ${err.file}: ${err.message}`);
147
- }
148
- process.exit(1);
149
- }
150
- } catch (err) {
151
- console.log(` FAIL`);
152
- console.error(` ${(err as Error).message}`);
153
- process.exit(1);
154
- }
155
-
156
- const t5 = performance.now();
157
- console.log(` \u2713 (${((t5 - t4) / 1000).toFixed(1)}s)`);
158
-
159
- // ─── Step 5: Compare ─────────────────────────────────────────────────────
160
- process.stdout.write(` Comparing IR...\n`);
161
- const t6 = performance.now();
162
-
163
- const diffs: TestDiff[] = [];
164
-
165
- // Metadata
166
- if (ir1.slug !== ir2.slug) {
167
- diffs.push({ path: 'slug', expected: ir1.slug, actual: ir2.slug, severity: 'error' });
168
- }
169
- if (ir1.version !== ir2.version) {
170
- diffs.push({ path: 'version', expected: ir1.version, actual: ir2.version, severity: 'error' });
171
- }
172
- if (JSON.stringify(ir1.category) !== JSON.stringify(ir2.category)) {
173
- diffs.push({ path: 'category', expected: ir1.category, actual: ir2.category, severity: 'error' });
174
- }
175
-
176
- // Fields
177
- const { fieldMatch, fieldCount } = compareFields(diffs, ir1.fields, ir2.fields);
178
- const fieldOk = diffs.filter(d => d.path.startsWith('fields') && d.severity === 'error').length === 0;
179
- console.log(` Fields: ${fieldMatch}/${fieldCount} match ${fieldOk ? '\u2713' : 'FAIL'}`);
180
-
181
- // States
182
- const { stateMatch, stateCount } = compareStates(diffs, ir1.states, ir2.states);
183
- const stateOk = diffs.filter(d => d.path.startsWith('states') && d.severity === 'error').length === 0;
184
- console.log(` States: ${stateMatch}/${stateCount} match ${stateOk ? '\u2713' : 'FAIL'}`);
185
-
186
- // Transitions
187
- const { transitionMatch, transitionCount } = compareTransitions(diffs, ir1.transitions, ir2.transitions);
188
- const transitionOk = diffs.filter(d => d.path.startsWith('transitions') && d.severity === 'error').length === 0;
189
- console.log(` Transitions: ${transitionMatch}/${transitionCount} match ${transitionOk ? '\u2713' : 'FAIL'}`);
190
-
191
- const t7 = performance.now();
192
-
193
- const errors = diffs.filter(d => d.severity === 'error');
194
- const success = errors.length === 0;
195
-
196
- if (success) {
197
- console.log(` PASS \u2014 round-trip produces identical IR`);
198
- } else {
199
- console.log(` FAIL \u2014 ${errors.length} difference(s) found:`);
200
- for (const diff of errors) {
201
- console.log(` ${diff.path}: expected ${JSON.stringify(diff.expected)}, got ${JSON.stringify(diff.actual)}`);
202
- }
203
- }
204
-
205
- return {
206
- success,
207
- fieldCount,
208
- fieldMatch,
209
- stateCount,
210
- stateMatch,
211
- transitionCount,
212
- transitionMatch,
213
- diffs,
214
- timings: {
215
- compile: t1 - t0,
216
- decompile: t3 - t2,
217
- recompile: t5 - t4,
218
- compare: t7 - t6,
219
- },
220
- };
221
- }
222
-
223
- // =============================================================================
224
- // Field Comparison
225
- // =============================================================================
226
-
227
- function compareFields(
228
- diffs: TestDiff[],
229
- fields1: IRWorkflowDefinition['fields'],
230
- fields2: IRWorkflowDefinition['fields'],
231
- ): { fieldMatch: number; fieldCount: number } {
232
- const map1 = new Map(fields1.map(f => [f.name, f]));
233
- const map2 = new Map(fields2.map(f => [f.name, f]));
234
- const fieldCount = map1.size;
235
- let fieldMatch = 0;
236
-
237
- for (const [name, f1] of map1) {
238
- const f2 = map2.get(name);
239
- if (!f2) {
240
- diffs.push({ path: `fields[${name}]`, expected: name, actual: undefined, severity: 'error' });
241
- continue;
242
- }
243
-
244
- if (f1.type !== f2.type && !isCompatibleType(f1.type, f2.type)) {
245
- diffs.push({ path: `fields[${name}].type`, expected: f1.type, actual: f2.type, severity: 'warning' });
246
- }
247
- fieldMatch++;
248
- }
249
-
250
- for (const [name] of map2) {
251
- if (!map1.has(name)) {
252
- diffs.push({ path: `fields[${name}]`, expected: undefined, actual: name, severity: 'warning' });
253
- }
254
- }
255
-
256
- return { fieldMatch, fieldCount };
257
- }
258
-
259
- function isCompatibleType(t1: string, t2: string): boolean {
260
- const stringTypes = new Set(['text', 'rich_text', 'email', 'url', 'phone', 'color', 'select']);
261
- const numberTypes = new Set(['number', 'currency', 'percentage', 'rating', 'duration']);
262
- if (stringTypes.has(t1) && stringTypes.has(t2)) return true;
263
- if (numberTypes.has(t1) && numberTypes.has(t2)) return true;
264
- return false;
265
- }
266
-
267
- // =============================================================================
268
- // State Comparison
269
- // =============================================================================
270
-
271
- function compareStates(
272
- diffs: TestDiff[],
273
- states1: IRWorkflowDefinition['states'],
274
- states2: IRWorkflowDefinition['states'],
275
- ): { stateMatch: number; stateCount: number } {
276
- const map1 = new Map(states1.map(s => [s.name, s]));
277
- const map2 = new Map(states2.map(s => [s.name, s]));
278
- const stateCount = map1.size;
279
- let stateMatch = 0;
280
-
281
- for (const [name, s1] of map1) {
282
- const s2 = map2.get(name);
283
- const hasActions = s1.on_enter.length > 0 || s1.on_exit.length > 0 || s1.during.length > 0;
284
-
285
- if (!s2 && hasActions) {
286
- diffs.push({ path: `states[${name}]`, expected: name, actual: undefined, severity: 'error' });
287
- continue;
288
- }
289
- if (!s2) {
290
- // States without actions may differ — still count as match
291
- stateMatch++;
292
- continue;
293
- }
294
-
295
- let matched = true;
296
- if (s1.on_enter.length !== s2.on_enter.length) {
297
- diffs.push({
298
- path: `states[${name}].on_enter.length`,
299
- expected: s1.on_enter.length,
300
- actual: s2.on_enter.length,
301
- severity: s1.on_enter.length > 0 ? 'error' : 'warning',
302
- });
303
- if (s1.on_enter.length > 0) matched = false;
304
- }
305
- if (s1.on_exit.length !== s2.on_exit.length) {
306
- diffs.push({
307
- path: `states[${name}].on_exit.length`,
308
- expected: s1.on_exit.length,
309
- actual: s2.on_exit.length,
310
- severity: s1.on_exit.length > 0 ? 'error' : 'warning',
311
- });
312
- if (s1.on_exit.length > 0) matched = false;
313
- }
314
- if (s1.during.length !== s2.during.length) {
315
- diffs.push({
316
- path: `states[${name}].during.length`,
317
- expected: s1.during.length,
318
- actual: s2.during.length,
319
- severity: s1.during.length > 0 ? 'error' : 'warning',
320
- });
321
- if (s1.during.length > 0) matched = false;
322
- }
323
-
324
- if (matched) stateMatch++;
325
- }
326
-
327
- return { stateMatch, stateCount };
328
- }
329
-
330
- // =============================================================================
331
- // Transition Comparison
332
- // =============================================================================
333
-
334
- function compareTransitions(
335
- diffs: TestDiff[],
336
- transitions1: IRWorkflowDefinition['transitions'],
337
- transitions2: IRWorkflowDefinition['transitions'],
338
- ): { transitionMatch: number; transitionCount: number } {
339
- const map1 = new Map(transitions1.map(t => [t.name, t]));
340
- const map2 = new Map(transitions2.map(t => [t.name, t]));
341
- const transitionCount = map1.size;
342
- let transitionMatch = 0;
343
-
344
- if (transitions1.length !== transitions2.length) {
345
- diffs.push({
346
- path: 'transitions.length',
347
- expected: transitions1.length,
348
- actual: transitions2.length,
349
- severity: 'error',
350
- });
351
- }
352
-
353
- for (const [name, t1] of map1) {
354
- const t2 = map2.get(name);
355
- if (!t2) {
356
- diffs.push({ path: `transitions[${name}]`, expected: name, actual: undefined, severity: 'error' });
357
- continue;
358
- }
359
-
360
- let matched = true;
361
-
362
- if (t1.to !== t2.to) {
363
- diffs.push({ path: `transitions[${name}].to`, expected: t1.to, actual: t2.to, severity: 'error' });
364
- matched = false;
365
- }
366
-
367
- const from1 = [...t1.from].sort();
368
- const from2 = [...t2.from].sort();
369
- if (JSON.stringify(from1) !== JSON.stringify(from2)) {
370
- diffs.push({ path: `transitions[${name}].from`, expected: from1, actual: from2, severity: 'error' });
371
- matched = false;
372
- }
373
-
374
- const roles1 = [...(t1.roles ?? [])].sort();
375
- const roles2 = [...(t2.roles ?? [])].sort();
376
- if (JSON.stringify(roles1) !== JSON.stringify(roles2)) {
377
- diffs.push({ path: `transitions[${name}].roles`, expected: roles1, actual: roles2, severity: 'error' });
378
- matched = false;
379
- }
380
-
381
- const req1 = [...(t1.required_fields ?? [])].sort();
382
- const req2 = [...(t2.required_fields ?? [])].sort();
383
- if (JSON.stringify(req1) !== JSON.stringify(req2)) {
384
- diffs.push({ path: `transitions[${name}].required_fields`, expected: req1, actual: req2, severity: 'error' });
385
- matched = false;
386
- }
387
-
388
- if (!!t1.auto !== !!t2.auto) {
389
- diffs.push({ path: `transitions[${name}].auto`, expected: t1.auto, actual: t2.auto, severity: 'error' });
390
- matched = false;
391
- }
392
-
393
- if (matched) transitionMatch++;
394
- }
395
-
396
- return { transitionMatch, transitionCount };
397
- }