@mmapp/react-compiler 0.1.0-alpha.1
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/README.md +107 -0
- package/compile-blueprint-chat.mjs +99 -0
- package/compile-blueprint-glass-console.mjs +98 -0
- package/compile-chat-defs.mjs +92 -0
- package/dist/babel/index.d.mts +3 -0
- package/dist/babel/index.d.ts +3 -0
- package/dist/babel/index.js +4851 -0
- package/dist/babel/index.mjs +7 -0
- package/dist/chunk-26U577GB.mjs +3465 -0
- package/dist/chunk-2FBDFAX6.mjs +2362 -0
- package/dist/chunk-2L4QSMXG.mjs +175 -0
- package/dist/chunk-2REDFOER.mjs +931 -0
- package/dist/chunk-46YKSHQR.mjs +175 -0
- package/dist/chunk-4XHK6FWL.mjs +2058 -0
- package/dist/chunk-5M7DKKBC.mjs +215 -0
- package/dist/chunk-5VNJ7C6N.mjs +154 -0
- package/dist/chunk-6CQOAAMV.mjs +1803 -0
- package/dist/chunk-6SEVAAVT.mjs +3516 -0
- package/dist/chunk-6YLR5ZDA.mjs +2829 -0
- package/dist/chunk-AOGY2GK6.mjs +3292 -0
- package/dist/chunk-AXXUXRNA.mjs +1434 -0
- package/dist/chunk-CHLVKMQW.mjs +175 -0
- package/dist/chunk-CKGOZAB7.mjs +939 -0
- package/dist/chunk-D34RAZUX.mjs +2223 -0
- package/dist/chunk-EQGA6A6D.mjs +121 -0
- package/dist/chunk-EY2CSXYA.mjs +822 -0
- package/dist/chunk-FIQ65CDR.mjs +925 -0
- package/dist/chunk-FOZXJFAR.mjs +186 -0
- package/dist/chunk-FX6URXWN.mjs +186 -0
- package/dist/chunk-G7SMOWOL.mjs +828 -0
- package/dist/chunk-GGB4G5YY.mjs +175 -0
- package/dist/chunk-HLRGCCIL.mjs +4839 -0
- package/dist/chunk-HOIUP6IF.mjs +690 -0
- package/dist/chunk-I3AU7GRD.mjs +120 -0
- package/dist/chunk-ILFGMUVD.mjs +1933 -0
- package/dist/chunk-IPTX5MJU.mjs +3223 -0
- package/dist/chunk-ITGUSH2Z.mjs +2783 -0
- package/dist/chunk-IXHBCAMF.mjs +3306 -0
- package/dist/chunk-J7TWJ3TM.mjs +2784 -0
- package/dist/chunk-JDPLDGVF.mjs +4810 -0
- package/dist/chunk-K53XP2DL.mjs +148 -0
- package/dist/chunk-K5HX2SVL.mjs +1902 -0
- package/dist/chunk-KFGYOOVS.mjs +214 -0
- package/dist/chunk-KFVVOS5N.mjs +925 -0
- package/dist/chunk-L2OZ4CDV.mjs +113 -0
- package/dist/chunk-MIZV3TAN.mjs +3293 -0
- package/dist/chunk-NKKLQE5V.mjs +148 -0
- package/dist/chunk-NOW23XFZ.mjs +186 -0
- package/dist/chunk-NRXQKQ74.mjs +148 -0
- package/dist/chunk-OWI6XWCD.mjs +3375 -0
- package/dist/chunk-PRUMNNDI.mjs +3192 -0
- package/dist/chunk-QTBD5B3F.mjs +148 -0
- package/dist/chunk-SKSDPPNT.mjs +3788 -0
- package/dist/chunk-SP2YUS33.mjs +186 -0
- package/dist/chunk-SU4E6E7B.mjs +3153 -0
- package/dist/chunk-SYUUKW5A.mjs +3379 -0
- package/dist/chunk-UL2XZEMA.mjs +3128 -0
- package/dist/chunk-XMWUHQVV.mjs +939 -0
- package/dist/chunk-XZNEDRGN.mjs +3876 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/chunk-YFS6JMYO.mjs +3342 -0
- package/dist/chunk-Z6AIQ4KL.mjs +113 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +11585 -0
- package/dist/cli/index.mjs +701 -0
- package/dist/codemod/cli.d.mts +1 -0
- package/dist/codemod/cli.d.ts +1 -0
- package/dist/codemod/cli.js +1104 -0
- package/dist/codemod/cli.mjs +157 -0
- package/dist/codemod/index.d.mts +148 -0
- package/dist/codemod/index.d.ts +148 -0
- package/dist/codemod/index.js +981 -0
- package/dist/codemod/index.mjs +25 -0
- package/dist/dev-server-Bs_sz2DG.d.mts +111 -0
- package/dist/dev-server-Bs_sz2DG.d.ts +111 -0
- package/dist/dev-server-CjoufJ-u.d.mts +109 -0
- package/dist/dev-server-CjoufJ-u.d.ts +109 -0
- package/dist/dev-server.d.mts +3 -0
- package/dist/dev-server.d.ts +3 -0
- package/dist/dev-server.js +7603 -0
- package/dist/dev-server.mjs +11 -0
- package/dist/envelope-DD7v0v6E.d.mts +265 -0
- package/dist/envelope-DD7v0v6E.d.ts +265 -0
- package/dist/envelope-vCVjrHlo.d.mts +265 -0
- package/dist/envelope-vCVjrHlo.d.ts +265 -0
- package/dist/envelope.d.mts +2 -0
- package/dist/envelope.d.ts +2 -0
- package/dist/envelope.js +5184 -0
- package/dist/envelope.mjs +9 -0
- package/dist/index-B5gSgvnd.d.mts +44 -0
- package/dist/index-B5gSgvnd.d.ts +44 -0
- package/dist/index-Bs0MnR54.d.mts +103 -0
- package/dist/index-Bs0MnR54.d.ts +103 -0
- package/dist/index-DR0nNc_f.d.mts +101 -0
- package/dist/index-DR0nNc_f.d.ts +101 -0
- package/dist/index-revho_gS.d.mts +104 -0
- package/dist/index-revho_gS.d.ts +104 -0
- package/dist/index.d.mts +1099 -0
- package/dist/index.d.ts +1099 -0
- package/dist/index.js +10162 -0
- package/dist/index.mjs +372 -0
- package/dist/init-IXEE2RCF.mjs +340 -0
- package/dist/project-compiler-EGJUTAJU.mjs +10 -0
- package/dist/project-compiler-VFR6CSDX.mjs +10 -0
- package/dist/project-decompiler-5GY2KSG4.mjs +7 -0
- package/dist/pull-A2QUHW4K.mjs +109 -0
- package/dist/pull-JBEQWVPE.mjs +109 -0
- package/dist/testing/index.d.mts +211 -0
- package/dist/testing/index.d.ts +211 -0
- package/dist/testing/index.js +5106 -0
- package/dist/testing/index.mjs +247 -0
- package/dist/vite/index.d.mts +59 -0
- package/dist/vite/index.d.ts +59 -0
- package/dist/vite/index.js +5023 -0
- package/dist/vite/index.mjs +8 -0
- package/examples/README.md +72 -0
- package/examples/authentication/main.workflow.tsx +139 -0
- package/examples/authentication/mm.config.ts +22 -0
- package/examples/authentication/models/auth.ts +45 -0
- package/examples/authentication/pages/LoginPage.tsx +79 -0
- package/examples/authentication/pages/SignupPage.tsx +87 -0
- package/examples/counter.workflow.tsx +65 -0
- package/examples/dashboard.workflow.tsx +419 -0
- package/examples/invoice-approval/actions/invoice.server.ts +72 -0
- package/examples/invoice-approval/main.workflow.tsx +168 -0
- package/examples/invoice-approval/mm.config.ts +18 -0
- package/examples/invoice-approval/models/invoice.ts +46 -0
- package/examples/invoice-approval/pages/InvoiceDetailPage.tsx +175 -0
- package/examples/invoice-approval/pages/InvoiceFormPage.tsx +198 -0
- package/examples/invoice-approval/pages/InvoiceListPage.tsx +141 -0
- package/examples/todo-app.workflow.tsx +131 -0
- package/examples/uber-app/actions/matching.server.ts +177 -0
- package/examples/uber-app/actions/notifications.server.ts +176 -0
- package/examples/uber-app/actions/payments.server.ts +184 -0
- package/examples/uber-app/actions/pricing.server.ts +176 -0
- package/examples/uber-app/app/admin/analytics.tsx +102 -0
- package/examples/uber-app/app/admin/fleet.tsx +102 -0
- package/examples/uber-app/app/admin/surge-pricing.tsx +95 -0
- package/examples/uber-app/app/driver/dashboard.tsx +87 -0
- package/examples/uber-app/app/driver/earnings.tsx +101 -0
- package/examples/uber-app/app/driver/navigation.tsx +94 -0
- package/examples/uber-app/app/driver/ride-acceptance.tsx +103 -0
- package/examples/uber-app/app/rider/home.tsx +109 -0
- package/examples/uber-app/app/rider/payment-methods.tsx +134 -0
- package/examples/uber-app/app/rider/ride-history.tsx +90 -0
- package/examples/uber-app/app/rider/ride-tracking.tsx +108 -0
- package/examples/uber-app/components/DriverCard.tsx +176 -0
- package/examples/uber-app/components/MapView.tsx +216 -0
- package/examples/uber-app/components/RatingStars.tsx +227 -0
- package/examples/uber-app/components/RideCard.tsx +167 -0
- package/examples/uber-app/mm.config.ts +30 -0
- package/examples/uber-app/models/location.model.ts +70 -0
- package/examples/uber-app/models/payment.model.ts +87 -0
- package/examples/uber-app/models/rating.model.ts +54 -0
- package/examples/uber-app/models/ride.model.ts +118 -0
- package/examples/uber-app/models/user.model.ts +66 -0
- package/examples/uber-app/models/vehicle.model.ts +63 -0
- package/examples/uber-app/tests/payment.test.tsx +129 -0
- package/examples/uber-app/tests/ride-flow.test.tsx +123 -0
- package/examples/uber-app/workflows/dispute-resolution.workflow.tsx +205 -0
- package/examples/uber-app/workflows/driver-onboarding.workflow.tsx +227 -0
- package/examples/uber-app/workflows/payment-processing.workflow.tsx +223 -0
- package/examples/uber-app/workflows/ride-request.workflow.tsx +194 -0
- package/package.json +77 -0
- package/package.json.backup +86 -0
- package/scripts/decompile.ts +226 -0
- package/scripts/seed-auth.ts +267 -0
- package/scripts/seed-uber.ts +248 -0
- package/scripts/validate-uber.ts +119 -0
- package/seed-blueprint-chat.mjs +444 -0
- package/seed-blueprint-glass-console.mjs +445 -0
- package/seed-compiled.mjs +318 -0
- package/src/RoundTripValidator.ts +400 -0
- package/src/__tests__/atom-rendering-coverage.test.ts +680 -0
- package/src/__tests__/auth-module-compilation.test.ts +247 -0
- package/src/__tests__/auth-template-compilation.test.ts +589 -0
- package/src/__tests__/change-extractor.test.ts +142 -0
- package/src/__tests__/cli-pull.test.ts +73 -0
- package/src/__tests__/cli-test.test.ts +72 -0
- package/src/__tests__/component-extractor.test.ts +331 -0
- package/src/__tests__/context-extractor.test.ts +145 -0
- package/src/__tests__/decompiler.test.ts +718 -0
- package/src/__tests__/define-blueprint.test.ts +133 -0
- package/src/__tests__/definition-validator.test.ts +519 -0
- package/src/__tests__/during-extractor.test.ts +152 -0
- package/src/__tests__/effect-extractor.test.ts +107 -0
- package/src/__tests__/event-emission.test.ts +127 -0
- package/src/__tests__/examples.test.ts +236 -0
- package/src/__tests__/full-blueprint-coverage.test.ts +1221 -0
- package/src/__tests__/golden-suite.test.ts +403 -0
- package/src/__tests__/grammar-island-extractor.test.ts +289 -0
- package/src/__tests__/instance-key.test.ts +82 -0
- package/src/__tests__/ir-migration.test.ts +255 -0
- package/src/__tests__/lock-file.test.ts +117 -0
- package/src/__tests__/model-extractor.test.ts +195 -0
- package/src/__tests__/model-field-acl.test.ts +237 -0
- package/src/__tests__/model-hooks.test.ts +130 -0
- package/src/__tests__/model-ref-resolution.test.ts +268 -0
- package/src/__tests__/model-roundtrip.test.ts +502 -0
- package/src/__tests__/model-runtime.test.ts +112 -0
- package/src/__tests__/model-transitions.test.ts +183 -0
- package/src/__tests__/nrt-action-trace.test.ts +391 -0
- package/src/__tests__/pipeline-hardening.test.ts +413 -0
- package/src/__tests__/project-compiler.test.ts +546 -0
- package/src/__tests__/project-decompiler.test.ts +343 -0
- package/src/__tests__/query-compilation.test.ts +145 -0
- package/src/__tests__/round-trip/PLAN.md +158 -0
- package/src/__tests__/round-trip/README.md +52 -0
- package/src/__tests__/round-trip/RESULTS.md +86 -0
- package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +55 -0
- package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +11 -0
- package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +54 -0
- package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +79 -0
- package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +12 -0
- package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +50 -0
- package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +25 -0
- package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +11 -0
- package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +32 -0
- package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +79 -0
- package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +10 -0
- package/src/__tests__/round-trip/round-trip.test.ts +2598 -0
- package/src/__tests__/round-trip-ir.test.ts +300 -0
- package/src/__tests__/round-trip.test.ts +1212 -0
- package/src/__tests__/route-merging.test.ts +372 -0
- package/src/__tests__/router-composition.test.ts +489 -0
- package/src/__tests__/router-extractor.test.ts +176 -0
- package/src/__tests__/server-action-extractor.test.ts +128 -0
- package/src/__tests__/smart-type-inference.test.ts +365 -0
- package/src/__tests__/source-envelope.test.ts +284 -0
- package/src/__tests__/source-fidelity.test.ts +516 -0
- package/src/__tests__/state-extractor.test.ts +115 -0
- package/src/__tests__/strict-mode.test.ts +227 -0
- package/src/__tests__/transition-effect-extractor.test.ts +119 -0
- package/src/__tests__/transition-extractor.test.ts +68 -0
- package/src/__tests__/ts-to-expression.test.ts +462 -0
- package/src/__tests__/type-generator.test.ts +201 -0
- package/src/__tests__/uber-validation.test.ts +502 -0
- package/src/action-compiler.ts +361 -0
- package/src/babel/emitters/experience-transform.ts +199 -0
- package/src/babel/emitters/ir-to-tsx-emitter.ts +110 -0
- package/src/babel/emitters/pure-form-emitter.ts +1023 -0
- package/src/babel/emitters/runtime-glue-emitter.ts +39 -0
- package/src/babel/extractors/change-extractor.ts +199 -0
- package/src/babel/extractors/component-extractor.ts +907 -0
- package/src/babel/extractors/computed-extractor.ts +262 -0
- package/src/babel/extractors/context-extractor.ts +277 -0
- package/src/babel/extractors/during-extractor.ts +295 -0
- package/src/babel/extractors/effect-extractor.ts +340 -0
- package/src/babel/extractors/event-extractor.ts +235 -0
- package/src/babel/extractors/grammar-island-extractor.ts +302 -0
- package/src/babel/extractors/model-extractor.ts +1018 -0
- package/src/babel/extractors/router-extractor.ts +303 -0
- package/src/babel/extractors/server-action-extractor.ts +173 -0
- package/src/babel/extractors/server-action-hook-extractor.ts +72 -0
- package/src/babel/extractors/server-state-extractor.ts +88 -0
- package/src/babel/extractors/state-extractor.ts +214 -0
- package/src/babel/extractors/transition-effect-extractor.ts +176 -0
- package/src/babel/extractors/transition-extractor.ts +143 -0
- package/src/babel/index.ts +24 -0
- package/src/babel/transpilers/ts-to-expression.ts +674 -0
- package/src/babel/visitor.ts +807 -0
- package/src/cli/auth.ts +255 -0
- package/src/cli/build.ts +288 -0
- package/src/cli/deploy.ts +206 -0
- package/src/cli/index.ts +328 -0
- package/src/cli/init.ts +388 -0
- package/src/cli/installer.ts +261 -0
- package/src/cli/lock-file.ts +94 -0
- package/src/cli/mmrc.ts +22 -0
- package/src/cli/pull.ts +172 -0
- package/src/cli/registry-client.ts +175 -0
- package/src/cli/test.ts +397 -0
- package/src/cli/type-generator.ts +243 -0
- package/src/codemod/__tests__/forward.test.ts +239 -0
- package/src/codemod/__tests__/reverse.test.ts +145 -0
- package/src/codemod/__tests__/round-trip.test.ts +137 -0
- package/src/codemod/annotation.ts +97 -0
- package/src/codemod/classify.ts +197 -0
- package/src/codemod/cli.ts +207 -0
- package/src/codemod/control-flow.ts +409 -0
- package/src/codemod/forward.ts +244 -0
- package/src/codemod/import-manager.ts +171 -0
- package/src/codemod/index.ts +120 -0
- package/src/codemod/reverse.ts +197 -0
- package/src/codemod/rules.ts +174 -0
- package/src/codemod/state-transform.ts +126 -0
- package/src/decompiler/ast-builder.ts +538 -0
- package/src/decompiler/config-generator.ts +151 -0
- package/src/decompiler/index.ts +315 -0
- package/src/decompiler/project-decompiler.ts +1776 -0
- package/src/decompiler/project.ts +862 -0
- package/src/decompiler/split-strategy.ts +140 -0
- package/src/decompiler/state-emitter.ts +1053 -0
- package/src/decompiler/sx-emitter.ts +318 -0
- package/src/decompiler/workspace-hydrator.ts +189 -0
- package/src/dev-server.ts +238 -0
- package/src/envelope/fs-tree.ts +217 -0
- package/src/envelope/source-envelope.ts +264 -0
- package/src/envelope.ts +315 -0
- package/src/incremental-compiler.ts +401 -0
- package/src/index.ts +99 -0
- package/src/model-compiler.ts +277 -0
- package/src/project-compiler.ts +1629 -0
- package/src/route-extractor.ts +333 -0
- package/src/testing/index.ts +32 -0
- package/src/testing/snapshot.ts +252 -0
- package/src/testing/test-utils.ts +226 -0
- package/src/types.ts +68 -0
- package/src/vite/index.ts +288 -0
- package/test-compile.mjs +142 -0
- package/tsconfig.json +25 -0
- package/tsup.config.ts +23 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element classifier — analyzes tag name + className to determine target atom.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type * as t from '@babel/types';
|
|
6
|
+
import { FORWARD_RULES, REVERSE_RULES, CONTROL_FLOW_ATOMS, TEXT_VARIANT_TO_TAG } from './rules';
|
|
7
|
+
import type { MappingRule } from './rules';
|
|
8
|
+
|
|
9
|
+
export interface ClassifyResult {
|
|
10
|
+
atom: string;
|
|
11
|
+
/** Classes to remove from className (implicit in atom) */
|
|
12
|
+
removeClasses: string[];
|
|
13
|
+
/** Props to add to the element */
|
|
14
|
+
addProps: Record<string, string | boolean>;
|
|
15
|
+
/** Original tag for annotation */
|
|
16
|
+
originalTag: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ReverseClassifyResult {
|
|
20
|
+
htmlTag: string;
|
|
21
|
+
/** Classes to inject into className */
|
|
22
|
+
injectClasses: string[];
|
|
23
|
+
/** Props to remove (encoded in HTML tag) */
|
|
24
|
+
removeProps: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract static class names from a className JSX attribute value.
|
|
29
|
+
* Handles: string literals, template literals (static parts), cn() first arg.
|
|
30
|
+
*/
|
|
31
|
+
export function extractStaticClasses(attrValue: t.Node | null | undefined): string[] {
|
|
32
|
+
if (!attrValue) return [];
|
|
33
|
+
|
|
34
|
+
// className="flex items-center"
|
|
35
|
+
if (attrValue.type === 'StringLiteral') {
|
|
36
|
+
return attrValue.value.split(/\s+/).filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// className={expr} — unwrap JSXExpressionContainer
|
|
40
|
+
if (attrValue.type === 'JSXExpressionContainer') {
|
|
41
|
+
return extractStaticClasses(attrValue.expression as t.Node);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// className={`flex items-center ${dynamic}`}
|
|
45
|
+
if (attrValue.type === 'TemplateLiteral') {
|
|
46
|
+
const staticParts = attrValue.quasis.map(q => q.value.raw).join(' ');
|
|
47
|
+
return staticParts.split(/\s+/).filter(Boolean);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// className={cn("flex items-center", dynamic)}
|
|
51
|
+
if (
|
|
52
|
+
attrValue.type === 'CallExpression' &&
|
|
53
|
+
attrValue.callee.type === 'Identifier' &&
|
|
54
|
+
attrValue.callee.name === 'cn' &&
|
|
55
|
+
attrValue.arguments.length > 0
|
|
56
|
+
) {
|
|
57
|
+
const firstArg = attrValue.arguments[0];
|
|
58
|
+
if (firstArg.type === 'StringLiteral') {
|
|
59
|
+
return firstArg.value.split(/\s+/).filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Classify a raw HTML element into its target workflow atom.
|
|
68
|
+
*/
|
|
69
|
+
export function classifyElement(
|
|
70
|
+
tagName: string,
|
|
71
|
+
classNames: string[],
|
|
72
|
+
): ClassifyResult | null {
|
|
73
|
+
// Skip elements that are already atoms or components (PascalCase)
|
|
74
|
+
if (tagName[0] === tagName[0].toUpperCase() && tagName[0] !== tagName[0].toLowerCase()) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Skip motion.* elements
|
|
79
|
+
if (tagName.startsWith('motion.')) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Find matching rules for this tag, sorted by priority descending
|
|
84
|
+
const candidates = FORWARD_RULES
|
|
85
|
+
.filter(r => r.htmlTag === tagName)
|
|
86
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
87
|
+
|
|
88
|
+
if (candidates.length === 0) return null;
|
|
89
|
+
|
|
90
|
+
// For div, we need to check className signals
|
|
91
|
+
if (tagName === 'div') {
|
|
92
|
+
for (const rule of candidates) {
|
|
93
|
+
if (!rule.classSignal || rule.classSignal.length === 0) {
|
|
94
|
+
// Default rule (no signal required) — only use if no better match
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const hasAllSignals = rule.classSignal.every(s => classNames.includes(s));
|
|
98
|
+
if (hasAllSignals) {
|
|
99
|
+
return makeResult(rule, tagName);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Fallback: default div rule (priority 0)
|
|
103
|
+
const defaultRule = candidates.find(r => (r.priority ?? 0) === 0);
|
|
104
|
+
if (defaultRule) {
|
|
105
|
+
return makeResult(defaultRule, tagName);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// For non-div elements, take the first (and usually only) match
|
|
110
|
+
const rule = candidates[0];
|
|
111
|
+
return makeResult(rule, tagName);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function makeResult(rule: MappingRule, tagName: string): ClassifyResult {
|
|
115
|
+
return {
|
|
116
|
+
atom: rule.atom,
|
|
117
|
+
removeClasses: rule.implicitClasses ?? [],
|
|
118
|
+
addProps: rule.props ?? {},
|
|
119
|
+
originalTag: tagName,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Reverse-classify a workflow atom into its target HTML element.
|
|
125
|
+
*/
|
|
126
|
+
export function reverseClassifyAtom(
|
|
127
|
+
atomName: string,
|
|
128
|
+
props: Map<string, t.Node>,
|
|
129
|
+
): ReverseClassifyResult | null {
|
|
130
|
+
// Skip control flow atoms
|
|
131
|
+
if (CONTROL_FLOW_ATOMS.has(atomName)) return null;
|
|
132
|
+
|
|
133
|
+
// Special case: Text with variant → heading tag
|
|
134
|
+
if (atomName === 'Text') {
|
|
135
|
+
const variantNode = props.get('variant');
|
|
136
|
+
if (variantNode && variantNode.type === 'StringLiteral') {
|
|
137
|
+
const tag = TEXT_VARIANT_TO_TAG[variantNode.value];
|
|
138
|
+
if (tag) {
|
|
139
|
+
return {
|
|
140
|
+
htmlTag: tag,
|
|
141
|
+
injectClasses: [],
|
|
142
|
+
removeProps: ['variant'],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Default Text → span
|
|
147
|
+
return { htmlTag: 'span', injectClasses: [], removeProps: [] };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Special case: TextInput with multiline → textarea
|
|
151
|
+
if (atomName === 'TextInput') {
|
|
152
|
+
const multilineNode = props.get('multiline');
|
|
153
|
+
if (multilineNode) {
|
|
154
|
+
const isMultiline =
|
|
155
|
+
(multilineNode.type === 'BooleanLiteral' && multilineNode.value) ||
|
|
156
|
+
(multilineNode.type === 'JSXExpressionContainer' &&
|
|
157
|
+
(multilineNode as any).expression?.type === 'BooleanLiteral' &&
|
|
158
|
+
(multilineNode as any).expression?.value);
|
|
159
|
+
if (isMultiline) {
|
|
160
|
+
return { htmlTag: 'textarea', injectClasses: [], removeProps: ['multiline'] };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { htmlTag: 'input', injectClasses: [], removeProps: [] };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const rule = REVERSE_RULES.find(r => r.atom === atomName);
|
|
167
|
+
if (!rule) return null;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
htmlTag: rule.htmlTag,
|
|
171
|
+
injectClasses: rule.injectClasses ?? [],
|
|
172
|
+
removeProps: rule.removeProps ?? [],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Remove classes from a className string.
|
|
178
|
+
*/
|
|
179
|
+
export function removeClassesFromString(className: string, toRemove: string[]): string {
|
|
180
|
+
if (toRemove.length === 0) return className;
|
|
181
|
+
const removeSet = new Set(toRemove);
|
|
182
|
+
return className
|
|
183
|
+
.split(/\s+/)
|
|
184
|
+
.filter(c => c && !removeSet.has(c))
|
|
185
|
+
.join(' ');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Add classes to the beginning of a className string.
|
|
190
|
+
*/
|
|
191
|
+
export function addClassesToString(className: string, toAdd: string[]): string {
|
|
192
|
+
if (toAdd.length === 0) return className;
|
|
193
|
+
const existing = new Set(className.split(/\s+/).filter(Boolean));
|
|
194
|
+
const newClasses = toAdd.filter(c => !existing.has(c));
|
|
195
|
+
if (newClasses.length === 0) return className;
|
|
196
|
+
return [...newClasses, ...className.split(/\s+/).filter(Boolean)].join(' ');
|
|
197
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI for the bidirectional React ↔ Workflow Atom codemod.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx mm-codemod forward src/features/chat/
|
|
7
|
+
* npx mm-codemod reverse packages/blueprint-chat/app/
|
|
8
|
+
* npx mm-codemod verify src/features/chat/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
12
|
+
import { resolve, relative, dirname } from 'path';
|
|
13
|
+
import { glob } from 'glob';
|
|
14
|
+
import { transform, type CodemodOptions } from './index';
|
|
15
|
+
import type { StateMode } from './state-transform';
|
|
16
|
+
|
|
17
|
+
interface CLIOptions {
|
|
18
|
+
direction: 'forward' | 'reverse' | 'verify';
|
|
19
|
+
paths: string[];
|
|
20
|
+
dryRun: boolean;
|
|
21
|
+
stripAnnotations: boolean;
|
|
22
|
+
stateMode: StateMode;
|
|
23
|
+
verbose: boolean;
|
|
24
|
+
outDir?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseArgs(argv: string[]): CLIOptions {
|
|
28
|
+
const args = argv.slice(2);
|
|
29
|
+
const direction = args[0] as CLIOptions['direction'];
|
|
30
|
+
|
|
31
|
+
if (!direction || !['forward', 'reverse', 'verify'].includes(direction)) {
|
|
32
|
+
console.error('Usage: mm-codemod <forward|reverse|verify> <paths...> [options]');
|
|
33
|
+
console.error('');
|
|
34
|
+
console.error('Options:');
|
|
35
|
+
console.error(' --dry-run Print diff without writing');
|
|
36
|
+
console.error(' --strip-annotations Remove @mm-original comments');
|
|
37
|
+
console.error(' --mode <mode> State mode: preserve|migrate|annotate');
|
|
38
|
+
console.error(' --out <dir> Output directory (default: in-place)');
|
|
39
|
+
console.error(' --verbose Log every transformation');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const paths: string[] = [];
|
|
44
|
+
let dryRun = false;
|
|
45
|
+
let stripAnnotations = false;
|
|
46
|
+
let stateMode: StateMode = 'preserve';
|
|
47
|
+
let verbose = false;
|
|
48
|
+
let outDir: string | undefined;
|
|
49
|
+
|
|
50
|
+
for (let i = 1; i < args.length; i++) {
|
|
51
|
+
const arg = args[i];
|
|
52
|
+
if (arg === '--dry-run') {
|
|
53
|
+
dryRun = true;
|
|
54
|
+
} else if (arg === '--strip-annotations') {
|
|
55
|
+
stripAnnotations = true;
|
|
56
|
+
} else if (arg === '--mode') {
|
|
57
|
+
stateMode = args[++i] as StateMode;
|
|
58
|
+
} else if (arg === '--out') {
|
|
59
|
+
outDir = args[++i];
|
|
60
|
+
} else if (arg === '--verbose') {
|
|
61
|
+
verbose = true;
|
|
62
|
+
} else if (!arg.startsWith('-')) {
|
|
63
|
+
paths.push(arg);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (paths.length === 0) {
|
|
68
|
+
console.error('Error: No paths specified');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { direction, paths, dryRun, stripAnnotations, stateMode, verbose, outDir };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function findFiles(paths: string[]): Promise<string[]> {
|
|
76
|
+
const files: string[] = [];
|
|
77
|
+
|
|
78
|
+
for (const p of paths) {
|
|
79
|
+
const resolved = resolve(p);
|
|
80
|
+
const matches = await glob('**/*.{tsx,jsx}', {
|
|
81
|
+
cwd: resolved,
|
|
82
|
+
absolute: true,
|
|
83
|
+
ignore: ['**/node_modules/**', '**/__tests__/**', '**/dist/**'],
|
|
84
|
+
});
|
|
85
|
+
files.push(...matches);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return files.sort();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function transformFile(
|
|
92
|
+
filePath: string,
|
|
93
|
+
options: CodemodOptions,
|
|
94
|
+
verbose: boolean,
|
|
95
|
+
): { code: string; changed: boolean } {
|
|
96
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const result = transform(source, options);
|
|
100
|
+
const changed = result.code !== source;
|
|
101
|
+
|
|
102
|
+
if (verbose && changed) {
|
|
103
|
+
console.log(` [${options.direction}] ${relative(process.cwd(), filePath)}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { code: result.code, changed };
|
|
107
|
+
} catch (err: any) {
|
|
108
|
+
console.error(` [ERROR] ${relative(process.cwd(), filePath)}: ${err.message}`);
|
|
109
|
+
return { code: source, changed: false };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function main() {
|
|
114
|
+
const opts = parseArgs(process.argv);
|
|
115
|
+
const files = await findFiles(opts.paths);
|
|
116
|
+
|
|
117
|
+
console.log(`mm-codemod: ${opts.direction} — ${files.length} file(s)`);
|
|
118
|
+
|
|
119
|
+
if (opts.direction === 'verify') {
|
|
120
|
+
// Round-trip verification: forward(source) → reverse(result) → diff
|
|
121
|
+
let pass = 0;
|
|
122
|
+
let fail = 0;
|
|
123
|
+
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
const source = readFileSync(file, 'utf-8');
|
|
126
|
+
const forward = transform(source, {
|
|
127
|
+
direction: 'forward',
|
|
128
|
+
annotate: true,
|
|
129
|
+
stateMode: opts.stateMode,
|
|
130
|
+
});
|
|
131
|
+
const roundTripped = transform(forward.code, {
|
|
132
|
+
direction: 'reverse',
|
|
133
|
+
annotate: false,
|
|
134
|
+
stateMode: opts.stateMode,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Normalize whitespace for comparison
|
|
138
|
+
const normalize = (s: string) => s.replace(/\s+/g, ' ').trim();
|
|
139
|
+
if (normalize(roundTripped.code) === normalize(source)) {
|
|
140
|
+
pass++;
|
|
141
|
+
if (opts.verbose) console.log(` [PASS] ${relative(process.cwd(), file)}`);
|
|
142
|
+
} else {
|
|
143
|
+
fail++;
|
|
144
|
+
console.log(` [FAIL] ${relative(process.cwd(), file)}`);
|
|
145
|
+
if (opts.verbose) {
|
|
146
|
+
console.log(' Expected (normalized):');
|
|
147
|
+
console.log(` ${normalize(source).slice(0, 200)}...`);
|
|
148
|
+
console.log(' Got (normalized):');
|
|
149
|
+
console.log(` ${normalize(roundTripped.code).slice(0, 200)}...`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(`\nResults: ${pass} pass, ${fail} fail out of ${files.length} files`);
|
|
155
|
+
process.exit(fail > 0 ? 1 : 0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Forward or reverse
|
|
159
|
+
const codemodOptions: CodemodOptions = {
|
|
160
|
+
direction: opts.direction as 'forward' | 'reverse',
|
|
161
|
+
annotate: !opts.stripAnnotations,
|
|
162
|
+
stateMode: opts.stateMode,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
let changed = 0;
|
|
166
|
+
let unchanged = 0;
|
|
167
|
+
let errors = 0;
|
|
168
|
+
|
|
169
|
+
for (const file of files) {
|
|
170
|
+
try {
|
|
171
|
+
const result = transformFile(file, codemodOptions, opts.verbose);
|
|
172
|
+
|
|
173
|
+
if (!result.changed) {
|
|
174
|
+
unchanged++;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
changed++;
|
|
179
|
+
|
|
180
|
+
if (opts.dryRun) {
|
|
181
|
+
console.log(` [WOULD CHANGE] ${relative(process.cwd(), file)}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Determine output path
|
|
186
|
+
let outPath = file;
|
|
187
|
+
if (opts.outDir) {
|
|
188
|
+
const rel = relative(resolve(opts.paths[0]), file);
|
|
189
|
+
outPath = resolve(opts.outDir, rel);
|
|
190
|
+
const dir = dirname(outPath);
|
|
191
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
writeFileSync(outPath, result.code, 'utf-8');
|
|
195
|
+
} catch (err: any) {
|
|
196
|
+
errors++;
|
|
197
|
+
console.error(` [ERROR] ${relative(process.cwd(), file)}: ${err.message}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log(`\nDone: ${changed} changed, ${unchanged} unchanged, ${errors} errors`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
main().catch(err => {
|
|
205
|
+
console.error(err);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
});
|