@deneb-ui/cli 2.0.88 → 2.0.90

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.
Files changed (55) hide show
  1. package/bin/index.js +31 -5
  2. package/package.json +2 -2
  3. package/src/arc/__tests__/acceptance-gates.test.cjs +62 -0
  4. package/src/arc/__tests__/arc.test.cjs +922 -0
  5. package/src/arc/__tests__/canonical.test.cjs +57 -0
  6. package/src/arc/__tests__/classification.test.cjs +54 -0
  7. package/src/arc/__tests__/corpus.test.cjs +144 -0
  8. package/src/arc/__tests__/explain-blocked.test.cjs +192 -0
  9. package/src/arc/__tests__/fuzz.test.cjs +106 -0
  10. package/src/arc/__tests__/interaction.test.cjs +85 -0
  11. package/src/arc/__tests__/visual-regression.test.cjs +99 -0
  12. package/src/arc/acceptance-gates.cjs +119 -0
  13. package/src/arc/canonical-paths.cjs +142 -23
  14. package/src/arc/corpus-verifier.cjs +295 -0
  15. package/src/arc/data-classification.cjs +182 -0
  16. package/src/arc/data-flow.cjs +62 -2
  17. package/src/arc/explain-blocked.cjs +388 -0
  18. package/src/arc/fuzz-engine.cjs +604 -0
  19. package/src/arc/index.cjs +148 -2
  20. package/src/arc/interaction-verifier.cjs +185 -0
  21. package/src/arc/ir-builder.cjs +170 -12
  22. package/src/arc/planner.cjs +16 -1
  23. package/src/arc/residual.cjs +22 -0
  24. package/src/arc/rsc-boundary.cjs +105 -4
  25. package/src/arc/runtime-validator.cjs +65 -0
  26. package/src/arc/semantic.cjs +12 -1
  27. package/src/arc/transformer.cjs +14 -2745
  28. package/src/arc/transforms/assets/tailwind-bg.cjs +38 -0
  29. package/src/arc/transforms/collections/list.cjs +474 -0
  30. package/src/arc/transforms/components/child-card.cjs +268 -0
  31. package/src/arc/transforms/components/compound-content.cjs +49 -0
  32. package/src/arc/transforms/components/reusable-component.cjs +173 -0
  33. package/src/arc/transforms/element-transform.cjs +192 -0
  34. package/src/arc/transforms/healing/conditionals.cjs +295 -0
  35. package/src/arc/transforms/healing/sanitizer.cjs +401 -0
  36. package/src/arc/transforms/index.cjs +195 -0
  37. package/src/arc/transforms/primitives/action.cjs +76 -0
  38. package/src/arc/transforms/primitives/text.cjs +65 -0
  39. package/src/arc/transforms/routing/page-key.cjs +84 -0
  40. package/src/arc/transforms/routing/shared-layout.cjs +294 -0
  41. package/src/arc/transforms/runtime/provider.cjs +230 -0
  42. package/src/arc/transforms/transform-context.cjs +118 -0
  43. package/src/arc/types/acceptance.d.ts +50 -0
  44. package/src/arc/types/canonical.d.ts +50 -0
  45. package/src/arc/types/classification.d.ts +42 -0
  46. package/src/arc/types/corpus.d.ts +52 -0
  47. package/src/arc/types/explain-blocked.d.ts +41 -0
  48. package/src/arc/types/fuzz.d.ts +74 -0
  49. package/src/arc/types/interaction.d.ts +43 -0
  50. package/src/arc/types/ir.d.ts +114 -0
  51. package/src/arc/types/rsc.d.ts +39 -0
  52. package/src/arc/types/visual-regression.d.ts +49 -0
  53. package/src/arc/visual-regression.cjs +195 -0
  54. package/src/arc/workspace.cjs +233 -0
  55. package/src/common/template-visual-edit-contract.ts +4 -1
