@deneb-ui/cli 2.0.84 → 2.0.86
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/bin/index.js +82 -12
- package/package.json +2 -2
- package/src/arc/__tests__/arc.test.cjs +1251 -0
- package/src/arc/adapters/accordion.cjs +32 -0
- package/src/arc/adapters/dialog.cjs +26 -0
- package/src/arc/adapters/embla.cjs +29 -0
- package/src/arc/adapters/gallery.cjs +23 -0
- package/src/arc/adapters/picture-source.cjs +71 -0
- package/src/arc/adapters/registry.cjs +156 -0
- package/src/arc/adapters/slick.cjs +26 -0
- package/src/arc/adapters/swiper.cjs +29 -0
- package/src/arc/adapters/tabs.cjs +29 -0
- package/src/arc/adapters/ui-frameworks.cjs +140 -0
- package/src/arc/adapters.cjs +20 -151
- package/src/arc/canonical-paths.cjs +95 -0
- package/src/arc/data-flow.cjs +158 -0
- package/src/arc/diff.cjs +136 -0
- package/src/arc/explain.cjs +113 -0
- package/src/arc/field-paths.cjs +25 -13
- package/src/arc/fs-utils.cjs +3 -0
- package/src/arc/index.cjs +126 -7
- package/src/arc/ir-builder.cjs +581 -0
- package/src/arc/manifest.cjs +18 -0
- package/src/arc/planner.cjs +106 -2
- package/src/arc/printer.cjs +18 -5
- package/src/arc/residual.cjs +9 -3
- package/src/arc/rsc-boundary.cjs +154 -0
- package/src/arc/runtime-validator.cjs +286 -0
- package/src/arc/semantic.cjs +217 -16
- package/src/arc/text-fragment-analyzer.cjs +173 -0
- package/src/arc/transformer.cjs +819 -20
- package/src/arc/version.cjs +1 -1
- package/src/tools/deneb-doctor.cjs +53 -9
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getJsxName } = require('../ast.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Radix & shadcn Accordion Adapter
|
|
7
|
+
* Supports Accordion, AccordionItem, AccordionTrigger, AccordionContent.
|
|
8
|
+
*/
|
|
9
|
+
const accordionAdapter = {
|
|
10
|
+
id: 'accordion',
|
|
11
|
+
|
|
12
|
+
detect: () => true,
|
|
13
|
+
|
|
14
|
+
recognizeNode(node) {
|
|
15
|
+
const name = getJsxName(node);
|
|
16
|
+
if (name === 'Accordion' || name.endsWith('Accordion')) {
|
|
17
|
+
return { library: 'accordion', kind: 'collection', role: 'accordion-container', tag: name };
|
|
18
|
+
}
|
|
19
|
+
if (name === 'AccordionItem') {
|
|
20
|
+
return { library: 'accordion', kind: 'collection-item', role: 'accordion-item', tag: name };
|
|
21
|
+
}
|
|
22
|
+
if (name === 'AccordionTrigger') {
|
|
23
|
+
return { library: 'accordion', kind: 'text', role: 'accordion-header', tag: name };
|
|
24
|
+
}
|
|
25
|
+
if (name === 'AccordionContent') {
|
|
26
|
+
return { library: 'accordion', kind: 'text', role: 'accordion-body', tag: name };
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
module.exports = accordionAdapter;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getJsxName } = require('../ast.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Radix & shadcn Dialog & Modal Adapter
|
|
7
|
+
* Supports DialogTitle, DialogDescription, AlertDialogTitle, AlertDialogDescription.
|
|
8
|
+
*/
|
|
9
|
+
const dialogAdapter = {
|
|
10
|
+
id: 'dialog',
|
|
11
|
+
|
|
12
|
+
detect: () => true,
|
|
13
|
+
|
|
14
|
+
recognizeNode(node) {
|
|
15
|
+
const name = getJsxName(node);
|
|
16
|
+
if (name === 'DialogTitle' || name === 'AlertDialogTitle') {
|
|
17
|
+
return { library: 'dialog', kind: 'text', role: 'dialog-title', tag: name };
|
|
18
|
+
}
|
|
19
|
+
if (name === 'DialogDescription' || name === 'AlertDialogDescription') {
|
|
20
|
+
return { library: 'dialog', kind: 'text', role: 'dialog-desc', tag: name };
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = dialogAdapter;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getJsxName } = require('../ast.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Embla & shadcn Carousel Adapter
|
|
7
|
+
* Supports Carousel, CarouselContent, CarouselItem from embla-carousel or shadcn.
|
|
8
|
+
*/
|
|
9
|
+
const emblaAdapter = {
|
|
10
|
+
id: 'embla',
|
|
11
|
+
|
|
12
|
+
detect: (project) =>
|
|
13
|
+
(project?.dependencies &&
|
|
14
|
+
('embla-carousel' in project.dependencies || 'embla-carousel-react' in project.dependencies)) ||
|
|
15
|
+
true,
|
|
16
|
+
|
|
17
|
+
recognizeNode(node) {
|
|
18
|
+
const name = getJsxName(node);
|
|
19
|
+
if (name === 'Carousel' || name === 'CarouselContent') {
|
|
20
|
+
return { library: 'embla', kind: 'collection', role: 'carousel-container', tag: name };
|
|
21
|
+
}
|
|
22
|
+
if (name === 'CarouselItem') {
|
|
23
|
+
return { library: 'embla', kind: 'collection-item', role: 'carousel-slide', tag: name };
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = emblaAdapter;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getJsxName } = require('../ast.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gallery & Masonry Grid Adapter
|
|
7
|
+
* Supports Masonry, Gallery, Lightbox, PhotoGrid components.
|
|
8
|
+
*/
|
|
9
|
+
const galleryAdapter = {
|
|
10
|
+
id: 'gallery',
|
|
11
|
+
|
|
12
|
+
detect: () => true,
|
|
13
|
+
|
|
14
|
+
recognizeNode(node) {
|
|
15
|
+
const name = getJsxName(node);
|
|
16
|
+
if (/Masonry|Gallery|Lightbox|PhotoGrid/i.test(name)) {
|
|
17
|
+
return { library: 'gallery', kind: 'collection', role: 'gallery-container', tag: name };
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
module.exports = galleryAdapter;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getJsxName, getJsxAttributeLiteral, findJsxAttribute, hasJsxAttribute } = require('../ast.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Responsive Media Adapter (<picture> & <source>)
|
|
7
|
+
* Supports HTML5 responsive image art direction with multi-breakpoint media queries.
|
|
8
|
+
*/
|
|
9
|
+
const pictureSourceAdapter = {
|
|
10
|
+
id: 'picture-source',
|
|
11
|
+
|
|
12
|
+
detect: () => true,
|
|
13
|
+
|
|
14
|
+
recognizeNode(node, ctx) {
|
|
15
|
+
const name = getJsxName(node);
|
|
16
|
+
if (name === 'picture') {
|
|
17
|
+
return {
|
|
18
|
+
library: 'html',
|
|
19
|
+
kind: 'responsive-image-container',
|
|
20
|
+
role: 'picture',
|
|
21
|
+
tag: 'picture',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (name === 'source') {
|
|
25
|
+
const srcSet = getJsxAttributeLiteral(node, 'srcSet') || getJsxAttributeLiteral(node, 'srcset');
|
|
26
|
+
const media = getJsxAttributeLiteral(node, 'media');
|
|
27
|
+
return {
|
|
28
|
+
library: 'html',
|
|
29
|
+
kind: 'responsive-image-source',
|
|
30
|
+
role: 'picture-source',
|
|
31
|
+
tag: 'source',
|
|
32
|
+
media: media || undefined,
|
|
33
|
+
srcSet: srcSet || undefined,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extracts responsive configuration from a <picture> JSX node.
|
|
41
|
+
*/
|
|
42
|
+
extractPictureDetails(pictureNode) {
|
|
43
|
+
const children = pictureNode.children || [];
|
|
44
|
+
const sources = [];
|
|
45
|
+
let fallbackImg = null;
|
|
46
|
+
|
|
47
|
+
for (const child of children) {
|
|
48
|
+
if (!child || child.type !== 'JSXElement') continue;
|
|
49
|
+
const childName = getJsxName(child);
|
|
50
|
+
if (childName === 'source') {
|
|
51
|
+
const srcSet = getJsxAttributeLiteral(child, 'srcSet') || getJsxAttributeLiteral(child, 'srcset');
|
|
52
|
+
const media = getJsxAttributeLiteral(child, 'media');
|
|
53
|
+
sources.push({
|
|
54
|
+
node: child,
|
|
55
|
+
media,
|
|
56
|
+
srcSet,
|
|
57
|
+
});
|
|
58
|
+
} else if (childName === 'img' || childName === 'Image') {
|
|
59
|
+
fallbackImg = child;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
sources,
|
|
65
|
+
fallbackImg,
|
|
66
|
+
hasResponsiveSources: sources.length > 0,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
module.exports = pictureSourceAdapter;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const pictureSourceAdapter = require('./picture-source.cjs');
|
|
4
|
+
const swiperAdapter = require('./swiper.cjs');
|
|
5
|
+
const emblaAdapter = require('./embla.cjs');
|
|
6
|
+
const slickAdapter = require('./slick.cjs');
|
|
7
|
+
const accordionAdapter = require('./accordion.cjs');
|
|
8
|
+
const tabsAdapter = require('./tabs.cjs');
|
|
9
|
+
const dialogAdapter = require('./dialog.cjs');
|
|
10
|
+
const galleryAdapter = require('./gallery.cjs');
|
|
11
|
+
const {
|
|
12
|
+
nextjsAdapter,
|
|
13
|
+
denebAdapter,
|
|
14
|
+
shadcnAdapter,
|
|
15
|
+
herouiAdapter,
|
|
16
|
+
framerMotionAdapter,
|
|
17
|
+
reactBitsAdapter,
|
|
18
|
+
radixAdapter,
|
|
19
|
+
} = require('./ui-frameworks.cjs');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Adapter Registry for Deneb ARC v2
|
|
23
|
+
* Manages detection, semantic recognition, and transformation across ecosystem libraries.
|
|
24
|
+
*/
|
|
25
|
+
class AdapterRegistry {
|
|
26
|
+
constructor() {
|
|
27
|
+
this._adapters = new Map();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Registers a new or custom adapter.
|
|
32
|
+
*/
|
|
33
|
+
register(adapter) {
|
|
34
|
+
if (!adapter || !adapter.id) {
|
|
35
|
+
throw new Error('Adapter must provide a unique "id" string');
|
|
36
|
+
}
|
|
37
|
+
this._adapters.set(adapter.id, adapter);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Retrieves an adapter by its ID.
|
|
42
|
+
*/
|
|
43
|
+
get(id) {
|
|
44
|
+
return this._adapters.get(id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns all registered adapters.
|
|
49
|
+
*/
|
|
50
|
+
getAll() {
|
|
51
|
+
return Array.from(this._adapters.values());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns adapters active for the given project profile.
|
|
56
|
+
*/
|
|
57
|
+
getActive(profile = {}) {
|
|
58
|
+
return this.getAll().filter((adapter) => {
|
|
59
|
+
try {
|
|
60
|
+
return typeof adapter.detect === 'function' ? adapter.detect(profile) : true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Dispatches node recognition through active adapters.
|
|
69
|
+
*/
|
|
70
|
+
recognizeNode(node, ctx, adaptersList) {
|
|
71
|
+
const active = adaptersList || this.getActive(ctx?.profile);
|
|
72
|
+
for (const adapter of active) {
|
|
73
|
+
if (!adapter.recognizeNode) continue;
|
|
74
|
+
const result = adapter.recognizeNode(node, ctx);
|
|
75
|
+
if (result) {
|
|
76
|
+
return { ...result, adapterId: adapter.id };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Dispatches action resolution through active adapters.
|
|
84
|
+
*/
|
|
85
|
+
resolveAction(node, adaptersList) {
|
|
86
|
+
const active = adaptersList || this.getAll();
|
|
87
|
+
for (const adapter of active) {
|
|
88
|
+
if (!adapter.resolveAction) continue;
|
|
89
|
+
const result = adapter.resolveAction(node);
|
|
90
|
+
if (result) return result;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Global default registry
|
|
97
|
+
const defaultRegistry = new AdapterRegistry();
|
|
98
|
+
|
|
99
|
+
// Register all standard built-in adapters
|
|
100
|
+
const BUILTIN_ADAPTERS = [
|
|
101
|
+
nextjsAdapter,
|
|
102
|
+
denebAdapter,
|
|
103
|
+
shadcnAdapter,
|
|
104
|
+
herouiAdapter,
|
|
105
|
+
framerMotionAdapter,
|
|
106
|
+
reactBitsAdapter,
|
|
107
|
+
radixAdapter,
|
|
108
|
+
pictureSourceAdapter,
|
|
109
|
+
swiperAdapter,
|
|
110
|
+
emblaAdapter,
|
|
111
|
+
slickAdapter,
|
|
112
|
+
accordionAdapter,
|
|
113
|
+
tabsAdapter,
|
|
114
|
+
dialogAdapter,
|
|
115
|
+
galleryAdapter,
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
for (const adapter of BUILTIN_ADAPTERS) {
|
|
119
|
+
defaultRegistry.register(adapter);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function registerAdapter(adapter) {
|
|
123
|
+
defaultRegistry.register(adapter);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getAdapter(id) {
|
|
127
|
+
return defaultRegistry.get(id);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getAllAdapters() {
|
|
131
|
+
return defaultRegistry.getAll();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function activeAdapters(profile) {
|
|
135
|
+
return defaultRegistry.getActive(profile);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function recognizeWithAdapters(node, ctx, adapters) {
|
|
139
|
+
return defaultRegistry.recognizeNode(node, ctx, adapters);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function resolveActionWithAdapters(node, adapters) {
|
|
143
|
+
return defaultRegistry.resolveAction(node, adapters);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
AdapterRegistry,
|
|
148
|
+
defaultRegistry,
|
|
149
|
+
BUILTIN_ADAPTERS,
|
|
150
|
+
registerAdapter,
|
|
151
|
+
getAdapter,
|
|
152
|
+
getAllAdapters,
|
|
153
|
+
activeAdapters,
|
|
154
|
+
recognizeWithAdapters,
|
|
155
|
+
resolveActionWithAdapters,
|
|
156
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getJsxName } = require('../ast.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* React-Slick Carousel Adapter
|
|
7
|
+
* Supports Slider from react-slick / slick-carousel.
|
|
8
|
+
*/
|
|
9
|
+
const slickAdapter = {
|
|
10
|
+
id: 'slick',
|
|
11
|
+
|
|
12
|
+
detect: (project) =>
|
|
13
|
+
(project?.dependencies &&
|
|
14
|
+
('react-slick' in project.dependencies || 'slick-carousel' in project.dependencies)) ||
|
|
15
|
+
true,
|
|
16
|
+
|
|
17
|
+
recognizeNode(node) {
|
|
18
|
+
const name = getJsxName(node);
|
|
19
|
+
if (name === 'Slider') {
|
|
20
|
+
return { library: 'slick', kind: 'collection', role: 'carousel-container', tag: name };
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = slickAdapter;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getJsxName } = require('../ast.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Swiper Carousel Adapter
|
|
7
|
+
* Supports Swiper & SwiperSlide from swiper/react.
|
|
8
|
+
*/
|
|
9
|
+
const swiperAdapter = {
|
|
10
|
+
id: 'swiper',
|
|
11
|
+
|
|
12
|
+
detect: (project) =>
|
|
13
|
+
(project?.dependencies &&
|
|
14
|
+
('swiper' in project.dependencies || 'swiper/react' in project.dependencies)) ||
|
|
15
|
+
true,
|
|
16
|
+
|
|
17
|
+
recognizeNode(node) {
|
|
18
|
+
const name = getJsxName(node);
|
|
19
|
+
if (name === 'Swiper') {
|
|
20
|
+
return { library: 'swiper', kind: 'collection', role: 'carousel-container', tag: name };
|
|
21
|
+
}
|
|
22
|
+
if (name === 'SwiperSlide') {
|
|
23
|
+
return { library: 'swiper', kind: 'collection-item', role: 'carousel-slide', tag: name };
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = swiperAdapter;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getJsxName } = require('../ast.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Radix & shadcn Tabs Adapter
|
|
7
|
+
* Supports Tabs, TabsList, TabsTrigger, TabsContent.
|
|
8
|
+
*/
|
|
9
|
+
const tabsAdapter = {
|
|
10
|
+
id: 'tabs',
|
|
11
|
+
|
|
12
|
+
detect: () => true,
|
|
13
|
+
|
|
14
|
+
recognizeNode(node) {
|
|
15
|
+
const name = getJsxName(node);
|
|
16
|
+
if (name === 'Tabs') {
|
|
17
|
+
return { library: 'tabs', kind: 'structure', role: 'tabs-container', tag: name };
|
|
18
|
+
}
|
|
19
|
+
if (name === 'TabsTrigger') {
|
|
20
|
+
return { library: 'tabs', kind: 'action', role: 'tab-button', tag: name };
|
|
21
|
+
}
|
|
22
|
+
if (name === 'TabsContent') {
|
|
23
|
+
return { library: 'tabs', kind: 'structure', role: 'tab-panel', tag: name };
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = tabsAdapter;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getJsxName, hasJsxAttribute } = require('../ast.cjs');
|
|
4
|
+
|
|
5
|
+
function detectFromProfile(profile, id) {
|
|
6
|
+
return (profile?.componentLibraries || []).includes(id) || profile?.[id] === true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ACTION_TAGS = new Set(['a', 'Link', 'NavLink', 'Button', 'button', 'IconButton', 'NavbarBrand']);
|
|
10
|
+
const HEADING_TAGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'Heading', 'Title', 'CardTitle', 'ModalHeader', 'DialogTitle']);
|
|
11
|
+
const TEXT_TAGS = new Set(['p', 'span', 'li', 'blockquote', 'figcaption', 'label', 'CardDescription', 'Description', 'Subtitle', 'Typography', 'Text', 'Badge', 'badge']);
|
|
12
|
+
|
|
13
|
+
const shadcnAdapter = {
|
|
14
|
+
id: 'shadcn',
|
|
15
|
+
detect: (project) => detectFromProfile(project, 'shadcn') || project?.shadcn,
|
|
16
|
+
recognizeNode(node, ctx) {
|
|
17
|
+
const name = getJsxName(node);
|
|
18
|
+
if (name === 'Button') {
|
|
19
|
+
return { library: 'shadcn', kind: 'button', asChild: hasJsxAttribute(node, 'asChild') };
|
|
20
|
+
}
|
|
21
|
+
if (name === 'CardTitle' || name === 'CardDescription' || name === 'Badge') {
|
|
22
|
+
return { library: 'shadcn', kind: 'text', tag: name };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
},
|
|
26
|
+
resolveAction(node) {
|
|
27
|
+
const name = getJsxName(node);
|
|
28
|
+
if (name === 'Button' && hasJsxAttribute(node, 'asChild')) {
|
|
29
|
+
const child = (node.children || []).find((c) => c && c.type === 'JSXElement');
|
|
30
|
+
if (child && ACTION_TAGS.has(getJsxName(child))) return child;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const herouiAdapter = {
|
|
37
|
+
id: 'heroui',
|
|
38
|
+
detect: (project) => detectFromProfile(project, 'heroui') || project?.heroui,
|
|
39
|
+
recognizeNode(node) {
|
|
40
|
+
const name = getJsxName(node);
|
|
41
|
+
if (['Button', 'Link', 'Navbar', 'NavbarBrand', 'NavbarItem', 'NavbarContent'].includes(name)) {
|
|
42
|
+
return { library: 'heroui', kind: name === 'Button' || name === 'Link' ? 'action' : 'structure', tag: name };
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
},
|
|
46
|
+
resolveAction(node) {
|
|
47
|
+
const name = getJsxName(node);
|
|
48
|
+
if (name === 'Button' && (hasJsxAttribute(node, 'href') || hasJsxAttribute(node, 'as'))) return node;
|
|
49
|
+
if (name === 'Link') return node;
|
|
50
|
+
return null;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const nextjsAdapter = {
|
|
55
|
+
id: 'nextjs',
|
|
56
|
+
detect: (project) => project?.framework === 'nextjs',
|
|
57
|
+
recognizeNode(node) {
|
|
58
|
+
const name = getJsxName(node);
|
|
59
|
+
if (name === 'Link') return { library: 'nextjs', kind: 'action', tag: 'Link' };
|
|
60
|
+
if (name === 'Image') return { library: 'nextjs', kind: 'image', tag: 'Image' };
|
|
61
|
+
return null;
|
|
62
|
+
},
|
|
63
|
+
resolveAction(node) {
|
|
64
|
+
return getJsxName(node) === 'Link' ? node : null;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const framerMotionAdapter = {
|
|
69
|
+
id: 'framer-motion',
|
|
70
|
+
detect: (project) => (project?.animationLibraries || []).includes('framer-motion'),
|
|
71
|
+
recognizeNode(node) {
|
|
72
|
+
const name = getJsxName(node);
|
|
73
|
+
if (name.startsWith('motion.')) {
|
|
74
|
+
const tag = name.slice('motion.'.length);
|
|
75
|
+
return { library: 'framer-motion', kind: HEADING_TAGS.has(tag) || TEXT_TAGS.has(tag) ? 'text' : 'structure', tag };
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const reactBitsAdapter = {
|
|
82
|
+
id: 'react-bits',
|
|
83
|
+
detect: (project) => project?.reactBits,
|
|
84
|
+
recognizeNode(node) {
|
|
85
|
+
const name = getJsxName(node);
|
|
86
|
+
if (/SplitText|BlurText|GradientText|ShinyText|CountUp|RotatingText/.test(name)) {
|
|
87
|
+
return { library: 'react-bits', kind: 'text', tag: name };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const denebAdapter = {
|
|
94
|
+
id: 'deneb',
|
|
95
|
+
detect: (project) =>
|
|
96
|
+
(project?.componentLibraries || []).includes('@deneb-ui/ui') ||
|
|
97
|
+
project?.['@deneb-ui/ui'] === true ||
|
|
98
|
+
Boolean(project?.dependencies && project?.dependencies['@deneb-ui/ui']),
|
|
99
|
+
recognizeNode(node) {
|
|
100
|
+
const name = getJsxName(node);
|
|
101
|
+
if (['EditableContactForm', 'ContactForm'].includes(name)) {
|
|
102
|
+
return { library: 'deneb', kind: 'form', tag: name };
|
|
103
|
+
}
|
|
104
|
+
if (['EditableGoogleFeedback', 'EditableCustomerReviews', 'CustomerReviews', 'GoogleFeedback'].includes(name)) {
|
|
105
|
+
return { library: 'deneb', kind: 'feedback', tag: name };
|
|
106
|
+
}
|
|
107
|
+
if (['EditableTestimonialSection', 'EditableTestimonialCard', 'TestimonialSection', 'Testimonials'].includes(name)) {
|
|
108
|
+
return { library: 'deneb', kind: 'testimonial', tag: name };
|
|
109
|
+
}
|
|
110
|
+
if (name === 'EditableMap' || name === 'Map') {
|
|
111
|
+
return { library: 'deneb', kind: 'map', tag: name };
|
|
112
|
+
}
|
|
113
|
+
if (['EditableImage', 'EditableText', 'EditableCard', 'EditableProductCard', 'Image', 'Text', 'Card'].includes(name)) {
|
|
114
|
+
return { library: 'deneb', kind: 'editable-primitive', tag: name };
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const radixAdapter = {
|
|
121
|
+
id: 'radix',
|
|
122
|
+
detect: (project) => (project?.componentLibraries || []).includes('radix'),
|
|
123
|
+
recognizeNode(node) {
|
|
124
|
+
const name = getJsxName(node);
|
|
125
|
+
if (name === 'Slot' || name.endsWith('.Root') || name.endsWith('.Trigger')) {
|
|
126
|
+
return { library: 'radix', kind: 'structure', tag: name };
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
shadcnAdapter,
|
|
134
|
+
herouiAdapter,
|
|
135
|
+
nextjsAdapter,
|
|
136
|
+
framerMotionAdapter,
|
|
137
|
+
reactBitsAdapter,
|
|
138
|
+
denebAdapter,
|
|
139
|
+
radixAdapter,
|
|
140
|
+
};
|