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

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 (251) hide show
  1. package/ATOM-PIPELINE.md +144 -0
  2. package/README.md +88 -40
  3. package/dist/babel/index.js +113 -6
  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-5RKTOVR5.mjs +244 -0
  11. package/dist/chunk-5YDMOO4X.mjs +214 -0
  12. package/dist/chunk-64ZWEMLJ.mjs +148 -0
  13. package/dist/chunk-6XP4KSWQ.mjs +2190 -0
  14. package/dist/chunk-72QWL54I.mjs +175 -0
  15. package/dist/chunk-7B4TRI7C.mjs +4835 -0
  16. package/dist/chunk-7ZKGHTNB.mjs +4952 -0
  17. package/dist/chunk-CIESM3BP.mjs +33 -0
  18. package/dist/chunk-DE3ZGQAC.mjs +148 -0
  19. package/dist/chunk-DMCY3BBG.mjs +1933 -0
  20. package/dist/chunk-DPIK3PJS.mjs +244 -0
  21. package/dist/chunk-E5IVH4RE.mjs +186 -0
  22. package/dist/chunk-E6FZNUR5.mjs +4953 -0
  23. package/dist/chunk-EJRBDQDP.mjs +2607 -0
  24. package/dist/chunk-ELO4TXJL.mjs +186 -0
  25. package/dist/chunk-FKRO52XH.mjs +3446 -0
  26. package/dist/chunk-FL4YAKU6.mjs +4941 -0
  27. package/dist/chunk-FYT47UBU.mjs +5076 -0
  28. package/dist/chunk-GCLGPOJZ.mjs +148 -0
  29. package/dist/chunk-GXB4JOP7.mjs +5072 -0
  30. package/dist/chunk-HFXOUMTD.mjs +175 -0
  31. package/dist/chunk-HWIZ47US.mjs +214 -0
  32. package/dist/chunk-IB7MNPQL.mjs +4953 -0
  33. package/dist/chunk-ICSIHQCG.mjs +148 -0
  34. package/dist/chunk-JLA5VNQ3.mjs +186 -0
  35. package/dist/chunk-JQLWFCTM.mjs +214 -0
  36. package/dist/chunk-KFJJCQAL.mjs +148 -0
  37. package/dist/chunk-KJUIIEQE.mjs +186 -0
  38. package/dist/chunk-KNWTHRVQ.mjs +175 -0
  39. package/dist/chunk-KSG4XSZF.mjs +175 -0
  40. package/dist/chunk-LF5N6DOU.mjs +175 -0
  41. package/dist/chunk-LJQCM2IM.mjs +214 -0
  42. package/dist/chunk-NW6555WJ.mjs +186 -0
  43. package/dist/chunk-OMZE6VLQ.mjs +214 -0
  44. package/dist/chunk-P4BR7WVO.mjs +2190 -0
  45. package/dist/chunk-QQHVYH2X.mjs +244 -0
  46. package/dist/chunk-S5QLWLLT.mjs +186 -0
  47. package/dist/chunk-SCWGT2FY.mjs +2190 -0
  48. package/dist/chunk-SMKJUSB3.mjs +2190 -0
  49. package/dist/chunk-VCAY2KGM.mjs +175 -0
  50. package/dist/chunk-WECAV6QB.mjs +148 -0
  51. package/dist/chunk-WMKBXUCE.mjs +3228 -0
  52. package/dist/chunk-XAJ5BKKL.mjs +4947 -0
  53. package/dist/chunk-XG2X7AEA.mjs +175 -0
  54. package/dist/chunk-XG7Z23NQ.mjs +148 -0
  55. package/dist/chunk-XWZAOCQ7.mjs +2607 -0
  56. package/dist/chunk-Y6MA7ULW.mjs +148 -0
  57. package/dist/chunk-YMS7Q7LG.mjs +214 -0
  58. package/dist/chunk-ZA37XTGA.mjs +175 -0
  59. package/dist/cli/index.js +1616 -366
  60. package/dist/cli/index.mjs +8 -8
  61. package/dist/codemod/cli.mjs +1 -1
  62. package/dist/codemod/index.mjs +1 -1
  63. package/dist/dev-server-RmGHIntF.d.mts +113 -0
  64. package/dist/dev-server-RmGHIntF.d.ts +113 -0
  65. package/dist/dev-server.d.mts +1 -1
  66. package/dist/dev-server.d.ts +1 -1
  67. package/dist/dev-server.js +982 -53
  68. package/dist/dev-server.mjs +5 -5
  69. package/dist/envelope.js +113 -6
  70. package/dist/envelope.mjs +3 -3
  71. package/dist/index.d.mts +5 -1
  72. package/dist/index.d.ts +5 -1
  73. package/dist/index.js +992 -63
  74. package/dist/index.mjs +8 -8
  75. package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
  76. package/dist/init-EHO4VQ22.mjs +369 -0
  77. package/dist/init-UC3FWPIW.mjs +367 -0
  78. package/dist/init-UNSMVKIK.mjs +366 -0
  79. package/dist/init-UNV5XIDE.mjs +367 -0
  80. package/dist/project-compiler-2P4N4DR7.mjs +10 -0
  81. package/dist/project-compiler-D2LCC27O.mjs +10 -0
  82. package/dist/project-compiler-EJ3GANJE.mjs +10 -0
  83. package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
  84. package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
  85. package/dist/project-compiler-VWNNCHGO.mjs +10 -0
  86. package/dist/project-compiler-XVAAU4C5.mjs +10 -0
  87. package/dist/project-compiler-YES5FGMD.mjs +10 -0
  88. package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
  89. package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
  90. package/dist/project-decompiler-VLPR22QF.mjs +7 -0
  91. package/dist/pull-FUS5QYZS.mjs +109 -0
  92. package/dist/pull-LD5ENLGY.mjs +109 -0
  93. package/dist/testing/index.js +113 -6
  94. package/dist/testing/index.mjs +2 -2
  95. package/dist/vite/index.js +113 -6
  96. package/dist/vite/index.mjs +3 -3
  97. package/examples/uber-app/app/admin/fleet.tsx +19 -19
  98. package/package.json +4 -3
  99. package/compile-blueprint-chat.mjs +0 -99
  100. package/compile-blueprint-glass-console.mjs +0 -98
  101. package/compile-chat-defs.mjs +0 -92
  102. package/examples/uber-app/tests/payment.test.tsx +0 -129
  103. package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
  104. package/package.json.backup +0 -86
  105. package/scripts/decompile.ts +0 -226
  106. package/scripts/seed-auth.ts +0 -267
  107. package/scripts/seed-uber.ts +0 -248
  108. package/scripts/validate-uber.ts +0 -119
  109. package/seed-blueprint-chat.mjs +0 -444
  110. package/seed-blueprint-glass-console.mjs +0 -445
  111. package/seed-compiled.mjs +0 -318
  112. package/src/RoundTripValidator.ts +0 -400
  113. package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
  114. package/src/__tests__/auth-module-compilation.test.ts +0 -247
  115. package/src/__tests__/auth-template-compilation.test.ts +0 -589
  116. package/src/__tests__/change-extractor.test.ts +0 -142
  117. package/src/__tests__/cli-pull.test.ts +0 -73
  118. package/src/__tests__/cli-test.test.ts +0 -72
  119. package/src/__tests__/component-extractor.test.ts +0 -331
  120. package/src/__tests__/context-extractor.test.ts +0 -145
  121. package/src/__tests__/decompiler.test.ts +0 -718
  122. package/src/__tests__/define-blueprint.test.ts +0 -133
  123. package/src/__tests__/definition-validator.test.ts +0 -519
  124. package/src/__tests__/during-extractor.test.ts +0 -152
  125. package/src/__tests__/effect-extractor.test.ts +0 -107
  126. package/src/__tests__/event-emission.test.ts +0 -127
  127. package/src/__tests__/examples.test.ts +0 -236
  128. package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
  129. package/src/__tests__/golden-suite.test.ts +0 -403
  130. package/src/__tests__/grammar-island-extractor.test.ts +0 -289
  131. package/src/__tests__/instance-key.test.ts +0 -82
  132. package/src/__tests__/ir-migration.test.ts +0 -255
  133. package/src/__tests__/lock-file.test.ts +0 -117
  134. package/src/__tests__/model-extractor.test.ts +0 -195
  135. package/src/__tests__/model-field-acl.test.ts +0 -237
  136. package/src/__tests__/model-hooks.test.ts +0 -130
  137. package/src/__tests__/model-ref-resolution.test.ts +0 -268
  138. package/src/__tests__/model-roundtrip.test.ts +0 -502
  139. package/src/__tests__/model-runtime.test.ts +0 -112
  140. package/src/__tests__/model-transitions.test.ts +0 -183
  141. package/src/__tests__/nrt-action-trace.test.ts +0 -391
  142. package/src/__tests__/pipeline-hardening.test.ts +0 -413
  143. package/src/__tests__/project-compiler.test.ts +0 -546
  144. package/src/__tests__/project-decompiler.test.ts +0 -343
  145. package/src/__tests__/query-compilation.test.ts +0 -145
  146. package/src/__tests__/round-trip/PLAN.md +0 -158
  147. package/src/__tests__/round-trip/README.md +0 -52
  148. package/src/__tests__/round-trip/RESULTS.md +0 -86
  149. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
  150. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
  151. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
  152. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
  153. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
  154. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
  155. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
  156. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
  157. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
  158. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
  159. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
  160. package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
  161. package/src/__tests__/round-trip-ir.test.ts +0 -300
  162. package/src/__tests__/round-trip.test.ts +0 -1212
  163. package/src/__tests__/route-merging.test.ts +0 -372
  164. package/src/__tests__/router-composition.test.ts +0 -489
  165. package/src/__tests__/router-extractor.test.ts +0 -176
  166. package/src/__tests__/server-action-extractor.test.ts +0 -128
  167. package/src/__tests__/smart-type-inference.test.ts +0 -365
  168. package/src/__tests__/source-envelope.test.ts +0 -284
  169. package/src/__tests__/source-fidelity.test.ts +0 -516
  170. package/src/__tests__/state-extractor.test.ts +0 -115
  171. package/src/__tests__/strict-mode.test.ts +0 -227
  172. package/src/__tests__/transition-effect-extractor.test.ts +0 -119
  173. package/src/__tests__/transition-extractor.test.ts +0 -68
  174. package/src/__tests__/ts-to-expression.test.ts +0 -462
  175. package/src/__tests__/type-generator.test.ts +0 -201
  176. package/src/__tests__/uber-validation.test.ts +0 -502
  177. package/src/action-compiler.ts +0 -361
  178. package/src/babel/emitters/experience-transform.ts +0 -199
  179. package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
  180. package/src/babel/emitters/pure-form-emitter.ts +0 -1023
  181. package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
  182. package/src/babel/extractors/change-extractor.ts +0 -199
  183. package/src/babel/extractors/component-extractor.ts +0 -907
  184. package/src/babel/extractors/computed-extractor.ts +0 -262
  185. package/src/babel/extractors/context-extractor.ts +0 -277
  186. package/src/babel/extractors/during-extractor.ts +0 -295
  187. package/src/babel/extractors/effect-extractor.ts +0 -340
  188. package/src/babel/extractors/event-extractor.ts +0 -235
  189. package/src/babel/extractors/grammar-island-extractor.ts +0 -302
  190. package/src/babel/extractors/model-extractor.ts +0 -1018
  191. package/src/babel/extractors/router-extractor.ts +0 -303
  192. package/src/babel/extractors/server-action-extractor.ts +0 -173
  193. package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
  194. package/src/babel/extractors/server-state-extractor.ts +0 -88
  195. package/src/babel/extractors/state-extractor.ts +0 -214
  196. package/src/babel/extractors/transition-effect-extractor.ts +0 -176
  197. package/src/babel/extractors/transition-extractor.ts +0 -143
  198. package/src/babel/index.ts +0 -24
  199. package/src/babel/transpilers/ts-to-expression.ts +0 -674
  200. package/src/babel/visitor.ts +0 -807
  201. package/src/cli/auth.ts +0 -255
  202. package/src/cli/build.ts +0 -288
  203. package/src/cli/deploy.ts +0 -206
  204. package/src/cli/index.ts +0 -328
  205. package/src/cli/installer.ts +0 -261
  206. package/src/cli/lock-file.ts +0 -94
  207. package/src/cli/mmrc.ts +0 -22
  208. package/src/cli/pull.ts +0 -172
  209. package/src/cli/registry-client.ts +0 -175
  210. package/src/cli/test.ts +0 -397
  211. package/src/cli/type-generator.ts +0 -243
  212. package/src/codemod/__tests__/forward.test.ts +0 -239
  213. package/src/codemod/__tests__/reverse.test.ts +0 -145
  214. package/src/codemod/__tests__/round-trip.test.ts +0 -137
  215. package/src/codemod/annotation.ts +0 -97
  216. package/src/codemod/classify.ts +0 -197
  217. package/src/codemod/cli.ts +0 -207
  218. package/src/codemod/control-flow.ts +0 -409
  219. package/src/codemod/forward.ts +0 -244
  220. package/src/codemod/import-manager.ts +0 -171
  221. package/src/codemod/index.ts +0 -120
  222. package/src/codemod/reverse.ts +0 -197
  223. package/src/codemod/rules.ts +0 -174
  224. package/src/codemod/state-transform.ts +0 -126
  225. package/src/decompiler/ast-builder.ts +0 -538
  226. package/src/decompiler/config-generator.ts +0 -151
  227. package/src/decompiler/index.ts +0 -315
  228. package/src/decompiler/project-decompiler.ts +0 -1776
  229. package/src/decompiler/project.ts +0 -862
  230. package/src/decompiler/split-strategy.ts +0 -140
  231. package/src/decompiler/state-emitter.ts +0 -1053
  232. package/src/decompiler/sx-emitter.ts +0 -318
  233. package/src/decompiler/workspace-hydrator.ts +0 -189
  234. package/src/dev-server.ts +0 -238
  235. package/src/envelope/fs-tree.ts +0 -217
  236. package/src/envelope/source-envelope.ts +0 -264
  237. package/src/envelope.ts +0 -315
  238. package/src/incremental-compiler.ts +0 -401
  239. package/src/index.ts +0 -99
  240. package/src/model-compiler.ts +0 -277
  241. package/src/project-compiler.ts +0 -1629
  242. package/src/route-extractor.ts +0 -333
  243. package/src/testing/index.ts +0 -32
  244. package/src/testing/snapshot.ts +0 -252
  245. package/src/testing/test-utils.ts +0 -226
  246. package/src/types.ts +0 -68
  247. package/src/vite/index.ts +0 -288
  248. package/test-compile.mjs +0 -142
  249. package/tsconfig.json +0 -25
  250. package/tsup.config.ts +0 -23
  251. package/vitest.config.ts +0 -9
