@embeddables/cli 0.1.0
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 +116 -0
- package/bin/embeddables.mjs +2 -0
- package/dist/auth/index.d.ts +43 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +100 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +75 -0
- package/dist/commands/build-workbench.d.ts +5 -0
- package/dist/commands/build-workbench.d.ts.map +1 -0
- package/dist/commands/build-workbench.js +122 -0
- package/dist/commands/build.d.ts +7 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +22 -0
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +153 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +112 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +18 -0
- package/dist/commands/pull.d.ts +7 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +97 -0
- package/dist/compiler/errors.d.ts +20 -0
- package/dist/compiler/errors.d.ts.map +1 -0
- package/dist/compiler/errors.js +35 -0
- package/dist/compiler/evalStatic.d.ts +3 -0
- package/dist/compiler/evalStatic.d.ts.map +1 -0
- package/dist/compiler/evalStatic.js +57 -0
- package/dist/compiler/flatten.js +1 -0
- package/dist/compiler/helpers/duplicateIds.d.ts +9 -0
- package/dist/compiler/helpers/duplicateIds.d.ts.map +1 -0
- package/dist/compiler/helpers/duplicateIds.js +71 -0
- package/dist/compiler/index.d.ts +16 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +934 -0
- package/dist/compiler/parsePage.d.ts +15 -0
- package/dist/compiler/parsePage.d.ts.map +1 -0
- package/dist/compiler/parsePage.js +562 -0
- package/dist/compiler/registry.d.ts +4 -0
- package/dist/compiler/registry.d.ts.map +1 -0
- package/dist/compiler/registry.js +44 -0
- package/dist/compiler/reverse.d.ts +17 -0
- package/dist/compiler/reverse.d.ts.map +1 -0
- package/dist/compiler/reverse.js +1632 -0
- package/dist/compiler/types.d.ts +21 -0
- package/dist/compiler/types.d.ts.map +1 -0
- package/dist/compiler/types.js +1 -0
- package/dist/components/index.d.ts +21 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +21 -0
- package/dist/components/primitives/BaseComponent.d.ts +32 -0
- package/dist/components/primitives/BaseComponent.d.ts.map +1 -0
- package/dist/components/primitives/BaseComponent.js +26 -0
- package/dist/components/primitives/BookMeeting.d.ts +18 -0
- package/dist/components/primitives/BookMeeting.d.ts.map +1 -0
- package/dist/components/primitives/BookMeeting.js +5 -0
- package/dist/components/primitives/Chart.d.ts +41 -0
- package/dist/components/primitives/Chart.d.ts.map +1 -0
- package/dist/components/primitives/Chart.js +5 -0
- package/dist/components/primitives/Container.d.ts +8 -0
- package/dist/components/primitives/Container.d.ts.map +1 -0
- package/dist/components/primitives/Container.js +5 -0
- package/dist/components/primitives/CustomButton.d.ts +37 -0
- package/dist/components/primitives/CustomButton.d.ts.map +1 -0
- package/dist/components/primitives/CustomButton.js +10 -0
- package/dist/components/primitives/CustomHTML.d.ts +8 -0
- package/dist/components/primitives/CustomHTML.d.ts.map +1 -0
- package/dist/components/primitives/CustomHTML.js +5 -0
- package/dist/components/primitives/FileUpload.d.ts +18 -0
- package/dist/components/primitives/FileUpload.d.ts.map +1 -0
- package/dist/components/primitives/FileUpload.js +16 -0
- package/dist/components/primitives/InputBox.d.ts +34 -0
- package/dist/components/primitives/InputBox.d.ts.map +1 -0
- package/dist/components/primitives/InputBox.js +25 -0
- package/dist/components/primitives/Lottie.d.ts +11 -0
- package/dist/components/primitives/Lottie.d.ts.map +1 -0
- package/dist/components/primitives/Lottie.js +5 -0
- package/dist/components/primitives/MediaEmbed.d.ts +13 -0
- package/dist/components/primitives/MediaEmbed.d.ts.map +1 -0
- package/dist/components/primitives/MediaEmbed.js +6 -0
- package/dist/components/primitives/MediaImage.d.ts +8 -0
- package/dist/components/primitives/MediaImage.d.ts.map +1 -0
- package/dist/components/primitives/MediaImage.js +5 -0
- package/dist/components/primitives/OptionSelector.d.ts +35 -0
- package/dist/components/primitives/OptionSelector.d.ts.map +1 -0
- package/dist/components/primitives/OptionSelector.js +8 -0
- package/dist/components/primitives/PaypalCheckout.d.ts +25 -0
- package/dist/components/primitives/PaypalCheckout.d.ts.map +1 -0
- package/dist/components/primitives/PaypalCheckout.js +5 -0
- package/dist/components/primitives/PlainText.d.ts +6 -0
- package/dist/components/primitives/PlainText.d.ts.map +1 -0
- package/dist/components/primitives/PlainText.js +5 -0
- package/dist/components/primitives/ProgressBar.d.ts +15 -0
- package/dist/components/primitives/ProgressBar.d.ts.map +1 -0
- package/dist/components/primitives/ProgressBar.js +5 -0
- package/dist/components/primitives/RichText.d.ts +6 -0
- package/dist/components/primitives/RichText.d.ts.map +1 -0
- package/dist/components/primitives/RichText.js +5 -0
- package/dist/components/primitives/RichTextMarkdown.d.ts +6 -0
- package/dist/components/primitives/RichTextMarkdown.d.ts.map +1 -0
- package/dist/components/primitives/RichTextMarkdown.js +5 -0
- package/dist/components/primitives/Rive.d.ts +16 -0
- package/dist/components/primitives/Rive.d.ts.map +1 -0
- package/dist/components/primitives/Rive.js +8 -0
- package/dist/components/primitives/StripeCheckout.d.ts +52 -0
- package/dist/components/primitives/StripeCheckout.d.ts.map +1 -0
- package/dist/components/primitives/StripeCheckout.js +5 -0
- package/dist/components/primitives/StripeCheckout2.d.ts +30 -0
- package/dist/components/primitives/StripeCheckout2.d.ts.map +1 -0
- package/dist/components/primitives/StripeCheckout2.js +7 -0
- package/dist/proxy/injectApiInterceptor.d.ts +6 -0
- package/dist/proxy/injectApiInterceptor.d.ts.map +1 -0
- package/dist/proxy/injectApiInterceptor.js +66 -0
- package/dist/proxy/injectReload.d.ts +2 -0
- package/dist/proxy/injectReload.d.ts.map +1 -0
- package/dist/proxy/injectReload.js +14 -0
- package/dist/proxy/injectWorkbench.d.ts +4 -0
- package/dist/proxy/injectWorkbench.d.ts.map +1 -0
- package/dist/proxy/injectWorkbench.js +16 -0
- package/dist/proxy/server.d.ts +11 -0
- package/dist/proxy/server.d.ts.map +1 -0
- package/dist/proxy/server.js +246 -0
- package/dist/proxy/sse.d.ts +5 -0
- package/dist/proxy/sse.d.ts.map +1 -0
- package/dist/proxy/sse.js +17 -0
- package/dist/types-builder.d.ts +800 -0
- package/dist/types-builder.d.ts.map +1 -0
- package/dist/types-builder.js +20 -0
- package/dist/workbench/ActionsPanel.d.ts +6 -0
- package/dist/workbench/ActionsPanel.d.ts.map +1 -0
- package/dist/workbench/ActionsPanel.js +47 -0
- package/dist/workbench/AutofillPanel.d.ts +6 -0
- package/dist/workbench/AutofillPanel.d.ts.map +1 -0
- package/dist/workbench/AutofillPanel.js +543 -0
- package/dist/workbench/ComputedFieldsPanel.d.ts +6 -0
- package/dist/workbench/ComputedFieldsPanel.d.ts.map +1 -0
- package/dist/workbench/ComputedFieldsPanel.js +31 -0
- package/dist/workbench/ExperimentsPanel.d.ts +6 -0
- package/dist/workbench/ExperimentsPanel.d.ts.map +1 -0
- package/dist/workbench/ExperimentsPanel.js +182 -0
- package/dist/workbench/FieldEditorPanel.d.ts +9 -0
- package/dist/workbench/FieldEditorPanel.d.ts.map +1 -0
- package/dist/workbench/FieldEditorPanel.js +650 -0
- package/dist/workbench/InspectorPanel.d.ts +6 -0
- package/dist/workbench/InspectorPanel.d.ts.map +1 -0
- package/dist/workbench/InspectorPanel.js +341 -0
- package/dist/workbench/PageNavigator.d.ts +6 -0
- package/dist/workbench/PageNavigator.d.ts.map +1 -0
- package/dist/workbench/PageNavigator.js +123 -0
- package/dist/workbench/SchemaPanel.d.ts +6 -0
- package/dist/workbench/SchemaPanel.d.ts.map +1 -0
- package/dist/workbench/SchemaPanel.js +222 -0
- package/dist/workbench/UserDataPanel.d.ts +6 -0
- package/dist/workbench/UserDataPanel.d.ts.map +1 -0
- package/dist/workbench/UserDataPanel.js +350 -0
- package/dist/workbench/WorkbenchApp.d.ts +6 -0
- package/dist/workbench/WorkbenchApp.d.ts.map +1 -0
- package/dist/workbench/WorkbenchApp.js +193 -0
- package/dist/workbench/cloudflare-worker/README.md +31 -0
- package/dist/workbench/cloudflare-worker/public/workbench.css +1614 -0
- package/dist/workbench/cloudflare-worker/public/workbench.js +77 -0
- package/dist/workbench/cloudflare-worker/worker.js +40 -0
- package/dist/workbench/cloudflare-worker/wrangler.toml +10 -0
- package/dist/workbench/index.d.ts +9 -0
- package/dist/workbench/index.d.ts.map +1 -0
- package/dist/workbench/index.js +44 -0
- package/dist/workbench/workbench.css +1614 -0
- package/dist/workbench/workbench.js +77 -0
- package/package.json +79 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ComponentJson, PageJson } from './types.js';
|
|
2
|
+
export declare function parsePageFromFile(args: {
|
|
3
|
+
code: string;
|
|
4
|
+
filePath: string;
|
|
5
|
+
pageKey: string;
|
|
6
|
+
}): PageJson;
|
|
7
|
+
/**
|
|
8
|
+
* Parses a global component file and returns the components.
|
|
9
|
+
* Similar to parsePageFromFile but simpler - no pageKey needed, just returns components.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseGlobalComponentsFromFile(args: {
|
|
12
|
+
code: string;
|
|
13
|
+
filePath: string;
|
|
14
|
+
}): ComponentJson[];
|
|
15
|
+
//# sourceMappingURL=parsePage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parsePage.d.ts","sourceRoot":"","sources":["../../src/compiler/parsePage.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAezD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,QAAQ,CA+GX;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,aAAa,EAAE,CAmGlB"}
|
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import traverseImport from '@babel/traverse';
|
|
3
|
+
import { ALLOWED_PRIMITIVES, TYPE_MAP, DEFAULTS_BY_TYPE } from './registry.js';
|
|
4
|
+
import { CompileError } from './errors.js';
|
|
5
|
+
import { evalStatic } from './evalStatic.js';
|
|
6
|
+
const traverse = (traverseImport.default ?? traverseImport);
|
|
7
|
+
export function parsePageFromFile(args) {
|
|
8
|
+
let ast;
|
|
9
|
+
try {
|
|
10
|
+
ast = parse(args.code, {
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
plugins: ['typescript', 'jsx'],
|
|
13
|
+
sourceFilename: args.filePath,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
// Babel parser errors have loc property with line/column
|
|
18
|
+
const line = error.loc?.line ?? error.loc?.start?.line;
|
|
19
|
+
const column = error.loc?.column ?? error.loc?.start?.column;
|
|
20
|
+
throw new CompileError(error.message || 'Syntax error', {
|
|
21
|
+
file: args.filePath,
|
|
22
|
+
line,
|
|
23
|
+
column,
|
|
24
|
+
pageKey: args.pageKey,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// collect static consts in module scope + inside page function (v1: module scope only is fine)
|
|
28
|
+
const constEnv = new Map();
|
|
29
|
+
traverse(ast, {
|
|
30
|
+
VariableDeclarator(path) {
|
|
31
|
+
const id = path.node.id;
|
|
32
|
+
const init = path.node.init;
|
|
33
|
+
if (id.type === 'Identifier' && init) {
|
|
34
|
+
// only accept if init is statically evaluable
|
|
35
|
+
try {
|
|
36
|
+
const v = evalStatic(init, constEnv, args.filePath);
|
|
37
|
+
constEnv.set(id.name, v);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// ignore non-static consts
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
// find exported function and its return JSX
|
|
46
|
+
// Supports both default exports (preferred) and named exports
|
|
47
|
+
let rootJsx = null;
|
|
48
|
+
let rootFragment = null;
|
|
49
|
+
// Helper to extract JSX from a function body
|
|
50
|
+
const extractJsxFromFunction = (fnBody) => {
|
|
51
|
+
const ret = fnBody.body.find((s) => s.type === 'ReturnStatement');
|
|
52
|
+
const arg = ret?.argument;
|
|
53
|
+
if (arg) {
|
|
54
|
+
if (arg.type === 'JSXElement') {
|
|
55
|
+
rootJsx = arg;
|
|
56
|
+
}
|
|
57
|
+
else if (arg.type === 'JSXFragment') {
|
|
58
|
+
rootFragment = arg;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
traverse(ast, {
|
|
63
|
+
// Handle: export default function PageName() { ... }
|
|
64
|
+
ExportDefaultDeclaration(path) {
|
|
65
|
+
const decl = path.node.declaration;
|
|
66
|
+
if (decl.type === 'FunctionDeclaration') {
|
|
67
|
+
extractJsxFromFunction(decl.body);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
// Handle: export function PageName() { ... } (legacy support)
|
|
71
|
+
ExportNamedDeclaration(path) {
|
|
72
|
+
const decl = path.node.declaration;
|
|
73
|
+
if (!decl)
|
|
74
|
+
return;
|
|
75
|
+
if (decl.type === 'FunctionDeclaration' && decl.id?.name) {
|
|
76
|
+
extractJsxFromFunction(decl.body);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const components = [];
|
|
81
|
+
if (rootJsx) {
|
|
82
|
+
// Single root element
|
|
83
|
+
const tree = jsxToTree(rootJsx, constEnv, args.filePath);
|
|
84
|
+
flatten(tree, null, components, args.pageKey, args.filePath);
|
|
85
|
+
}
|
|
86
|
+
else if (rootFragment) {
|
|
87
|
+
// Multiple root elements (fragment) - allow empty fragments for blank pages
|
|
88
|
+
const fragmentChildren = [];
|
|
89
|
+
const fragment = rootFragment;
|
|
90
|
+
for (const c of fragment.children) {
|
|
91
|
+
if (c.type === 'JSXElement') {
|
|
92
|
+
fragmentChildren.push(c);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Allow blank pages - if fragment has no JSXElement children, components array stays empty
|
|
96
|
+
for (const child of fragmentChildren) {
|
|
97
|
+
const tree = jsxToTree(child, constEnv, args.filePath);
|
|
98
|
+
flatten(tree, null, components, args.pageKey, args.filePath);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Allow blank pages - if no JSX found, components array stays empty
|
|
103
|
+
// This handles pages that return null, undefined, or have no return statement
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
key: args.pageKey,
|
|
107
|
+
showNav: true,
|
|
108
|
+
tags: [],
|
|
109
|
+
id: `page_${args.pageKey}`,
|
|
110
|
+
components,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Parses a global component file and returns the components.
|
|
115
|
+
* Similar to parsePageFromFile but simpler - no pageKey needed, just returns components.
|
|
116
|
+
*/
|
|
117
|
+
export function parseGlobalComponentsFromFile(args) {
|
|
118
|
+
let ast;
|
|
119
|
+
try {
|
|
120
|
+
ast = parse(args.code, {
|
|
121
|
+
sourceType: 'module',
|
|
122
|
+
plugins: ['typescript', 'jsx'],
|
|
123
|
+
sourceFilename: args.filePath,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
// Babel parser errors have loc property with line/column
|
|
128
|
+
const line = error.loc?.line ?? error.loc?.start?.line;
|
|
129
|
+
const column = error.loc?.column ?? error.loc?.start?.column;
|
|
130
|
+
throw new CompileError(error.message || 'Syntax error', {
|
|
131
|
+
file: args.filePath,
|
|
132
|
+
line,
|
|
133
|
+
column,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// collect static consts in module scope
|
|
137
|
+
const constEnv = new Map();
|
|
138
|
+
traverse(ast, {
|
|
139
|
+
VariableDeclarator(path) {
|
|
140
|
+
const id = path.node.id;
|
|
141
|
+
const init = path.node.init;
|
|
142
|
+
if (id.type === 'Identifier' && init) {
|
|
143
|
+
try {
|
|
144
|
+
const v = evalStatic(init, constEnv, args.filePath);
|
|
145
|
+
constEnv.set(id.name, v);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// ignore non-static consts
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
// find exported function and its return JSX
|
|
154
|
+
// Supports both default exports (preferred) and named exports
|
|
155
|
+
let rootJsx = null;
|
|
156
|
+
let rootFragment = null;
|
|
157
|
+
// Helper to extract JSX from a function body
|
|
158
|
+
const extractJsxFromFunction = (fnBody) => {
|
|
159
|
+
const ret = fnBody.body.find((s) => s.type === 'ReturnStatement');
|
|
160
|
+
const arg = ret?.argument;
|
|
161
|
+
if (arg) {
|
|
162
|
+
if (arg.type === 'JSXElement') {
|
|
163
|
+
rootJsx = arg;
|
|
164
|
+
}
|
|
165
|
+
else if (arg.type === 'JSXFragment') {
|
|
166
|
+
rootFragment = arg;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
traverse(ast, {
|
|
171
|
+
// Handle: export default function ComponentName() { ... }
|
|
172
|
+
ExportDefaultDeclaration(path) {
|
|
173
|
+
const decl = path.node.declaration;
|
|
174
|
+
if (decl.type === 'FunctionDeclaration') {
|
|
175
|
+
extractJsxFromFunction(decl.body);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
// Handle: export function ComponentName() { ... } (legacy support)
|
|
179
|
+
ExportNamedDeclaration(path) {
|
|
180
|
+
const decl = path.node.declaration;
|
|
181
|
+
if (!decl)
|
|
182
|
+
return;
|
|
183
|
+
if (decl.type === 'FunctionDeclaration' && decl.id?.name) {
|
|
184
|
+
extractJsxFromFunction(decl.body);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
const components = [];
|
|
189
|
+
if (rootJsx) {
|
|
190
|
+
// Single root element
|
|
191
|
+
const tree = jsxToTree(rootJsx, constEnv, args.filePath);
|
|
192
|
+
flatten(tree, null, components, '', args.filePath); // Empty pageKey for global components
|
|
193
|
+
}
|
|
194
|
+
else if (rootFragment) {
|
|
195
|
+
// Multiple root elements (fragment)
|
|
196
|
+
const fragmentChildren = [];
|
|
197
|
+
const fragment = rootFragment;
|
|
198
|
+
for (const c of fragment.children) {
|
|
199
|
+
if (c.type === 'JSXElement') {
|
|
200
|
+
fragmentChildren.push(c);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const child of fragmentChildren) {
|
|
204
|
+
const tree = jsxToTree(child, constEnv, args.filePath);
|
|
205
|
+
flatten(tree, null, components, '', args.filePath); // Empty pageKey for global components
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return components;
|
|
209
|
+
}
|
|
210
|
+
function jsxToTree(el, constEnv, filePath) {
|
|
211
|
+
const opening = el.openingElement;
|
|
212
|
+
const tagName = getJsxTagName(opening.name);
|
|
213
|
+
if (!ALLOWED_PRIMITIVES.has(tagName)) {
|
|
214
|
+
throw new CompileError(`Unknown/unsupported primitive <${tagName}>.`, loc(filePath, opening));
|
|
215
|
+
}
|
|
216
|
+
const jsonType = TYPE_MAP[tagName];
|
|
217
|
+
const attrs = attrsToProps(opening.attributes, constEnv, filePath);
|
|
218
|
+
const id = requireString(attrs, 'id', filePath, opening);
|
|
219
|
+
const key = requireString(attrs, 'key', filePath, opening);
|
|
220
|
+
const tags = (attrs.tags ?? []);
|
|
221
|
+
if (!Array.isArray(tags) || tags.some((t) => typeof t !== 'string')) {
|
|
222
|
+
throw new CompileError(`Prop "tags" must be an array of strings.`, loc(filePath, opening));
|
|
223
|
+
}
|
|
224
|
+
delete attrs.id;
|
|
225
|
+
delete attrs.key;
|
|
226
|
+
delete attrs.tags;
|
|
227
|
+
// For CustomHTML, convert all children (including text) to HTML string
|
|
228
|
+
if (tagName === 'CustomHTML') {
|
|
229
|
+
// Check if there are any children (including text nodes and fragments)
|
|
230
|
+
const hasChildren = el.children.some((c) => c.type === 'JSXElement' ||
|
|
231
|
+
c.type === 'JSXFragment' ||
|
|
232
|
+
(c.type === 'JSXText' && c.value.trim().length > 0) ||
|
|
233
|
+
c.type === 'JSXExpressionContainer');
|
|
234
|
+
if (hasChildren) {
|
|
235
|
+
// Convert JSX children to HTML string
|
|
236
|
+
const htmlString = jsxChildrenToHTML(el.children, constEnv, filePath);
|
|
237
|
+
attrs.text = htmlString;
|
|
238
|
+
}
|
|
239
|
+
// Return with empty children array since we've converted them to HTML
|
|
240
|
+
return {
|
|
241
|
+
tag: tagName,
|
|
242
|
+
type: jsonType,
|
|
243
|
+
id,
|
|
244
|
+
key,
|
|
245
|
+
tags,
|
|
246
|
+
props: attrs,
|
|
247
|
+
children: [],
|
|
248
|
+
loc: loc(filePath, opening),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
// children JSXElements only (ignore whitespace text nodes)
|
|
252
|
+
const children = el.children
|
|
253
|
+
.filter((c) => c.type === 'JSXElement')
|
|
254
|
+
.map((c) => jsxToTree(c, constEnv, filePath));
|
|
255
|
+
// v1 rule: only Container can have children (CustomHTML handled above)
|
|
256
|
+
if (children.length > 0 && tagName !== 'Container') {
|
|
257
|
+
throw new CompileError(`<${tagName}> cannot have children. Only <Container> and <CustomHTML> may contain children.`, loc(filePath, opening));
|
|
258
|
+
}
|
|
259
|
+
// enforce OptionSelector uses buttons
|
|
260
|
+
if (tagName === 'OptionSelector' && 'options' in attrs) {
|
|
261
|
+
throw new CompileError(`OptionSelector does not support prop "options". Use "buttons".`, loc(filePath, opening));
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
tag: tagName,
|
|
265
|
+
type: jsonType,
|
|
266
|
+
id,
|
|
267
|
+
key,
|
|
268
|
+
tags,
|
|
269
|
+
props: attrs,
|
|
270
|
+
children,
|
|
271
|
+
loc: loc(filePath, opening),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function flatten(node, parentId, out, pageKey, filePath) {
|
|
275
|
+
const base = {
|
|
276
|
+
type: node.type,
|
|
277
|
+
id: node.id,
|
|
278
|
+
key: node.key,
|
|
279
|
+
tags: node.tags,
|
|
280
|
+
};
|
|
281
|
+
if (parentId)
|
|
282
|
+
base.parent_id = parentId;
|
|
283
|
+
const defaults = DEFAULTS_BY_TYPE[node.type] ?? {};
|
|
284
|
+
// Extract extra props from props={...} (round-trip from reverse compiler) and merge into component
|
|
285
|
+
const { props: extraProps, ...restProps } = node.props;
|
|
286
|
+
const mergedExtra = extraProps != null && typeof extraProps === 'object' && !Array.isArray(extraProps)
|
|
287
|
+
? extraProps
|
|
288
|
+
: {};
|
|
289
|
+
const json = {
|
|
290
|
+
...base,
|
|
291
|
+
...defaults,
|
|
292
|
+
...restProps,
|
|
293
|
+
...mergedExtra,
|
|
294
|
+
};
|
|
295
|
+
// Helper to create errors with component context
|
|
296
|
+
const createError = (message, loc) => {
|
|
297
|
+
return new CompileError(message, {
|
|
298
|
+
...loc,
|
|
299
|
+
pageKey,
|
|
300
|
+
componentId: node.id,
|
|
301
|
+
componentKey: node.key,
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
// Normalize OptionSelector buttons: generate IDs + defaults
|
|
305
|
+
if (json.type === 'OptionSelector') {
|
|
306
|
+
const buttons = json.buttons;
|
|
307
|
+
if (buttons != null) {
|
|
308
|
+
if (!Array.isArray(buttons)) {
|
|
309
|
+
throw createError(`OptionSelector requires "buttons" as an array. Got: ${typeof buttons}`, node.loc);
|
|
310
|
+
}
|
|
311
|
+
json.buttons = buttons.map((b) => {
|
|
312
|
+
if (!b || typeof b !== 'object') {
|
|
313
|
+
throw createError(`Each button must be an object.`, node.loc);
|
|
314
|
+
}
|
|
315
|
+
// key can be string or null, text is optional
|
|
316
|
+
if (b.key !== null && typeof b.key !== 'string') {
|
|
317
|
+
throw createError(`Each button requires "key" to be a string or null. Got: ${typeof b.key}`, node.loc);
|
|
318
|
+
}
|
|
319
|
+
if (b.text != null && typeof b.text !== 'string') {
|
|
320
|
+
throw createError(`Button "text" must be a string if provided. Got: ${typeof b.text}`, node.loc);
|
|
321
|
+
}
|
|
322
|
+
// Preserve all OptionSelectorButton properties from the input
|
|
323
|
+
// Required properties
|
|
324
|
+
const result = {
|
|
325
|
+
id: typeof b.id === 'string' ? b.id : `option_${b.key}`,
|
|
326
|
+
key: b.key,
|
|
327
|
+
};
|
|
328
|
+
// Optional properties - only include if present
|
|
329
|
+
if (b.text != null)
|
|
330
|
+
result.text = b.text;
|
|
331
|
+
if (b.description != null)
|
|
332
|
+
result.description = b.description;
|
|
333
|
+
// triggerEvent defaults to 'no-action' if not provided
|
|
334
|
+
result.triggerEvent = typeof b.triggerEvent === 'string' ? b.triggerEvent : 'no-action';
|
|
335
|
+
if (b.conditions != null)
|
|
336
|
+
result.conditions = b.conditions;
|
|
337
|
+
if (b.openUrlInNewTab != null)
|
|
338
|
+
result.openUrlInNewTab = b.openUrlInNewTab;
|
|
339
|
+
if (b.url != null)
|
|
340
|
+
result.url = b.url;
|
|
341
|
+
if (b.single_select != null)
|
|
342
|
+
result.single_select = b.single_select;
|
|
343
|
+
if (b.icon != null)
|
|
344
|
+
result.icon = b.icon;
|
|
345
|
+
if (b.emojiIcon != null)
|
|
346
|
+
result.emojiIcon = b.emojiIcon;
|
|
347
|
+
if (b.imageUrl != null)
|
|
348
|
+
result.imageUrl = b.imageUrl;
|
|
349
|
+
if (b.imageAltText != null)
|
|
350
|
+
result.imageAltText = b.imageAltText;
|
|
351
|
+
if (b.hide != null)
|
|
352
|
+
result.hide = b.hide;
|
|
353
|
+
if (b.is_repeatable_button != null)
|
|
354
|
+
result.is_repeatable_button = b.is_repeatable_button;
|
|
355
|
+
return result;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
// If buttons is null/undefined, leave it as is (optional prop)
|
|
359
|
+
}
|
|
360
|
+
out.push(json);
|
|
361
|
+
for (const child of node.children) {
|
|
362
|
+
flatten(child, node.id, out, pageKey, filePath);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function attrsToProps(attrs, constEnv, filePath) {
|
|
366
|
+
const out = {};
|
|
367
|
+
for (const a of attrs) {
|
|
368
|
+
if (a.type === 'JSXSpreadAttribute') {
|
|
369
|
+
throw new CompileError('JSX spread props are not supported.', loc(filePath, a));
|
|
370
|
+
}
|
|
371
|
+
const name = a.name.type === 'JSXIdentifier' ? a.name.name : `${a.name.namespace.name}:${a.name.name.name}`;
|
|
372
|
+
if (!a.value) {
|
|
373
|
+
// <X disabled />
|
|
374
|
+
out[name] = true;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (a.value.type === 'StringLiteral') {
|
|
378
|
+
out[name] = a.value.value;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (a.value.type === 'JSXExpressionContainer') {
|
|
382
|
+
out[name] = evalStatic(a.value.expression, constEnv, filePath);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
throw new CompileError(`Unsupported JSX attribute value for "${name}".`, loc(filePath, a));
|
|
386
|
+
}
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
function getJsxTagName(n) {
|
|
390
|
+
if (n.type === 'JSXIdentifier')
|
|
391
|
+
return n.name;
|
|
392
|
+
throw new CompileError('Only simple JSX identifiers are supported (no member expressions).');
|
|
393
|
+
}
|
|
394
|
+
function requireString(obj, prop, filePath, node) {
|
|
395
|
+
const v = obj[prop];
|
|
396
|
+
if (typeof v !== 'string' || v.length === 0) {
|
|
397
|
+
throw new CompileError(`Missing required string prop "${prop}".`, loc(filePath, node));
|
|
398
|
+
}
|
|
399
|
+
return v;
|
|
400
|
+
}
|
|
401
|
+
function loc(file, node) {
|
|
402
|
+
return { file, line: node.loc?.start?.line, column: node.loc?.start?.column };
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Converts JSX children to HTML string.
|
|
406
|
+
* Handles JSXElements, JSXText, JSXExpressions, and JSXFragments.
|
|
407
|
+
* @param preserveWhitespace If true, preserves all whitespace (for <style> and <script> tags)
|
|
408
|
+
*/
|
|
409
|
+
function jsxChildrenToHTML(children, constEnv, filePath, preserveWhitespace = false) {
|
|
410
|
+
let html = '';
|
|
411
|
+
for (const child of children) {
|
|
412
|
+
if (child.type === 'JSXElement') {
|
|
413
|
+
html += jsxElementToHTML(child, constEnv, filePath);
|
|
414
|
+
}
|
|
415
|
+
else if (child.type === 'JSXText') {
|
|
416
|
+
const text = child.value;
|
|
417
|
+
// For <style> and <script> tags, preserve all whitespace
|
|
418
|
+
// For other tags, skip whitespace-only text (it's likely indentation)
|
|
419
|
+
if (preserveWhitespace || text.trim().length > 0) {
|
|
420
|
+
html += escapeHTML(text);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
else if (child.type === 'JSXExpressionContainer') {
|
|
424
|
+
// Try to evaluate static expressions
|
|
425
|
+
try {
|
|
426
|
+
const value = evalStatic(child.expression, constEnv, filePath);
|
|
427
|
+
if (typeof value === 'string') {
|
|
428
|
+
html += escapeHTML(value);
|
|
429
|
+
}
|
|
430
|
+
else if (value != null) {
|
|
431
|
+
html += escapeHTML(String(value));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
throw new CompileError('CustomHTML children must be static. Dynamic expressions are not supported.', loc(filePath, child));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
else if (child.type === 'JSXFragment') {
|
|
439
|
+
// Handle fragments by recursively processing their children
|
|
440
|
+
html += jsxChildrenToHTML(child.children, constEnv, filePath, preserveWhitespace);
|
|
441
|
+
}
|
|
442
|
+
// Ignore JSXSpreadChild and other unsupported types
|
|
443
|
+
}
|
|
444
|
+
return html;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* HTML5 void elements - these are the only elements that can be self-closing in HTML.
|
|
448
|
+
* For all other elements, we must emit explicit closing tags even if they're self-closing in JSX.
|
|
449
|
+
* See: https://html.spec.whatwg.org/multipage/syntax.html#void-elements
|
|
450
|
+
*/
|
|
451
|
+
const HTML_VOID_ELEMENTS = new Set([
|
|
452
|
+
'area',
|
|
453
|
+
'base',
|
|
454
|
+
'br',
|
|
455
|
+
'col',
|
|
456
|
+
'embed',
|
|
457
|
+
'hr',
|
|
458
|
+
'img',
|
|
459
|
+
'input',
|
|
460
|
+
'link',
|
|
461
|
+
'meta',
|
|
462
|
+
'param',
|
|
463
|
+
'source',
|
|
464
|
+
'track',
|
|
465
|
+
'wbr',
|
|
466
|
+
]);
|
|
467
|
+
/**
|
|
468
|
+
* Converts a JSX element to HTML string.
|
|
469
|
+
*/
|
|
470
|
+
function jsxElementToHTML(el, constEnv, filePath) {
|
|
471
|
+
const opening = el.openingElement;
|
|
472
|
+
const tagName = getJsxTagName(opening.name);
|
|
473
|
+
// Build attributes string
|
|
474
|
+
const attrs = [];
|
|
475
|
+
for (const attr of opening.attributes) {
|
|
476
|
+
if (attr.type === 'JSXAttribute') {
|
|
477
|
+
const name = attr.name.type === 'JSXIdentifier'
|
|
478
|
+
? attr.name.name
|
|
479
|
+
: `${attr.name.namespace.name}:${attr.name.name.name}`;
|
|
480
|
+
if (!attr.value) {
|
|
481
|
+
// Boolean attribute
|
|
482
|
+
attrs.push(name);
|
|
483
|
+
}
|
|
484
|
+
else if (attr.value.type === 'StringLiteral') {
|
|
485
|
+
attrs.push(`${name}="${escapeHTMLAttribute(attr.value.value)}"`);
|
|
486
|
+
}
|
|
487
|
+
else if (attr.value.type === 'JSXExpressionContainer') {
|
|
488
|
+
try {
|
|
489
|
+
const value = evalStatic(attr.value.expression, constEnv, filePath);
|
|
490
|
+
if (name === 'style' && typeof value === 'object' && value !== null) {
|
|
491
|
+
// Convert style object to CSS string
|
|
492
|
+
const cssString = styleObjectToCSS(value);
|
|
493
|
+
attrs.push(`${name}="${escapeHTMLAttribute(cssString)}"`);
|
|
494
|
+
}
|
|
495
|
+
else if (typeof value === 'string') {
|
|
496
|
+
attrs.push(`${name}="${escapeHTMLAttribute(value)}"`);
|
|
497
|
+
}
|
|
498
|
+
else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
499
|
+
attrs.push(`${name}="${String(value)}"`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
throw new CompileError('CustomHTML attribute values must be static. Dynamic expressions are not supported.', loc(filePath, attr));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const attrsStr = attrs.length > 0 ? ' ' + attrs.join(' ') : '';
|
|
509
|
+
// For <style> and <script> tags, preserve all whitespace to maintain CSS/JS formatting
|
|
510
|
+
const isRawTextTag = tagName.toLowerCase() === 'style' || tagName.toLowerCase() === 'script';
|
|
511
|
+
const childrenHTML = jsxChildrenToHTML(el.children, constEnv, filePath, isRawTextTag);
|
|
512
|
+
if (el.closingElement) {
|
|
513
|
+
return `<${tagName}${attrsStr}>${childrenHTML}</${tagName}>`;
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
// Self-closing in JSX - check if it's an HTML void element
|
|
517
|
+
const isVoidElement = HTML_VOID_ELEMENTS.has(tagName.toLowerCase());
|
|
518
|
+
if (isVoidElement) {
|
|
519
|
+
// Void elements can be self-closing in HTML
|
|
520
|
+
return `<${tagName}${attrsStr} />`;
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
// Non-void elements MUST have explicit closing tags in HTML
|
|
524
|
+
// Otherwise browsers interpret <div /> as <div> (unclosed)
|
|
525
|
+
return `<${tagName}${attrsStr}></${tagName}>`;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Escapes HTML entities in text content.
|
|
531
|
+
*/
|
|
532
|
+
function escapeHTML(text) {
|
|
533
|
+
return text
|
|
534
|
+
.replace(/&/g, '&')
|
|
535
|
+
.replace(/</g, '<')
|
|
536
|
+
.replace(/>/g, '>')
|
|
537
|
+
.replace(/"/g, '"')
|
|
538
|
+
.replace(/'/g, ''');
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Escapes HTML entities in attribute values.
|
|
542
|
+
*/
|
|
543
|
+
function escapeHTMLAttribute(text) {
|
|
544
|
+
return text
|
|
545
|
+
.replace(/&/g, '&')
|
|
546
|
+
.replace(/</g, '<')
|
|
547
|
+
.replace(/>/g, '>')
|
|
548
|
+
.replace(/"/g, '"');
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Converts a JSX style object to CSS string.
|
|
552
|
+
* Example: { margin: 0, padding: 0, color: "#171717" } -> "margin: 0; padding: 0; color: #171717;"
|
|
553
|
+
*/
|
|
554
|
+
function styleObjectToCSS(styleObj) {
|
|
555
|
+
const cssParts = [];
|
|
556
|
+
for (const [key, value] of Object.entries(styleObj)) {
|
|
557
|
+
// Convert camelCase to kebab-case (e.g., lineHeight -> line-height)
|
|
558
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
559
|
+
cssParts.push(`${cssKey}: ${value};`);
|
|
560
|
+
}
|
|
561
|
+
return cssParts.join(' ');
|
|
562
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/compiler/registry.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,aAoB7B,CAAA;AAGF,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAoB3C,CAAA;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAM,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const ALLOWED_PRIMITIVES = new Set([
|
|
2
|
+
'Container',
|
|
3
|
+
'PlainText',
|
|
4
|
+
'RichText',
|
|
5
|
+
'RichTextMarkdown',
|
|
6
|
+
'CustomHTML',
|
|
7
|
+
'InputBox',
|
|
8
|
+
'OptionSelector',
|
|
9
|
+
'CustomButton',
|
|
10
|
+
'StripeCheckout',
|
|
11
|
+
'StripeCheckout2',
|
|
12
|
+
'PaypalCheckout',
|
|
13
|
+
'MediaImage',
|
|
14
|
+
'MediaEmbed',
|
|
15
|
+
'Lottie',
|
|
16
|
+
'Rive',
|
|
17
|
+
'ProgressBar',
|
|
18
|
+
'FileUpload',
|
|
19
|
+
'BookMeeting',
|
|
20
|
+
'Chart',
|
|
21
|
+
]);
|
|
22
|
+
// TSX tag name -> JSON "type"
|
|
23
|
+
export const TYPE_MAP = {
|
|
24
|
+
Container: 'Container',
|
|
25
|
+
PlainText: 'PlainText',
|
|
26
|
+
RichText: 'RichText',
|
|
27
|
+
RichTextMarkdown: 'RichTextMarkdown',
|
|
28
|
+
CustomHTML: 'CustomHTML',
|
|
29
|
+
InputBox: 'InputBox',
|
|
30
|
+
OptionSelector: 'OptionSelector',
|
|
31
|
+
CustomButton: 'CustomButton',
|
|
32
|
+
StripeCheckout: 'StripeCheckout',
|
|
33
|
+
StripeCheckout2: 'StripeCheckout2',
|
|
34
|
+
PaypalCheckout: 'PaypalCheckout',
|
|
35
|
+
MediaImage: 'MediaImage',
|
|
36
|
+
MediaEmbed: 'MediaEmbed',
|
|
37
|
+
Lottie: 'Lottie',
|
|
38
|
+
Rive: 'Rive',
|
|
39
|
+
ProgressBar: 'ProgressBar',
|
|
40
|
+
FileUpload: 'FileUpload',
|
|
41
|
+
BookMeeting: 'BookMeeting',
|
|
42
|
+
Chart: 'Chart',
|
|
43
|
+
};
|
|
44
|
+
export const DEFAULTS_BY_TYPE = {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PageJson } from './types.js';
|
|
2
|
+
export declare function reverseCompile(embeddable: {
|
|
3
|
+
id?: string;
|
|
4
|
+
pages: PageJson[];
|
|
5
|
+
styles?: Record<string, any>;
|
|
6
|
+
computedFields?: any[];
|
|
7
|
+
dataOutputs?: any[];
|
|
8
|
+
components?: any[];
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}, embeddableId: string, opts?: {
|
|
11
|
+
fix?: boolean;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Sanitizes a string to be safe for use as a filename.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sanitizeFileName(str: string): string;
|
|
17
|
+
//# sourceMappingURL=reverse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reverse.d.ts","sourceRoot":"","sources":["../../src/compiler/reverse.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,YAAY,CAAA;AAihBzD,wBAAsB,cAAc,CAClC,UAAU,EAAE;IACV,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,cAAc,CAAC,EAAE,GAAG,EAAE,CAAA;IACtB,WAAW,CAAC,EAAE,GAAG,EAAE,CAAA;IACnB,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB,EACD,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBAmDzB;AA0yCD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKpD"}
|