@embeddables/cli 0.8.0 → 0.8.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/dist/auth/index.d.ts +43 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +102 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +210 -0
- package/dist/command-history.d.ts +13 -0
- package/dist/command-history.d.ts.map +1 -0
- package/dist/command-history.js +34 -0
- package/dist/commands/branch.d.ts +4 -0
- package/dist/commands/branch.d.ts.map +1 -0
- package/dist/commands/branch.js +67 -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 +116 -0
- package/dist/commands/build.d.ts +8 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +60 -0
- package/dist/commands/builder-open.d.ts +4 -0
- package/dist/commands/builder-open.d.ts.map +1 -0
- package/dist/commands/builder-open.js +74 -0
- package/dist/commands/dev.d.ts +12 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +226 -0
- package/dist/commands/diff.d.ts +76 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +653 -0
- package/dist/commands/experiments-connect.d.ts +6 -0
- package/dist/commands/experiments-connect.d.ts.map +1 -0
- package/dist/commands/experiments-connect.js +140 -0
- package/dist/commands/feedback.d.ts +29 -0
- package/dist/commands/feedback.d.ts.map +1 -0
- package/dist/commands/feedback.js +267 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +384 -0
- package/dist/commands/inspect.d.ts +9 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +293 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +117 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +19 -0
- package/dist/commands/pull.d.ts +16 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +395 -0
- package/dist/commands/save.d.ts +30 -0
- package/dist/commands/save.d.ts.map +1 -0
- package/dist/commands/save.js +597 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +50 -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/helpers/numericLeadingKeys.d.ts +8 -0
- package/dist/compiler/helpers/numericLeadingKeys.d.ts.map +1 -0
- package/dist/compiler/helpers/numericLeadingKeys.js +17 -0
- package/dist/compiler/index.d.ts +18 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +1272 -0
- package/dist/compiler/parsePage.d.ts +15 -0
- package/dist/compiler/parsePage.d.ts.map +1 -0
- package/dist/compiler/parsePage.js +654 -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 +23 -0
- package/dist/compiler/reverse.d.ts.map +1 -0
- package/dist/compiler/reverse.js +1920 -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 +33 -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 +38 -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/config/index.d.ts +23 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +42 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +9 -0
- package/dist/helpers/TEMP helpers file.d.ts +1 -0
- package/dist/helpers/TEMP helpers file.d.ts.map +1 -0
- package/dist/helpers/TEMP helpers file.js +1 -0
- package/dist/helpers/dates.d.ts +5 -0
- package/dist/helpers/dates.d.ts.map +1 -0
- package/dist/helpers/dates.js +7 -0
- package/dist/helpers/json.d.ts +47 -0
- package/dist/helpers/json.d.ts.map +1 -0
- package/dist/helpers/json.js +622 -0
- package/dist/helpers/prompt.d.ts +15 -0
- package/dist/helpers/prompt.d.ts.map +1 -0
- package/dist/helpers/prompt.js +35 -0
- package/dist/helpers/utils.d.ts +13 -0
- package/dist/helpers/utils.d.ts.map +1 -0
- package/dist/helpers/utils.js +28 -0
- package/dist/logger.d.ts +11 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +21 -0
- package/dist/patches/prompts-escape.d.ts +14 -0
- package/dist/patches/prompts-escape.d.ts.map +1 -0
- package/dist/patches/prompts-escape.js +23 -0
- package/dist/prompts/branches.d.ts +20 -0
- package/dist/prompts/branches.d.ts.map +1 -0
- package/dist/prompts/branches.js +86 -0
- package/dist/prompts/embeddables.d.ts +43 -0
- package/dist/prompts/embeddables.d.ts.map +1 -0
- package/dist/prompts/embeddables.js +200 -0
- package/dist/prompts/experiments.d.ts +28 -0
- package/dist/prompts/experiments.d.ts.map +1 -0
- package/dist/prompts/experiments.js +89 -0
- package/dist/prompts/index.d.ts +11 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +6 -0
- package/dist/prompts/projects.d.ts +22 -0
- package/dist/prompts/projects.d.ts.map +1 -0
- package/dist/prompts/projects.js +92 -0
- package/dist/prompts/versions.d.ts +18 -0
- package/dist/prompts/versions.d.ts.map +1 -0
- package/dist/prompts/versions.js +95 -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 +5 -0
- package/dist/proxy/injectWorkbench.d.ts.map +1 -0
- package/dist/proxy/injectWorkbench.js +22 -0
- package/dist/proxy/server.d.ts +11 -0
- package/dist/proxy/server.d.ts.map +1 -0
- package/dist/proxy/server.js +304 -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/sentry-context.d.ts +48 -0
- package/dist/sentry-context.d.ts.map +1 -0
- package/dist/sentry-context.js +156 -0
- package/dist/stdout.d.ts +61 -0
- package/dist/stdout.d.ts.map +1 -0
- package/dist/stdout.js +163 -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 +7 -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 +10 -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 +1 -1
|
@@ -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,CAuHX;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,aAAa,EAAE,CAqGlB"}
|
|
@@ -0,0 +1,654 @@
|
|
|
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 + collect function declarations for custom_validation_function
|
|
46
|
+
// Supports both default exports (preferred) and named exports
|
|
47
|
+
let rootJsx = null;
|
|
48
|
+
let rootFragment = null;
|
|
49
|
+
const validationFunctionSource = new Map();
|
|
50
|
+
// Helper to extract JSX from a function body and collect inner function declarations
|
|
51
|
+
const extractJsxAndFunctions = (fnBody, code) => {
|
|
52
|
+
const ret = fnBody.body.find((s) => s.type === 'ReturnStatement');
|
|
53
|
+
const arg = ret?.argument;
|
|
54
|
+
if (arg) {
|
|
55
|
+
if (arg.type === 'JSXElement') {
|
|
56
|
+
rootJsx = arg;
|
|
57
|
+
}
|
|
58
|
+
else if (arg.type === 'JSXFragment') {
|
|
59
|
+
rootFragment = arg;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const stmt of fnBody.body) {
|
|
63
|
+
if (stmt.type === 'FunctionDeclaration' && stmt.id?.name) {
|
|
64
|
+
const start = stmt.start ?? 0;
|
|
65
|
+
const end = stmt.end ?? code.length;
|
|
66
|
+
validationFunctionSource.set(stmt.id.name, code.slice(start, end));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
traverse(ast, {
|
|
71
|
+
// Handle: export default function PageName() { ... }
|
|
72
|
+
ExportDefaultDeclaration(path) {
|
|
73
|
+
const decl = path.node.declaration;
|
|
74
|
+
if (decl.type === 'FunctionDeclaration') {
|
|
75
|
+
extractJsxAndFunctions(decl.body, args.code);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
// Handle: export function PageName() { ... } (legacy support)
|
|
79
|
+
ExportNamedDeclaration(path) {
|
|
80
|
+
const decl = path.node.declaration;
|
|
81
|
+
if (!decl)
|
|
82
|
+
return;
|
|
83
|
+
if (decl.type === 'FunctionDeclaration' && decl.id?.name) {
|
|
84
|
+
extractJsxAndFunctions(decl.body, args.code);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const components = [];
|
|
89
|
+
if (rootJsx) {
|
|
90
|
+
// Single root element
|
|
91
|
+
const tree = jsxToTree(rootJsx, constEnv, args.filePath, validationFunctionSource);
|
|
92
|
+
flatten(tree, null, components, args.pageKey, args.filePath);
|
|
93
|
+
}
|
|
94
|
+
else if (rootFragment) {
|
|
95
|
+
// Multiple root elements (fragment) - allow empty fragments for blank pages
|
|
96
|
+
const fragmentChildren = [];
|
|
97
|
+
const fragment = rootFragment;
|
|
98
|
+
for (const c of fragment.children) {
|
|
99
|
+
if (c.type === 'JSXElement') {
|
|
100
|
+
fragmentChildren.push(c);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Allow blank pages - if fragment has no JSXElement children, components array stays empty
|
|
104
|
+
for (const child of fragmentChildren) {
|
|
105
|
+
const tree = jsxToTree(child, constEnv, args.filePath, validationFunctionSource);
|
|
106
|
+
flatten(tree, null, components, args.pageKey, args.filePath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Allow blank pages - if no JSX found, components array stays empty
|
|
111
|
+
// This handles pages that return null, undefined, or have no return statement
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
key: args.pageKey,
|
|
115
|
+
showNav: true,
|
|
116
|
+
tags: [],
|
|
117
|
+
id: `page_${args.pageKey}`,
|
|
118
|
+
components,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Parses a global component file and returns the components.
|
|
123
|
+
* Similar to parsePageFromFile but simpler - no pageKey needed, just returns components.
|
|
124
|
+
*/
|
|
125
|
+
export function parseGlobalComponentsFromFile(args) {
|
|
126
|
+
let ast;
|
|
127
|
+
try {
|
|
128
|
+
ast = parse(args.code, {
|
|
129
|
+
sourceType: 'module',
|
|
130
|
+
plugins: ['typescript', 'jsx'],
|
|
131
|
+
sourceFilename: args.filePath,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
// Babel parser errors have loc property with line/column
|
|
136
|
+
const line = error.loc?.line ?? error.loc?.start?.line;
|
|
137
|
+
const column = error.loc?.column ?? error.loc?.start?.column;
|
|
138
|
+
throw new CompileError(error.message || 'Syntax error', {
|
|
139
|
+
file: args.filePath,
|
|
140
|
+
line,
|
|
141
|
+
column,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// collect static consts in module scope
|
|
145
|
+
const constEnv = new Map();
|
|
146
|
+
traverse(ast, {
|
|
147
|
+
VariableDeclarator(path) {
|
|
148
|
+
const id = path.node.id;
|
|
149
|
+
const init = path.node.init;
|
|
150
|
+
if (id.type === 'Identifier' && init) {
|
|
151
|
+
try {
|
|
152
|
+
const v = evalStatic(init, constEnv, args.filePath);
|
|
153
|
+
constEnv.set(id.name, v);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// ignore non-static consts
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
// find exported function and its return JSX + collect function declarations for custom_validation_function
|
|
162
|
+
let rootJsx = null;
|
|
163
|
+
let rootFragment = null;
|
|
164
|
+
const validationFunctionSource = new Map();
|
|
165
|
+
const extractJsxAndFunctions = (fnBody, code) => {
|
|
166
|
+
const ret = fnBody.body.find((s) => s.type === 'ReturnStatement');
|
|
167
|
+
const arg = ret?.argument;
|
|
168
|
+
if (arg) {
|
|
169
|
+
if (arg.type === 'JSXElement') {
|
|
170
|
+
rootJsx = arg;
|
|
171
|
+
}
|
|
172
|
+
else if (arg.type === 'JSXFragment') {
|
|
173
|
+
rootFragment = arg;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const stmt of fnBody.body) {
|
|
177
|
+
if (stmt.type === 'FunctionDeclaration' && stmt.id?.name) {
|
|
178
|
+
const start = stmt.start ?? 0;
|
|
179
|
+
const end = stmt.end ?? code.length;
|
|
180
|
+
validationFunctionSource.set(stmt.id.name, code.slice(start, end));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
traverse(ast, {
|
|
185
|
+
ExportDefaultDeclaration(path) {
|
|
186
|
+
const decl = path.node.declaration;
|
|
187
|
+
if (decl.type === 'FunctionDeclaration') {
|
|
188
|
+
extractJsxAndFunctions(decl.body, args.code);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
ExportNamedDeclaration(path) {
|
|
192
|
+
const decl = path.node.declaration;
|
|
193
|
+
if (!decl)
|
|
194
|
+
return;
|
|
195
|
+
if (decl.type === 'FunctionDeclaration' && decl.id?.name) {
|
|
196
|
+
extractJsxAndFunctions(decl.body, args.code);
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
const components = [];
|
|
201
|
+
if (rootJsx) {
|
|
202
|
+
const tree = jsxToTree(rootJsx, constEnv, args.filePath, validationFunctionSource);
|
|
203
|
+
flatten(tree, null, components, '', args.filePath);
|
|
204
|
+
}
|
|
205
|
+
else if (rootFragment) {
|
|
206
|
+
const fragmentChildren = [];
|
|
207
|
+
const fragment = rootFragment;
|
|
208
|
+
for (const c of fragment.children) {
|
|
209
|
+
if (c.type === 'JSXElement') {
|
|
210
|
+
fragmentChildren.push(c);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const child of fragmentChildren) {
|
|
214
|
+
const tree = jsxToTree(child, constEnv, args.filePath, validationFunctionSource);
|
|
215
|
+
flatten(tree, null, components, '', args.filePath);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return components;
|
|
219
|
+
}
|
|
220
|
+
function jsxToTree(el, constEnv, filePath, validationFunctionSource) {
|
|
221
|
+
const opening = el.openingElement;
|
|
222
|
+
const tagName = getJsxTagName(opening.name);
|
|
223
|
+
if (!ALLOWED_PRIMITIVES.has(tagName)) {
|
|
224
|
+
throw new CompileError(`Unknown/unsupported primitive <${tagName}>.`, loc(filePath, opening));
|
|
225
|
+
}
|
|
226
|
+
const jsonType = TYPE_MAP[tagName];
|
|
227
|
+
const attrs = attrsToProps(opening.attributes, constEnv, filePath, validationFunctionSource);
|
|
228
|
+
const id = requireString(attrs, 'id', filePath, opening);
|
|
229
|
+
const key = requireString(attrs, 'key', filePath, opening);
|
|
230
|
+
const tags = (attrs.tags ?? []);
|
|
231
|
+
if (!Array.isArray(tags) || tags.some((t) => typeof t !== 'string')) {
|
|
232
|
+
throw new CompileError(`Prop "tags" must be an array of strings.`, loc(filePath, opening));
|
|
233
|
+
}
|
|
234
|
+
delete attrs.id;
|
|
235
|
+
delete attrs.key;
|
|
236
|
+
delete attrs.tags;
|
|
237
|
+
// For CustomHTML, convert all children (including text) to HTML string
|
|
238
|
+
if (tagName === 'CustomHTML') {
|
|
239
|
+
// Check if there are any children (including text nodes and fragments)
|
|
240
|
+
const hasChildren = el.children.some((c) => c.type === 'JSXElement' ||
|
|
241
|
+
c.type === 'JSXFragment' ||
|
|
242
|
+
(c.type === 'JSXText' && c.value.trim().length > 0) ||
|
|
243
|
+
c.type === 'JSXExpressionContainer');
|
|
244
|
+
if (hasChildren) {
|
|
245
|
+
// Convert JSX children to HTML string
|
|
246
|
+
const htmlString = jsxChildrenToHTML(el.children, constEnv, filePath);
|
|
247
|
+
attrs.text = htmlString;
|
|
248
|
+
}
|
|
249
|
+
// Return with empty children array since we've converted them to HTML
|
|
250
|
+
return {
|
|
251
|
+
tag: tagName,
|
|
252
|
+
type: jsonType,
|
|
253
|
+
id,
|
|
254
|
+
key,
|
|
255
|
+
tags,
|
|
256
|
+
props: attrs,
|
|
257
|
+
children: [],
|
|
258
|
+
loc: loc(filePath, opening),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
// children JSXElements only (ignore whitespace text nodes)
|
|
262
|
+
const children = el.children
|
|
263
|
+
.filter((c) => c.type === 'JSXElement')
|
|
264
|
+
.map((c) => jsxToTree(c, constEnv, filePath, validationFunctionSource));
|
|
265
|
+
// v1 rule: only Container can have children (CustomHTML handled above)
|
|
266
|
+
if (children.length > 0 && tagName !== 'Container') {
|
|
267
|
+
throw new CompileError(`<${tagName}> cannot have children. Only <Container> and <CustomHTML> may contain children.`, loc(filePath, opening));
|
|
268
|
+
}
|
|
269
|
+
// enforce OptionSelector uses buttons
|
|
270
|
+
if (tagName === 'OptionSelector' && 'options' in attrs) {
|
|
271
|
+
throw new CompileError(`OptionSelector does not support prop "options". Use "buttons".`, loc(filePath, opening));
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
tag: tagName,
|
|
275
|
+
type: jsonType,
|
|
276
|
+
id,
|
|
277
|
+
key,
|
|
278
|
+
tags,
|
|
279
|
+
props: attrs,
|
|
280
|
+
children,
|
|
281
|
+
loc: loc(filePath, opening),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function flatten(node, parentId, out, pageKey, filePath) {
|
|
285
|
+
const base = {
|
|
286
|
+
type: node.type,
|
|
287
|
+
id: node.id,
|
|
288
|
+
key: node.key,
|
|
289
|
+
tags: node.tags,
|
|
290
|
+
};
|
|
291
|
+
if (parentId)
|
|
292
|
+
base.parent_id = parentId;
|
|
293
|
+
const defaults = DEFAULTS_BY_TYPE[node.type] ?? {};
|
|
294
|
+
// Extract extra props from props={...} (round-trip from reverse compiler) and merge into component
|
|
295
|
+
// Extract languages prop separately to expand into lang-- properties
|
|
296
|
+
const { props: extraProps, languages, ...restProps } = node.props;
|
|
297
|
+
const mergedExtra = extraProps != null && typeof extraProps === 'object' && !Array.isArray(extraProps)
|
|
298
|
+
? extraProps
|
|
299
|
+
: {};
|
|
300
|
+
// Expand languages prop into flat lang--{langCode}--{attrKey} properties
|
|
301
|
+
const langProps = {};
|
|
302
|
+
if (languages != null && typeof languages === 'object' && !Array.isArray(languages)) {
|
|
303
|
+
for (const [langCode, attrs] of Object.entries(languages)) {
|
|
304
|
+
if (attrs != null && typeof attrs === 'object' && !Array.isArray(attrs)) {
|
|
305
|
+
for (const [attrKey, value] of Object.entries(attrs)) {
|
|
306
|
+
langProps[`lang--${langCode}--${attrKey}`] = value;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const json = {
|
|
312
|
+
...base,
|
|
313
|
+
...defaults,
|
|
314
|
+
...restProps,
|
|
315
|
+
...mergedExtra,
|
|
316
|
+
...langProps,
|
|
317
|
+
};
|
|
318
|
+
// Helper to create errors with component context
|
|
319
|
+
const createError = (message, loc) => {
|
|
320
|
+
return new CompileError(message, {
|
|
321
|
+
...loc,
|
|
322
|
+
pageKey,
|
|
323
|
+
componentId: node.id,
|
|
324
|
+
componentKey: node.key,
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
// Normalize OptionSelector buttons: generate IDs + defaults
|
|
328
|
+
if (json.type === 'OptionSelector') {
|
|
329
|
+
const buttons = json.buttons;
|
|
330
|
+
if (buttons != null) {
|
|
331
|
+
if (!Array.isArray(buttons)) {
|
|
332
|
+
throw createError(`OptionSelector requires "buttons" as an array. Got: ${typeof buttons}`, node.loc);
|
|
333
|
+
}
|
|
334
|
+
json.buttons = buttons.map((b) => {
|
|
335
|
+
if (!b || typeof b !== 'object') {
|
|
336
|
+
throw createError(`Each button must be an object.`, node.loc);
|
|
337
|
+
}
|
|
338
|
+
// key can be string or null, text is optional
|
|
339
|
+
if (b.key !== null && typeof b.key !== 'string') {
|
|
340
|
+
throw createError(`Each button requires "key" to be a string or null. Got: ${typeof b.key}`, node.loc);
|
|
341
|
+
}
|
|
342
|
+
if (b.text != null && typeof b.text !== 'string') {
|
|
343
|
+
throw createError(`Button "text" must be a string if provided. Got: ${typeof b.text}`, node.loc);
|
|
344
|
+
}
|
|
345
|
+
// Preserve all OptionSelectorButton properties from the input
|
|
346
|
+
// Required properties
|
|
347
|
+
const result = {
|
|
348
|
+
id: typeof b.id === 'string' ? b.id : `option_${b.key}`,
|
|
349
|
+
key: b.key,
|
|
350
|
+
};
|
|
351
|
+
// Optional properties - only include if present
|
|
352
|
+
if (b.text != null)
|
|
353
|
+
result.text = b.text;
|
|
354
|
+
if (b.description != null)
|
|
355
|
+
result.description = b.description;
|
|
356
|
+
// triggerEvent defaults to 'no-action' if not provided
|
|
357
|
+
result.triggerEvent = typeof b.triggerEvent === 'string' ? b.triggerEvent : 'no-action';
|
|
358
|
+
if (b.conditions != null)
|
|
359
|
+
result.conditions = b.conditions;
|
|
360
|
+
if (b.openUrlInNewTab != null)
|
|
361
|
+
result.openUrlInNewTab = b.openUrlInNewTab;
|
|
362
|
+
if (b.url != null)
|
|
363
|
+
result.url = b.url;
|
|
364
|
+
if (b.single_select != null)
|
|
365
|
+
result.single_select = b.single_select;
|
|
366
|
+
if (b.icon != null)
|
|
367
|
+
result.icon = b.icon;
|
|
368
|
+
if (b.emojiIcon != null)
|
|
369
|
+
result.emojiIcon = b.emojiIcon;
|
|
370
|
+
if (b.imageUrl != null)
|
|
371
|
+
result.imageUrl = b.imageUrl;
|
|
372
|
+
if (b.imageAltText != null)
|
|
373
|
+
result.imageAltText = b.imageAltText;
|
|
374
|
+
if (b.hide != null)
|
|
375
|
+
result.hide = b.hide;
|
|
376
|
+
if (b.is_repeatable_button != null)
|
|
377
|
+
result.is_repeatable_button = b.is_repeatable_button;
|
|
378
|
+
// Expand languages prop into flat lang--{langCode}--{attrKey} properties
|
|
379
|
+
if (b.languages != null && typeof b.languages === 'object' && !Array.isArray(b.languages)) {
|
|
380
|
+
for (const [langCode, attrs] of Object.entries(b.languages)) {
|
|
381
|
+
if (attrs != null && typeof attrs === 'object' && !Array.isArray(attrs)) {
|
|
382
|
+
for (const [attrKey, value] of Object.entries(attrs)) {
|
|
383
|
+
result[`lang--${langCode}--${attrKey}`] = value;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return result;
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
// If buttons is null/undefined, leave it as is (optional prop)
|
|
392
|
+
}
|
|
393
|
+
out.push(json);
|
|
394
|
+
for (const child of node.children) {
|
|
395
|
+
flatten(child, node.id, out, pageKey, filePath);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Normalize validation function source for embeddable.json: use name "validate" and strip TS (param types).
|
|
400
|
+
*/
|
|
401
|
+
function normalizeValidationFunctionSource(source) {
|
|
402
|
+
// 1. Rename function to "validate"
|
|
403
|
+
let out = source.replace(/function\s+\w+\s*\(/, 'function validate(');
|
|
404
|
+
// 2. Strip parameter type annotations in the first parameter list (e.g. value: string -> value)
|
|
405
|
+
const openParen = out.indexOf('(', out.indexOf('function'));
|
|
406
|
+
if (openParen !== -1) {
|
|
407
|
+
let depth = 1;
|
|
408
|
+
let i = openParen + 1;
|
|
409
|
+
while (depth > 0 && i < out.length) {
|
|
410
|
+
if (out[i] === '(')
|
|
411
|
+
depth++;
|
|
412
|
+
else if (out[i] === ')')
|
|
413
|
+
depth--;
|
|
414
|
+
i++;
|
|
415
|
+
}
|
|
416
|
+
const params = out.slice(openParen, i);
|
|
417
|
+
const paramsNoTypes = params.replace(/:\s*[^,)]+/g, '');
|
|
418
|
+
out = out.slice(0, openParen) + paramsNoTypes + out.slice(i);
|
|
419
|
+
}
|
|
420
|
+
return out;
|
|
421
|
+
}
|
|
422
|
+
function attrsToProps(attrs, constEnv, filePath, validationFunctionSource) {
|
|
423
|
+
const out = {};
|
|
424
|
+
for (const a of attrs) {
|
|
425
|
+
if (a.type === 'JSXSpreadAttribute') {
|
|
426
|
+
throw new CompileError('JSX spread props are not supported.', loc(filePath, a));
|
|
427
|
+
}
|
|
428
|
+
const name = a.name.type === 'JSXIdentifier' ? a.name.name : `${a.name.namespace.name}:${a.name.name.name}`;
|
|
429
|
+
if (!a.value) {
|
|
430
|
+
// <X disabled />
|
|
431
|
+
out[name] = true;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (a.value.type === 'StringLiteral') {
|
|
435
|
+
out[name] = a.value.value;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (a.value.type === 'JSXExpressionContainer') {
|
|
439
|
+
const expr = a.value.expression;
|
|
440
|
+
// custom_validation_function: resolve to source and normalize to "validate" + JS for embeddable.json
|
|
441
|
+
if (name === 'custom_validation_function' &&
|
|
442
|
+
expr.type === 'Identifier' &&
|
|
443
|
+
validationFunctionSource?.has(expr.name)) {
|
|
444
|
+
out[name] = normalizeValidationFunctionSource(validationFunctionSource.get(expr.name));
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
out[name] = evalStatic(expr, constEnv, filePath);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
throw new CompileError(`Unsupported JSX attribute value for "${name}".`, loc(filePath, a));
|
|
451
|
+
}
|
|
452
|
+
// When validation_formula is not "custom", custom_validation_function is ignored and omitted (removed on save).
|
|
453
|
+
if (out.validation_formula != null && out.validation_formula !== 'custom' && 'custom_validation_function' in out) {
|
|
454
|
+
delete out.custom_validation_function;
|
|
455
|
+
}
|
|
456
|
+
return out;
|
|
457
|
+
}
|
|
458
|
+
function getJsxTagName(n) {
|
|
459
|
+
if (n.type === 'JSXIdentifier')
|
|
460
|
+
return n.name;
|
|
461
|
+
throw new CompileError('Only simple JSX identifiers are supported (no member expressions).');
|
|
462
|
+
}
|
|
463
|
+
function requireString(obj, prop, filePath, node) {
|
|
464
|
+
const v = obj[prop];
|
|
465
|
+
if (typeof v !== 'string' || v.length === 0) {
|
|
466
|
+
throw new CompileError(`Missing required string prop "${prop}".`, loc(filePath, node));
|
|
467
|
+
}
|
|
468
|
+
return v;
|
|
469
|
+
}
|
|
470
|
+
function loc(file, node) {
|
|
471
|
+
return { file, line: node.loc?.start?.line, column: node.loc?.start?.column };
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Converts JSX children to HTML string.
|
|
475
|
+
* Handles JSXElements, JSXText, JSXExpressions, and JSXFragments.
|
|
476
|
+
* @param preserveWhitespace If true, preserves all whitespace (for <style> and <script> tags)
|
|
477
|
+
*/
|
|
478
|
+
function jsxChildrenToHTML(children, constEnv, filePath, preserveWhitespace = false) {
|
|
479
|
+
let html = '';
|
|
480
|
+
for (const child of children) {
|
|
481
|
+
if (child.type === 'JSXElement') {
|
|
482
|
+
html += jsxElementToHTML(child, constEnv, filePath);
|
|
483
|
+
}
|
|
484
|
+
else if (child.type === 'JSXText') {
|
|
485
|
+
const text = child.value;
|
|
486
|
+
// For <style> and <script> tags, preserve all whitespace
|
|
487
|
+
// For other tags, skip whitespace-only text (it's likely indentation)
|
|
488
|
+
if (preserveWhitespace || text.trim().length > 0) {
|
|
489
|
+
html += escapeHTML(text);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else if (child.type === 'JSXExpressionContainer') {
|
|
493
|
+
// Try to evaluate static expressions
|
|
494
|
+
try {
|
|
495
|
+
const value = evalStatic(child.expression, constEnv, filePath);
|
|
496
|
+
if (typeof value === 'string') {
|
|
497
|
+
html += escapeHTML(value);
|
|
498
|
+
}
|
|
499
|
+
else if (value != null) {
|
|
500
|
+
html += escapeHTML(String(value));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
throw new CompileError('CustomHTML children must be static. Dynamic expressions are not supported.', loc(filePath, child));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
else if (child.type === 'JSXFragment') {
|
|
508
|
+
// Handle fragments by recursively processing their children
|
|
509
|
+
html += jsxChildrenToHTML(child.children, constEnv, filePath, preserveWhitespace);
|
|
510
|
+
}
|
|
511
|
+
// Ignore JSXSpreadChild and other unsupported types
|
|
512
|
+
}
|
|
513
|
+
return html;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* HTML5 void elements - these are the only elements that can be self-closing in HTML.
|
|
517
|
+
* For all other elements, we must emit explicit closing tags even if they're self-closing in JSX.
|
|
518
|
+
* See: https://html.spec.whatwg.org/multipage/syntax.html#void-elements
|
|
519
|
+
*/
|
|
520
|
+
const HTML_VOID_ELEMENTS = new Set([
|
|
521
|
+
'area',
|
|
522
|
+
'base',
|
|
523
|
+
'br',
|
|
524
|
+
'col',
|
|
525
|
+
'embed',
|
|
526
|
+
'hr',
|
|
527
|
+
'img',
|
|
528
|
+
'input',
|
|
529
|
+
'link',
|
|
530
|
+
'meta',
|
|
531
|
+
'param',
|
|
532
|
+
'source',
|
|
533
|
+
'track',
|
|
534
|
+
'wbr',
|
|
535
|
+
]);
|
|
536
|
+
/**
|
|
537
|
+
* Map React/JSX attribute names to HTML attribute names so serialized HTML
|
|
538
|
+
* uses valid attributes (e.g. class for CSS, not className).
|
|
539
|
+
*/
|
|
540
|
+
function jsxAttrNameToHTMLAttrName(jsxName) {
|
|
541
|
+
switch (jsxName) {
|
|
542
|
+
case 'className':
|
|
543
|
+
return 'class';
|
|
544
|
+
case 'htmlFor':
|
|
545
|
+
return 'for';
|
|
546
|
+
case 'tabIndex':
|
|
547
|
+
return 'tabindex';
|
|
548
|
+
default:
|
|
549
|
+
return jsxName;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Converts a JSX element to HTML string.
|
|
554
|
+
*/
|
|
555
|
+
function jsxElementToHTML(el, constEnv, filePath) {
|
|
556
|
+
const opening = el.openingElement;
|
|
557
|
+
const tagName = getJsxTagName(opening.name);
|
|
558
|
+
// Build attributes string (use HTML attribute names so class selectors work)
|
|
559
|
+
const attrs = [];
|
|
560
|
+
for (const attr of opening.attributes) {
|
|
561
|
+
if (attr.type === 'JSXAttribute') {
|
|
562
|
+
const name = attr.name.type === 'JSXIdentifier'
|
|
563
|
+
? attr.name.name
|
|
564
|
+
: `${attr.name.namespace.name}:${attr.name.name.name}`;
|
|
565
|
+
const htmlName = jsxAttrNameToHTMLAttrName(name);
|
|
566
|
+
if (!attr.value) {
|
|
567
|
+
// Boolean attribute
|
|
568
|
+
attrs.push(htmlName);
|
|
569
|
+
}
|
|
570
|
+
else if (attr.value.type === 'StringLiteral') {
|
|
571
|
+
attrs.push(`${htmlName}="${escapeHTMLAttribute(attr.value.value)}"`);
|
|
572
|
+
}
|
|
573
|
+
else if (attr.value.type === 'JSXExpressionContainer') {
|
|
574
|
+
try {
|
|
575
|
+
const value = evalStatic(attr.value.expression, constEnv, filePath);
|
|
576
|
+
if (name === 'style' && typeof value === 'object' && value !== null) {
|
|
577
|
+
// Convert style object to CSS string
|
|
578
|
+
const cssString = styleObjectToCSS(value);
|
|
579
|
+
attrs.push(`${htmlName}="${escapeHTMLAttribute(cssString)}"`);
|
|
580
|
+
}
|
|
581
|
+
else if (typeof value === 'string') {
|
|
582
|
+
attrs.push(`${htmlName}="${escapeHTMLAttribute(value)}"`);
|
|
583
|
+
}
|
|
584
|
+
else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
585
|
+
attrs.push(`${htmlName}="${String(value)}"`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
throw new CompileError('CustomHTML attribute values must be static. Dynamic expressions are not supported.', loc(filePath, attr));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const attrsStr = attrs.length > 0 ? ' ' + attrs.join(' ') : '';
|
|
595
|
+
// For <style> and <script> tags, preserve all whitespace to maintain CSS/JS formatting
|
|
596
|
+
const isRawTextTag = tagName.toLowerCase() === 'style' || tagName.toLowerCase() === 'script';
|
|
597
|
+
const childrenHTML = jsxChildrenToHTML(el.children, constEnv, filePath, isRawTextTag);
|
|
598
|
+
if (el.closingElement) {
|
|
599
|
+
return `<${tagName}${attrsStr}>${childrenHTML}</${tagName}>`;
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
// Self-closing in JSX - check if it's an HTML void element
|
|
603
|
+
const isVoidElement = HTML_VOID_ELEMENTS.has(tagName.toLowerCase());
|
|
604
|
+
if (isVoidElement) {
|
|
605
|
+
// Void elements can be self-closing in HTML
|
|
606
|
+
return `<${tagName}${attrsStr} />`;
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
// Non-void elements MUST have explicit closing tags in HTML
|
|
610
|
+
// Otherwise browsers interpret <div /> as <div> (unclosed)
|
|
611
|
+
return `<${tagName}${attrsStr}></${tagName}>`;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Escapes HTML entities in text content.
|
|
617
|
+
*/
|
|
618
|
+
function escapeHTML(text) {
|
|
619
|
+
return text
|
|
620
|
+
.replace(/&/g, '&')
|
|
621
|
+
.replace(/</g, '<')
|
|
622
|
+
.replace(/>/g, '>')
|
|
623
|
+
.replace(/"/g, '"')
|
|
624
|
+
.replace(/'/g, ''');
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Escapes HTML entities in attribute values.
|
|
628
|
+
*/
|
|
629
|
+
function escapeHTMLAttribute(text) {
|
|
630
|
+
return text
|
|
631
|
+
.replace(/&/g, '&')
|
|
632
|
+
.replace(/</g, '<')
|
|
633
|
+
.replace(/>/g, '>')
|
|
634
|
+
.replace(/"/g, '"');
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Converts a JSX style object to CSS string.
|
|
638
|
+
* Example: { margin: 0, padding: 0, color: "#171717" } -> "margin: 0; padding: 0; color: #171717;"
|
|
639
|
+
*/
|
|
640
|
+
function styleObjectToCSS(styleObj) {
|
|
641
|
+
const cssParts = [];
|
|
642
|
+
for (const [key, value] of Object.entries(styleObj)) {
|
|
643
|
+
// Preserve CSS custom properties (e.g., --ta, --my-variable) as-is
|
|
644
|
+
if (key.startsWith('--')) {
|
|
645
|
+
cssParts.push(`${key}: ${value};`);
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
// Convert camelCase to kebab-case (e.g., lineHeight -> line-height)
|
|
649
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
650
|
+
cssParts.push(`${cssKey}: ${value};`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return cssParts.join(' ');
|
|
654
|
+
}
|
|
@@ -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"}
|