@@ -1,333 +0,0 @@
1
- /**
2
- * Route Extractor — parses app/ directory structure into a routing table.
3
- *
4
- * Supports Next.js-style file-based routing conventions:
5
- * - app/page.tsx → route '/'
6
- * - app/rider/home.tsx → route '/rider/home'
7
- * - app/[id]/page.tsx → route '/:id'
8
- * - app/layout.tsx → wrapping layout boundary
9
- * - app/admin/layout.tsx → nested layout boundary
10
- *
11
- * Generates a router configuration that maps to IR states and transitions.
12
- */
13
-
14
- import type { IRWorkflowDefinition, IRStateDefinition, IRTransitionDefinition, IRFieldDefinition } from '@mindmatrix/player-core';
15
-
16
- // =============================================================================
17
- // Types
18
- // =============================================================================
19
-
20
- export interface RouteEntry {
21
- /** URL path pattern (e.g., '/rider/home', '/chat/:channelId'). */
22
- path: string;
23
- /** IR state name (e.g., 'RIDER_HOME', 'CHAT_CHANNELID'). */
24
- stateName: string;
25
- /** Source file relative path. */
26
- sourceFile: string;
27
- /** Dynamic route parameters. */
28
- params: string[];
29
- /** Nearest layout boundary file. */
30
- layoutBoundary?: string;
31
- /** Route segment depth (0 = root). */
32
- depth: number;
33
- }
34
-
35
- export interface LayoutEntry {
36
- /** Relative path to layout file. */
37
- sourceFile: string;
38
- /** Directory this layout covers. */
39
- directory: string;
40
- /** Depth in the layout hierarchy. */
41
- depth: number;
42
- }
43
-
44
- export interface RouteExtractionResult {
45
- /** All extracted routes. */
46
- routes: RouteEntry[];
47
- /** All discovered layout boundaries. */
48
- layouts: LayoutEntry[];
49
- /** Generated router workflow IR. */
50
- routerIR: IRWorkflowDefinition;
51
- /** Route parameters discovered (for field generation). */
52
- allParams: string[];
53
- }
54
-
55
- export interface RouteExtractorOptions {
56
- /** Blueprint slug prefix for the router. */
57
- slugPrefix?: string;
58
- /** Root directory name (default: 'app'). */
59
- rootDir?: string;
60
- /** Page file naming convention (default: 'page.tsx'). */
61
- pageFileName?: string;
62
- /** Layout file naming convention (default: 'layout.tsx'). */
63
- layoutFileName?: string;
64
- }
65
-
66
- // =============================================================================
67
- // Path Utilities
68
- // =============================================================================
69
-
70
- /**
71
- * Converts a file path to a URL pattern.
72
- * 'app/rider/home.tsx' → '/rider/home'
73
- * 'app/chat/[channelId]/page.tsx' → '/chat/:channelId'
74
- */
75
- function filePathToUrlPattern(filePath: string, rootDir: string): string {
76
- let route = filePath;
77
-
78
- // Remove root directory prefix
79
- const rootPrefix = rootDir + '/';
80
- if (route.startsWith(rootPrefix)) {
81
- route = route.slice(rootPrefix.length);
82
- }
83
-
84
- // Remove file extension
85
- route = route.replace(/\.(tsx?|jsx?)$/, '');
86
-
87
- // Remove trailing /page or /index
88
- route = route.replace(/\/(page|index)$/, '');
89
-
90
- // Handle root page
91
- if (route === 'page' || route === 'index' || route === '') {
92
- return '/';
93
- }
94
-
95
- // Convert [param] to :param
96
- route = route.replace(/\[([^\]]+)\]/g, ':$1');
97
-
98
- // Ensure leading slash
99
- return '/' + route;
100
- }
101
-
102
- /**
103
- * Converts a URL path to an IR state name.
104
- * '/rider/home' → 'RIDER_HOME'
105
- * '/' → 'HOME'
106
- * '/chat/:channelId' → 'CHAT_CHANNELID'
107
- */
108
- function urlToStateName(urlPath: string): string {
109
- if (urlPath === '/') return 'HOME';
110
-
111
- return urlPath
112
- .split('/')
113
- .filter(Boolean)
114
- .map(segment => segment.replace(/^:/, ''))
115
- .join('_')
116
- .replace(/([a-z])([A-Z])/g, '$1_$2')
117
- .replace(/-/g, '_')
118
- .toUpperCase();
119
- }
120
-
121
- /**
122
- * Extracts dynamic parameters from a file path.
123
- * 'app/chat/[channelId]/[messageId]/page.tsx' → ['channelId', 'messageId']
124
- */
125
- function extractRouteParams(filePath: string): string[] {
126
- const params: string[] = [];
127
- const regex = /\[([^\]]+)\]/g;
128
- let match;
129
- while ((match = regex.exec(filePath)) !== null) {
130
- params.push(match[1]);
131
- }
132
- return params;
133
- }
134
-
135
- /**
136
- * Calculates route depth from URL path.
137
- * '/' → 0, '/rider' → 1, '/rider/home' → 2
138
- */
139
- function routeDepth(urlPath: string): number {
140
- if (urlPath === '/') return 0;
141
- return urlPath.split('/').filter(Boolean).length;
142
- }
143
-
144
- // =============================================================================
145
- // File Classification
146
- // =============================================================================
147
-
148
- function isPageFile(filename: string, pageFileName: string): boolean {
149
- // Match exact page file name or any .tsx/.ts in the app directory
150
- if (filename.endsWith('/' + pageFileName)) return true;
151
- // Also match direct component files like app/rider/home.tsx
152
- return /\.(tsx|ts|jsx|js)$/.test(filename)
153
- && !filename.endsWith('.test.ts')
154
- && !filename.endsWith('.test.tsx')
155
- && !filename.includes('layout');
156
- }
157
-
158
- function isLayoutFile(filename: string, layoutFileName: string): boolean {
159
- return filename.endsWith('/' + layoutFileName) || filename.endsWith(layoutFileName);
160
- }
161
-
162
- // =============================================================================
163
- // Main API
164
- // =============================================================================
165
-
166
- /**
167
- * Extracts routes from a set of files in an app/ directory structure.
168
- *
169
- * @param files - Record of filename → source code (only app/ files needed)
170
- * @param options - Extraction options
171
- * @returns RouteExtractionResult with routes, layouts, and router IR
172
- */
173
- export function extractRoutes(
174
- files: Record<string, string>,
175
- options: RouteExtractorOptions = {},
176
- ): RouteExtractionResult {
177
- const rootDir = options.rootDir || 'app';
178
- const pageFileName = options.pageFileName || 'page.tsx';
179
- const layoutFileName = options.layoutFileName || 'layout.tsx';
180
- const slugPrefix = options.slugPrefix || 'app';
181
-
182
- // Discover layouts
183
- const layouts: LayoutEntry[] = [];
184
- for (const filename of Object.keys(files)) {
185
- if (isLayoutFile(filename, layoutFileName)) {
186
- const dir = filename.replace(/\/[^/]+$/, '');
187
- layouts.push({
188
- sourceFile: filename,
189
- directory: dir,
190
- depth: dir.split('/').filter(Boolean).length,
191
- });
192
- }
193
- }
194
- // Sort layouts by depth (shallowest first) for boundary lookup
195
- layouts.sort((a, b) => a.depth - b.depth);
196
-
197
- // Discover routes
198
- const routes: RouteEntry[] = [];
199
- const allParams = new Set<string>();
200
-
201
- for (const filename of Object.keys(files)) {
202
- // Skip layout files and non-app files
203
- if (isLayoutFile(filename, layoutFileName)) continue;
204
- if (!filename.startsWith(rootDir + '/') && !filename.startsWith(rootDir)) continue;
205
- if (!isPageFile(filename, pageFileName)) continue;
206
-
207
- const urlPath = filePathToUrlPattern(filename, rootDir);
208
- const stateName = urlToStateName(urlPath);
209
- const params = extractRouteParams(filename);
210
- const depth = routeDepth(urlPath);
211
-
212
- // Find nearest layout boundary
213
- const fileDir = filename.replace(/\/[^/]+$/, '');
214
- let layoutBoundary: string | undefined;
215
- for (let i = layouts.length - 1; i >= 0; i--) {
216
- if (fileDir.startsWith(layouts[i].directory)) {
217
- layoutBoundary = layouts[i].sourceFile;
218
- break;
219
- }
220
- }
221
-
222
- for (const p of params) allParams.add(p);
223
-
224
- routes.push({
225
- path: urlPath,
226
- stateName,
227
- sourceFile: filename,
228
- params,
229
- layoutBoundary,
230
- depth,
231
- });
232
- }
233
-
234
- // Sort routes by path for deterministic output
235
- routes.sort((a, b) => a.path.localeCompare(b.path));
236
-
237
- // Generate router workflow IR
238
- const routerIR = buildRouterIR(routes, layouts, Array.from(allParams), slugPrefix);
239
-
240
- return {
241
- routes,
242
- layouts,
243
- routerIR,
244
- allParams: Array.from(allParams),
245
- };
246
- }
247
-
248
- /**
249
- * Builds a router IRWorkflowDefinition from extracted routes.
250
- */
251
- function buildRouterIR(
252
- routes: RouteEntry[],
253
- layouts: LayoutEntry[],
254
- allParams: string[],
255
- slugPrefix: string,
256
- ): IRWorkflowDefinition {
257
- // Build states — one per route
258
- const states: IRStateDefinition[] = routes.map(route => ({
259
- name: route.stateName,
260
- type: route.path === '/' ? 'START' as const : 'REGULAR' as const,
261
- description: 'Route: ' + route.path,
262
- on_enter: [],
263
- during: [],
264
- on_exit: [],
265
- }));
266
-
267
- // Ensure at least one START state
268
- if (states.length > 0 && !states.some(s => s.type === 'START')) {
269
- states[0].type = 'START';
270
- }
271
-
272
- // Build transitions — navigate from any state to any other state
273
- const transitions: IRTransitionDefinition[] = [];
274
- for (const from of routes) {
275
- for (const to of routes) {
276
- if (from.stateName === to.stateName) continue;
277
- transitions.push({
278
- name: 'nav_' + from.stateName.toLowerCase() + '_to_' + to.stateName.toLowerCase(),
279
- from: [from.stateName],
280
- to: to.stateName,
281
- actions: [],
282
- conditions: [],
283
- });
284
- }
285
- }
286
-
287
- // Build fields for route parameters
288
- const fields: IRFieldDefinition[] = allParams.map(param => ({
289
- name: param,
290
- type: 'text' as const,
291
- label: param.replace(/([A-Z])/g, ' $1').replace(/^./, c => c.toUpperCase()),
292
- required: false,
293
- default_value: '',
294
- }));
295
-
296
- // Add current_route field
297
- fields.push({
298
- name: 'current_route',
299
- type: 'text',
300
- label: 'Current Route',
301
- required: false,
302
- default_value: '/',
303
- });
304
-
305
- return {
306
- slug: slugPrefix + '-router',
307
- name: 'Router',
308
- version: '1.0.0',
309
- description: 'Auto-generated router from app/ directory structure',
310
- category: 'router',
311
- fields,
312
- states,
313
- transitions,
314
- roles: [],
315
- tags: [{ tag_name: 'auto-derived' }, { tag_name: 'router' }],
316
- metadata: {
317
- runtime: 'local',
318
- routes: routes.map(r => ({
319
- state: r.stateName,
320
- path: r.path,
321
- params: r.params,
322
- layoutBoundary: r.layoutBoundary,
323
- sourceFile: r.sourceFile,
324
- })),
325
- layoutBoundaries: layouts.map(l => l.sourceFile),
326
- provenance: {
327
- frontend: 'react-compiler',
328
- source: 'route-extractor',
329
- compiler_version: '2.0.0',
330
- },
331
- },
332
- };
333
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Testing utilities — public API for @mindmatrix/react-compiler/testing.
3
- */
4
-
5
- // Compilation helpers
6
- export {
7
- compileWorkflow,
8
- compileModel,
9
- compileServerAction,
10
- compileRaw,
11
- extractFields,
12
- extractStates,
13
- extractTransitions,
14
- createMockBlueprintContext,
15
- createMockWorkflowContext,
16
- } from './test-utils';
17
- export type {
18
- CompileOptions,
19
- MockBlueprintProviderProps,
20
- MockWorkflowProviderProps,
21
- } from './test-utils';
22
-
23
- // Snapshot utilities
24
- export {
25
- snapshotIR,
26
- irToSnapshot,
27
- installIRMatchers,
28
- } from './snapshot';
29
- export type {
30
- IRSnapshot,
31
- IRMatcher,
32
- } from './snapshot';
@@ -1,252 +0,0 @@
1
- /**
2
- * IR Snapshot Utilities — snapshot and compare compiled IR for regression testing.
3
- *
4
- * snapshotIR() compiles a .workflow.tsx source and returns a stable, serializable
5
- * IR representation suitable for vitest's toMatchSnapshot() or the custom
6
- * toMatchIR() matcher.
7
- *
8
- * Usage:
9
- * import { snapshotIR, installIRMatchers } from '@mindmatrix/react-compiler/testing';
10
- *
11
- * // In vitest setup or test file:
12
- * installIRMatchers();
13
- *
14
- * it('counter IR is stable', () => {
15
- * const snapshot = snapshotIR(`
16
- * import { useState } from 'react';
17
- * export function Counter() {
18
- * const [count, setCount] = useState(0);
19
- * return <div>{count}</div>;
20
- * }
21
- * `);
22
- * expect(snapshot).toMatchSnapshot();
23
- * });
24
- *
25
- * it('IR matches expected shape', () => {
26
- * const ir = compileWorkflow(`...`);
27
- * expect(ir).toMatchIR({
28
- * fields: [{ name: 'count', type: 'number' }],
29
- * states: expect.arrayContaining([expect.objectContaining({ name: 'draft' })]),
30
- * });
31
- * });
32
- */
33
-
34
- import { compileWorkflow, compileModel, type CompileOptions } from './test-utils';
35
- import type { IRWorkflowDefinition } from '@mindmatrix/player-core';
36
-
37
- // =============================================================================
38
- // Snapshot
39
- // =============================================================================
40
-
41
- export interface IRSnapshot {
42
- /** Workflow slug. */
43
- slug: string;
44
- /** Version. */
45
- version: string;
46
- /** Category. */
47
- category: string;
48
- /** Field names and types. */
49
- fields: Array<{ name: string; type: string; required?: boolean }>;
50
- /** State names and types. */
51
- states: Array<{ name: string; type: string; enterActions: number; exitActions: number }>;
52
- /** Transition names with from→to. */
53
- transitions: Array<{ name: string; from: string | string[]; to: string; conditions: number }>;
54
- /** Whether experience (JSX) is present. */
55
- hasExperience: boolean;
56
- /** Extension tags present (cedar, cron, etc.). */
57
- extensionTags: string[];
58
- /** Number of data sources. */
59
- dataSourceCount: number;
60
- /** Number of mutation targets. */
61
- mutationTargetCount: number;
62
- /** Error count. */
63
- errorCount: number;
64
- }
65
-
66
- /**
67
- * Compile workflow source and extract a stable snapshot of its IR structure.
68
- *
69
- * The snapshot omits volatile data (action IDs, generated code) and keeps only
70
- * the structural shape — making it safe for snapshot testing across refactors.
71
- */
72
- export function snapshotIR(code: string, options?: CompileOptions): IRSnapshot {
73
- const ir = options?.filename?.includes('models/')
74
- ? compileModel(code, options)
75
- : compileWorkflow(code, options);
76
-
77
- return irToSnapshot(ir);
78
- }
79
-
80
- /**
81
- * Convert an already-compiled IR to a snapshot.
82
- */
83
- export function irToSnapshot(ir: IRWorkflowDefinition): IRSnapshot {
84
- const meta = (ir.metadata ?? {}) as Record<string, unknown>;
85
-
86
- return {
87
- slug: ir.slug ?? '',
88
- version: ir.version ?? '',
89
- category: Array.isArray(ir.category) ? ir.category[0] : (ir.category ?? 'workflow'),
90
- fields: ir.fields.map((f) => ({
91
- name: f.name,
92
- type: f.type,
93
- ...(f.required === false ? { required: false } : {}),
94
- })),
95
- states: ir.states.map((s) => ({
96
- name: s.name,
97
- type: s.type,
98
- enterActions: s.on_enter?.length ?? 0,
99
- exitActions: s.on_exit?.length ?? 0,
100
- })),
101
- transitions: ir.transitions.map((t) => ({
102
- name: t.name,
103
- from: t.from,
104
- to: t.to,
105
- conditions: t.conditions?.length ?? 0,
106
- })),
107
- hasExperience: !!meta.experience || !!(ir as any).experience,
108
- extensionTags: ir.extensions ? Object.keys(ir.extensions) : [],
109
- dataSourceCount: Array.isArray(meta.dataSources) ? meta.dataSources.length : 0,
110
- mutationTargetCount: Array.isArray(meta.mutationTargets) ? meta.mutationTargets.length : 0,
111
- errorCount: Array.isArray(meta.errors) ? meta.errors.length : 0,
112
- };
113
- }
114
-
115
- // =============================================================================
116
- // Custom Matcher: toMatchIR
117
- // =============================================================================
118
-
119
- /**
120
- * Partial IR shape for matching.
121
- * Every field is optional — only specified fields are checked.
122
- */
123
- export interface IRMatcher {
124
- slug?: string;
125
- version?: string;
126
- category?: string;
127
- fields?: Array<Record<string, unknown>>;
128
- states?: Array<Record<string, unknown>>;
129
- transitions?: Array<Record<string, unknown>>;
130
- description?: string;
131
- [key: string]: unknown;
132
- }
133
-
134
- /**
135
- * Install the toMatchIR() custom matcher into vitest's expect.
136
- *
137
- * Call this once in your test setup file or at the top of a test file:
138
- * import { installIRMatchers } from '@mindmatrix/react-compiler/testing';
139
- * installIRMatchers();
140
- */
141
- export function installIRMatchers(): void {
142
- // Dynamic import of expect to avoid requiring vitest at module parse time
143
- try {
144
- // eslint-disable-next-line @typescript-eslint/no-require-imports
145
- const { expect } = require('vitest');
146
-
147
- expect.extend({
148
- toMatchIR(received: IRWorkflowDefinition, expected: IRMatcher) {
149
- const failures: string[] = [];
150
-
151
- if (expected.slug !== undefined && received.slug !== expected.slug) {
152
- failures.push(`slug: expected "${expected.slug}", got "${received.slug}"`);
153
- }
154
- if (expected.version !== undefined && received.version !== expected.version) {
155
- failures.push(`version: expected "${expected.version}", got "${received.version}"`);
156
- }
157
- if (expected.category !== undefined && received.category !== expected.category) {
158
- failures.push(`category: expected "${expected.category}", got "${received.category}"`);
159
- }
160
- if (expected.description !== undefined && received.description !== expected.description) {
161
- failures.push(`description: expected "${expected.description}", got "${received.description}"`);
162
- }
163
-
164
- // Field matching — check each expected field exists with matching properties
165
- if (expected.fields !== undefined) {
166
- const expectedFields = expected.fields as Array<Record<string, unknown>>;
167
- for (const exp of expectedFields) {
168
- const match = received.fields.find((f) => f.name === exp.name);
169
- if (!match) {
170
- failures.push(`fields: expected field "${exp.name}" not found`);
171
- } else {
172
- const matchRec = match as unknown as Record<string, unknown>;
173
- for (const [key, val] of Object.entries(exp)) {
174
- if (matchRec[key] !== val) {
175
- failures.push(`fields.${exp.name}.${key}: expected ${JSON.stringify(val)}, got ${JSON.stringify(matchRec[key])}`);
176
- }
177
- }
178
- }
179
- }
180
- }
181
-
182
- // State matching
183
- if (expected.states !== undefined && Array.isArray(expected.states)) {
184
- const expectedStates = expected.states as Array<Record<string, unknown>>;
185
- for (const exp of expectedStates) {
186
- const match = received.states.find((s) => s.name === exp.name);
187
- if (!match) {
188
- failures.push(`states: expected state "${exp.name}" not found`);
189
- } else {
190
- const matchRec = match as unknown as Record<string, unknown>;
191
- for (const [key, val] of Object.entries(exp)) {
192
- if (key === 'on_enter' || key === 'on_exit' || key === 'during') {
193
- const arr = matchRec[key] as unknown[] | undefined;
194
- if (typeof val === 'number' && (arr?.length ?? 0) !== val) {
195
- failures.push(`states.${exp.name}.${key}: expected length ${val}, got ${arr?.length ?? 0}`);
196
- }
197
- } else if (matchRec[key] !== val) {
198
- failures.push(`states.${exp.name}.${key}: expected ${JSON.stringify(val)}, got ${JSON.stringify(matchRec[key])}`);
199
- }
200
- }
201
- }
202
- }
203
- }
204
-
205
- // Transition matching
206
- if (expected.transitions !== undefined && Array.isArray(expected.transitions)) {
207
- const expectedTransitions = expected.transitions as Array<Record<string, unknown>>;
208
- for (const exp of expectedTransitions) {
209
- const match = received.transitions.find((t) => t.name === exp.name);
210
- if (!match) {
211
- failures.push(`transitions: expected transition "${exp.name}" not found`);
212
- } else {
213
- if (exp.from !== undefined) {
214
- const fromMatch = JSON.stringify(match.from) === JSON.stringify(exp.from);
215
- if (!fromMatch) {
216
- failures.push(`transitions.${exp.name}.from: expected ${JSON.stringify(exp.from)}, got ${JSON.stringify(match.from)}`);
217
- }
218
- }
219
- if (exp.to !== undefined && match.to !== exp.to) {
220
- failures.push(`transitions.${exp.name}.to: expected "${exp.to}", got "${match.to}"`);
221
- }
222
- }
223
- }
224
- }
225
-
226
- const pass = failures.length === 0;
227
- return {
228
- pass,
229
- message: () =>
230
- pass
231
- ? `expected IR not to match, but it did`
232
- : `IR mismatch:\n${failures.map((f) => ` - ${f}`).join('\n')}`,
233
- };
234
- },
235
- });
236
- } catch {
237
- // vitest not available — skip matcher installation
238
- }
239
- }
240
-
241
- // =============================================================================
242
- // Type augmentation for vitest
243
- // =============================================================================
244
-
245
- // Note: To use the toMatchIR() custom matcher with full type support,
246
- // add the following to your test setup or a global .d.ts file:
247
- //
248
- // import type { IRMatcher } from '@mindmatrix/react-compiler/testing';
249
- // declare module 'vitest' {
250
- // interface Assertion<T> { toMatchIR(expected: IRMatcher): void; }
251
- // interface AsymmetricMatchersContaining { toMatchIR(expected: IRMatcher): void; }
252
- // }