@mmapp/react-compiler 0.1.0-alpha.1 → 0.1.0-alpha.4
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/auth-3UK75242.mjs +17 -0
- package/dist/babel/index.d.mts +2 -2
- package/dist/babel/index.d.ts +2 -2
- package/dist/babel/index.js +2816 -279
- 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-52XHYD2V.mjs +214 -0
- package/dist/chunk-5FTDWKHH.mjs +244 -0
- package/dist/chunk-5GUFFFGL.mjs +148 -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-7JRAEFRB.mjs +7510 -0
- package/dist/chunk-7T6Q5KAA.mjs +7506 -0
- package/dist/chunk-7ZKGHTNB.mjs +4952 -0
- package/dist/chunk-ABYPKRSB.mjs +215 -0
- package/dist/chunk-BZEXUPDH.mjs +175 -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-EO6SYNCG.mjs +175 -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-HRYR54PT.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-J3M4GUS7.mjs +161 -0
- package/dist/chunk-J7JUAHS4.mjs +186 -0
- package/dist/chunk-JLA5VNQ3.mjs +186 -0
- package/dist/chunk-JQLWFCTM.mjs +214 -0
- package/dist/chunk-JRGFBWTN.mjs +2918 -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-NTB7OEX2.mjs +2918 -0
- package/dist/chunk-NW6555WJ.mjs +186 -0
- package/dist/chunk-O4AUS7EU.mjs +148 -0
- package/dist/chunk-OMZE6VLQ.mjs +214 -0
- package/dist/chunk-OPJKP747.mjs +7506 -0
- package/dist/chunk-P4BR7WVO.mjs +2190 -0
- package/dist/chunk-QQHVYH2X.mjs +244 -0
- package/dist/chunk-R2DD5GTY.mjs +186 -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-THFYE5ZX.mjs +244 -0
- package/dist/chunk-UDDTWG5J.mjs +734 -0
- package/dist/chunk-VCAY2KGM.mjs +175 -0
- package/dist/chunk-VLTKQDJ3.mjs +244 -0
- package/dist/chunk-WBYMW4NQ.mjs +3450 -0
- package/dist/chunk-WECAV6QB.mjs +148 -0
- package/dist/chunk-WMKBXUCE.mjs +3228 -0
- package/dist/chunk-WVYY32LD.mjs +939 -0
- package/dist/chunk-XAJ5BKKL.mjs +4947 -0
- package/dist/chunk-XDVM4YHX.mjs +3450 -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-Z2G5RZ4H.mjs +186 -0
- package/dist/chunk-ZA37XTGA.mjs +175 -0
- package/dist/chunk-ZE3KCHBM.mjs +2918 -0
- package/dist/cli/index.js +14720 -7199
- package/dist/cli/index.mjs +224 -183
- package/dist/codemod/cli.js +1 -1
- package/dist/codemod/cli.mjs +2 -2
- package/dist/codemod/index.d.mts +3 -3
- package/dist/codemod/index.d.ts +3 -3
- package/dist/codemod/index.js +1 -1
- package/dist/codemod/index.mjs +2 -2
- package/dist/config-PL24KEWL.mjs +219 -0
- package/dist/deploy-YAJGW6II.mjs +9 -0
- package/dist/dev-server-CrQ041KP.d.mts +79 -0
- package/dist/dev-server-CrQ041KP.d.ts +79 -0
- package/dist/dev-server-RmGHIntF.d.mts +113 -0
- package/dist/dev-server-RmGHIntF.d.ts +113 -0
- package/dist/dev-server.d.mts +2 -2
- package/dist/dev-server.d.ts +2 -2
- package/dist/dev-server.js +6424 -1597
- package/dist/dev-server.mjs +5 -5
- package/dist/envelope-ChEkuHij.d.mts +265 -0
- package/dist/envelope-ChEkuHij.d.ts +265 -0
- package/dist/envelope.d.mts +2 -2
- package/dist/envelope.d.ts +2 -2
- package/dist/envelope.js +2814 -277
- package/dist/envelope.mjs +3 -3
- package/dist/index-CEKyyazf.d.mts +104 -0
- package/dist/index-CEKyyazf.d.ts +104 -0
- package/dist/index.d.mts +168 -9
- package/dist/index.d.ts +168 -9
- package/dist/index.js +5606 -681
- package/dist/index.mjs +217 -9
- package/dist/init-7FJENUDK.mjs +407 -0
- package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
- package/dist/init-DQDX3QK6.mjs +369 -0
- 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-NNK32MPG.mjs +10 -0
- package/dist/project-compiler-OP2VVGJQ.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-ZB4RUYVL.mjs +10 -0
- package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
- package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
- package/dist/project-decompiler-U55HQUHW.mjs +7 -0
- package/dist/project-decompiler-US7GAVIC.mjs +7 -0
- package/dist/project-decompiler-VLPR22QF.mjs +7 -0
- package/dist/pull-FUS5QYZS.mjs +109 -0
- package/dist/pull-KOL2QAYQ.mjs +109 -0
- package/dist/pull-LD5ENLGY.mjs +109 -0
- package/dist/pull-P44LDRWB.mjs +109 -0
- package/dist/seed-KOGEPGOJ.mjs +154 -0
- package/dist/server-VW6UPCHO.mjs +277 -0
- package/dist/testing/index.d.mts +8 -8
- package/dist/testing/index.d.ts +8 -8
- package/dist/testing/index.js +2824 -287
- package/dist/testing/index.mjs +2 -2
- package/dist/verify-BYHUKARQ.mjs +1833 -0
- package/dist/verify-OQDEQYMS.mjs +1833 -0
- package/dist/verify-SEIXUGN4.mjs +1833 -0
- package/dist/vite/index.d.mts +1 -1
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +2817 -280
- package/dist/vite/index.mjs +3 -3
- package/examples/authentication/main.workflow.tsx +1 -1
- package/examples/authentication/mm.config.ts +1 -1
- package/examples/authentication/pages/LoginPage.tsx +2 -2
- package/examples/authentication/pages/SignupPage.tsx +2 -2
- package/examples/counter.workflow.tsx +1 -1
- package/examples/dashboard.workflow.tsx +1 -1
- package/examples/invoice-approval/actions/invoice.server.ts +1 -1
- package/examples/invoice-approval/main.workflow.tsx +1 -1
- package/examples/invoice-approval/mm.config.ts +1 -1
- package/examples/invoice-approval/pages/InvoiceDetailPage.tsx +1 -1
- package/examples/invoice-approval/pages/InvoiceFormPage.tsx +1 -1
- package/examples/invoice-approval/pages/InvoiceListPage.tsx +1 -1
- package/examples/todo-app.workflow.tsx +1 -1
- package/examples/uber-app/actions/matching.server.ts +1 -1
- package/examples/uber-app/actions/notifications.server.ts +1 -1
- package/examples/uber-app/actions/payments.server.ts +1 -1
- package/examples/uber-app/actions/pricing.server.ts +1 -1
- package/examples/uber-app/app/admin/analytics.tsx +2 -2
- package/examples/uber-app/app/admin/fleet.tsx +21 -21
- package/examples/uber-app/app/admin/surge-pricing.tsx +2 -2
- package/examples/uber-app/app/driver/dashboard.tsx +2 -2
- package/examples/uber-app/app/driver/earnings.tsx +2 -2
- package/examples/uber-app/app/driver/navigation.tsx +2 -2
- package/examples/uber-app/app/driver/ride-acceptance.tsx +2 -2
- package/examples/uber-app/app/rider/home.tsx +2 -2
- package/examples/uber-app/app/rider/payment-methods.tsx +2 -2
- package/examples/uber-app/app/rider/ride-history.tsx +2 -2
- package/examples/uber-app/app/rider/ride-tracking.tsx +2 -2
- package/examples/uber-app/components/DriverCard.tsx +1 -1
- package/examples/uber-app/components/MapView.tsx +3 -3
- package/examples/uber-app/components/RatingStars.tsx +2 -2
- package/examples/uber-app/components/RideCard.tsx +1 -1
- package/examples/uber-app/mm.config.ts +1 -1
- package/examples/uber-app/workflows/dispute-resolution.workflow.tsx +2 -2
- package/examples/uber-app/workflows/driver-onboarding.workflow.tsx +2 -2
- package/examples/uber-app/workflows/payment-processing.workflow.tsx +2 -2
- package/examples/uber-app/workflows/ride-request.workflow.tsx +2 -2
- package/package.json +10 -4
- 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,862 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* project.ts — Multi-file project decompiler.
|
|
3
|
-
*
|
|
4
|
-
* Takes a full IRWorkflowDefinition and produces a project file tree:
|
|
5
|
-
* - main.workflow.tsx (root layout + routing + state machine hooks)
|
|
6
|
-
* - pages/*.tsx (separate page components per route/view)
|
|
7
|
-
* - models/*.ts (TypeScript interfaces from field definitions)
|
|
8
|
-
* - actions/*.server.ts (server action stubs from transition actions)
|
|
9
|
-
* - mm.config.ts (blueprint configuration: defineBlueprint)
|
|
10
|
-
*
|
|
11
|
-
* This is the multi-file counterpart to the single-file decompile() function.
|
|
12
|
-
* It splits complex definitions into cohesive, focused files matching the
|
|
13
|
-
* envelope fs-tree file role conventions (view-entry, page, model, server-action, config).
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type {
|
|
17
|
-
IRWorkflowDefinition,
|
|
18
|
-
IRExperienceNode,
|
|
19
|
-
IRFieldDefinition,
|
|
20
|
-
IRStateDefinition,
|
|
21
|
-
IRTransitionDefinition,
|
|
22
|
-
IRActionDefinition,
|
|
23
|
-
} from '@mindmatrix/player-core';
|
|
24
|
-
import { decompile } from './index';
|
|
25
|
-
import type { DecompilerInput, DecompileOptions } from './index';
|
|
26
|
-
import type { FileRole } from '../envelope/fs-tree';
|
|
27
|
-
|
|
28
|
-
// =============================================================================
|
|
29
|
-
// Public Types
|
|
30
|
-
// =============================================================================
|
|
31
|
-
|
|
32
|
-
/** A single file in the decompiled project. */
|
|
33
|
-
export interface ProjectFile {
|
|
34
|
-
/** Relative path from project root (e.g. "main.workflow.tsx"). */
|
|
35
|
-
path: string;
|
|
36
|
-
/** File role matching the envelope fs-tree convention. */
|
|
37
|
-
role: FileRole;
|
|
38
|
-
/** Generated source code. */
|
|
39
|
-
content: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Result of decompiling a definition into a multi-file project. */
|
|
43
|
-
export interface DecompileProjectResult {
|
|
44
|
-
/** All generated files. */
|
|
45
|
-
files: ProjectFile[];
|
|
46
|
-
/** The main entry file path. */
|
|
47
|
-
entryFile: string;
|
|
48
|
-
/** The slug used for naming. */
|
|
49
|
-
slug: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Options for project decompilation. */
|
|
53
|
-
export interface DecompileProjectOptions extends DecompileOptions {
|
|
54
|
-
/** Skip generating mm.config.ts. Default: false. */
|
|
55
|
-
skipConfig?: boolean;
|
|
56
|
-
/** Skip generating model files. Default: false. */
|
|
57
|
-
skipModels?: boolean;
|
|
58
|
-
/** Skip generating server action stubs. Default: false. */
|
|
59
|
-
skipActions?: boolean;
|
|
60
|
-
/** Skip splitting pages into separate files. Default: false. */
|
|
61
|
-
skipPages?: boolean;
|
|
62
|
-
/** Skip decompiling child definitions into separate files. Default: false. */
|
|
63
|
-
skipChildren?: boolean;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// =============================================================================
|
|
67
|
-
// Name Helpers
|
|
68
|
-
// =============================================================================
|
|
69
|
-
|
|
70
|
-
function pascalCase(slug: string): string {
|
|
71
|
-
return slug
|
|
72
|
-
.split(/[-_]/)
|
|
73
|
-
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
74
|
-
.join('');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function camelCase(str: string): string {
|
|
78
|
-
return str.replace(/[-_]([a-z])/g, (_, c: string) => c.toUpperCase());
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Map IR field type to TypeScript type string, with union type support. */
|
|
82
|
-
function fieldTypeToTS(fieldType: string, field?: IRFieldDefinition): string {
|
|
83
|
-
// Check for options → emit union type
|
|
84
|
-
const options = getFieldOptions(field);
|
|
85
|
-
if (options && options.length > 0 && (fieldType === 'select' || fieldType === 'text')) {
|
|
86
|
-
return options.map(o => `'${o.replace(/'/g, "\\'")}'`).join(' | ');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
switch (fieldType) {
|
|
90
|
-
case 'text':
|
|
91
|
-
case 'rich_text':
|
|
92
|
-
case 'email':
|
|
93
|
-
case 'url':
|
|
94
|
-
case 'phone':
|
|
95
|
-
case 'color':
|
|
96
|
-
case 'select':
|
|
97
|
-
return 'string';
|
|
98
|
-
case 'number':
|
|
99
|
-
case 'currency':
|
|
100
|
-
case 'percentage':
|
|
101
|
-
case 'rating':
|
|
102
|
-
case 'duration':
|
|
103
|
-
case 'auto_number':
|
|
104
|
-
return 'number';
|
|
105
|
-
case 'boolean':
|
|
106
|
-
return 'boolean';
|
|
107
|
-
case 'date':
|
|
108
|
-
case 'datetime':
|
|
109
|
-
case 'created_at':
|
|
110
|
-
case 'updated_at':
|
|
111
|
-
return 'Date';
|
|
112
|
-
case 'multi_select':
|
|
113
|
-
return 'string[]';
|
|
114
|
-
case 'json':
|
|
115
|
-
return 'Record<string, unknown>';
|
|
116
|
-
case 'file':
|
|
117
|
-
case 'image':
|
|
118
|
-
return 'string'; // URL
|
|
119
|
-
case 'relation':
|
|
120
|
-
case 'lookup':
|
|
121
|
-
return 'string'; // ID reference
|
|
122
|
-
default:
|
|
123
|
-
return 'unknown';
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** Extracts options from field validation, metadata, or legacy field.options. */
|
|
128
|
-
function getFieldOptions(field?: IRFieldDefinition): string[] | null {
|
|
129
|
-
if (!field) return null;
|
|
130
|
-
const f = field as unknown as Record<string, unknown>;
|
|
131
|
-
const validation = f.validation as Record<string, unknown> | undefined;
|
|
132
|
-
if (validation?.options && Array.isArray(validation.options)) return validation.options as string[];
|
|
133
|
-
const meta = f.metadata as Record<string, unknown> | undefined;
|
|
134
|
-
if (meta?.options && Array.isArray(meta.options)) return meta.options as string[];
|
|
135
|
-
if (f.options && Array.isArray(f.options)) return f.options as string[];
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// =============================================================================
|
|
140
|
-
// Model Generation
|
|
141
|
-
// =============================================================================
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Generates a TypeScript interface file from field definitions.
|
|
145
|
-
*
|
|
146
|
-
* Output example:
|
|
147
|
-
* export interface Invoice {
|
|
148
|
-
* amount: number;
|
|
149
|
-
* vendor: string;
|
|
150
|
-
* date: Date;
|
|
151
|
-
* status: string;
|
|
152
|
-
* }
|
|
153
|
-
*/
|
|
154
|
-
function generateModelFile(
|
|
155
|
-
slug: string,
|
|
156
|
-
fields: IRFieldDefinition[],
|
|
157
|
-
): string {
|
|
158
|
-
const typeName = pascalCase(slug);
|
|
159
|
-
const lines: string[] = [
|
|
160
|
-
`/**`,
|
|
161
|
-
` * ${typeName} — data model interface.`,
|
|
162
|
-
` *`,
|
|
163
|
-
` * Auto-generated from workflow definition "${slug}".`,
|
|
164
|
-
` * Fields: ${fields.length}`,
|
|
165
|
-
` */`,
|
|
166
|
-
``,
|
|
167
|
-
`export interface ${typeName} {`,
|
|
168
|
-
];
|
|
169
|
-
|
|
170
|
-
for (const field of fields) {
|
|
171
|
-
const tsType = fieldTypeToTS(field.type, field);
|
|
172
|
-
const optional = field.required ? '' : '?';
|
|
173
|
-
const comment = field.label ? ` /** ${field.label} */` : '';
|
|
174
|
-
if (comment) lines.push(` ${comment}`);
|
|
175
|
-
lines.push(` ${camelCase(field.name)}${optional}: ${tsType};`);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
lines.push(`}`);
|
|
179
|
-
lines.push(``);
|
|
180
|
-
|
|
181
|
-
// Export state enum if states exist (added by caller)
|
|
182
|
-
return lines.join('\n');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Generates a state enum from state definitions.
|
|
187
|
-
*
|
|
188
|
-
* Output:
|
|
189
|
-
* export enum InvoiceState {
|
|
190
|
-
* Draft = 'draft',
|
|
191
|
-
* Submitted = 'submitted',
|
|
192
|
-
* }
|
|
193
|
-
*/
|
|
194
|
-
function generateStateEnum(
|
|
195
|
-
slug: string,
|
|
196
|
-
states: IRStateDefinition[],
|
|
197
|
-
): string {
|
|
198
|
-
if (states.length === 0) return '';
|
|
199
|
-
|
|
200
|
-
const enumName = `${pascalCase(slug)}State`;
|
|
201
|
-
const lines: string[] = [
|
|
202
|
-
``,
|
|
203
|
-
`export enum ${enumName} {`,
|
|
204
|
-
];
|
|
205
|
-
|
|
206
|
-
for (const state of states) {
|
|
207
|
-
const enumKey = pascalCase(state.name);
|
|
208
|
-
lines.push(` ${enumKey} = '${state.name}',`);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
lines.push(`}`);
|
|
212
|
-
lines.push(``);
|
|
213
|
-
return lines.join('\n');
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// =============================================================================
|
|
217
|
-
// Server Action Generation
|
|
218
|
-
// =============================================================================
|
|
219
|
-
|
|
220
|
-
/** Extracts unique server-side action types from states and transitions. */
|
|
221
|
-
function extractServerActions(
|
|
222
|
-
states: IRStateDefinition[],
|
|
223
|
-
transitions: IRTransitionDefinition[],
|
|
224
|
-
): Array<{ name: string; type: string; config: Record<string, unknown> }> {
|
|
225
|
-
const seen = new Set<string>();
|
|
226
|
-
const actions: Array<{ name: string; type: string; config: Record<string, unknown> }> = [];
|
|
227
|
-
|
|
228
|
-
const SERVER_ACTION_TYPES = new Set([
|
|
229
|
-
'http_request', 'webhook', 'call_webhook',
|
|
230
|
-
'notify', 'send_notification',
|
|
231
|
-
'call_workflow', 'spawn_instance', 'spawn_subworkflow',
|
|
232
|
-
'emit_event',
|
|
233
|
-
]);
|
|
234
|
-
|
|
235
|
-
function collectFromActions(defs: IRActionDefinition[]) {
|
|
236
|
-
for (const action of defs) {
|
|
237
|
-
if (SERVER_ACTION_TYPES.has(action.type) && !seen.has(action.id)) {
|
|
238
|
-
seen.add(action.id);
|
|
239
|
-
const name = actionToFunctionName(action);
|
|
240
|
-
actions.push({ name, type: action.type, config: action.config });
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
for (const state of states) {
|
|
246
|
-
collectFromActions(state.on_enter);
|
|
247
|
-
collectFromActions(state.on_exit);
|
|
248
|
-
for (const during of state.during) {
|
|
249
|
-
collectFromActions(during.actions);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
for (const trans of transitions) {
|
|
253
|
-
collectFromActions(trans.actions);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return actions;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function actionToFunctionName(action: IRActionDefinition): string {
|
|
260
|
-
// Use a descriptive name from config or fall back to type + id
|
|
261
|
-
const slug = (action.config.name ?? action.config.event ?? action.config.slug ?? action.type) as string;
|
|
262
|
-
return camelCase(String(slug).replace(/[^a-zA-Z0-9_-]/g, '-'));
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Generates a *.server.ts file with server action stubs.
|
|
267
|
-
*
|
|
268
|
-
* Output example:
|
|
269
|
-
* import type { TransitionContext } from '@mindmatrix/react';
|
|
270
|
-
*
|
|
271
|
-
* export async function sendNotification(ctx: TransitionContext) {
|
|
272
|
-
* // TODO: Implement notification logic
|
|
273
|
-
* }
|
|
274
|
-
*/
|
|
275
|
-
function generateServerActionFile(
|
|
276
|
-
actions: Array<{ name: string; type: string; config: Record<string, unknown> }>,
|
|
277
|
-
): string {
|
|
278
|
-
const lines: string[] = [
|
|
279
|
-
`/**`,
|
|
280
|
-
` * Server actions — backend handlers for workflow transitions.`,
|
|
281
|
-
` *`,
|
|
282
|
-
` * These functions run server-side during state transitions.`,
|
|
283
|
-
` * Each receives a TransitionContext with instance data and utilities.`,
|
|
284
|
-
` */`,
|
|
285
|
-
``,
|
|
286
|
-
`import type { TransitionContext } from '@mindmatrix/react';`,
|
|
287
|
-
``,
|
|
288
|
-
];
|
|
289
|
-
|
|
290
|
-
for (const action of actions) {
|
|
291
|
-
const comment = actionComment(action.type, action.config);
|
|
292
|
-
lines.push(`/** ${comment} */`);
|
|
293
|
-
lines.push(`export async function ${action.name}(ctx: TransitionContext): Promise<void> {`);
|
|
294
|
-
lines.push(` const { instance, env } = ctx;`);
|
|
295
|
-
|
|
296
|
-
// Emit a type-specific stub body
|
|
297
|
-
switch (action.type) {
|
|
298
|
-
case 'http_request':
|
|
299
|
-
case 'webhook':
|
|
300
|
-
case 'call_webhook': {
|
|
301
|
-
const url = String(action.config.url ?? 'https://api.example.com/webhook');
|
|
302
|
-
const method = String(action.config.method ?? 'POST');
|
|
303
|
-
lines.push(` await fetch('${url}', {`);
|
|
304
|
-
lines.push(` method: '${method}',`);
|
|
305
|
-
lines.push(` headers: { 'Content-Type': 'application/json' },`);
|
|
306
|
-
lines.push(` body: JSON.stringify({ instanceId: instance.id }),`);
|
|
307
|
-
lines.push(` });`);
|
|
308
|
-
break;
|
|
309
|
-
}
|
|
310
|
-
case 'notify':
|
|
311
|
-
case 'send_notification': {
|
|
312
|
-
const msg = String(action.config.message ?? action.config.body ?? 'Notification');
|
|
313
|
-
lines.push(` // Send notification: "${msg}"`);
|
|
314
|
-
lines.push(` await env.notify({ message: '${msg}', instanceId: instance.id });`);
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
case 'call_workflow':
|
|
318
|
-
case 'spawn_instance':
|
|
319
|
-
case 'spawn_subworkflow': {
|
|
320
|
-
const slug = String(action.config.slug ?? action.config.workflow ?? 'child-workflow');
|
|
321
|
-
lines.push(` await env.spawn('${slug}', { parentId: instance.id });`);
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
case 'emit_event': {
|
|
325
|
-
const event = String(action.config.event ?? action.config.name ?? 'custom-event');
|
|
326
|
-
lines.push(` await env.emit('${event}', { instanceId: instance.id });`);
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
default:
|
|
330
|
-
lines.push(` // TODO: Implement ${action.type} logic`);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
lines.push(`}`);
|
|
334
|
-
lines.push(``);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return lines.join('\n');
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function actionComment(type: string, config: Record<string, unknown>): string {
|
|
341
|
-
switch (type) {
|
|
342
|
-
case 'http_request':
|
|
343
|
-
case 'webhook':
|
|
344
|
-
case 'call_webhook':
|
|
345
|
-
return `HTTP ${config.method ?? 'POST'} to ${config.url ?? 'webhook endpoint'}`;
|
|
346
|
-
case 'notify':
|
|
347
|
-
case 'send_notification':
|
|
348
|
-
return `Send notification: ${config.message ?? config.body ?? ''}`;
|
|
349
|
-
case 'call_workflow':
|
|
350
|
-
case 'spawn_instance':
|
|
351
|
-
case 'spawn_subworkflow':
|
|
352
|
-
return `Spawn child workflow: ${config.slug ?? config.workflow ?? ''}`;
|
|
353
|
-
case 'emit_event':
|
|
354
|
-
return `Emit event: ${config.event ?? config.name ?? ''}`;
|
|
355
|
-
default:
|
|
356
|
-
return `Server action: ${type}`;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// =============================================================================
|
|
361
|
-
// Config Generation
|
|
362
|
-
// =============================================================================
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Generates mm.config.ts with defineBlueprint configuration.
|
|
366
|
-
*/
|
|
367
|
-
function generateConfigFile(def: IRWorkflowDefinition): string {
|
|
368
|
-
const lines: string[] = [
|
|
369
|
-
`/**`,
|
|
370
|
-
` * Blueprint configuration for "${def.name || pascalCase(def.slug)}".`,
|
|
371
|
-
` */`,
|
|
372
|
-
``,
|
|
373
|
-
`import { defineBlueprint } from '@mindmatrix/react';`,
|
|
374
|
-
``,
|
|
375
|
-
`export default defineBlueprint({`,
|
|
376
|
-
` slug: '${def.slug}',`,
|
|
377
|
-
` name: '${(def.name || pascalCase(def.slug)).replace(/'/g, "\\'")}',`,
|
|
378
|
-
` version: '${def.version}',`,
|
|
379
|
-
` category: '${def.category}',`,
|
|
380
|
-
];
|
|
381
|
-
|
|
382
|
-
if (def.description) {
|
|
383
|
-
lines.push(` description: '${def.description.replace(/'/g, "\\'")}',`);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Roles
|
|
387
|
-
if (def.roles && def.roles.length > 0) {
|
|
388
|
-
lines.push(` roles: [`);
|
|
389
|
-
for (const role of def.roles) {
|
|
390
|
-
const perms = role.permissions.map(p => `'${p}'`).join(', ');
|
|
391
|
-
lines.push(` { name: '${role.name}', permissions: [${perms}] },`);
|
|
392
|
-
}
|
|
393
|
-
lines.push(` ],`);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
lines.push(`});`);
|
|
397
|
-
lines.push(``);
|
|
398
|
-
return lines.join('\n');
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// =============================================================================
|
|
402
|
-
// Page Extraction
|
|
403
|
-
// =============================================================================
|
|
404
|
-
|
|
405
|
-
interface ExtractedPage {
|
|
406
|
-
/** Route path or state name this page represents. */
|
|
407
|
-
route: string;
|
|
408
|
-
/** PascalCase component name for the page. */
|
|
409
|
-
componentName: string;
|
|
410
|
-
/** The experience subtree for this page. */
|
|
411
|
-
tree: IRExperienceNode;
|
|
412
|
-
/** File path relative to project root. */
|
|
413
|
-
filePath: string;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Extracts page components from an experience tree.
|
|
418
|
-
*
|
|
419
|
-
* Heuristics for page detection:
|
|
420
|
-
* 1. Top-level children with visible_when conditions referencing route/state
|
|
421
|
-
* 2. Children with id containing "page", "route", "view"
|
|
422
|
-
* 3. Children wrapped in Show components with route conditions
|
|
423
|
-
*/
|
|
424
|
-
function extractPages(
|
|
425
|
-
experience: IRExperienceNode | undefined,
|
|
426
|
-
slug: string,
|
|
427
|
-
): ExtractedPage[] {
|
|
428
|
-
if (!experience || !experience.children) return [];
|
|
429
|
-
|
|
430
|
-
const pages: ExtractedPage[] = [];
|
|
431
|
-
const rootChildren = experience.children;
|
|
432
|
-
|
|
433
|
-
for (const child of rootChildren) {
|
|
434
|
-
// Check for route-conditional sections (visible_when with route/activeRoute)
|
|
435
|
-
const isRoutePage = child.visible_when && (
|
|
436
|
-
/activeRoute|route|isOn/i.test(child.visible_when)
|
|
437
|
-
);
|
|
438
|
-
|
|
439
|
-
// Check for page-like IDs
|
|
440
|
-
const isPageId = child.id && (
|
|
441
|
-
/page|route|view/i.test(child.id)
|
|
442
|
-
);
|
|
443
|
-
|
|
444
|
-
// Check for Show wrappers with route conditions
|
|
445
|
-
const isShowRoute = child.component === 'Show' && child.config?.when &&
|
|
446
|
-
/activeRoute|route/i.test(String(child.config.when));
|
|
447
|
-
|
|
448
|
-
if (isRoutePage || isPageId || isShowRoute) {
|
|
449
|
-
// Extract route name from visible_when or id
|
|
450
|
-
const routeName = extractRouteName(child) || child.id || `page-${pages.length}`;
|
|
451
|
-
const componentName = pascalCase(routeName.replace(/^\//, '').replace(/\//g, '-') || `${slug}-page`);
|
|
452
|
-
|
|
453
|
-
pages.push({
|
|
454
|
-
route: routeName,
|
|
455
|
-
componentName,
|
|
456
|
-
tree: child,
|
|
457
|
-
filePath: `pages/${componentName}.tsx`,
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return pages;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function extractRouteName(node: IRExperienceNode): string {
|
|
466
|
-
// Try to extract route from visible_when: "activeRoute === '/dashboard'"
|
|
467
|
-
if (node.visible_when) {
|
|
468
|
-
const match = node.visible_when.match(/['"]\/([^'"]+)['"]/);
|
|
469
|
-
if (match) return match[1];
|
|
470
|
-
// Try: "isOnDashboard" → "dashboard"
|
|
471
|
-
const boolMatch = node.visible_when.match(/isOn([A-Z]\w+)/);
|
|
472
|
-
if (boolMatch) return boolMatch[1].toLowerCase();
|
|
473
|
-
}
|
|
474
|
-
// Try from Show when config
|
|
475
|
-
if (node.config?.when) {
|
|
476
|
-
const match = String(node.config.when).match(/['"]\/([^'"]+)['"]/);
|
|
477
|
-
if (match) return match[1];
|
|
478
|
-
}
|
|
479
|
-
return '';
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// =============================================================================
|
|
483
|
-
// Page File Generation
|
|
484
|
-
// =============================================================================
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Generates a page component TSX file from an extracted page.
|
|
488
|
-
* Uses the single-file decompiler for the experience subtree.
|
|
489
|
-
*/
|
|
490
|
-
function generatePageFile(page: ExtractedPage, _slug: string): string {
|
|
491
|
-
// Create a minimal definition with just the experience subtree
|
|
492
|
-
const pageInput: DecompilerInput = {
|
|
493
|
-
slug: page.route || 'page',
|
|
494
|
-
name: page.componentName,
|
|
495
|
-
version: '1.0.0',
|
|
496
|
-
category: 'page',
|
|
497
|
-
states: [],
|
|
498
|
-
transitions: [],
|
|
499
|
-
fields: [],
|
|
500
|
-
roles: [],
|
|
501
|
-
experience: page.tree,
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
const result = decompile(pageInput, {
|
|
505
|
-
componentName: page.componentName,
|
|
506
|
-
includeAnnotation: false,
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
return result.code;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// =============================================================================
|
|
513
|
-
// Main Entry File Generation
|
|
514
|
-
// =============================================================================
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Generates the main.workflow.tsx file.
|
|
518
|
-
*
|
|
519
|
-
* When pages are extracted, this file contains:
|
|
520
|
-
* - Field declarations (useState)
|
|
521
|
-
* - State effects (useOnEnter/useOnExit)
|
|
522
|
-
* - Transition declarations (useTransition)
|
|
523
|
-
* - Page imports
|
|
524
|
-
* - Root layout with page rendering
|
|
525
|
-
*/
|
|
526
|
-
function generateMainFile(
|
|
527
|
-
def: DecompilerInput,
|
|
528
|
-
pages: ExtractedPage[],
|
|
529
|
-
hasModel: boolean,
|
|
530
|
-
serverActionNames: string[],
|
|
531
|
-
options?: DecompileOptions,
|
|
532
|
-
): string {
|
|
533
|
-
// If no pages to split, use the single-file decompiler directly
|
|
534
|
-
if (pages.length === 0) {
|
|
535
|
-
const result = decompile(def, options);
|
|
536
|
-
return result.code;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// Build main file with page imports and layout
|
|
540
|
-
const slug = def.slug;
|
|
541
|
-
const componentName = options?.componentName ?? pascalCase(slug);
|
|
542
|
-
|
|
543
|
-
const importLines: string[] = [];
|
|
544
|
-
|
|
545
|
-
// Collect @mindmatrix/react imports based on what's used
|
|
546
|
-
const reactImports = new Set<string>();
|
|
547
|
-
|
|
548
|
-
// Fields need useState
|
|
549
|
-
const nonComputed = def.fields.filter(f => !f.computed);
|
|
550
|
-
if (nonComputed.length > 0) reactImports.add('useState');
|
|
551
|
-
|
|
552
|
-
// State effects
|
|
553
|
-
for (const state of def.states) {
|
|
554
|
-
if (state.on_enter.length > 0) reactImports.add('useOnEnter');
|
|
555
|
-
if (state.on_exit.length > 0) reactImports.add('useOnExit');
|
|
556
|
-
if (state.during.length > 0) reactImports.add('useWhileIn');
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Transitions
|
|
560
|
-
if (def.transitions.length > 0) reactImports.add('useTransition');
|
|
561
|
-
|
|
562
|
-
// Layout atoms for the root wrapper
|
|
563
|
-
reactImports.add('Stack');
|
|
564
|
-
|
|
565
|
-
if (reactImports.size > 0) {
|
|
566
|
-
const sorted = [...reactImports].sort();
|
|
567
|
-
importLines.push(`import { ${sorted.join(', ')} } from '@mindmatrix/react';`);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Model import
|
|
571
|
-
if (hasModel) {
|
|
572
|
-
const typeName = pascalCase(slug);
|
|
573
|
-
importLines.push(`import type { ${typeName} } from './models/${slug}';`);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// Server action imports
|
|
577
|
-
if (serverActionNames.length > 0) {
|
|
578
|
-
const names = serverActionNames.join(', ');
|
|
579
|
-
importLines.push(`import { ${names} } from './actions/${slug}.server';`);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Page imports
|
|
583
|
-
for (const page of pages) {
|
|
584
|
-
importLines.push(`import { ${page.componentName} } from './${page.filePath.replace(/\.tsx$/, '')}';`);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Build component body lines
|
|
588
|
-
const bodyLines: string[] = [];
|
|
589
|
-
|
|
590
|
-
// Field declarations
|
|
591
|
-
for (const field of nonComputed) {
|
|
592
|
-
const name = camelCase(field.name);
|
|
593
|
-
const setter = `set${pascalCase(field.name)}`;
|
|
594
|
-
const defaultVal = fieldDefaultLiteral(field);
|
|
595
|
-
bodyLines.push(` const [${name}, ${setter}] = useState(${defaultVal});`);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if (nonComputed.length > 0) bodyLines.push(``);
|
|
599
|
-
|
|
600
|
-
// State effects
|
|
601
|
-
for (const state of def.states) {
|
|
602
|
-
if (state.on_enter.length > 0) {
|
|
603
|
-
const actions = state.on_enter.map(a => actionToInlineCode(a)).filter(Boolean);
|
|
604
|
-
bodyLines.push(` useOnEnter('${state.name}', () => {`);
|
|
605
|
-
for (const a of actions) bodyLines.push(` ${a}`);
|
|
606
|
-
bodyLines.push(` });`);
|
|
607
|
-
}
|
|
608
|
-
if (state.on_exit.length > 0) {
|
|
609
|
-
const actions = state.on_exit.map(a => actionToInlineCode(a)).filter(Boolean);
|
|
610
|
-
bodyLines.push(` useOnExit('${state.name}', () => {`);
|
|
611
|
-
for (const a of actions) bodyLines.push(` ${a}`);
|
|
612
|
-
bodyLines.push(` });`);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Transitions
|
|
617
|
-
for (const trans of def.transitions) {
|
|
618
|
-
const varName = camelCase(trans.name);
|
|
619
|
-
const from = trans.from.length === 1
|
|
620
|
-
? `'${trans.from[0]}'`
|
|
621
|
-
: `[${trans.from.map(f => `'${f}'`).join(', ')}]`;
|
|
622
|
-
bodyLines.push(` const ${varName} = useTransition('${trans.name}', { from: ${from}, to: '${trans.to}' });`);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
if (def.transitions.length > 0) bodyLines.push(``);
|
|
626
|
-
|
|
627
|
-
// Build root layout JSX with page rendering
|
|
628
|
-
bodyLines.push(` return (`);
|
|
629
|
-
bodyLines.push(` <Stack sx={{ minHeight: '100vh' }}>`);
|
|
630
|
-
for (const page of pages) {
|
|
631
|
-
bodyLines.push(` <${page.componentName} />`);
|
|
632
|
-
}
|
|
633
|
-
bodyLines.push(` </Stack>`);
|
|
634
|
-
bodyLines.push(` );`);
|
|
635
|
-
|
|
636
|
-
// Assemble file
|
|
637
|
-
const lines: string[] = [
|
|
638
|
-
...importLines,
|
|
639
|
-
``,
|
|
640
|
-
`/**`,
|
|
641
|
-
` * @workflow slug="${slug}" version="${def.version}" category="${def.category}"`,
|
|
642
|
-
...(def.description ? [` * @description ${def.description}`] : []),
|
|
643
|
-
` */`,
|
|
644
|
-
`export default function ${componentName}() {`,
|
|
645
|
-
...bodyLines,
|
|
646
|
-
`}`,
|
|
647
|
-
``,
|
|
648
|
-
];
|
|
649
|
-
|
|
650
|
-
return lines.join('\n');
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
function fieldDefaultLiteral(field: IRFieldDefinition): string {
|
|
654
|
-
if (field.default_value !== undefined) {
|
|
655
|
-
if (typeof field.default_value === 'string') return `'${field.default_value}'`;
|
|
656
|
-
return JSON.stringify(field.default_value);
|
|
657
|
-
}
|
|
658
|
-
switch (field.type) {
|
|
659
|
-
case 'text': case 'rich_text': case 'email': case 'url': case 'phone':
|
|
660
|
-
case 'color': case 'select':
|
|
661
|
-
return "''";
|
|
662
|
-
case 'number': case 'currency': case 'percentage': case 'rating':
|
|
663
|
-
case 'duration': case 'auto_number':
|
|
664
|
-
return '0';
|
|
665
|
-
case 'boolean':
|
|
666
|
-
return 'false';
|
|
667
|
-
case 'date': case 'datetime':
|
|
668
|
-
return 'null';
|
|
669
|
-
case 'multi_select': case 'json':
|
|
670
|
-
return '[]';
|
|
671
|
-
default:
|
|
672
|
-
return 'null';
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
function actionToInlineCode(action: IRActionDefinition): string {
|
|
677
|
-
switch (action.type) {
|
|
678
|
-
case 'set_field': {
|
|
679
|
-
const field = String(action.config.field ?? '');
|
|
680
|
-
const setter = `set${pascalCase(field)}`;
|
|
681
|
-
const val = action.config.expression
|
|
682
|
-
? String(action.config.expression)
|
|
683
|
-
: JSON.stringify(action.config.value ?? null);
|
|
684
|
-
return `${setter}(${val});`;
|
|
685
|
-
}
|
|
686
|
-
case 'log_event':
|
|
687
|
-
case 'log':
|
|
688
|
-
return `console.log('${action.config.message ?? action.config.event ?? action.type}');`;
|
|
689
|
-
case 'noop':
|
|
690
|
-
return '';
|
|
691
|
-
default:
|
|
692
|
-
return `// ${action.type}: ${JSON.stringify(action.config)}`;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// =============================================================================
|
|
697
|
-
// Main: decompileProject
|
|
698
|
-
// =============================================================================
|
|
699
|
-
|
|
700
|
-
/**
|
|
701
|
-
* Decompiles an IR definition into a multi-file project structure.
|
|
702
|
-
*
|
|
703
|
-
* Produces files matching the envelope fs-tree file role conventions:
|
|
704
|
-
* - main.workflow.tsx → view-entry (root layout + state machine)
|
|
705
|
-
* - pages/*.tsx → page (per-route components)
|
|
706
|
-
* - models/<slug>.ts → model (TypeScript interfaces)
|
|
707
|
-
* - actions/<slug>.server.ts → server-action (backend handlers)
|
|
708
|
-
* - mm.config.ts → config (defineBlueprint)
|
|
709
|
-
*
|
|
710
|
-
* For simple definitions (no states/transitions/models), falls back
|
|
711
|
-
* to single-file output in main.workflow.tsx.
|
|
712
|
-
*/
|
|
713
|
-
export function decompileProject(
|
|
714
|
-
definition: DecompilerInput,
|
|
715
|
-
options?: DecompileProjectOptions,
|
|
716
|
-
): DecompileProjectResult {
|
|
717
|
-
const files: ProjectFile[] = [];
|
|
718
|
-
const slug = definition.slug;
|
|
719
|
-
|
|
720
|
-
// 1. Extract pages from experience tree
|
|
721
|
-
const pages = options?.skipPages
|
|
722
|
-
? []
|
|
723
|
-
: extractPages(definition.experience, slug);
|
|
724
|
-
|
|
725
|
-
// 2. Extract server actions from states + transitions
|
|
726
|
-
const serverActions = options?.skipActions
|
|
727
|
-
? []
|
|
728
|
-
: extractServerActions(definition.states, definition.transitions);
|
|
729
|
-
|
|
730
|
-
// 3. Generate model file
|
|
731
|
-
const hasFields = definition.fields.length > 0;
|
|
732
|
-
if (hasFields && !options?.skipModels) {
|
|
733
|
-
let modelContent = generateModelFile(slug, definition.fields);
|
|
734
|
-
if (definition.states.length > 0) {
|
|
735
|
-
modelContent += generateStateEnum(slug, definition.states);
|
|
736
|
-
}
|
|
737
|
-
files.push({
|
|
738
|
-
path: `models/${slug}.ts`,
|
|
739
|
-
role: 'model',
|
|
740
|
-
content: modelContent,
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
// 4. Generate server action file
|
|
745
|
-
if (serverActions.length > 0) {
|
|
746
|
-
files.push({
|
|
747
|
-
path: `actions/${slug}.server.ts`,
|
|
748
|
-
role: 'server-action',
|
|
749
|
-
content: generateServerActionFile(serverActions),
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// 5. Generate page files
|
|
754
|
-
for (const page of pages) {
|
|
755
|
-
files.push({
|
|
756
|
-
path: page.filePath,
|
|
757
|
-
role: 'page',
|
|
758
|
-
content: generatePageFile(page, slug),
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// 6. Generate config file
|
|
763
|
-
if (!options?.skipConfig) {
|
|
764
|
-
files.push({
|
|
765
|
-
path: 'mm.config.ts',
|
|
766
|
-
role: 'config',
|
|
767
|
-
content: generateConfigFile(definition),
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// 7. Decompile child definitions into separate .workflow.tsx files
|
|
772
|
-
// Each child becomes its own file in the project (UBER-SCALE support)
|
|
773
|
-
if (definition.childDefinitions && definition.childDefinitions.length > 0) {
|
|
774
|
-
for (const child of definition.childDefinitions) {
|
|
775
|
-
const childSlug = child.slug;
|
|
776
|
-
const childFileName = `${childSlug}.workflow.tsx`;
|
|
777
|
-
|
|
778
|
-
// Build a DecompilerInput from the child definition
|
|
779
|
-
const childInput: DecompilerInput = {
|
|
780
|
-
...child,
|
|
781
|
-
experience: (child as any).views?.default as IRExperienceNode | undefined,
|
|
782
|
-
};
|
|
783
|
-
|
|
784
|
-
// Generate model files for child data definitions
|
|
785
|
-
if (child.category === 'data' && child.fields.length > 0 && !options?.skipModels) {
|
|
786
|
-
let childModelContent = generateModelFile(childSlug, child.fields);
|
|
787
|
-
if (child.states.length > 0) {
|
|
788
|
-
childModelContent += generateStateEnum(childSlug, child.states);
|
|
789
|
-
}
|
|
790
|
-
files.push({
|
|
791
|
-
path: `models/${childSlug}.ts`,
|
|
792
|
-
role: 'model',
|
|
793
|
-
content: childModelContent,
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// Generate server action files for child workflows with server actions
|
|
798
|
-
if (!options?.skipActions) {
|
|
799
|
-
const childServerActions = extractServerActions(child.states, child.transitions);
|
|
800
|
-
if (childServerActions.length > 0) {
|
|
801
|
-
files.push({
|
|
802
|
-
path: `actions/${childSlug}.server.ts`,
|
|
803
|
-
role: 'server-action',
|
|
804
|
-
content: generateServerActionFile(childServerActions),
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Generate the child workflow file itself
|
|
810
|
-
const childResult = decompile(childInput, {
|
|
811
|
-
componentName: pascalCase(childSlug),
|
|
812
|
-
includeAnnotation: true,
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
files.push({
|
|
816
|
-
path: childFileName,
|
|
817
|
-
role: 'view-entry',
|
|
818
|
-
content: childResult.code,
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// 8. Generate main entry file
|
|
824
|
-
const mainContent = generateMainFile(
|
|
825
|
-
definition,
|
|
826
|
-
pages,
|
|
827
|
-
hasFields && !options?.skipModels,
|
|
828
|
-
serverActions.map(a => a.name),
|
|
829
|
-
options,
|
|
830
|
-
);
|
|
831
|
-
|
|
832
|
-
files.push({
|
|
833
|
-
path: 'main.workflow.tsx',
|
|
834
|
-
role: 'view-entry',
|
|
835
|
-
content: mainContent,
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
return {
|
|
839
|
-
files,
|
|
840
|
-
entryFile: 'main.workflow.tsx',
|
|
841
|
-
slug,
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
/**
|
|
846
|
-
* Returns true if a definition is complex enough to warrant multi-file output.
|
|
847
|
-
*
|
|
848
|
-
* Criteria: has states + transitions + either fields or experience tree.
|
|
849
|
-
*/
|
|
850
|
-
export function shouldDecompileAsProject(def: IRWorkflowDefinition): boolean {
|
|
851
|
-
// Check for child definitions in metadata (UBER-SCALE projects)
|
|
852
|
-
const meta = def.metadata as Record<string, unknown> | undefined;
|
|
853
|
-
const childSlugs = meta?.childSlugs;
|
|
854
|
-
const hasChildren = Array.isArray(childSlugs) && childSlugs.length > 0;
|
|
855
|
-
|
|
856
|
-
return (
|
|
857
|
-
hasChildren ||
|
|
858
|
-
(def.states.length > 1 &&
|
|
859
|
-
def.transitions.length > 0 &&
|
|
860
|
-
(def.fields.length > 0 || def.category === 'blueprint'))
|
|
861
|
-
);
|
|
862
|
-
}
|