codescoop 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/bin/codescoop.js +276 -0
- package/package.json +75 -0
- package/src/cli/interactive.js +153 -0
- package/src/index.js +303 -0
- package/src/output/conversion-generator.js +501 -0
- package/src/output/markdown.js +562 -0
- package/src/parsers/css-analyzer.js +488 -0
- package/src/parsers/html-parser.js +455 -0
- package/src/parsers/js-analyzer.js +413 -0
- package/src/utils/file-scanner.js +191 -0
- package/src/utils/ghost-detector.js +174 -0
- package/src/utils/library-detector.js +335 -0
- package/src/utils/specificity-calculator.js +251 -0
- package/src/utils/template-parser.js +260 -0
- package/src/utils/url-fetcher.js +123 -0
- package/src/utils/validation.js +278 -0
- package/src/utils/variable-extractor.js +271 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Conversion Context Generator
|
|
3
|
+
* Generates structured output optimized for LLM-assisted React/Next.js conversion
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Analyze jQuery/vanilla JS patterns and suggest React equivalents
|
|
10
|
+
* @param {Array} jsMatches - JS matches from analysis
|
|
11
|
+
* @returns {Object} Detected patterns and suggestions
|
|
12
|
+
*/
|
|
13
|
+
function analyzeJSPatterns(jsMatches) {
|
|
14
|
+
const patterns = {
|
|
15
|
+
stateNeeded: [],
|
|
16
|
+
eventHandlers: [],
|
|
17
|
+
effects: [],
|
|
18
|
+
animations: [],
|
|
19
|
+
apiCalls: [],
|
|
20
|
+
domManipulations: []
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const allContent = jsMatches.map(m => m.content || '').join('\n');
|
|
24
|
+
|
|
25
|
+
// Detect jQuery patterns
|
|
26
|
+
const jqueryPatterns = [
|
|
27
|
+
{ regex: /\$\(['"]([\.\#][^'"]+)['"]\)\.click/g, type: 'event', event: 'onClick' },
|
|
28
|
+
{ regex: /\$\(['"]([\.\#][^'"]+)['"]\)\.on\(['"]click/g, type: 'event', event: 'onClick' },
|
|
29
|
+
{ regex: /\$\(['"]([\.\#][^'"]+)['"]\)\.hover/g, type: 'event', event: 'onMouseEnter/onMouseLeave' },
|
|
30
|
+
{ regex: /\$\(['"]([\.\#][^'"]+)['"]\)\.submit/g, type: 'event', event: 'onSubmit' },
|
|
31
|
+
{ regex: /\$\(['"]([\.\#][^'"]+)['"]\)\.change/g, type: 'event', event: 'onChange' },
|
|
32
|
+
{ regex: /\$\(['"]([\.\#][^'"]+)['"]\)\.keyup/g, type: 'event', event: 'onKeyUp' },
|
|
33
|
+
{ regex: /\$\(['"]([\.\#][^'"]+)['"]\)\.keydown/g, type: 'event', event: 'onKeyDown' },
|
|
34
|
+
{ regex: /\.addClass\(['"]([^'"]+)['"]\)/g, type: 'state', name: 'classToggle' },
|
|
35
|
+
{ regex: /\.removeClass\(['"]([^'"]+)['"]\)/g, type: 'state', name: 'classToggle' },
|
|
36
|
+
{ regex: /\.toggleClass\(['"]([^'"]+)['"]\)/g, type: 'state', name: 'classToggle' },
|
|
37
|
+
{ regex: /\.show\(\)/g, type: 'state', name: 'isVisible' },
|
|
38
|
+
{ regex: /\.hide\(\)/g, type: 'state', name: 'isVisible' },
|
|
39
|
+
{ regex: /\.toggle\(\)/g, type: 'state', name: 'isVisible' },
|
|
40
|
+
{ regex: /\.slideToggle/g, type: 'animation', name: 'slideToggle', suggestion: 'framer-motion or CSS transitions' },
|
|
41
|
+
{ regex: /\.slideDown/g, type: 'animation', name: 'slideDown', suggestion: 'framer-motion AnimatePresence' },
|
|
42
|
+
{ regex: /\.slideUp/g, type: 'animation', name: 'slideUp', suggestion: 'framer-motion AnimatePresence' },
|
|
43
|
+
{ regex: /\.fadeIn/g, type: 'animation', name: 'fadeIn', suggestion: 'framer-motion or CSS transitions' },
|
|
44
|
+
{ regex: /\.fadeOut/g, type: 'animation', name: 'fadeOut', suggestion: 'framer-motion or CSS transitions' },
|
|
45
|
+
{ regex: /\.animate\(/g, type: 'animation', name: 'custom', suggestion: 'framer-motion or GSAP' },
|
|
46
|
+
{ regex: /\$\.ajax\(/g, type: 'api', suggestion: 'fetch + useEffect or React Query' },
|
|
47
|
+
{ regex: /\$\.get\(/g, type: 'api', suggestion: 'fetch + useEffect' },
|
|
48
|
+
{ regex: /\$\.post\(/g, type: 'api', suggestion: 'fetch + useEffect' },
|
|
49
|
+
{ regex: /\.html\(/g, type: 'dom', warning: 'Use dangerouslySetInnerHTML or restructure' },
|
|
50
|
+
{ regex: /\.text\(/g, type: 'dom', suggestion: 'Use state + JSX interpolation' },
|
|
51
|
+
{ regex: /\.val\(/g, type: 'state', name: 'inputValue', suggestion: 'Controlled input with useState' },
|
|
52
|
+
{ regex: /\.attr\(/g, type: 'dom', suggestion: 'Use props or state' },
|
|
53
|
+
{ regex: /\.css\(/g, type: 'dom', suggestion: 'Use inline styles or CSS modules' },
|
|
54
|
+
{ regex: /\.append\(/g, type: 'dom', warning: 'Restructure as state-driven rendering' },
|
|
55
|
+
{ regex: /\.prepend\(/g, type: 'dom', warning: 'Restructure as state-driven rendering' },
|
|
56
|
+
{ regex: /\.remove\(/g, type: 'dom', warning: 'Use conditional rendering' },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// Vanilla JS patterns
|
|
60
|
+
const vanillaPatterns = [
|
|
61
|
+
{ regex: /addEventListener\(['"]click['"]/g, type: 'event', event: 'onClick' },
|
|
62
|
+
{ regex: /addEventListener\(['"]submit['"]/g, type: 'event', event: 'onSubmit' },
|
|
63
|
+
{ regex: /addEventListener\(['"]change['"]/g, type: 'event', event: 'onChange' },
|
|
64
|
+
{ regex: /addEventListener\(['"]keyup['"]/g, type: 'event', event: 'onKeyUp' },
|
|
65
|
+
{ regex: /addEventListener\(['"]scroll['"]/g, type: 'effect', suggestion: 'useEffect with scroll listener' },
|
|
66
|
+
{ regex: /addEventListener\(['"]resize['"]/g, type: 'effect', suggestion: 'useEffect with resize listener or useWindowSize hook' },
|
|
67
|
+
{ regex: /classList\.add\(/g, type: 'state', name: 'classToggle' },
|
|
68
|
+
{ regex: /classList\.remove\(/g, type: 'state', name: 'classToggle' },
|
|
69
|
+
{ regex: /classList\.toggle\(/g, type: 'state', name: 'classToggle' },
|
|
70
|
+
{ regex: /\.style\./g, type: 'dom', suggestion: 'Use inline styles object or CSS modules' },
|
|
71
|
+
{ regex: /innerHTML/g, type: 'dom', warning: 'Avoid - use JSX or dangerouslySetInnerHTML' },
|
|
72
|
+
{ regex: /textContent/g, type: 'dom', suggestion: 'Use state + JSX' },
|
|
73
|
+
{ regex: /fetch\(/g, type: 'api', suggestion: 'Keep fetch, wrap in useEffect or use React Query' },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// GSAP patterns
|
|
77
|
+
const gsapPatterns = [
|
|
78
|
+
{ regex: /gsap\.to\(/g, type: 'animation', name: 'gsap.to', suggestion: 'Use @gsap/react useGSAP hook' },
|
|
79
|
+
{ regex: /gsap\.from\(/g, type: 'animation', name: 'gsap.from', suggestion: 'Use @gsap/react useGSAP hook' },
|
|
80
|
+
{ regex: /gsap\.fromTo\(/g, type: 'animation', name: 'gsap.fromTo', suggestion: 'Use @gsap/react useGSAP hook' },
|
|
81
|
+
{ regex: /gsap\.timeline\(/g, type: 'animation', name: 'gsap.timeline', suggestion: 'Use @gsap/react useGSAP hook' },
|
|
82
|
+
{ regex: /ScrollTrigger/g, type: 'animation', name: 'ScrollTrigger', suggestion: 'Use GSAP ScrollTrigger with useGSAP' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// Three.js patterns
|
|
86
|
+
const threePatterns = [
|
|
87
|
+
{ regex: /THREE\.Scene/g, type: 'library', name: 'Three.js', suggestion: 'Use @react-three/fiber Canvas component' },
|
|
88
|
+
{ regex: /THREE\.WebGLRenderer/g, type: 'library', name: 'Three.js', suggestion: 'Use @react-three/fiber Canvas' },
|
|
89
|
+
{ regex: /THREE\.PerspectiveCamera/g, type: 'library', name: 'Three.js', suggestion: 'Use @react-three/fiber PerspectiveCamera' },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const allPatterns = [...jqueryPatterns, ...vanillaPatterns, ...gsapPatterns, ...threePatterns];
|
|
93
|
+
|
|
94
|
+
for (const pattern of allPatterns) {
|
|
95
|
+
const matches = allContent.match(pattern.regex);
|
|
96
|
+
if (matches) {
|
|
97
|
+
const entry = {
|
|
98
|
+
pattern: pattern.regex.source,
|
|
99
|
+
count: matches.length,
|
|
100
|
+
...pattern
|
|
101
|
+
};
|
|
102
|
+
delete entry.regex;
|
|
103
|
+
|
|
104
|
+
switch (pattern.type) {
|
|
105
|
+
case 'event':
|
|
106
|
+
patterns.eventHandlers.push(entry);
|
|
107
|
+
break;
|
|
108
|
+
case 'state':
|
|
109
|
+
patterns.stateNeeded.push(entry);
|
|
110
|
+
break;
|
|
111
|
+
case 'animation':
|
|
112
|
+
patterns.animations.push(entry);
|
|
113
|
+
break;
|
|
114
|
+
case 'api':
|
|
115
|
+
patterns.apiCalls.push(entry);
|
|
116
|
+
break;
|
|
117
|
+
case 'dom':
|
|
118
|
+
patterns.domManipulations.push(entry);
|
|
119
|
+
break;
|
|
120
|
+
case 'effect':
|
|
121
|
+
patterns.effects.push(entry);
|
|
122
|
+
break;
|
|
123
|
+
case 'library':
|
|
124
|
+
patterns.animations.push(entry); // Libraries go under animations for now
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Detect $(document).ready patterns
|
|
131
|
+
if (/\$\(document\)\.ready/g.test(allContent) || /\$\(function/g.test(allContent)) {
|
|
132
|
+
patterns.effects.push({
|
|
133
|
+
pattern: 'document.ready',
|
|
134
|
+
suggestion: 'useEffect(() => { ... }, []) - runs once on mount'
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Detect DOMContentLoaded
|
|
139
|
+
if (/DOMContentLoaded/g.test(allContent)) {
|
|
140
|
+
patterns.effects.push({
|
|
141
|
+
pattern: 'DOMContentLoaded',
|
|
142
|
+
suggestion: 'useEffect(() => { ... }, []) - runs once on mount'
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return patterns;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generate suggested React state from patterns
|
|
151
|
+
* @param {Object} patterns - Patterns from analyzeJSPatterns
|
|
152
|
+
* @returns {Array} Suggested state variables
|
|
153
|
+
*/
|
|
154
|
+
function generateStateSuggestions(patterns) {
|
|
155
|
+
const stateVars = new Map();
|
|
156
|
+
|
|
157
|
+
// From class toggles
|
|
158
|
+
patterns.stateNeeded.forEach(p => {
|
|
159
|
+
if (p.name === 'classToggle') {
|
|
160
|
+
stateVars.set('isActive', { type: 'boolean', default: 'false', reason: 'Class toggle detected' });
|
|
161
|
+
}
|
|
162
|
+
if (p.name === 'isVisible') {
|
|
163
|
+
stateVars.set('isVisible', { type: 'boolean', default: 'true', reason: 'Show/hide detected' });
|
|
164
|
+
}
|
|
165
|
+
if (p.name === 'inputValue') {
|
|
166
|
+
stateVars.set('inputValue', { type: 'string', default: "''", reason: 'Input value manipulation detected' });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// From animations
|
|
171
|
+
patterns.animations.forEach(p => {
|
|
172
|
+
if (p.name === 'slideToggle' || p.name === 'slideDown' || p.name === 'slideUp') {
|
|
173
|
+
stateVars.set('isExpanded', { type: 'boolean', default: 'false', reason: 'Slide animation detected' });
|
|
174
|
+
}
|
|
175
|
+
if (p.name === 'fadeIn' || p.name === 'fadeOut') {
|
|
176
|
+
stateVars.set('isVisible', { type: 'boolean', default: 'true', reason: 'Fade animation detected' });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return Array.from(stateVars.entries()).map(([name, info]) => ({
|
|
181
|
+
name,
|
|
182
|
+
...info
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generate suggested dependencies
|
|
188
|
+
* @param {Object} patterns - Patterns from analyzeJSPatterns
|
|
189
|
+
* @param {Object} detectedLibraries - Libraries detected from the analysis
|
|
190
|
+
* @returns {Array} Suggested npm packages
|
|
191
|
+
*/
|
|
192
|
+
function generateDependencySuggestions(patterns, detectedLibraries = {}) {
|
|
193
|
+
const deps = new Map();
|
|
194
|
+
|
|
195
|
+
// Animation libraries
|
|
196
|
+
if (patterns.animations.length > 0) {
|
|
197
|
+
const hasGSAP = patterns.animations.some(p => p.name?.includes('gsap'));
|
|
198
|
+
const hasThree = patterns.animations.some(p => p.name === 'Three.js');
|
|
199
|
+
|
|
200
|
+
if (hasGSAP) {
|
|
201
|
+
deps.set('gsap', { version: '^3.12.0', reason: 'GSAP animations detected' });
|
|
202
|
+
deps.set('@gsap/react', { version: '^2.0.0', reason: 'React GSAP integration' });
|
|
203
|
+
} else if (patterns.animations.some(p => p.suggestion?.includes('framer-motion'))) {
|
|
204
|
+
deps.set('framer-motion', { version: '^10.0.0', reason: 'Animations need React-compatible library' });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (hasThree) {
|
|
208
|
+
deps.set('@react-three/fiber', { version: '^8.0.0', reason: 'Three.js detected, use R3F for React' });
|
|
209
|
+
deps.set('@react-three/drei', { version: '^9.0.0', reason: 'Helpful Three.js React utilities' });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// API calls
|
|
214
|
+
if (patterns.apiCalls.length > 0) {
|
|
215
|
+
deps.set('@tanstack/react-query', { version: '^5.0.0', reason: 'Better data fetching than raw useEffect' });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// From detected libraries
|
|
219
|
+
const libFiles = detectedLibraries.fromFiles || {};
|
|
220
|
+
Object.keys(libFiles).forEach(libName => {
|
|
221
|
+
const lowerName = libName.toLowerCase();
|
|
222
|
+
if (lowerName.includes('swiper')) {
|
|
223
|
+
deps.set('swiper', { version: '^11.0.0', reason: 'Swiper detected, use React-compatible version' });
|
|
224
|
+
}
|
|
225
|
+
if (lowerName.includes('aos')) {
|
|
226
|
+
deps.set('aos', { version: '^2.3.4', reason: 'AOS animations' });
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
return Array.from(deps.entries()).map(([name, info]) => ({
|
|
231
|
+
package: name,
|
|
232
|
+
...info
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Clean HTML for React conversion
|
|
238
|
+
* Removes jQuery-specific attributes, normalizes for JSX
|
|
239
|
+
* @param {string} html - Original HTML
|
|
240
|
+
* @returns {Object} Cleaned HTML and notes
|
|
241
|
+
*/
|
|
242
|
+
function cleanHTMLForReact(html) {
|
|
243
|
+
const notes = [];
|
|
244
|
+
let cleaned = html;
|
|
245
|
+
|
|
246
|
+
// Replace class with className
|
|
247
|
+
cleaned = cleaned.replace(/\bclass="/g, 'className="');
|
|
248
|
+
notes.push('Replaced `class` with `className`');
|
|
249
|
+
|
|
250
|
+
// Replace for with htmlFor
|
|
251
|
+
cleaned = cleaned.replace(/\bfor="/g, 'htmlFor="');
|
|
252
|
+
if (html.includes('for="')) {
|
|
253
|
+
notes.push('Replaced `for` with `htmlFor`');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Self-close void elements
|
|
257
|
+
const voidElements = ['br', 'hr', 'img', 'input', 'meta', 'link', 'source'];
|
|
258
|
+
voidElements.forEach(tag => {
|
|
259
|
+
const regex = new RegExp(`<${tag}([^>]*)(?<!/)>`, 'gi');
|
|
260
|
+
cleaned = cleaned.replace(regex, `<${tag}$1 />`);
|
|
261
|
+
});
|
|
262
|
+
notes.push('Self-closed void elements');
|
|
263
|
+
|
|
264
|
+
// Note about inline event handlers
|
|
265
|
+
if (/\bon\w+="/i.test(html)) {
|
|
266
|
+
notes.push('⚠️ Inline event handlers detected - convert to JSX handlers');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Note about inline styles
|
|
270
|
+
if (/style="[^"]+"/i.test(html)) {
|
|
271
|
+
notes.push('⚠️ Inline styles detected - convert to style={{ }} objects');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Remove data-* jQuery plugin attributes
|
|
275
|
+
const jqueryDataAttrs = ['data-toggle', 'data-target', 'data-dismiss', 'data-slide', 'data-ride'];
|
|
276
|
+
jqueryDataAttrs.forEach(attr => {
|
|
277
|
+
if (html.includes(attr)) {
|
|
278
|
+
notes.push(`⚠️ Bootstrap/jQuery \`${attr}\` detected - needs custom React handling`);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
return { cleaned, notes };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Generate the conversion context markdown
|
|
287
|
+
* @param {Object} analysis - Full analysis object
|
|
288
|
+
* @returns {string} Markdown optimized for LLM conversion
|
|
289
|
+
*/
|
|
290
|
+
function generateConversionContext(analysis) {
|
|
291
|
+
const {
|
|
292
|
+
targetInfo,
|
|
293
|
+
cssResults,
|
|
294
|
+
jsResults,
|
|
295
|
+
inlineStyles,
|
|
296
|
+
variableData = {},
|
|
297
|
+
detectedLibraries = {}
|
|
298
|
+
} = analysis;
|
|
299
|
+
|
|
300
|
+
// Analyze JS patterns
|
|
301
|
+
const allJsMatches = [
|
|
302
|
+
...jsResults.flatMap(r => r.matches || []),
|
|
303
|
+
...(analysis.inlineScripts || [])
|
|
304
|
+
];
|
|
305
|
+
const patterns = analyzeJSPatterns(allJsMatches);
|
|
306
|
+
const stateSuggestions = generateStateSuggestions(patterns);
|
|
307
|
+
const dependencies = generateDependencySuggestions(patterns, detectedLibraries);
|
|
308
|
+
|
|
309
|
+
// Clean HTML
|
|
310
|
+
const { cleaned: cleanedHTML, notes: htmlNotes } = cleanHTMLForReact(targetInfo.html);
|
|
311
|
+
|
|
312
|
+
let md = '';
|
|
313
|
+
|
|
314
|
+
// Header
|
|
315
|
+
md += `# React/Next.js Conversion Context\n\n`;
|
|
316
|
+
md += `> Generated for converting \`${targetInfo.selector}\` to React\n\n`;
|
|
317
|
+
md += `---\n\n`;
|
|
318
|
+
|
|
319
|
+
// Quick Start
|
|
320
|
+
md += `## 🚀 Quick Start for LLM\n\n`;
|
|
321
|
+
md += `Paste this entire document to Claude/GPT with the prompt:\n\n`;
|
|
322
|
+
md += `> "Convert this HTML component to a React functional component with TypeScript. `;
|
|
323
|
+
md += `Use the suggested state, implement the event handlers, and apply the CSS as CSS Modules."\n\n`;
|
|
324
|
+
md += `---\n\n`;
|
|
325
|
+
|
|
326
|
+
// Suggested State
|
|
327
|
+
if (stateSuggestions.length > 0) {
|
|
328
|
+
md += `## 📊 Suggested React State\n\n`;
|
|
329
|
+
md += `Based on detected JS patterns, you'll likely need:\n\n`;
|
|
330
|
+
md += `\`\`\`typescript\n`;
|
|
331
|
+
stateSuggestions.forEach(s => {
|
|
332
|
+
md += `const [${s.name}, set${s.name.charAt(0).toUpperCase() + s.name.slice(1)}] = useState<${s.type}>(${s.default}); // ${s.reason}\n`;
|
|
333
|
+
});
|
|
334
|
+
md += `\`\`\`\n\n`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Event Handlers
|
|
338
|
+
if (patterns.eventHandlers.length > 0) {
|
|
339
|
+
md += `## 🎯 Event Handlers to Implement\n\n`;
|
|
340
|
+
md += `| jQuery/Vanilla Pattern | React Equivalent | Count |\n`;
|
|
341
|
+
md += `|------------------------|------------------|-------|\n`;
|
|
342
|
+
patterns.eventHandlers.forEach(e => {
|
|
343
|
+
md += `| \`${e.pattern.substring(0, 40)}\` | \`${e.event}\` | ${e.count} |\n`;
|
|
344
|
+
});
|
|
345
|
+
md += `\n`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Animations
|
|
349
|
+
if (patterns.animations.length > 0) {
|
|
350
|
+
md += `## ✨ Animations Detected\n\n`;
|
|
351
|
+
patterns.animations.forEach(a => {
|
|
352
|
+
md += `- **${a.name || a.pattern}** (${a.count}x) → ${a.suggestion}\n`;
|
|
353
|
+
});
|
|
354
|
+
md += `\n`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// DOM Manipulations (Warnings)
|
|
358
|
+
if (patterns.domManipulations.length > 0) {
|
|
359
|
+
md += `## ⚠️ DOM Manipulations to Refactor\n\n`;
|
|
360
|
+
md += `These imperative patterns need to become declarative React:\n\n`;
|
|
361
|
+
patterns.domManipulations.forEach(d => {
|
|
362
|
+
const msg = d.warning || d.suggestion;
|
|
363
|
+
md += `- \`${d.pattern.substring(0, 30)}\` → ${msg}\n`;
|
|
364
|
+
});
|
|
365
|
+
md += `\n`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Effects
|
|
369
|
+
if (patterns.effects.length > 0) {
|
|
370
|
+
md += `## 🔄 useEffect Patterns Needed\n\n`;
|
|
371
|
+
patterns.effects.forEach(e => {
|
|
372
|
+
md += `- ${e.pattern} → ${e.suggestion}\n`;
|
|
373
|
+
});
|
|
374
|
+
md += `\n`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Dependencies
|
|
378
|
+
if (dependencies.length > 0) {
|
|
379
|
+
md += `## 📦 Suggested Dependencies\n\n`;
|
|
380
|
+
md += `\`\`\`bash\nnpm install`;
|
|
381
|
+
dependencies.forEach(d => {
|
|
382
|
+
md += ` ${d.package}`;
|
|
383
|
+
});
|
|
384
|
+
md += `\n\`\`\`\n\n`;
|
|
385
|
+
md += `| Package | Version | Reason |\n`;
|
|
386
|
+
md += `|---------|---------|--------|\n`;
|
|
387
|
+
dependencies.forEach(d => {
|
|
388
|
+
md += `| ${d.package} | ${d.version} | ${d.reason} |\n`;
|
|
389
|
+
});
|
|
390
|
+
md += `\n`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
md += `---\n\n`;
|
|
394
|
+
|
|
395
|
+
// HTML Section
|
|
396
|
+
md += `## 📄 Component HTML (JSX-Ready)\n\n`;
|
|
397
|
+
if (htmlNotes.length > 0) {
|
|
398
|
+
md += `**Transformations applied:**\n`;
|
|
399
|
+
htmlNotes.forEach(n => md += `- ${n}\n`);
|
|
400
|
+
md += `\n`;
|
|
401
|
+
}
|
|
402
|
+
md += `\`\`\`jsx\n${cleanedHTML}\n\`\`\`\n\n`;
|
|
403
|
+
|
|
404
|
+
md += `---\n\n`;
|
|
405
|
+
|
|
406
|
+
// CSS Variables
|
|
407
|
+
if (variableData && variableData.usedVariables && variableData.usedVariables.length > 0) {
|
|
408
|
+
md += `## 🎨 CSS Variables Required\n\n`;
|
|
409
|
+
const cssVars = variableData.cssVariables || {};
|
|
410
|
+
const scssVars = variableData.scssVariables || {};
|
|
411
|
+
|
|
412
|
+
if (Object.keys(cssVars).length > 0) {
|
|
413
|
+
md += `### CSS Custom Properties\n\n`;
|
|
414
|
+
md += `Add to your global styles or component:\n\n`;
|
|
415
|
+
md += `\`\`\`css\n:root {\n`;
|
|
416
|
+
for (const [name, defs] of Object.entries(cssVars)) {
|
|
417
|
+
if (defs[0]) {
|
|
418
|
+
md += ` ${name}: ${defs[0].value};\n`;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
md += `}\n\`\`\`\n\n`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (Object.keys(scssVars).length > 0) {
|
|
425
|
+
md += `### SCSS Variables\n\n`;
|
|
426
|
+
md += `\`\`\`scss\n`;
|
|
427
|
+
for (const [name, defs] of Object.entries(scssVars)) {
|
|
428
|
+
if (defs[0]) {
|
|
429
|
+
md += `${name}: ${defs[0].value};\n`;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
md += `\`\`\`\n\n`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Undefined variables warning
|
|
436
|
+
const undefinedVars = [
|
|
437
|
+
...(variableData.undefinedCSSVariables || []),
|
|
438
|
+
...(variableData.undefinedSCSSVariables || [])
|
|
439
|
+
];
|
|
440
|
+
if (undefinedVars.length > 0) {
|
|
441
|
+
md += `### ⚠️ Variables Used But Not Found\n\n`;
|
|
442
|
+
undefinedVars.forEach(v => md += `- \`${v}\`\n`);
|
|
443
|
+
md += `\n> These may come from a framework or external stylesheet.\n\n`;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
md += `---\n\n`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// CSS Section
|
|
450
|
+
md += `## 🎨 Component CSS\n\n`;
|
|
451
|
+
md += `Convert to CSS Modules (\`Component.module.css\`) or styled-components:\n\n`;
|
|
452
|
+
|
|
453
|
+
// Collect all CSS
|
|
454
|
+
const allCSS = [];
|
|
455
|
+
cssResults.forEach(r => {
|
|
456
|
+
if (r.matches && r.matches.length > 0) {
|
|
457
|
+
r.matches.forEach(m => {
|
|
458
|
+
allCSS.push(m.content);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
inlineStyles?.forEach(s => {
|
|
463
|
+
allCSS.push(s.content);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (allCSS.length > 0) {
|
|
467
|
+
// Limit in conversion mode too
|
|
468
|
+
const limitedCSS = allCSS.slice(0, 50);
|
|
469
|
+
md += `\`\`\`css\n${limitedCSS.join('\n\n')}\n\`\`\`\n\n`;
|
|
470
|
+
if (allCSS.length > 50) {
|
|
471
|
+
md += `> ⚠️ ${allCSS.length - 50} more CSS rules omitted. Use \`--max-rules\` to adjust.\n\n`;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
md += `---\n\n`;
|
|
476
|
+
|
|
477
|
+
// Checklist
|
|
478
|
+
md += `## ✅ Conversion Checklist\n\n`;
|
|
479
|
+
md += `- [ ] Create \`Component.tsx\` file\n`;
|
|
480
|
+
md += `- [ ] Create \`Component.module.css\` file\n`;
|
|
481
|
+
md += `- [ ] Implement useState hooks\n`;
|
|
482
|
+
md += `- [ ] Convert event handlers to JSX\n`;
|
|
483
|
+
if (patterns.animations.length > 0) {
|
|
484
|
+
md += `- [ ] Set up animation library (framer-motion/GSAP)\n`;
|
|
485
|
+
}
|
|
486
|
+
if (patterns.apiCalls.length > 0) {
|
|
487
|
+
md += `- [ ] Convert API calls to fetch/React Query\n`;
|
|
488
|
+
}
|
|
489
|
+
md += `- [ ] Test component in isolation\n`;
|
|
490
|
+
md += `- [ ] Integrate into Next.js page/layout\n\n`;
|
|
491
|
+
|
|
492
|
+
return md;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
module.exports = {
|
|
496
|
+
analyzeJSPatterns,
|
|
497
|
+
generateStateSuggestions,
|
|
498
|
+
generateDependencySuggestions,
|
|
499
|
+
cleanHTMLForReact,
|
|
500
|
+
generateConversionContext
|
|
501
|
+
};
|