@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.
- package/ATOM-PIPELINE.md +144 -0
- package/README.md +88 -40
- package/dist/babel/index.js +113 -6
- package/dist/babel/index.mjs +2 -2
- package/dist/chunk-3USIFFE4.mjs +2190 -0
- package/dist/chunk-45YMGEVT.mjs +186 -0
- package/dist/chunk-4FN2AISW.mjs +148 -0
- package/dist/chunk-4OPI5L7G.mjs +2593 -0
- package/dist/chunk-4RYTKOOJ.mjs +186 -0
- package/dist/chunk-5RKTOVR5.mjs +244 -0
- package/dist/chunk-5YDMOO4X.mjs +214 -0
- package/dist/chunk-64ZWEMLJ.mjs +148 -0
- package/dist/chunk-6XP4KSWQ.mjs +2190 -0
- package/dist/chunk-72QWL54I.mjs +175 -0
- package/dist/chunk-7B4TRI7C.mjs +4835 -0
- package/dist/chunk-7ZKGHTNB.mjs +4952 -0
- package/dist/chunk-CIESM3BP.mjs +33 -0
- package/dist/chunk-DE3ZGQAC.mjs +148 -0
- package/dist/chunk-DMCY3BBG.mjs +1933 -0
- package/dist/chunk-DPIK3PJS.mjs +244 -0
- package/dist/chunk-E5IVH4RE.mjs +186 -0
- package/dist/chunk-E6FZNUR5.mjs +4953 -0
- package/dist/chunk-EJRBDQDP.mjs +2607 -0
- package/dist/chunk-ELO4TXJL.mjs +186 -0
- package/dist/chunk-FKRO52XH.mjs +3446 -0
- package/dist/chunk-FL4YAKU6.mjs +4941 -0
- package/dist/chunk-FYT47UBU.mjs +5076 -0
- package/dist/chunk-GCLGPOJZ.mjs +148 -0
- package/dist/chunk-GXB4JOP7.mjs +5072 -0
- package/dist/chunk-HFXOUMTD.mjs +175 -0
- package/dist/chunk-HWIZ47US.mjs +214 -0
- package/dist/chunk-IB7MNPQL.mjs +4953 -0
- package/dist/chunk-ICSIHQCG.mjs +148 -0
- package/dist/chunk-JLA5VNQ3.mjs +186 -0
- package/dist/chunk-JQLWFCTM.mjs +214 -0
- package/dist/chunk-KFJJCQAL.mjs +148 -0
- package/dist/chunk-KJUIIEQE.mjs +186 -0
- package/dist/chunk-KNWTHRVQ.mjs +175 -0
- package/dist/chunk-KSG4XSZF.mjs +175 -0
- package/dist/chunk-LF5N6DOU.mjs +175 -0
- package/dist/chunk-LJQCM2IM.mjs +214 -0
- package/dist/chunk-NW6555WJ.mjs +186 -0
- package/dist/chunk-OMZE6VLQ.mjs +214 -0
- package/dist/chunk-P4BR7WVO.mjs +2190 -0
- package/dist/chunk-QQHVYH2X.mjs +244 -0
- package/dist/chunk-S5QLWLLT.mjs +186 -0
- package/dist/chunk-SCWGT2FY.mjs +2190 -0
- package/dist/chunk-SMKJUSB3.mjs +2190 -0
- package/dist/chunk-VCAY2KGM.mjs +175 -0
- package/dist/chunk-WECAV6QB.mjs +148 -0
- package/dist/chunk-WMKBXUCE.mjs +3228 -0
- package/dist/chunk-XAJ5BKKL.mjs +4947 -0
- package/dist/chunk-XG2X7AEA.mjs +175 -0
- package/dist/chunk-XG7Z23NQ.mjs +148 -0
- package/dist/chunk-XWZAOCQ7.mjs +2607 -0
- package/dist/chunk-Y6MA7ULW.mjs +148 -0
- package/dist/chunk-YMS7Q7LG.mjs +214 -0
- package/dist/chunk-ZA37XTGA.mjs +175 -0
- package/dist/cli/index.js +1616 -366
- package/dist/cli/index.mjs +8 -8
- package/dist/codemod/cli.mjs +1 -1
- package/dist/codemod/index.mjs +1 -1
- package/dist/dev-server-RmGHIntF.d.mts +113 -0
- package/dist/dev-server-RmGHIntF.d.ts +113 -0
- package/dist/dev-server.d.mts +1 -1
- package/dist/dev-server.d.ts +1 -1
- package/dist/dev-server.js +982 -53
- package/dist/dev-server.mjs +5 -5
- package/dist/envelope.js +113 -6
- package/dist/envelope.mjs +3 -3
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +992 -63
- package/dist/index.mjs +8 -8
- package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
- package/dist/init-EHO4VQ22.mjs +369 -0
- package/dist/init-UC3FWPIW.mjs +367 -0
- package/dist/init-UNSMVKIK.mjs +366 -0
- package/dist/init-UNV5XIDE.mjs +367 -0
- package/dist/project-compiler-2P4N4DR7.mjs +10 -0
- package/dist/project-compiler-D2LCC27O.mjs +10 -0
- package/dist/project-compiler-EJ3GANJE.mjs +10 -0
- package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
- package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
- package/dist/project-compiler-VWNNCHGO.mjs +10 -0
- package/dist/project-compiler-XVAAU4C5.mjs +10 -0
- package/dist/project-compiler-YES5FGMD.mjs +10 -0
- package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
- package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
- package/dist/project-decompiler-VLPR22QF.mjs +7 -0
- package/dist/pull-FUS5QYZS.mjs +109 -0
- package/dist/pull-LD5ENLGY.mjs +109 -0
- package/dist/testing/index.js +113 -6
- package/dist/testing/index.mjs +2 -2
- package/dist/vite/index.js +113 -6
- package/dist/vite/index.mjs +3 -3
- package/examples/uber-app/app/admin/fleet.tsx +19 -19
- package/package.json +4 -3
- package/compile-blueprint-chat.mjs +0 -99
- package/compile-blueprint-glass-console.mjs +0 -98
- package/compile-chat-defs.mjs +0 -92
- package/examples/uber-app/tests/payment.test.tsx +0 -129
- package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
- package/package.json.backup +0 -86
- package/scripts/decompile.ts +0 -226
- package/scripts/seed-auth.ts +0 -267
- package/scripts/seed-uber.ts +0 -248
- package/scripts/validate-uber.ts +0 -119
- package/seed-blueprint-chat.mjs +0 -444
- package/seed-blueprint-glass-console.mjs +0 -445
- package/seed-compiled.mjs +0 -318
- package/src/RoundTripValidator.ts +0 -400
- package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
- package/src/__tests__/auth-module-compilation.test.ts +0 -247
- package/src/__tests__/auth-template-compilation.test.ts +0 -589
- package/src/__tests__/change-extractor.test.ts +0 -142
- package/src/__tests__/cli-pull.test.ts +0 -73
- package/src/__tests__/cli-test.test.ts +0 -72
- package/src/__tests__/component-extractor.test.ts +0 -331
- package/src/__tests__/context-extractor.test.ts +0 -145
- package/src/__tests__/decompiler.test.ts +0 -718
- package/src/__tests__/define-blueprint.test.ts +0 -133
- package/src/__tests__/definition-validator.test.ts +0 -519
- package/src/__tests__/during-extractor.test.ts +0 -152
- package/src/__tests__/effect-extractor.test.ts +0 -107
- package/src/__tests__/event-emission.test.ts +0 -127
- package/src/__tests__/examples.test.ts +0 -236
- package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
- package/src/__tests__/golden-suite.test.ts +0 -403
- package/src/__tests__/grammar-island-extractor.test.ts +0 -289
- package/src/__tests__/instance-key.test.ts +0 -82
- package/src/__tests__/ir-migration.test.ts +0 -255
- package/src/__tests__/lock-file.test.ts +0 -117
- package/src/__tests__/model-extractor.test.ts +0 -195
- package/src/__tests__/model-field-acl.test.ts +0 -237
- package/src/__tests__/model-hooks.test.ts +0 -130
- package/src/__tests__/model-ref-resolution.test.ts +0 -268
- package/src/__tests__/model-roundtrip.test.ts +0 -502
- package/src/__tests__/model-runtime.test.ts +0 -112
- package/src/__tests__/model-transitions.test.ts +0 -183
- package/src/__tests__/nrt-action-trace.test.ts +0 -391
- package/src/__tests__/pipeline-hardening.test.ts +0 -413
- package/src/__tests__/project-compiler.test.ts +0 -546
- package/src/__tests__/project-decompiler.test.ts +0 -343
- package/src/__tests__/query-compilation.test.ts +0 -145
- package/src/__tests__/round-trip/PLAN.md +0 -158
- package/src/__tests__/round-trip/README.md +0 -52
- package/src/__tests__/round-trip/RESULTS.md +0 -86
- package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
- package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
- package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
- package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
- package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
- package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
- package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
- package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
- package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
- package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
- package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
- package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
- package/src/__tests__/round-trip-ir.test.ts +0 -300
- package/src/__tests__/round-trip.test.ts +0 -1212
- package/src/__tests__/route-merging.test.ts +0 -372
- package/src/__tests__/router-composition.test.ts +0 -489
- package/src/__tests__/router-extractor.test.ts +0 -176
- package/src/__tests__/server-action-extractor.test.ts +0 -128
- package/src/__tests__/smart-type-inference.test.ts +0 -365
- package/src/__tests__/source-envelope.test.ts +0 -284
- package/src/__tests__/source-fidelity.test.ts +0 -516
- package/src/__tests__/state-extractor.test.ts +0 -115
- package/src/__tests__/strict-mode.test.ts +0 -227
- package/src/__tests__/transition-effect-extractor.test.ts +0 -119
- package/src/__tests__/transition-extractor.test.ts +0 -68
- package/src/__tests__/ts-to-expression.test.ts +0 -462
- package/src/__tests__/type-generator.test.ts +0 -201
- package/src/__tests__/uber-validation.test.ts +0 -502
- package/src/action-compiler.ts +0 -361
- package/src/babel/emitters/experience-transform.ts +0 -199
- package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
- package/src/babel/emitters/pure-form-emitter.ts +0 -1023
- package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
- package/src/babel/extractors/change-extractor.ts +0 -199
- package/src/babel/extractors/component-extractor.ts +0 -907
- package/src/babel/extractors/computed-extractor.ts +0 -262
- package/src/babel/extractors/context-extractor.ts +0 -277
- package/src/babel/extractors/during-extractor.ts +0 -295
- package/src/babel/extractors/effect-extractor.ts +0 -340
- package/src/babel/extractors/event-extractor.ts +0 -235
- package/src/babel/extractors/grammar-island-extractor.ts +0 -302
- package/src/babel/extractors/model-extractor.ts +0 -1018
- package/src/babel/extractors/router-extractor.ts +0 -303
- package/src/babel/extractors/server-action-extractor.ts +0 -173
- package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
- package/src/babel/extractors/server-state-extractor.ts +0 -88
- package/src/babel/extractors/state-extractor.ts +0 -214
- package/src/babel/extractors/transition-effect-extractor.ts +0 -176
- package/src/babel/extractors/transition-extractor.ts +0 -143
- package/src/babel/index.ts +0 -24
- package/src/babel/transpilers/ts-to-expression.ts +0 -674
- package/src/babel/visitor.ts +0 -807
- package/src/cli/auth.ts +0 -255
- package/src/cli/build.ts +0 -288
- package/src/cli/deploy.ts +0 -206
- package/src/cli/index.ts +0 -328
- package/src/cli/installer.ts +0 -261
- package/src/cli/lock-file.ts +0 -94
- package/src/cli/mmrc.ts +0 -22
- package/src/cli/pull.ts +0 -172
- package/src/cli/registry-client.ts +0 -175
- package/src/cli/test.ts +0 -397
- package/src/cli/type-generator.ts +0 -243
- package/src/codemod/__tests__/forward.test.ts +0 -239
- package/src/codemod/__tests__/reverse.test.ts +0 -145
- package/src/codemod/__tests__/round-trip.test.ts +0 -137
- package/src/codemod/annotation.ts +0 -97
- package/src/codemod/classify.ts +0 -197
- package/src/codemod/cli.ts +0 -207
- package/src/codemod/control-flow.ts +0 -409
- package/src/codemod/forward.ts +0 -244
- package/src/codemod/import-manager.ts +0 -171
- package/src/codemod/index.ts +0 -120
- package/src/codemod/reverse.ts +0 -197
- package/src/codemod/rules.ts +0 -174
- package/src/codemod/state-transform.ts +0 -126
- package/src/decompiler/ast-builder.ts +0 -538
- package/src/decompiler/config-generator.ts +0 -151
- package/src/decompiler/index.ts +0 -315
- package/src/decompiler/project-decompiler.ts +0 -1776
- package/src/decompiler/project.ts +0 -862
- package/src/decompiler/split-strategy.ts +0 -140
- package/src/decompiler/state-emitter.ts +0 -1053
- package/src/decompiler/sx-emitter.ts +0 -318
- package/src/decompiler/workspace-hydrator.ts +0 -189
- package/src/dev-server.ts +0 -238
- package/src/envelope/fs-tree.ts +0 -217
- package/src/envelope/source-envelope.ts +0 -264
- package/src/envelope.ts +0 -315
- package/src/incremental-compiler.ts +0 -401
- package/src/index.ts +0 -99
- package/src/model-compiler.ts +0 -277
- package/src/project-compiler.ts +0 -1629
- package/src/route-extractor.ts +0 -333
- package/src/testing/index.ts +0 -32
- package/src/testing/snapshot.ts +0 -252
- package/src/testing/test-utils.ts +0 -226
- package/src/types.ts +0 -68
- package/src/vite/index.ts +0 -288
- package/test-compile.mjs +0 -142
- package/tsconfig.json +0 -25
- package/tsup.config.ts +0 -23
- package/vitest.config.ts +0 -9
package/src/route-extractor.ts
DELETED
|
@@ -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
|
-
}
|
package/src/testing/index.ts
DELETED
|
@@ -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';
|
package/src/testing/snapshot.ts
DELETED
|
@@ -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
|
-
// }
|