@@ -0,0 +1,268 @@
1
+ 'use strict';
2
+
3
+ const recast = require('recast');
4
+ const {
5
+ getJsxName,
6
+ findJsxAttribute,
7
+ hasJsxAttribute,
8
+ jsxTemplatePathAttr,
9
+ b,
10
+ } = require('../../ast.cjs');
11
+
12
+ function instrumentChildCardComponent(ast, transform, isTypeScript = false) {
13
+ const componentName = transform.componentName;
14
+ const listPath = transform.listField || transform.listPath || '';
15
+ if (!componentName) return false;
16
+
17
+ let instrumented = false;
18
+
19
+ function updateTsInterface(rootAst, interfaceName) {
20
+ recast.types.visit(rootAst, {
21
+ visitTSInterfaceDeclaration(pathNode) {
22
+ if (pathNode.node.id?.name === interfaceName) {
23
+ const body = pathNode.node.body?.body || [];
24
+ if (!body.some((prop) => prop.key?.name === 'previewItemPath')) {
25
+ const sig1 = b.tsPropertySignature(b.identifier('previewItemPath'), b.tsTypeAnnotation(b.tsStringKeyword()));
26
+ sig1.optional = true;
27
+ body.push(sig1);
28
+ instrumented = true;
29
+ }
30
+ if (!body.some((prop) => prop.key?.name === 'index')) {
31
+ const sig2 = b.tsPropertySignature(b.identifier('index'), b.tsTypeAnnotation(b.tsNumberKeyword()));
32
+ sig2.optional = true;
33
+ body.push(sig2);
34
+ instrumented = true;
35
+ }
36
+ }
37
+ this.traverse(pathNode);
38
+ },
39
+ visitTSTypeAliasDeclaration(pathNode) {
40
+ if (pathNode.node.id?.name === interfaceName && pathNode.node.typeAnnotation?.type === 'TSTypeLiteral') {
41
+ const members = pathNode.node.typeAnnotation.members || [];
42
+ if (!members.some((m) => m.key?.name === 'previewItemPath')) {
43
+ const sig1 = b.tsPropertySignature(b.identifier('previewItemPath'), b.tsTypeAnnotation(b.tsStringKeyword()));
44
+ sig1.optional = true;
45
+ members.push(sig1);
46
+ instrumented = true;
47
+ }
48
+ if (!members.some((m) => m.key?.name === 'index')) {
49
+ const sig2 = b.tsPropertySignature(b.identifier('index'), b.tsTypeAnnotation(b.tsNumberKeyword()));
50
+ sig2.optional = true;
51
+ members.push(sig2);
52
+ instrumented = true;
53
+ }
54
+ }
55
+ this.traverse(pathNode);
56
+ },
57
+ });
58
+ }
59
+
60
+ function instrumentFunctionProps(fnNode, isTs) {
61
+ let changed = false;
62
+ let params = fnNode.params || [];
63
+ if (!params.length) {
64
+ fnNode.params = [
65
+ b.objectPattern([
66
+ b.property('init', b.identifier('previewItemPath'), b.identifier('previewItemPath')),
67
+ b.property('init', b.identifier('index'), b.identifier('index')),
68
+ ]),
69
+ ];
70
+ changed = true;
71
+ } else {
72
+ const firstParam = params[0];
73
+ if (firstParam.type === 'ObjectPattern') {
74
+ const hasPreviewPath = firstParam.properties.some(
75
+ (p) => p.key?.name === 'previewItemPath' || p.argument?.name === 'previewItemPath'
76
+ );
77
+ if (!hasPreviewPath) {
78
+ const prop = b.property('init', b.identifier('previewItemPath'), b.identifier('previewItemPath'));
79
+ prop.shorthand = true;
80
+ firstParam.properties.push(prop);
81
+ changed = true;
82
+ }
83
+ const hasIndex = firstParam.properties.some(
84
+ (p) => p.key?.name === 'index' || p.argument?.name === 'index'
85
+ );
86
+ if (!hasIndex) {
87
+ const prop = b.property('init', b.identifier('index'), b.identifier('index'));
88
+ prop.shorthand = true;
89
+ firstParam.properties.push(prop);
90
+ changed = true;
91
+ }
92
+
93
+ if (isTs && firstParam.typeAnnotation?.typeAnnotation) {
94
+ const typeAnn = firstParam.typeAnnotation.typeAnnotation;
95
+ if (typeAnn.type === 'TSTypeReference' && typeAnn.typeName?.name) {
96
+ updateTsInterface(ast, typeAnn.typeName.name);
97
+ }
98
+ }
99
+ } else if (firstParam.type === 'Identifier') {
100
+ const paramName = firstParam.name;
101
+ if (isTs && firstParam.typeAnnotation?.typeAnnotation) {
102
+ const typeAnn = firstParam.typeAnnotation.typeAnnotation;
103
+ if (typeAnn.type === 'TSTypeReference' && typeAnn.typeName?.name) {
104
+ updateTsInterface(ast, typeAnn.typeName.name);
105
+ }
106
+ }
107
+ recast.types.visit(fnNode.body || fnNode, {
108
+ visitVariableDeclarator(vPath) {
109
+ if (vPath.node.id?.type === 'ObjectPattern' && vPath.node.init?.name === paramName) {
110
+ const propsList = vPath.node.id.properties;
111
+ if (!propsList.some((p) => p.key?.name === 'previewItemPath')) {
112
+ const prop = b.property('init', b.identifier('previewItemPath'), b.identifier('previewItemPath'));
113
+ prop.shorthand = true;
114
+ propsList.push(prop);
115
+ changed = true;
116
+ }
117
+ if (!propsList.some((p) => p.key?.name === 'index')) {
118
+ const prop = b.property('init', b.identifier('index'), b.identifier('index'));
119
+ prop.shorthand = true;
120
+ propsList.push(prop);
121
+ changed = true;
122
+ }
123
+ }
124
+ this.traverse(vPath);
125
+ },
126
+ });
127
+ }
128
+ }
129
+ return changed;
130
+ }
131
+
132
+ recast.types.visit(ast, {
133
+ visitFunctionDeclaration(pathNode) {
134
+ if (pathNode.node.id?.name === componentName) {
135
+ if (instrumentFunctionProps(pathNode.node, isTypeScript)) instrumented = true;
136
+ }
137
+ this.traverse(pathNode);
138
+ },
139
+ visitVariableDeclarator(pathNode) {
140
+ if (pathNode.node.id?.name === componentName) {
141
+ const init = pathNode.node.init;
142
+ if (init && (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression')) {
143
+ if (instrumentFunctionProps(init, isTypeScript)) instrumented = true;
144
+ }
145
+ }
146
+ this.traverse(pathNode);
147
+ },
148
+ visitExportDefaultDeclaration(pathNode) {
149
+ const decl = pathNode.node.declaration;
150
+ if (decl && (decl.type === 'FunctionDeclaration' || decl.type === 'ArrowFunctionExpression' || decl.type === 'FunctionExpression')) {
151
+ const fnName = decl.id?.name;
152
+ if (!fnName || fnName === componentName) {
153
+ if (instrumentFunctionProps(decl, isTypeScript)) instrumented = true;
154
+ }
155
+ }
156
+ this.traverse(pathNode);
157
+ },
158
+ });
159
+
160
+ let rootElement = null;
161
+ recast.types.visit(ast, {
162
+ visitReturnStatement(pathNode) {
163
+ if (!rootElement && pathNode.node.argument) {
164
+ let arg = pathNode.node.argument;
165
+ if (arg.type === 'ParenthesizedExpression') arg = arg.expression;
166
+ if (arg.type === 'JSXElement') {
167
+ rootElement = arg;
168
+ }
169
+ }
170
+ this.traverse(pathNode);
171
+ },
172
+ });
173
+
174
+ if (rootElement) {
175
+ if (!hasJsxAttribute(rootElement, 'data-preview-item-path')) {
176
+ if (listPath) {
177
+ rootElement.openingElement.attributes.push(
178
+ jsxTemplatePathAttr('data-preview-item-path', listPath, 'index')
179
+ );
180
+ } else {
181
+ rootElement.openingElement.attributes.push(
182
+ b.jsxAttribute(
183
+ b.jsxIdentifier('data-preview-item-path'),
184
+ b.jsxExpressionContainer(b.identifier('previewItemPath'))
185
+ )
186
+ );
187
+ }
188
+ instrumented = true;
189
+ }
190
+ }
191
+
192
+ recast.types.visit(ast, {
193
+ visitJSXElement(pathNode) {
194
+ const node = pathNode.node;
195
+ const tagName = getJsxName(node);
196
+
197
+ const srcAttr = findJsxAttribute(node, 'src');
198
+ if (srcAttr && (tagName === 'img' || tagName === 'Image') && !hasJsxAttribute(node, 'data-preview-field-path')) {
199
+ const rawCode = recast.print(srcAttr.value).code;
200
+ if (/image|photo|avatar|thumb|img|src/i.test(rawCode)) {
201
+ const imgField = (transform.itemFields || []).find((f) => f.type === 'image' || /image|photo/i.test(f.key))?.key || 'image';
202
+ if (listPath) {
203
+ node.openingElement.attributes.push(
204
+ jsxTemplatePathAttr('data-preview-field-path', listPath, 'index', `.${imgField}`)
205
+ );
206
+ } else {
207
+ node.openingElement.attributes.push(
208
+ b.jsxAttribute(
209
+ b.jsxIdentifier('data-preview-field-path'),
210
+ b.jsxExpressionContainer(
211
+ b.templateLiteral(
212
+ [
213
+ b.templateElement({ raw: '', cooked: '' }, false),
214
+ b.templateElement({ raw: `.${imgField}`, cooked: `.${imgField}` }, true),
215
+ ],
216
+ [b.identifier('previewItemPath')]
217
+ )
218
+ )
219
+ )
220
+ );
221
+ }
222
+ instrumented = true;
223
+ }
224
+ }
225
+
226
+ for (const child of node.children || []) {
227
+ if (child.type === 'JSXExpressionContainer' && child.expression) {
228
+ const exprCode = recast.print(child.expression).code;
229
+ const candidateField = (transform.itemFields || []).find(
230
+ (f) => exprCode.includes(`.${f.key}`) || exprCode === f.key
231
+ );
232
+ if (candidateField && !hasJsxAttribute(node, 'data-preview-field-path')) {
233
+ const fieldSuffix = `.${candidateField.key}`;
234
+ if (listPath) {
235
+ node.openingElement.attributes.push(
236
+ jsxTemplatePathAttr('data-preview-field-path', listPath, 'index', fieldSuffix)
237
+ );
238
+ } else {
239
+ node.openingElement.attributes.push(
240
+ b.jsxAttribute(
241
+ b.jsxIdentifier('data-preview-field-path'),
242
+ b.jsxExpressionContainer(
243
+ b.templateLiteral(
244
+ [
245
+ b.templateElement({ raw: '', cooked: '' }, false),
246
+ b.templateElement({ raw: fieldSuffix, cooked: fieldSuffix }, true),
247
+ ],
248
+ [b.identifier('previewItemPath')]
249
+ )
250
+ )
251
+ )
252
+ );
253
+ }
254
+ instrumented = true;
255
+ break;
256
+ }
257
+ }
258
+ }
259
+ this.traverse(pathNode);
260
+ },
261
+ });
262
+
263
+ return instrumented;
264
+ }
265
+
266
+ module.exports = {
267
+ instrumentChildCardComponent,
268
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ ensureStyleAttrs,
5
+ siteDataBinding,
6
+ b,
7
+ } = require('../../ast.cjs');
8
+ const { ensurePreviewPath } = require('../transform-context.cjs');
9
+
10
+ function bindButtonWithIcon(node, transform) {
11
+ ensurePreviewPath(node, transform.field);
12
+ ensureStyleAttrs(node, transform.field, 'button');
13
+ for (let i = 0; i < (node.children || []).length; i++) {
14
+ const child = node.children[i];
15
+ if (child.type === 'JSXText' && child.value.trim().length > 0) {
16
+ node.children[i] = b.jsxExpressionContainer(
17
+ siteDataBinding(transform.field.split('.'), child.value.trim(), 'text')
18
+ );
19
+ }
20
+ }
21
+ }
22
+
23
+ function bindHighlightedHeading(node, transform) {
24
+ ensurePreviewPath(node, transform.field);
25
+ ensureStyleAttrs(node, transform.field, 'text');
26
+ for (let i = 0; i < (node.children || []).length; i++) {
27
+ const child = node.children[i];
28
+ if (child.type === 'JSXText' && child.value.trim().length > 0) {
29
+ node.children[i] = b.jsxExpressionContainer(
30
+ siteDataBinding(transform.field.split('.'), child.value.trim(), 'text')
31
+ );
32
+ } else if (child.type === 'JSXElement') {
33
+ const highlightField = `${transform.field}Highlight`;
34
+ ensurePreviewPath(child, highlightField);
35
+ for (let j = 0; j < (child.children || []).length; j++) {
36
+ if (child.children[j].type === 'JSXText' && child.children[j].value.trim().length > 0) {
37
+ child.children[j] = b.jsxExpressionContainer(
38
+ siteDataBinding(highlightField.split('.'), child.children[j].value.trim(), 'text')
39
+ );
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ module.exports = {
47
+ bindButtonWithIcon,
48
+ bindHighlightedHeading,
49
+ };
@@ -0,0 +1,173 @@
1
+ 'use strict';
2
+
3
+ const recast = require('recast');
4
+ const {
5
+ hasJsxAttribute,
6
+ b,
7
+ } = require('../../ast.cjs');
8
+
9
+ function instrumentReusableComponent(ast, transform, isTypeScript = false) {
10
+ const componentName = transform.componentName;
11
+ const propTransforms = transform.propTransforms || {};
12
+ const propNames = Object.keys(propTransforms);
13
+ if (!propNames.length) return false;
14
+
15
+ let instrumented = false;
16
+
17
+ function updateTsInterface(rootAst, interfaceName) {
18
+ recast.types.visit(rootAst, {
19
+ visitTSInterfaceDeclaration(pathNode) {
20
+ if (pathNode.node.id?.name === interfaceName) {
21
+ const body = pathNode.node.body?.body || [];
22
+ if (!body.some((m) => m.key?.name === 'previewPath')) {
23
+ const sig = b.tsPropertySignature(b.identifier('previewPath'), b.tsTypeAnnotation(b.tsStringKeyword()));
24
+ sig.optional = true;
25
+ body.push(sig);
26
+ instrumented = true;
27
+ }
28
+ }
29
+ this.traverse(pathNode);
30
+ },
31
+ visitTSTypeAliasDeclaration(pathNode) {
32
+ if (pathNode.node.id?.name === interfaceName && pathNode.node.typeAnnotation?.type === 'TSTypeLiteral') {
33
+ const members = pathNode.node.typeAnnotation.members || [];
34
+ if (!members.some((m) => m.key?.name === 'previewPath')) {
35
+ const sig = b.tsPropertySignature(b.identifier('previewPath'), b.tsTypeAnnotation(b.tsStringKeyword()));
36
+ sig.optional = true;
37
+ members.push(sig);
38
+ instrumented = true;
39
+ }
40
+ }
41
+ this.traverse(pathNode);
42
+ },
43
+ });
44
+ }
45
+
46
+ function instrumentFunctionProps(fnNode, isTs) {
47
+ let changed = false;
48
+ let params = fnNode.params || [];
49
+ if (!params.length) {
50
+ fnNode.params = [
51
+ b.objectPattern([
52
+ b.property('init', b.identifier('previewPath'), b.identifier('previewPath')),
53
+ ]),
54
+ ];
55
+ changed = true;
56
+ } else {
57
+ const firstParam = params[0];
58
+ if (firstParam.type === 'ObjectPattern') {
59
+ const hasPreviewPath = firstParam.properties.some(
60
+ (p) => p.key?.name === 'previewPath' || p.argument?.name === 'previewPath'
61
+ );
62
+ if (!hasPreviewPath) {
63
+ const prop = b.property('init', b.identifier('previewPath'), b.identifier('previewPath'));
64
+ prop.shorthand = true;
65
+ firstParam.properties.push(prop);
66
+ changed = true;
67
+ }
68
+ if (isTs && firstParam.typeAnnotation?.typeAnnotation) {
69
+ const typeAnn = firstParam.typeAnnotation.typeAnnotation;
70
+ if (typeAnn.type === 'TSTypeReference' && typeAnn.typeName?.name) {
71
+ updateTsInterface(ast, typeAnn.typeName.name);
72
+ }
73
+ }
74
+ } else if (firstParam.type === 'Identifier') {
75
+ const paramName = firstParam.name;
76
+ if (isTs && firstParam.typeAnnotation?.typeAnnotation) {
77
+ const typeAnn = firstParam.typeAnnotation.typeAnnotation;
78
+ if (typeAnn.type === 'TSTypeReference' && typeAnn.typeName?.name) {
79
+ updateTsInterface(ast, typeAnn.typeName.name);
80
+ }
81
+ }
82
+ recast.types.visit(fnNode.body || fnNode, {
83
+ visitVariableDeclarator(vPath) {
84
+ if (vPath.node.id?.type === 'ObjectPattern' && vPath.node.init?.name === paramName) {
85
+ const propsList = vPath.node.id.properties;
86
+ if (!propsList.some((p) => p.key?.name === 'previewPath')) {
87
+ const prop = b.property('init', b.identifier('previewPath'), b.identifier('previewPath'));
88
+ prop.shorthand = true;
89
+ propsList.push(prop);
90
+ changed = true;
91
+ }
92
+ }
93
+ this.traverse(vPath);
94
+ },
95
+ });
96
+ }
97
+ }
98
+ return changed;
99
+ }
100
+
101
+ recast.types.visit(ast, {
102
+ visitFunctionDeclaration(pathNode) {
103
+ if (!componentName || pathNode.node.id?.name === componentName) {
104
+ if (instrumentFunctionProps(pathNode.node, isTypeScript)) instrumented = true;
105
+ }
106
+ this.traverse(pathNode);
107
+ },
108
+ visitVariableDeclarator(pathNode) {
109
+ if (pathNode.node.id?.name === componentName) {
110
+ const init = pathNode.node.init;
111
+ if (init && (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression')) {
112
+ if (instrumentFunctionProps(init, isTypeScript)) instrumented = true;
113
+ }
114
+ }
115
+ this.traverse(pathNode);
116
+ },
117
+ visitExportDefaultDeclaration(pathNode) {
118
+ const decl = pathNode.node.declaration;
119
+ if (decl && (decl.type === 'FunctionDeclaration' || decl.type === 'ArrowFunctionExpression' || decl.type === 'FunctionExpression')) {
120
+ const fnName = decl.id?.name;
121
+ if (!fnName || fnName === componentName) {
122
+ if (instrumentFunctionProps(decl, isTypeScript)) instrumented = true;
123
+ }
124
+ }
125
+ this.traverse(pathNode);
126
+ },
127
+ });
128
+
129
+ recast.types.visit(ast, {
130
+ visitJSXElement(pathNode) {
131
+ const node = pathNode.node;
132
+ for (const child of node.children || []) {
133
+ if (child.type === 'JSXExpressionContainer') {
134
+ const expr = child.expression;
135
+ let propMatch = null;
136
+ if (expr && expr.type === 'Identifier' && propNames.includes(expr.name)) {
137
+ propMatch = expr.name;
138
+ } else if (expr && expr.type === 'LogicalExpression' && expr.left?.name && propNames.includes(expr.left.name)) {
139
+ propMatch = expr.left.name;
140
+ }
141
+ if (propMatch && !hasJsxAttribute(node, 'data-preview-field-path')) {
142
+ const previewAttr = b.jsxAttribute(
143
+ b.jsxIdentifier('data-preview-field-path'),
144
+ b.jsxExpressionContainer(
145
+ b.conditionalExpression(
146
+ b.identifier('previewPath'),
147
+ b.templateLiteral(
148
+ [
149
+ b.templateElement({ raw: '', cooked: '' }, false),
150
+ b.templateElement({ raw: `.${propMatch}`, cooked: `.${propMatch}` }, true),
151
+ ],
152
+ [b.identifier('previewPath')]
153
+ ),
154
+ b.identifier('undefined')
155
+ )
156
+ )
157
+ );
158
+ node.openingElement.attributes.push(previewAttr);
159
+ instrumented = true;
160
+ break;
161
+ }
162
+ }
163
+ }
164
+ this.traverse(pathNode);
165
+ },
166
+ });
167
+
168
+ return instrumented;
169
+ }
170
+
171
+ module.exports = {
172
+ instrumentReusableComponent,
173
+ };
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ getJsxName,
5
+ findJsxAttribute,
6
+ hasJsxAttribute,
7
+ siteDataBinding,
8
+ ensureStyleAttrs,
9
+ b,
10
+ } = require('../ast.cjs');
11
+ const { BROAD_CONTENT_CONTAINERS } = require('../fivora-contract.cjs');
12
+ const {
13
+ replaceAttrValue,
14
+ ensurePreviewPath,
15
+ findAncestorJsxElement,
16
+ inferButtonKind,
17
+ } = require('./transform-context.cjs');
18
+ const { replaceTextChildren, wrapLiteralTextChildren } = require('./primitives/text.cjs');
19
+ const { splitActionChildren } = require('./primitives/action.cjs');
20
+ const { bindButtonWithIcon, bindHighlightedHeading } = require('./components/compound-content.cjs');
21
+ const { extractTailwindBg } = require('./assets/tailwind-bg.cjs');
22
+
23
+ function applyTransformToElement(pathNode, transform) {
24
+ const node = pathNode.node;
25
+ if (transform.operation === 'form-submit-action') {
26
+ const tagName = getJsxName(node);
27
+ if (!hasJsxAttribute(node, 'type') && tagName === 'button') {
28
+ node.openingElement.attributes.push(
29
+ b.jsxAttribute(b.jsxIdentifier('type'), b.stringLiteral('submit'))
30
+ );
31
+ }
32
+ if (transform.labelField) {
33
+ if (hasJsxAttribute(node, 'data-preview-static')) {
34
+ node.openingElement.attributes = node.openingElement.attributes.filter(
35
+ (attr) => !(attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'data-preview-static')
36
+ );
37
+ }
38
+ if (hasJsxAttribute(node, 'data-preview-field-path')) {
39
+ node.openingElement.attributes = node.openingElement.attributes.filter(
40
+ (attr) => !(attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'data-preview-field-path')
41
+ );
42
+ }
43
+ splitActionChildren(node, transform.labelField, transform.labelFallback || '');
44
+ } else if (!hasJsxAttribute(node, 'data-preview-static')) {
45
+ node.openingElement.attributes.push(
46
+ b.jsxAttribute(b.jsxIdentifier('data-preview-static'), b.stringLiteral('action-button'))
47
+ );
48
+ }
49
+ return;
50
+ }
51
+ if (transform.operation === 'split-action-contract') {
52
+ const tagName = getJsxName(node);
53
+ if (tagName === 'button') {
54
+ node.openingElement.name = b.jsxIdentifier('a');
55
+ if (node.closingElement) {
56
+ node.closingElement.name = b.jsxIdentifier('a');
57
+ }
58
+ node.openingElement.attributes = (node.openingElement.attributes || []).filter(
59
+ (attr) => !(attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'type')
60
+ );
61
+ }
62
+
63
+ const urlParts = transform.urlField.split('.');
64
+ replaceAttrValue(node, 'href', siteDataBinding(urlParts, transform.fallback, 'url'));
65
+
66
+ const isExternal =
67
+ transform.extra?.external ||
68
+ ['whatsapp', 'directions', 'location'].includes(transform.extra?.action) ||
69
+ /^https?:\/\//i.test(String(transform.fallback || ''));
70
+
71
+ if (isExternal && (tagName === 'button' || tagName === 'a')) {
72
+ if (!hasJsxAttribute(node, 'target')) {
73
+ node.openingElement.attributes.push(
74
+ b.jsxAttribute(b.jsxIdentifier('target'), b.stringLiteral('_blank'))
75
+ );
76
+ }
77
+ if (!hasJsxAttribute(node, 'rel')) {
78
+ node.openingElement.attributes.push(
79
+ b.jsxAttribute(b.jsxIdentifier('rel'), b.stringLiteral('noopener noreferrer'))
80
+ );
81
+ }
82
+ }
83
+
84
+ replaceAttrValue(node, 'data-preview-field-path', b.stringLiteral(transform.urlField));
85
+ if (hasJsxAttribute(node, 'data-preview-static')) {
86
+ node.openingElement.attributes = node.openingElement.attributes.filter(
87
+ (attr) => !(attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'data-preview-static')
88
+ );
89
+ }
90
+ splitActionChildren(node, transform.labelField, transform.labelFallback || '');
91
+ return;
92
+ }
93
+ if (transform.operation === 'extract-url') {
94
+ replaceAttrValue(node, 'href', siteDataBinding(transform.field.split('.'), transform.fallback, 'url'));
95
+ ensurePreviewPath(node, transform.field);
96
+ return;
97
+ }
98
+ if (transform.operation === 'extract-image' || transform.operation === 'extract-picture') {
99
+ const tagName = getJsxName(node);
100
+ if (tagName === 'picture') {
101
+ const children = node.children || [];
102
+ const imgChild = children.find((c) => c && c.type === 'JSXElement' && (getJsxName(c) === 'img' || getJsxName(c) === 'Image'));
103
+ if (imgChild) {
104
+ replaceAttrValue(imgChild, 'src', siteDataBinding(transform.field.split('.'), transform.fallback, 'image'));
105
+ ensurePreviewPath(imgChild, transform.field);
106
+ }
107
+ ensurePreviewPath(node, transform.field);
108
+ return;
109
+ }
110
+ replaceAttrValue(node, 'src', siteDataBinding(transform.field.split('.'), transform.fallback, 'image'));
111
+ ensurePreviewPath(node, transform.field);
112
+ return;
113
+ }
114
+ if (transform.operation === 'extract-alt') {
115
+ replaceAttrValue(node, 'alt', siteDataBinding(transform.field.split('.'), transform.fallback, 'text'));
116
+ return;
117
+ }
118
+ if (transform.operation === 'extract-placeholder') {
119
+ replaceAttrValue(node, 'placeholder', siteDataBinding(transform.field.split('.'), transform.fallback, 'text'));
120
+ ensurePreviewPath(node, transform.field);
121
+ return;
122
+ }
123
+ if (transform.operation === 'extract-prop') {
124
+ const propName = transform.propName || 'title';
125
+ replaceAttrValue(node, propName, siteDataBinding(transform.field.split('.'), transform.fallback, transform.fieldType || 'text'));
126
+ if (!hasJsxAttribute(node, 'data-preview-field-path') && !hasJsxAttribute(node, 'data-preview-list-path')) {
127
+ ensurePreviewPath(node, transform.field);
128
+ }
129
+ return;
130
+ }
131
+ if (transform.operation === 'bind-button-with-icon') {
132
+ bindButtonWithIcon(node, transform);
133
+ return;
134
+ }
135
+ if (transform.operation === 'bind-highlighted-heading') {
136
+ bindHighlightedHeading(node, transform);
137
+ return;
138
+ }
139
+ if (transform.operation === 'prop-flow-callsite') {
140
+ if (transform.propTransforms) {
141
+ for (const [propName, propInfo] of Object.entries(transform.propTransforms)) {
142
+ replaceAttrValue(
143
+ node,
144
+ propName,
145
+ siteDataBinding(propInfo.field.split('.'), propInfo.fallback, propInfo.type || 'text')
146
+ );
147
+ }
148
+ }
149
+ if (transform.previewPath && !hasJsxAttribute(node, 'previewPath')) {
150
+ node.openingElement.attributes.push(
151
+ b.jsxAttribute(b.jsxIdentifier('previewPath'), b.stringLiteral(transform.previewPath))
152
+ );
153
+ }
154
+ return;
155
+ }
156
+ if (transform.operation === 'wrap-text-span') {
157
+ wrapLiteralTextChildren(node, transform.field, transform.fallback);
158
+ return;
159
+ }
160
+ if (transform.operation === 'extract-text') {
161
+ const tagName = getJsxName(node);
162
+ if (BROAD_CONTENT_CONTAINERS.has(tagName)) {
163
+ wrapLiteralTextChildren(node, transform.field, transform.fallback);
164
+ return;
165
+ }
166
+ ensurePreviewPath(node, transform.field);
167
+ ensureStyleAttrs(node, transform.field, inferButtonKind(transform.tag));
168
+ replaceTextChildren(node, transform.field, transform.fallback, transform.fieldType || 'text');
169
+ return;
170
+ }
171
+ if (transform.operation === 'style-bind') {
172
+ if (transform.styleKind === 'grid') {
173
+ const container = findAncestorJsxElement(pathNode) || node;
174
+ ensureStyleAttrs(container, transform.stylePath, 'grid');
175
+ return;
176
+ }
177
+ if (transform.styleKind === 'card') {
178
+ ensureStyleAttrs(node, transform.stylePath, 'card');
179
+ return;
180
+ }
181
+ ensureStyleAttrs(node, transform.stylePath, transform.styleKind || 'text');
182
+ return;
183
+ }
184
+ if (transform.operation === 'extract-tailwind-bg') {
185
+ extractTailwindBg(node, transform);
186
+ return;
187
+ }
188
+ }
189
+
190
+ module.exports = {
191
+ applyTransformToElement,
192
+ };