@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
|
@@ -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
|
-
}
|