@feardread/fear 1.0.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.
Files changed (99) hide show
  1. package/FEAR.js +459 -0
  2. package/FEARServer.js +280 -0
  3. package/controllers/agent.js +438 -0
  4. package/controllers/auth/index.js +345 -0
  5. package/controllers/auth/token.js +50 -0
  6. package/controllers/blog.js +105 -0
  7. package/controllers/brand.js +10 -0
  8. package/controllers/cart.js +425 -0
  9. package/controllers/category.js +9 -0
  10. package/controllers/coupon.js +63 -0
  11. package/controllers/crud/crud.js +508 -0
  12. package/controllers/crud/index.js +36 -0
  13. package/controllers/email.js +34 -0
  14. package/controllers/enquiry.js +65 -0
  15. package/controllers/events.js +9 -0
  16. package/controllers/order.js +125 -0
  17. package/controllers/payment.js +31 -0
  18. package/controllers/product.js +147 -0
  19. package/controllers/review.js +247 -0
  20. package/controllers/tag.js +10 -0
  21. package/controllers/task.js +10 -0
  22. package/controllers/upload.js +41 -0
  23. package/controllers/user.js +401 -0
  24. package/index.js +7 -0
  25. package/libs/agent/index.js +561 -0
  26. package/libs/agent/modules/ai/ai.js +285 -0
  27. package/libs/agent/modules/ai/chat.js +518 -0
  28. package/libs/agent/modules/ai/config.js +688 -0
  29. package/libs/agent/modules/ai/operations.js +787 -0
  30. package/libs/agent/modules/analyze/api.js +546 -0
  31. package/libs/agent/modules/analyze/dorks.js +395 -0
  32. package/libs/agent/modules/ccard/README.md +454 -0
  33. package/libs/agent/modules/ccard/audit.js +479 -0
  34. package/libs/agent/modules/ccard/checker.js +674 -0
  35. package/libs/agent/modules/ccard/payment-processors.json +16 -0
  36. package/libs/agent/modules/ccard/validator.js +629 -0
  37. package/libs/agent/modules/code/analyzer.js +303 -0
  38. package/libs/agent/modules/code/jquery.js +1093 -0
  39. package/libs/agent/modules/code/react.js +1536 -0
  40. package/libs/agent/modules/code/refactor.js +499 -0
  41. package/libs/agent/modules/crypto/exchange.js +564 -0
  42. package/libs/agent/modules/net/proxy.js +409 -0
  43. package/libs/agent/modules/security/cve.js +442 -0
  44. package/libs/agent/modules/security/monitor.js +360 -0
  45. package/libs/agent/modules/security/scanner.js +300 -0
  46. package/libs/agent/modules/security/vulnerability.js +506 -0
  47. package/libs/agent/modules/security/web.js +465 -0
  48. package/libs/agent/modules/utils/browser.js +492 -0
  49. package/libs/agent/modules/utils/colorizer.js +285 -0
  50. package/libs/agent/modules/utils/manager.js +478 -0
  51. package/libs/cloud/index.js +228 -0
  52. package/libs/config/db.js +21 -0
  53. package/libs/config/validator.js +82 -0
  54. package/libs/db/index.js +318 -0
  55. package/libs/emailer/imap.js +126 -0
  56. package/libs/emailer/info.js +41 -0
  57. package/libs/emailer/smtp.js +77 -0
  58. package/libs/handler/async.js +3 -0
  59. package/libs/handler/error.js +66 -0
  60. package/libs/handler/index.js +161 -0
  61. package/libs/logger/index.js +49 -0
  62. package/libs/logger/morgan.js +24 -0
  63. package/libs/passport/passport.js +109 -0
  64. package/libs/search/api.js +384 -0
  65. package/libs/search/features.js +219 -0
  66. package/libs/search/service.js +64 -0
  67. package/libs/swagger/config.js +18 -0
  68. package/libs/swagger/index.js +35 -0
  69. package/libs/validator/index.js +254 -0
  70. package/models/blog.js +31 -0
  71. package/models/brand.js +12 -0
  72. package/models/cart.js +14 -0
  73. package/models/category.js +11 -0
  74. package/models/coupon.js +9 -0
  75. package/models/customer.js +0 -0
  76. package/models/enquiry.js +29 -0
  77. package/models/events.js +13 -0
  78. package/models/order.js +94 -0
  79. package/models/product.js +32 -0
  80. package/models/review.js +14 -0
  81. package/models/tag.js +10 -0
  82. package/models/task.js +11 -0
  83. package/models/user.js +68 -0
  84. package/package.json +12 -0
  85. package/routes/agent.js +615 -0
  86. package/routes/auth.js +13 -0
  87. package/routes/blog.js +19 -0
  88. package/routes/brand.js +15 -0
  89. package/routes/cart.js +105 -0
  90. package/routes/category.js +16 -0
  91. package/routes/coupon.js +15 -0
  92. package/routes/enquiry.js +14 -0
  93. package/routes/events.js +16 -0
  94. package/routes/mail.js +170 -0
  95. package/routes/order.js +19 -0
  96. package/routes/product.js +22 -0
  97. package/routes/review.js +11 -0
  98. package/routes/task.js +12 -0
  99. package/routes/user.js +17 -0
@@ -0,0 +1,1536 @@
1
+ // modules/html-to-react.js - Enhanced HTML to React Component Converter
2
+ const fs = require('fs').promises;
3
+ const path = require('path');
4
+ const colorizer = require('../utils/colorizer');
5
+
6
+ const HTMLToReact = function() {
7
+ this.componentCounter = 0;
8
+ this.extractedComponents = [];
9
+ this.externalStylesheets = [];
10
+ this.externalScripts = [];
11
+ this.jsModules = new Map();
12
+ this.globalVariables = new Set();
13
+ this.dependencies = new Set();
14
+ this.helperFunctions = new Set();
15
+ }
16
+
17
+ HTMLToReact.prototype = {
18
+
19
+ convert(args) {
20
+ const filePath = args[0];
21
+ const outputDir = args[1] || './react-components';
22
+
23
+ if (!filePath) {
24
+ console.log(colorizer.error('Usage: html-to-react <html-file> [output-dir]'));
25
+ console.log(colorizer.info('Examples:'));
26
+ console.log(colorizer.dim(' html-to-react index.html'));
27
+ console.log(colorizer.dim(' html-to-react page.html ./src/components\n'));
28
+ return Promise.resolve();
29
+ }
30
+
31
+ console.log(colorizer.header('HTML to React Converter'));
32
+ console.log(colorizer.separator());
33
+ console.log(colorizer.cyan('Input: ') + colorizer.bright(filePath));
34
+ console.log(colorizer.cyan('Output: ') + colorizer.bright(outputDir));
35
+ console.log();
36
+
37
+ const inputDir = path.dirname(filePath);
38
+
39
+ return fs.readFile(filePath, 'utf8')
40
+ .then(html => {
41
+ console.log(colorizer.info('Parsing HTML...'));
42
+
43
+ const converted = this.convertHTML(html, path.basename(filePath, '.html'), inputDir);
44
+
45
+ return this.saveComponents(converted, outputDir, inputDir);
46
+ })
47
+ .then(files => {
48
+ console.log(colorizer.success('\nConversion complete!'));
49
+ console.log(colorizer.cyan('Generated files:'));
50
+ files.forEach(file => {
51
+ console.log(colorizer.bullet(file));
52
+ });
53
+ console.log();
54
+
55
+ this.printConversionSummary();
56
+ })
57
+ .catch(err => {
58
+ console.log(colorizer.error('Conversion failed: ' + err.message));
59
+ if (err.stack) {
60
+ console.log(colorizer.dim(err.stack));
61
+ }
62
+ console.log();
63
+ });
64
+ },
65
+
66
+ convertHTML(html, baseName, inputDir) {
67
+ this.extractedComponents = [];
68
+ this.externalStylesheets = [];
69
+ this.externalScripts = [];
70
+ this.jsModules.clear();
71
+ this.globalVariables.clear();
72
+ this.dependencies.clear();
73
+ this.helperFunctions.clear();
74
+
75
+ this.extractExternalResources(html, inputDir);
76
+ html = this.cleanHTML(html);
77
+
78
+ const mainComponent = this.createMainComponent(html, baseName);
79
+
80
+ mainComponent.jsx = this.convertInlineStyles(mainComponent.jsx);
81
+ mainComponent.jsx = this.convertAttributes(mainComponent.jsx);
82
+ mainComponent.jsx = this.convertEventHandlers(mainComponent.jsx);
83
+ mainComponent.jsx = this.fixJSXIssues(mainComponent.jsx);
84
+
85
+ this.extractComponents(mainComponent);
86
+
87
+ if (this.externalStylesheets.length > 0) {
88
+ mainComponent.externalStyles = this.externalStylesheets;
89
+ }
90
+
91
+ if (this.externalScripts.length > 0) {
92
+ mainComponent.externalScripts = this.externalScripts;
93
+ }
94
+
95
+ mainComponent.dependencies = Array.from(this.dependencies);
96
+ mainComponent.helperFunctions = Array.from(this.helperFunctions);
97
+
98
+ return {
99
+ main: mainComponent,
100
+ components: this.extractedComponents,
101
+ jsModules: Array.from(this.jsModules.values())
102
+ };
103
+ },
104
+
105
+ extractExternalResources(html, inputDir) {
106
+ const linkRegex = /<link[^>]+rel=["']stylesheet["'][^>]*href=["']([^"']+)["'][^>]*>/gi;
107
+ let match;
108
+
109
+ while ((match = linkRegex.exec(html)) !== null) {
110
+ const href = match[1];
111
+ if (!href.startsWith('http://') && !href.startsWith('https://') && !href.startsWith('//')) {
112
+ this.externalStylesheets.push({
113
+ original: href,
114
+ local: path.join(inputDir, href)
115
+ });
116
+ console.log(colorizer.info(' Found stylesheet: ' + href));
117
+ } else {
118
+ console.log(colorizer.warning(' Skipping external stylesheet: ' + href));
119
+ }
120
+ }
121
+
122
+ const scriptRegex = /<script[^>]*src=["']([^"']+)["'][^>]*>[\s\S]*?<\/script>/gi;
123
+
124
+ while ((match = scriptRegex.exec(html)) !== null) {
125
+ const src = match[1];
126
+ const scriptTag = match[0];
127
+ const isModule = /type=["']module["']/i.test(scriptTag);
128
+ const isDefer = /defer/i.test(scriptTag);
129
+ const isAsync = /async/i.test(scriptTag);
130
+
131
+ if (!src.startsWith('http://') && !src.startsWith('https://') && !src.startsWith('//')) {
132
+ this.externalScripts.push({
133
+ original: src,
134
+ local: path.join(inputDir, src),
135
+ isModule,
136
+ isDefer,
137
+ isAsync
138
+ });
139
+ console.log(colorizer.info(' Found script: ' + src + (isModule ? ' (module)' : '')));
140
+ } else {
141
+ this.detectLibrary(src);
142
+ console.log(colorizer.warning(' Skipping CDN script: ' + src));
143
+ }
144
+ }
145
+ },
146
+
147
+ detectLibrary(src) {
148
+ const libraries = {
149
+ 'jquery': 'jquery',
150
+ 'lodash': 'lodash',
151
+ 'axios': 'axios',
152
+ 'moment': 'moment',
153
+ 'chart': 'chart.js',
154
+ 'three': 'three',
155
+ 'd3': 'd3',
156
+ 'gsap': 'gsap'
157
+ };
158
+
159
+ for (const [key, pkg] of Object.entries(libraries)) {
160
+ if (src.toLowerCase().includes(key)) {
161
+ this.dependencies.add(pkg);
162
+ console.log(colorizer.info(' Detected library: ' + pkg));
163
+ break;
164
+ }
165
+ }
166
+ },
167
+
168
+ cleanHTML(html) {
169
+ html = html.replace(/<!DOCTYPE[^>]*>/gi, '');
170
+ html = html.replace(/<html[^>]*>/gi, '');
171
+ html = html.replace(/<\/html>/gi, '');
172
+ html = html.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, '');
173
+ html = html.replace(/<body[^>]*>/gi, '');
174
+ html = html.replace(/<\/body>/gi, '');
175
+ html = html.replace(/<!--(?!\[if)[\s\S]*?-->/g, '');
176
+ html = html.replace(/<link[^>]+>/gi, '');
177
+
178
+ return html.trim();
179
+ },
180
+
181
+ createMainComponent(html, baseName) {
182
+ const componentName = this.toPascalCase(baseName);
183
+
184
+ const styleMatches = html.match(/<style[^>]*>([\s\S]*?)<\/style>/gi);
185
+ let css = '';
186
+
187
+ if (styleMatches) {
188
+ styleMatches.forEach(match => {
189
+ const content = match.replace(/<\/?style[^>]*>/gi, '');
190
+ css += content + '\n';
191
+ });
192
+ html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
193
+ }
194
+
195
+ const scriptMatches = html.match(/<script(?![^>]*src=)[^>]*>([\s\S]*?)<\/script>/gi);
196
+ const hooks = [];
197
+ const functions = [];
198
+ const effects = [];
199
+ const utilities = [];
200
+
201
+ if (scriptMatches) {
202
+ scriptMatches.forEach(match => {
203
+ const jsContent = match.replace(/<\/?script[^>]*>/gi, '');
204
+ if (jsContent.trim()) {
205
+ const converted = this.convertJavaScriptToReact(jsContent);
206
+
207
+ if (converted.hooks.length > 0) {
208
+ hooks.push(...converted.hooks);
209
+ }
210
+ if (converted.functions.length > 0) {
211
+ functions.push(...converted.functions);
212
+ }
213
+ if (converted.effects.length > 0) {
214
+ effects.push(...converted.effects);
215
+ }
216
+ if (converted.utilities.length > 0) {
217
+ utilities.push(...converted.utilities);
218
+ }
219
+ }
220
+ });
221
+ html = html.replace(/<script(?![^>]*src=)[^>]*>[\s\S]*?<\/script>/gi, '');
222
+ }
223
+
224
+ return {
225
+ name: componentName,
226
+ jsx: html,
227
+ css: css,
228
+ imports: this.determineRequiredImports(hooks, functions, effects, utilities),
229
+ hooks: hooks,
230
+ functions: functions,
231
+ effects: effects,
232
+ utilities: utilities
233
+ };
234
+ },
235
+
236
+ determineRequiredImports(hooks, functions, effects, utilities) {
237
+ const imports = new Set(['React']);
238
+
239
+ if (hooks.length > 0 || hooks.some(h => h.includes('useState'))) {
240
+ imports.add('useState');
241
+ }
242
+ if (effects.length > 0 || hooks.some(h => h.includes('useEffect'))) {
243
+ imports.add('useEffect');
244
+ }
245
+ if (hooks.some(h => h.includes('useRef')) || functions.some(f => f.body && f.body.includes('ref'))) {
246
+ imports.add('useRef');
247
+ }
248
+ if (hooks.some(h => h.includes('useCallback')) || functions.some(f => f.useCallback)) {
249
+ imports.add('useCallback');
250
+ }
251
+ if (hooks.some(h => h.includes('useMemo'))) {
252
+ imports.add('useMemo');
253
+ }
254
+
255
+ return Array.from(imports);
256
+ },
257
+
258
+ convertJavaScriptToReact(jsCode) {
259
+ const hooks = [];
260
+ const functions = [];
261
+ const effects = [];
262
+ const utilities = [];
263
+
264
+ const analysis = this.analyzeJavaScript(jsCode);
265
+
266
+ // Convert variables to state
267
+ analysis.variables.forEach(variable => {
268
+ if (!this.isConstant(variable.name) && this.needsState(variable, jsCode)) {
269
+ const initialValue = variable.value || 'null';
270
+ const stateVar = `const [${variable.name}, set${this.toPascalCase(variable.name)}] = useState(${initialValue});`;
271
+ hooks.push(stateVar);
272
+ this.globalVariables.add(variable.name);
273
+ console.log(colorizer.info(' Converting variable to state: ' + variable.name));
274
+ } else {
275
+ hooks.push(`const ${variable.name} = ${variable.value};`);
276
+ }
277
+ });
278
+
279
+ // Convert DOM ready handlers
280
+ if (this.hasDOMReadyHandler(jsCode)) {
281
+ const effectCode = this.extractDOMReadyCode(jsCode);
282
+ if (effectCode) {
283
+ effects.push({
284
+ code: effectCode,
285
+ dependencies: '[]',
286
+ comment: 'Component mount effect (converted from DOM ready)'
287
+ });
288
+ console.log(colorizer.info(' Converting DOM ready to useEffect'));
289
+ }
290
+ }
291
+
292
+ // Convert event listeners
293
+ const listeners = this.extractEventListeners(jsCode);
294
+ listeners.forEach(listener => {
295
+ effects.push({
296
+ code: this.convertEventListenerToEffect(listener),
297
+ dependencies: listener.dependencies || '[]',
298
+ comment: `Event listener for ${listener.event}`
299
+ });
300
+ console.log(colorizer.info(' Converting event listener: ' + listener.event));
301
+ });
302
+
303
+ // Extract functions with better handling
304
+ const functionMatches = this.extractFunctions(jsCode);
305
+ functionMatches.forEach(func => {
306
+ const convertedFunc = this.convertFunctionToReact(func);
307
+ functions.push(convertedFunc);
308
+ console.log(colorizer.info(' Extracting function: ' + func.name));
309
+ });
310
+
311
+ // Extract utility functions that can be outside component
312
+ const utilityFuncs = this.extractUtilityFunctions(jsCode);
313
+ utilityFuncs.forEach(util => {
314
+ utilities.push(util);
315
+ this.helperFunctions.add(util.name);
316
+ console.log(colorizer.info(' Extracting utility function: ' + util.name));
317
+ });
318
+
319
+ // Extract classes
320
+ const classes = this.extractClasses(jsCode);
321
+ if (classes.length > 0) {
322
+ console.log(colorizer.warning(' Found ' + classes.length + ' class(es) - consider refactoring to hooks'));
323
+ }
324
+
325
+ return { hooks, functions, effects, utilities };
326
+ },
327
+
328
+ needsState(variable, code) {
329
+ // Check if variable is reassigned in the code
330
+ const reassignmentRegex = new RegExp(`\\b${variable.name}\\s*=`, 'g');
331
+ const matches = code.match(reassignmentRegex);
332
+ return matches && matches.length > 1; // More than initial assignment
333
+ },
334
+
335
+ hasDOMReadyHandler(jsCode) {
336
+ return jsCode.includes('DOMContentLoaded') ||
337
+ jsCode.includes('window.onload') ||
338
+ jsCode.includes('$(document).ready') ||
339
+ jsCode.includes('$(function');
340
+ },
341
+
342
+ analyzeJavaScript(code) {
343
+ const variables = [];
344
+
345
+ const varRegex = /(let|var|const)\s+(\w+)\s*=\s*([^;]+);/g;
346
+ let match;
347
+
348
+ while ((match = varRegex.exec(code)) !== null) {
349
+ const [, type, name, value] = match;
350
+ variables.push({ type, name, value: value.trim() });
351
+ }
352
+
353
+ return { variables };
354
+ },
355
+
356
+ isConstant(varName) {
357
+ return /^[A-Z_]+$/.test(varName) ||
358
+ varName.startsWith('CONFIG') ||
359
+ varName.startsWith('DEFAULT') ||
360
+ varName.startsWith('CONST');
361
+ },
362
+
363
+ extractDOMReadyCode(jsCode) {
364
+ let code = jsCode;
365
+
366
+ // Remove various DOM ready wrappers
367
+ code = code.replace(/document\.addEventListener\s*\(\s*['"]DOMContentLoaded['"]\s*,\s*function\s*\(\)\s*\{/g, '');
368
+ code = code.replace(/document\.addEventListener\s*\(\s*['"]DOMContentLoaded['"]\s*,\s*\(\)\s*=>\s*\{/g, '');
369
+ code = code.replace(/window\.onload\s*=\s*function\s*\(\)\s*\{/g, '');
370
+ code = code.replace(/window\.onload\s*=\s*\(\)\s*=>\s*\{/g, '');
371
+ code = code.replace(/\$\(document\)\.ready\s*\(\s*function\s*\(\)\s*\{/g, '');
372
+ code = code.replace(/\$\(function\s*\(\)\s*\{/g, '');
373
+
374
+ // Remove closing braces
375
+ code = code.replace(/\}\s*\)\s*;?\s*$/g, '');
376
+ code = code.replace(/\}\s*;?\s*$/g, '');
377
+
378
+ return this.cleanJSCode(code);
379
+ },
380
+
381
+ extractEventListeners(jsCode) {
382
+ const listeners = [];
383
+
384
+ // Standard addEventListener
385
+ const listenerRegex = /(\w+)\.addEventListener\s*\(\s*['"](\w+)['"]\s*,\s*((?:function[^}]+\}|\(\)[^}]+\}|\w+))\s*\)/g;
386
+ let match;
387
+
388
+ while ((match = listenerRegex.exec(jsCode)) !== null) {
389
+ const [, element, event, handler] = match;
390
+ listeners.push({
391
+ element,
392
+ event,
393
+ handler,
394
+ dependencies: this.extractDependencies(handler)
395
+ });
396
+ }
397
+
398
+ // jQuery event listeners
399
+ const jqueryRegex = /\$\([^)]+\)\.(on|click|change|submit)\s*\(\s*['"]?(\w+)?['"]?\s*,?\s*(function[^}]+\}|\(\)[^}]+\})\s*\)/g;
400
+
401
+ while ((match = jqueryRegex.exec(jsCode)) !== null) {
402
+ const [, method, event, handler] = match;
403
+ listeners.push({
404
+ element: 'element',
405
+ event: event || method,
406
+ handler,
407
+ dependencies: this.extractDependencies(handler),
408
+ isJQuery: true
409
+ });
410
+ }
411
+
412
+ return listeners;
413
+ },
414
+
415
+ extractDependencies(code) {
416
+ const vars = new Set();
417
+ const varRegex = /\b([a-z_$][a-z0-9_$]*)\b/gi;
418
+ let match;
419
+
420
+ const reserved = ['function', 'return', 'const', 'let', 'var', 'if', 'else',
421
+ 'for', 'while', 'true', 'false', 'null', 'undefined',
422
+ 'this', 'window', 'document', 'console', 'e', 'event'];
423
+
424
+ while ((match = varRegex.exec(code)) !== null) {
425
+ const varName = match[1];
426
+ if (!reserved.includes(varName) && this.globalVariables.has(varName)) {
427
+ vars.add(varName);
428
+ }
429
+ }
430
+
431
+ return Array.from(vars).length > 0 ? `[${Array.from(vars).join(', ')}]` : '[]';
432
+ },
433
+
434
+ convertEventListenerToEffect(listener) {
435
+ const handlerName = `handle${this.toPascalCase(listener.event)}`;
436
+ let code = '';
437
+
438
+ if (listener.isJQuery) {
439
+ code = `// TODO: Replace jQuery selector with ref\n`;
440
+ code += `const ${handlerName} = ${listener.handler};\n`;
441
+ code += `// $(element).on('${listener.event}', ${handlerName});\n`;
442
+ code += `return () => {}; // Add cleanup`;
443
+ } else {
444
+ code = `const ${handlerName} = ${listener.handler};\n`;
445
+ code += `const element = ${listener.element}; // TODO: Use ref\n`;
446
+ code += `if (element) {\n`;
447
+ code += ` element.addEventListener('${listener.event}', ${handlerName});\n`;
448
+ code += ` return () => element.removeEventListener('${listener.event}', ${handlerName});\n`;
449
+ code += `}`;
450
+ }
451
+
452
+ return code;
453
+ },
454
+
455
+ extractFunctions(jsCode) {
456
+ const functions = [];
457
+
458
+ // Regular function declarations
459
+ const functionRegex = /(async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*\{([\s\S]*?)\n\}/g;
460
+ let match;
461
+
462
+ while ((match = functionRegex.exec(jsCode)) !== null) {
463
+ const [, asyncKeyword, name, params, body] = match;
464
+ functions.push({
465
+ name,
466
+ params: params.trim(),
467
+ body: body.trim(),
468
+ isAsync: !!asyncKeyword,
469
+ type: 'function'
470
+ });
471
+ }
472
+
473
+ // Arrow functions
474
+ const arrowRegex = /(const|let|var)\s+(\w+)\s*=\s*(async\s+)?\(([^)]*)\)\s*=>\s*\{([\s\S]*?)\n\}/g;
475
+
476
+ while ((match = arrowRegex.exec(jsCode)) !== null) {
477
+ const [, , name, asyncKeyword, params, body] = match;
478
+ functions.push({
479
+ name,
480
+ params: params.trim(),
481
+ body: body.trim(),
482
+ isAsync: !!asyncKeyword,
483
+ type: 'arrow'
484
+ });
485
+ }
486
+
487
+ // Single-line arrow functions
488
+ const shortArrowRegex = /(const|let|var)\s+(\w+)\s*=\s*(async\s+)?\(([^)]*)\)\s*=>\s*([^;{]+);/g;
489
+
490
+ while ((match = shortArrowRegex.exec(jsCode)) !== null) {
491
+ const [, , name, asyncKeyword, params, body] = match;
492
+ functions.push({
493
+ name,
494
+ params: params.trim(),
495
+ body: `return ${body.trim()};`,
496
+ isAsync: !!asyncKeyword,
497
+ type: 'arrow',
498
+ isShort: true
499
+ });
500
+ }
501
+
502
+ return functions;
503
+ },
504
+
505
+ extractUtilityFunctions(jsCode) {
506
+ const utilities = [];
507
+
508
+ // Functions that are pure utilities (no DOM, no state dependencies)
509
+ const functionRegex = /function\s+(\w+)\s*\(([^)]*)\)\s*\{([\s\S]*?)\n\}/g;
510
+ let match;
511
+
512
+ while ((match = functionRegex.exec(jsCode)) !== null) {
513
+ const [, name, params, body] = match;
514
+
515
+ // Check if it's a pure utility (no DOM access, no external state)
516
+ if (this.isPureUtility(body)) {
517
+ utilities.push({
518
+ name,
519
+ params: params.trim(),
520
+ body: body.trim(),
521
+ comment: 'Pure utility function'
522
+ });
523
+ }
524
+ }
525
+
526
+ return utilities;
527
+ },
528
+
529
+ isPureUtility(code) {
530
+ // Check if function uses DOM or external state
531
+ const domPatterns = ['document.', 'window.', 'getElementById', 'querySelector', '$'];
532
+ const hasDOM = domPatterns.some(pattern => code.includes(pattern));
533
+
534
+ // Check if it only does computations
535
+ const isComputation = /^[\s\S]*return\s+/.test(code.trim());
536
+
537
+ return !hasDOM && isComputation;
538
+ },
539
+
540
+ extractClasses(jsCode) {
541
+ const classes = [];
542
+ const classRegex = /class\s+(\w+)(?:\s+extends\s+(\w+))?\s*\{([\s\S]*?)\n\}/g;
543
+ let match;
544
+
545
+ while ((match = classRegex.exec(jsCode)) !== null) {
546
+ const [, name, parentClass, body] = match;
547
+ classes.push({ name, parentClass, body });
548
+ }
549
+
550
+ return classes;
551
+ },
552
+
553
+ convertFunctionToReact(func) {
554
+ let converted = {
555
+ name: func.name,
556
+ params: func.params,
557
+ body: func.body,
558
+ isAsync: func.isAsync,
559
+ useCallback: false
560
+ };
561
+
562
+ // Convert DOM manipulation
563
+ converted.body = this.convertDOMManipulation(converted.body);
564
+
565
+ // Convert jQuery calls
566
+ converted.body = this.convertJQueryToReact(converted.body);
567
+
568
+ // Convert AJAX/fetch calls
569
+ converted.body = this.convertAsyncCalls(converted.body);
570
+
571
+ // Detect if function should use useCallback (if it depends on state)
572
+ if (this.shouldUseCallback(converted.body)) {
573
+ converted.useCallback = true;
574
+ }
575
+
576
+ return converted;
577
+ },
578
+
579
+ convertDOMManipulation(code) {
580
+ let converted = code;
581
+
582
+ // getElementById
583
+ converted = converted.replace(
584
+ /document\.getElementById\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
585
+ (match, id) => {
586
+ this.dependencies.add('useRef');
587
+ return `${this.toCamelCase(id)}Ref.current`;
588
+ }
589
+ );
590
+
591
+ // querySelector
592
+ converted = converted.replace(
593
+ /document\.querySelector\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
594
+ (match, selector) => {
595
+ const refName = this.selectorToRefName(selector);
596
+ this.dependencies.add('useRef');
597
+ return `${refName}Ref.current`;
598
+ }
599
+ );
600
+
601
+ // innerHTML - suggest dangerouslySetInnerHTML
602
+ converted = converted.replace(
603
+ /(\w+)\.innerHTML\s*=\s*(['"`])(.*?)\2/g,
604
+ (match, element, quote, content) => {
605
+ return `// TODO: Use state or <div dangerouslySetInnerHTML={{__html: ${quote}${content}${quote}}} />\n// ${match}`;
606
+ }
607
+ );
608
+
609
+ // textContent - suggest state
610
+ converted = converted.replace(
611
+ /(\w+)\.textContent\s*=\s*(.+);/g,
612
+ '// TODO: Use state to update text\n// $1.textContent = $2;'
613
+ );
614
+
615
+ // classList operations
616
+ converted = converted.replace(
617
+ /(\w+)\.classList\.(add|remove|toggle)\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
618
+ (match, element, operation, className) => {
619
+ return `// TODO: Use className state or conditional className\n// ${match}`;
620
+ }
621
+ );
622
+
623
+ return converted;
624
+ },
625
+
626
+ convertJQueryToReact(code) {
627
+ let converted = code;
628
+
629
+ // jQuery selectors
630
+ converted = converted.replace(
631
+ /\$\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
632
+ (match, selector) => {
633
+ const refName = this.selectorToRefName(selector);
634
+ return `${refName}Ref.current /* TODO: Create ref */`;
635
+ }
636
+ );
637
+
638
+ // jQuery .text()
639
+ converted = converted.replace(
640
+ /\$\([^)]+\)\.text\s*\(\s*([^)]+)\s*\)/g,
641
+ '/* TODO: Use state */ // jQuery .text($1)'
642
+ );
643
+
644
+ // jQuery .html()
645
+ converted = converted.replace(
646
+ /\$\([^)]+\)\.html\s*\(\s*([^)]+)\s*\)/g,
647
+ '/* TODO: Use dangerouslySetInnerHTML or state */ // jQuery .html($1)'
648
+ );
649
+
650
+ // jQuery .val()
651
+ converted = converted.replace(
652
+ /\$\([^)]+\)\.val\s*\(\s*\)/g,
653
+ '/* TODO: Use controlled input with state */ // jQuery .val()'
654
+ );
655
+
656
+ // jQuery AJAX
657
+ converted = converted.replace(
658
+ /\$\.ajax\s*\(/g,
659
+ '/* TODO: Use fetch or axios */ // $.ajax('
660
+ );
661
+
662
+ converted = converted.replace(
663
+ /\$\.get\s*\(/g,
664
+ '/* TODO: Use fetch */ // $.get('
665
+ );
666
+
667
+ converted = converted.replace(
668
+ /\$\.post\s*\(/g,
669
+ '/* TODO: Use fetch with POST */ // $.post('
670
+ );
671
+
672
+ return converted;
673
+ },
674
+
675
+ convertAsyncCalls(code) {
676
+ let converted = code;
677
+
678
+ // Suggest useEffect for fetch
679
+ if (converted.includes('fetch(')) {
680
+ converted = converted.replace(
681
+ /(fetch\s*\([^)]+\))/g,
682
+ '/* Consider wrapping in useEffect */ $1'
683
+ );
684
+ }
685
+
686
+ // XMLHttpRequest
687
+ if (converted.includes('XMLHttpRequest')) {
688
+ converted = '/* TODO: Replace XMLHttpRequest with fetch or axios */\n' + converted;
689
+ }
690
+
691
+ return converted;
692
+ },
693
+
694
+ shouldUseCallback(body) {
695
+ // If function accesses state variables, it should use useCallback
696
+ let needsCallback = false;
697
+
698
+ this.globalVariables.forEach(varName => {
699
+ if (body.includes(varName)) {
700
+ needsCallback = true;
701
+ }
702
+ });
703
+
704
+ return needsCallback;
705
+ },
706
+
707
+ selectorToRefName(selector) {
708
+ // Convert CSS selector to a valid ref name
709
+ return selector
710
+ .replace(/^[#.]/, '')
711
+ .replace(/[^a-zA-Z0-9]/g, '_')
712
+ .replace(/_+/g, '_')
713
+ .replace(/^_|_$/g, '');
714
+ },
715
+
716
+ cleanJSCode(code) {
717
+ return code
718
+ .split('\n')
719
+ .map(line => line.trim())
720
+ .filter(line => line)
721
+ .join('\n ');
722
+ },
723
+
724
+ convertAttributes(jsx) {
725
+ jsx = jsx.replace(/\sclass=/gi, ' className=');
726
+ jsx = jsx.replace(/\sfor=/gi, ' htmlFor=');
727
+
728
+ const events = {
729
+ 'onclick': 'onClick',
730
+ 'onchange': 'onChange',
731
+ 'onsubmit': 'onSubmit',
732
+ 'onmouseover': 'onMouseOver',
733
+ 'onmouseout': 'onMouseOut',
734
+ 'onmousedown': 'onMouseDown',
735
+ 'onmouseup': 'onMouseUp',
736
+ 'onmousemove': 'onMouseMove',
737
+ 'onkeydown': 'onKeyDown',
738
+ 'onkeyup': 'onKeyUp',
739
+ 'onkeypress': 'onKeyPress',
740
+ 'onfocus': 'onFocus',
741
+ 'onblur': 'onBlur',
742
+ 'oninput': 'onInput',
743
+ 'onscroll': 'onScroll',
744
+ 'onload': 'onLoad',
745
+ 'onerror': 'onError',
746
+ 'ondblclick': 'onDoubleClick',
747
+ 'oncontextmenu': 'onContextMenu'
748
+ };
749
+
750
+ Object.entries(events).forEach(([html, react]) => {
751
+ const regex = new RegExp(html + '=', 'gi');
752
+ jsx = jsx.replace(regex, react + '=');
753
+ });
754
+
755
+ jsx = jsx.replace(/\s(checked|disabled|readonly|required|selected|autofocus|autoplay|controls|loop|muted)(?=\s|>|\/)/gi, ' $1={true}');
756
+ jsx = jsx.replace(/<(img|input|br|hr|meta|link|source|track|embed|area|base|col|param)([^>]*)>/gi, '<$1$2 />');
757
+
758
+ // Fix attribute casing
759
+ const attributeMap = {
760
+ 'charset': 'charSet',
761
+ 'maxlength': 'maxLength',
762
+ 'minlength': 'minLength',
763
+ 'readonly': 'readOnly',
764
+ 'autofocus': 'autoFocus',
765
+ 'autocomplete': 'autoComplete',
766
+ 'novalidate': 'noValidate',
767
+ 'frameborder': 'frameBorder',
768
+ 'cellpadding': 'cellPadding',
769
+ 'cellspacing': 'cellSpacing',
770
+ 'rowspan': 'rowSpan',
771
+ 'colspan': 'colSpan',
772
+ 'tabindex': 'tabIndex'
773
+ };
774
+
775
+ Object.entries(attributeMap).forEach(([html, react]) => {
776
+ const regex = new RegExp('\\s' + html + '=', 'gi');
777
+ jsx = jsx.replace(regex, ' ' + react + '=');
778
+ });
779
+
780
+ return jsx;
781
+ },
782
+
783
+ convertEventHandlers(jsx) {
784
+ const eventRegex = /on([A-Z]\w+)=["']([^"']+)["']/g;
785
+
786
+ return jsx.replace(eventRegex, (match, eventName, handler) => {
787
+ let cleanHandler = handler
788
+ .replace(/this\./g, '')
789
+ .replace(/;$/, '')
790
+ .trim();
791
+
792
+ if (cleanHandler.includes('(')) {
793
+ return `on${eventName}={() => ${cleanHandler}}`;
794
+ } else {
795
+ return `on${eventName}={${cleanHandler}}`;
796
+ }
797
+ });
798
+ },
799
+
800
+ convertInlineStyles(jsx) {
801
+ const styleRegex = /style=["']([^"']*)["']/gi;
802
+
803
+ return jsx.replace(styleRegex, (match, styleContent) => {
804
+ if (!styleContent.trim()) {
805
+ return '';
806
+ }
807
+
808
+ try {
809
+ const styleObj = this.parseInlineStyle(styleContent);
810
+ const styleString = JSON.stringify(styleObj, null, 0)
811
+ .replace(/"/g, "'");
812
+ return `style={${styleString}}`;
813
+ } catch (e) {
814
+ console.log(colorizer.warning(' Could not parse inline style: ' + styleContent));
815
+ return match;
816
+ }
817
+ });
818
+ },
819
+
820
+ parseInlineStyle(styleString) {
821
+ const styles = {};
822
+ const declarations = styleString.split(';').filter(s => s.trim());
823
+
824
+ declarations.forEach(decl => {
825
+ const colonIndex = decl.indexOf(':');
826
+ if (colonIndex === -1) return;
827
+
828
+ const property = decl.substring(0, colonIndex).trim();
829
+ const value = decl.substring(colonIndex + 1).trim();
830
+
831
+ if (property && value) {
832
+ const camelProperty = this.toCamelCase(property);
833
+ const numericValue = this.parseStyleValue(value);
834
+ styles[camelProperty] = numericValue;
835
+ }
836
+ });
837
+
838
+ return styles;
839
+ },
840
+
841
+ parseStyleValue(value) {
842
+ if (value.includes('px') || value.includes('%') || value.includes('em') ||
843
+ value.includes('rem') || value.includes('vh') || value.includes('vw')) {
844
+ return value;
845
+ }
846
+
847
+ if (/^\d+$/.test(value)) {
848
+ return parseInt(value);
849
+ }
850
+
851
+ return value;
852
+ },
853
+
854
+ fixJSXIssues(jsx) {
855
+ jsx = jsx.replace(/>\s*\{(?!\s*\/?\w)/g, '>{"{"}');
856
+ jsx = jsx.replace(/(?<!\w)\}\s*</g, '{"}"}}<');
857
+ jsx = jsx.replace(/&nbsp;/g, '{" "}');
858
+ jsx = jsx.replace(/&lt;/g, '{"<"}');
859
+ jsx = jsx.replace(/&gt;/g, '{">"}');
860
+ jsx = jsx.replace(/&amp;/g, '{"&"}');
861
+ jsx = jsx.replace(/\sclassName=[""']\s*["']/g, '');
862
+
863
+ return jsx;
864
+ },
865
+
866
+ extractComponents(mainComponent) {
867
+ const patterns = [
868
+ { tag: 'nav', name: 'Navigation', minOccurrences: 1 },
869
+ { tag: 'header', name: 'Header', minOccurrences: 1 },
870
+ { tag: 'footer', name: 'Footer', minOccurrences: 1 },
871
+ { tag: 'aside', name: 'Sidebar', minOccurrences: 1 },
872
+ { tag: 'article', name: 'Article', minOccurrences: 2 },
873
+ { tag: 'section', name: 'Section', minOccurrences: 3 }
874
+ ];
875
+
876
+ patterns.forEach(pattern => {
877
+ const regex = new RegExp(`<${pattern.tag}[^>]*>([\\s\\S]*?)<\\/${pattern.tag}>`, 'gi');
878
+ const matches = [...mainComponent.jsx.matchAll(regex)];
879
+
880
+ if (matches.length >= pattern.minOccurrences) {
881
+ matches.forEach((match, index) => {
882
+ const componentName = pattern.name + (matches.length > 1 ? index + 1 : '');
883
+ const placeholder = `<${componentName} />`;
884
+
885
+ if (match[0].length > 50) {
886
+ this.extractedComponents.push({
887
+ name: componentName,
888
+ jsx: match[0],
889
+ imports: ['React']
890
+ });
891
+
892
+ mainComponent.jsx = mainComponent.jsx.replace(match[0], placeholder);
893
+ if (!mainComponent.imports.includes(componentName)) {
894
+ mainComponent.imports.push(componentName);
895
+ }
896
+ console.log(colorizer.info(' Extracted component: ' + componentName));
897
+ }
898
+ });
899
+ }
900
+ });
901
+ },
902
+
903
+ generateComponent(component, isMain = false) {
904
+ const imports = [];
905
+ const hooksList = new Set();
906
+
907
+ if (component.hooks && component.hooks.length > 0) {
908
+ component.hooks.forEach(hook => {
909
+ if (hook.includes('useState')) hooksList.add('useState');
910
+ if (hook.includes('useEffect')) hooksList.add('useEffect');
911
+ if (hook.includes('useRef')) hooksList.add('useRef');
912
+ if (hook.includes('useCallback')) hooksList.add('useCallback');
913
+ if (hook.includes('useMemo')) hooksList.add('useMemo');
914
+ });
915
+ }
916
+
917
+ if (component.effects && component.effects.length > 0) {
918
+ hooksList.add('useEffect');
919
+ }
920
+
921
+ if (component.functions && component.functions.some(f => f.useCallback)) {
922
+ hooksList.add('useCallback');
923
+ }
924
+
925
+ if (hooksList.size > 0) {
926
+ imports.push(`import React, { ${Array.from(hooksList).join(', ')} } from 'react';`);
927
+ } else {
928
+ imports.push("import React from 'react';");
929
+ }
930
+
931
+ if (component.imports && component.imports.length > 0) {
932
+ component.imports.forEach(imp => {
933
+ if (imp !== 'React' && !imp.includes('use')) {
934
+ imports.push(`import ${imp} from './${imp}';`);
935
+ }
936
+ });
937
+ }
938
+
939
+ if (component.css) {
940
+ imports.push(`import './${component.name}.css';`);
941
+ }
942
+
943
+ if (component.externalStyles && component.externalStyles.length > 0) {
944
+ component.externalStyles.forEach(style => {
945
+ const styleName = path.basename(style.original);
946
+ imports.push(`import './${styleName}';`);
947
+ });
948
+ }
949
+
950
+ let code = imports.join('\n') + '\n\n';
951
+
952
+ // Add utility functions outside component
953
+ if (component.utilities && component.utilities.length > 0) {
954
+ code += '// Utility Functions\n';
955
+ component.utilities.forEach(util => {
956
+ if (util.comment) {
957
+ code += `// ${util.comment}\n`;
958
+ }
959
+ code += `function ${util.name}(${util.params}) {\n`;
960
+ code += ` ${util.body.split('\n').join('\n ')}\n`;
961
+ code += `}\n\n`;
962
+ });
963
+ }
964
+
965
+ code += `function ${component.name}() {\n`;
966
+
967
+ // Add hooks
968
+ if (component.hooks && component.hooks.length > 0) {
969
+ component.hooks.forEach(hook => {
970
+ code += ` ${hook}\n`;
971
+ });
972
+ code += '\n';
973
+ }
974
+
975
+ // Add effects
976
+ if (component.effects && component.effects.length > 0) {
977
+ component.effects.forEach(effect => {
978
+ if (effect.comment) {
979
+ code += ` // ${effect.comment}\n`;
980
+ }
981
+ code += ` useEffect(() => {\n`;
982
+ code += ` ${effect.code.split('\n').join('\n ')}\n`;
983
+ code += ` }, ${effect.dependencies});\n\n`;
984
+ });
985
+ }
986
+
987
+ // Add functions
988
+ if (component.functions && component.functions.length > 0) {
989
+ component.functions.forEach(func => {
990
+ const asyncKeyword = func.isAsync ? 'async ' : '';
991
+ const params = func.params || '';
992
+
993
+ if (func.useCallback) {
994
+ code += ` const ${func.name} = useCallback(${asyncKeyword}(${params}) => {\n`;
995
+ code += ` ${func.body.split('\n').join('\n ')}\n`;
996
+ const deps = this.extractDependencies(func.body);
997
+ code += ` }, ${deps});\n\n`;
998
+ } else {
999
+ code += ` const ${func.name} = ${asyncKeyword}(${params}) => {\n`;
1000
+ code += ` ${func.body.split('\n').join('\n ')}\n`;
1001
+ code += ` };\n\n`;
1002
+ }
1003
+ });
1004
+ }
1005
+
1006
+ code += ' return (\n';
1007
+ const formattedJSX = this.formatJSX(component.jsx, 4);
1008
+ code += formattedJSX + '\n';
1009
+ code += ' );\n';
1010
+ code += '}\n\n';
1011
+ code += `export default ${component.name};\n`;
1012
+
1013
+ return code;
1014
+ },
1015
+
1016
+ formatJSX(jsx, indent = 0) {
1017
+ const lines = jsx.split('\n');
1018
+ let indentLevel = 0;
1019
+ const indentStr = ' '.repeat(indent);
1020
+
1021
+ return lines.map(line => {
1022
+ const trimmed = line.trim();
1023
+ if (!trimmed) return '';
1024
+
1025
+ if (trimmed.startsWith('</')) {
1026
+ indentLevel = Math.max(0, indentLevel - 1);
1027
+ }
1028
+
1029
+ const formatted = indentStr + ' '.repeat(indentLevel * 2) + trimmed;
1030
+
1031
+ if (trimmed.startsWith('<') && !trimmed.startsWith('</') && !trimmed.endsWith('/>') && !trimmed.includes('</')) {
1032
+ indentLevel++;
1033
+ }
1034
+
1035
+ return formatted;
1036
+ }).filter(line => line.trim()).join('\n');
1037
+ },
1038
+
1039
+ async saveComponents(converted, outputDir, inputDir) {
1040
+ const savedFiles = [];
1041
+
1042
+ try {
1043
+ await fs.mkdir(outputDir, { recursive: true });
1044
+
1045
+ const mainFile = path.join(outputDir, converted.main.name + '.jsx');
1046
+ const mainCode = this.generateComponent(converted.main, true);
1047
+
1048
+ await fs.writeFile(mainFile, mainCode);
1049
+ savedFiles.push(mainFile);
1050
+ console.log(colorizer.success('Created: ' + mainFile));
1051
+
1052
+ if (converted.main.css) {
1053
+ const cssFile = path.join(outputDir, converted.main.name + '.css');
1054
+ await fs.writeFile(cssFile, converted.main.css);
1055
+ savedFiles.push(cssFile);
1056
+ console.log(colorizer.success('Created: ' + cssFile));
1057
+ }
1058
+
1059
+ for (const style of this.externalStylesheets) {
1060
+ const destFile = path.join(outputDir, path.basename(style.original));
1061
+
1062
+ try {
1063
+ await fs.copyFile(style.local, destFile);
1064
+ savedFiles.push(destFile);
1065
+ console.log(colorizer.success('Copied stylesheet: ' + destFile));
1066
+ } catch (err) {
1067
+ console.log(colorizer.warning('Could not copy stylesheet: ' + style.original));
1068
+ }
1069
+ }
1070
+
1071
+ if (converted.jsModules.length > 0) {
1072
+ const utilsDir = path.join(outputDir, 'utils');
1073
+ await fs.mkdir(utilsDir, { recursive: true });
1074
+
1075
+ for (const jsModule of converted.jsModules) {
1076
+ const destFile = path.join(utilsDir, jsModule.name);
1077
+ await fs.writeFile(destFile, jsModule.code);
1078
+ savedFiles.push(destFile);
1079
+ console.log(colorizer.success('Created JS module: ' + destFile));
1080
+ }
1081
+ }
1082
+
1083
+ for (const script of this.externalScripts) {
1084
+ try {
1085
+ const scriptContent = await fs.readFile(script.local, 'utf8');
1086
+ const convertedScript = await this.convertExternalScript(scriptContent, script);
1087
+
1088
+ const utilsDir = path.join(outputDir, 'utils');
1089
+ await fs.mkdir(utilsDir, { recursive: true });
1090
+
1091
+ const baseName = path.basename(script.original, path.extname(script.original));
1092
+ const destFile = path.join(utilsDir, baseName + (script.isModule ? '.js' : '.util.js'));
1093
+
1094
+ await fs.writeFile(destFile, convertedScript);
1095
+ savedFiles.push(destFile);
1096
+ console.log(colorizer.success('Converted script: ' + destFile));
1097
+ } catch (err) {
1098
+ console.log(colorizer.warning('Could not process script: ' + script.original + ' - ' + err.message));
1099
+ }
1100
+ }
1101
+
1102
+ for (const comp of converted.components) {
1103
+ const compFile = path.join(outputDir, comp.name + '.jsx');
1104
+ const compCode = this.generateComponent(comp);
1105
+
1106
+ await fs.writeFile(compFile, compCode);
1107
+ savedFiles.push(compFile);
1108
+ console.log(colorizer.success('Created: ' + compFile));
1109
+ }
1110
+
1111
+ if (this.dependencies.size > 0) {
1112
+ await this.generatePackageJson(outputDir);
1113
+ savedFiles.push(path.join(outputDir, 'package.json'));
1114
+ }
1115
+
1116
+ await this.generateReadme(outputDir, converted);
1117
+ savedFiles.push(path.join(outputDir, 'README.md'));
1118
+
1119
+ return savedFiles;
1120
+ } catch (err) {
1121
+ throw new Error('Failed to save components: ' + err.message);
1122
+ }
1123
+ },
1124
+
1125
+ async convertExternalScript(scriptContent, scriptInfo) {
1126
+ if (scriptInfo.isModule) {
1127
+ return scriptContent;
1128
+ }
1129
+
1130
+ let converted = scriptContent;
1131
+ const exports = this.detectExports(scriptContent);
1132
+
1133
+ if (exports.length > 0) {
1134
+ converted += '\n\n// Auto-generated exports\n';
1135
+ converted += `export { ${exports.join(', ')} };\n`;
1136
+ console.log(colorizer.info(' Detected exports: ' + exports.join(', ')));
1137
+ } else {
1138
+ converted = `// Converted from: ${scriptInfo.original}\n// Note: This file may need manual adjustments\n\n${converted}`;
1139
+ }
1140
+
1141
+ return converted;
1142
+ },
1143
+
1144
+ detectExports(code) {
1145
+ const exports = [];
1146
+
1147
+ const functionRegex = /function\s+(\w+)\s*\(/g;
1148
+ let match;
1149
+
1150
+ while ((match = functionRegex.exec(code)) !== null) {
1151
+ exports.push(match[1]);
1152
+ }
1153
+
1154
+ const varRegex = /^(?:const|let|var)\s+(\w+)\s*=/gm;
1155
+
1156
+ while ((match = varRegex.exec(code)) !== null) {
1157
+ exports.push(match[1]);
1158
+ }
1159
+
1160
+ const classRegex = /class\s+(\w+)/g;
1161
+
1162
+ while ((match = classRegex.exec(code)) !== null) {
1163
+ exports.push(match[1]);
1164
+ }
1165
+
1166
+ return exports;
1167
+ },
1168
+
1169
+ async generatePackageJson(outputDir) {
1170
+ const packageJson = {
1171
+ name: path.basename(outputDir),
1172
+ version: '1.0.0',
1173
+ description: 'React components generated from HTML',
1174
+ main: 'index.js',
1175
+ scripts: {
1176
+ start: 'react-scripts start',
1177
+ build: 'react-scripts build',
1178
+ test: 'react-scripts test'
1179
+ },
1180
+ dependencies: {
1181
+ react: '^18.2.0',
1182
+ 'react-dom': '^18.2.0'
1183
+ },
1184
+ devDependencies: {
1185
+ 'react-scripts': '^5.0.1'
1186
+ }
1187
+ };
1188
+
1189
+ this.dependencies.forEach(dep => {
1190
+ packageJson.dependencies[dep] = 'latest';
1191
+ });
1192
+
1193
+ const pkgFile = path.join(outputDir, 'package.json');
1194
+ await fs.writeFile(pkgFile, JSON.stringify(packageJson, null, 2));
1195
+ console.log(colorizer.success('Created: ' + pkgFile));
1196
+ },
1197
+
1198
+ async generateReadme(outputDir, converted) {
1199
+ let readme = '# React Components\n\n';
1200
+ readme += 'This project was generated from HTML using html-to-react converter.\n\n';
1201
+
1202
+ readme += '## Installation\n\n';
1203
+ readme += '```bash\n';
1204
+ readme += 'npm install\n';
1205
+ readme += '```\n\n';
1206
+
1207
+ readme += '## Components\n\n';
1208
+ readme += `- **${converted.main.name}** - Main component\n`;
1209
+
1210
+ converted.components.forEach(comp => {
1211
+ readme += `- **${comp.name}** - Extracted component\n`;
1212
+ });
1213
+
1214
+ if (converted.main.hooks.length > 0) {
1215
+ readme += '\n## State Management\n\n';
1216
+ readme += 'The components use React hooks for state management:\n\n';
1217
+ converted.main.hooks.forEach(hook => {
1218
+ if (hook.includes('useState')) {
1219
+ readme += `- ${hook.split('=')[0].trim()}\n`;
1220
+ }
1221
+ });
1222
+ }
1223
+
1224
+ if (converted.main.functions && converted.main.functions.length > 0) {
1225
+ readme += '\n## Functions\n\n';
1226
+ readme += 'The following functions were converted:\n\n';
1227
+ converted.main.functions.forEach(func => {
1228
+ readme += `- **${func.name}** ${func.isAsync ? '(async)' : ''}\n`;
1229
+ });
1230
+ }
1231
+
1232
+ if (converted.main.utilities && converted.main.utilities.length > 0) {
1233
+ readme += '\n## Utility Functions\n\n';
1234
+ readme += 'Pure utility functions extracted outside the component:\n\n';
1235
+ converted.main.utilities.forEach(util => {
1236
+ readme += `- **${util.name}**\n`;
1237
+ });
1238
+ }
1239
+
1240
+ if (this.dependencies.size > 0) {
1241
+ readme += '\n## Dependencies\n\n';
1242
+ readme += 'The following external libraries were detected:\n\n';
1243
+ this.dependencies.forEach(dep => {
1244
+ readme += `- ${dep}\n`;
1245
+ });
1246
+ }
1247
+
1248
+ readme += '\n## Manual Review Required\n\n';
1249
+ readme += 'Please review the following:\n\n';
1250
+ readme += '- Event handlers and their implementations\n';
1251
+ readme += '- State management logic\n';
1252
+ readme += '- Any TODO comments in the code\n';
1253
+ readme += '- DOM manipulation converted to refs\n';
1254
+ readme += '- jQuery calls converted to React patterns\n';
1255
+ readme += '- External script integrations\n';
1256
+ readme += '- useCallback dependencies\n';
1257
+
1258
+ const readmeFile = path.join(outputDir, 'README.md');
1259
+ await fs.writeFile(readmeFile, readme);
1260
+ console.log(colorizer.success('Created: ' + readmeFile));
1261
+ },
1262
+
1263
+ printConversionSummary() {
1264
+ console.log(colorizer.section('Conversion Summary'));
1265
+ console.log(colorizer.cyan(' Components created: ') + (this.extractedComponents.length + 1));
1266
+ console.log(colorizer.cyan(' Stylesheets: ') + this.externalStylesheets.length);
1267
+ console.log(colorizer.cyan(' Scripts processed: ') + this.externalScripts.length);
1268
+ console.log(colorizer.cyan(' Helper functions: ') + this.helperFunctions.size);
1269
+
1270
+ if (this.dependencies.size > 0) {
1271
+ console.log(colorizer.cyan(' Dependencies detected: ') + Array.from(this.dependencies).join(', '));
1272
+ }
1273
+
1274
+ if (this.globalVariables.size > 0) {
1275
+ console.log(colorizer.cyan(' State variables: ') + this.globalVariables.size);
1276
+ }
1277
+
1278
+ console.log();
1279
+ },
1280
+
1281
+ // Batch conversion and analysis methods remain the same...
1282
+ convertBatch(args) {
1283
+ const inputDir = args[0] || '.';
1284
+ const outputDir = args[1] || './react-components';
1285
+
1286
+ console.log(colorizer.header('Batch HTML to React Conversion'));
1287
+ console.log(colorizer.separator());
1288
+ console.log(colorizer.cyan('Input Directory: ') + colorizer.bright(inputDir));
1289
+ console.log(colorizer.cyan('Output Directory: ') + colorizer.bright(outputDir));
1290
+ console.log();
1291
+
1292
+ return this.findHTMLFiles(inputDir)
1293
+ .then(files => {
1294
+ if (files.length === 0) {
1295
+ console.log(colorizer.warning('No HTML files found in ' + inputDir + '\n'));
1296
+ return;
1297
+ }
1298
+
1299
+ console.log(colorizer.info('Found ' + files.length + ' HTML file(s)\n'));
1300
+
1301
+ const promises = files.map(file => {
1302
+ const relativePath = path.relative(inputDir, file);
1303
+ const outputSubDir = path.join(outputDir, path.dirname(relativePath));
1304
+
1305
+ console.log(colorizer.cyan('Converting: ') + relativePath);
1306
+
1307
+ return this.convert([file, outputSubDir])
1308
+ .catch(err => {
1309
+ console.log(colorizer.warning('Failed to convert ' + file + ': ' + err.message));
1310
+ });
1311
+ });
1312
+
1313
+ return Promise.all(promises);
1314
+ })
1315
+ .then(() => {
1316
+ console.log(colorizer.success('\nBatch conversion complete!\n'));
1317
+ })
1318
+ .catch(err => {
1319
+ console.log(colorizer.error('Batch conversion failed: ' + err.message + '\n'));
1320
+ });
1321
+ },
1322
+
1323
+ findHTMLFiles(dir, files = []) {
1324
+ return fs.readdir(dir, { withFileTypes: true })
1325
+ .then(items => {
1326
+ const promises = items.map(item => {
1327
+ const fullPath = path.join(dir, item.name);
1328
+
1329
+ if (['node_modules', '.git', 'dist', 'build', 'react-components'].includes(item.name)) {
1330
+ return Promise.resolve();
1331
+ }
1332
+
1333
+ if (item.isDirectory()) {
1334
+ return this.findHTMLFiles(fullPath, files);
1335
+ } else if (item.isFile() && path.extname(item.name).toLowerCase() === '.html') {
1336
+ files.push(fullPath);
1337
+ }
1338
+
1339
+ return Promise.resolve();
1340
+ });
1341
+
1342
+ return Promise.all(promises);
1343
+ })
1344
+ .then(() => files)
1345
+ .catch(err => {
1346
+ console.log(colorizer.warning('Error reading directory ' + dir + ': ' + err.message));
1347
+ return files;
1348
+ });
1349
+ },
1350
+
1351
+ analyzeHTML(args) {
1352
+ const filePath = args[0];
1353
+
1354
+ if (!filePath) {
1355
+ console.log(colorizer.error('Usage: analyze-html <html-file>\n'));
1356
+ return Promise.resolve();
1357
+ }
1358
+
1359
+ console.log(colorizer.header('HTML Structure Analysis'));
1360
+ console.log(colorizer.separator());
1361
+ console.log(colorizer.cyan('File: ') + colorizer.bright(filePath));
1362
+ console.log();
1363
+
1364
+ return fs.readFile(filePath, 'utf8')
1365
+ .then(html => {
1366
+ const analysis = {
1367
+ totalLines: html.split('\n').length,
1368
+ totalSize: Buffer.byteLength(html, 'utf8'),
1369
+ tags: this.countTags(html),
1370
+ hasCSS: html.includes('<style') || html.includes('style='),
1371
+ hasInlineCSS: html.includes('style='),
1372
+ hasExternalCSS: html.includes('<link') && html.includes('stylesheet'),
1373
+ hasJS: html.includes('<script'),
1374
+ hasInlineJS: /<script(?![^>]*src=)[^>]*>[\s\S]*?<\/script>/gi.test(html),
1375
+ hasExternalJS: /<script[^>]+src=/gi.test(html),
1376
+ forms: (html.match(/<form/gi) || []).length,
1377
+ inputs: (html.match(/<input/gi) || []).length,
1378
+ images: (html.match(/<img/gi) || []).length,
1379
+ links: (html.match(/<a/gi) || []).length,
1380
+ semanticTags: this.countSemanticTags(html),
1381
+ eventHandlers: this.countEventHandlers(html),
1382
+ externalScripts: this.countExternalScripts(html),
1383
+ dataAttributes: (html.match(/data-\w+=/gi) || []).length
1384
+ };
1385
+
1386
+ console.log(colorizer.section('File Statistics'));
1387
+ console.log(colorizer.cyan(' Total Lines: ') + analysis.totalLines);
1388
+ console.log(colorizer.cyan(' File Size: ') + (analysis.totalSize / 1024).toFixed(2) + ' KB');
1389
+ console.log(colorizer.cyan(' Total Tags: ') + analysis.tags);
1390
+ console.log(colorizer.cyan(' Forms: ') + analysis.forms);
1391
+ console.log(colorizer.cyan(' Input Fields: ') + analysis.inputs);
1392
+ console.log(colorizer.cyan(' Images: ') + analysis.images);
1393
+ console.log(colorizer.cyan(' Links: ') + analysis.links);
1394
+ console.log(colorizer.cyan(' Data Attributes: ') + analysis.dataAttributes);
1395
+
1396
+ console.log(colorizer.section('CSS'));
1397
+ console.log(colorizer.cyan(' Has Inline CSS: ') + (analysis.hasInlineCSS ? colorizer.green('Yes') : colorizer.red('No')));
1398
+ console.log(colorizer.cyan(' Has External CSS: ') + (analysis.hasExternalCSS ? colorizer.green('Yes') : colorizer.red('No')));
1399
+
1400
+ console.log(colorizer.section('JavaScript'));
1401
+ console.log(colorizer.cyan(' Has Inline JS: ') + (analysis.hasInlineJS ? colorizer.green('Yes') : colorizer.red('No')));
1402
+ console.log(colorizer.cyan(' Has External JS: ') + (analysis.hasExternalJS ? colorizer.green('Yes') : colorizer.red('No')));
1403
+ console.log(colorizer.cyan(' External Scripts: ') + analysis.externalScripts.length);
1404
+ console.log(colorizer.cyan(' Event Handlers: ') + analysis.eventHandlers);
1405
+
1406
+ if (analysis.externalScripts.length > 0) {
1407
+ console.log(colorizer.cyan(' Detected Scripts:'));
1408
+ analysis.externalScripts.forEach(script => {
1409
+ console.log(colorizer.bullet(script));
1410
+ });
1411
+ }
1412
+
1413
+ console.log(colorizer.section('Semantic Tags'));
1414
+ Object.entries(analysis.semanticTags).forEach(([tag, count]) => {
1415
+ if (count > 0) {
1416
+ console.log(colorizer.bullet(tag + ': ' + count));
1417
+ }
1418
+ });
1419
+
1420
+ console.log(colorizer.section('Conversion Complexity'));
1421
+ let complexity = 'Low';
1422
+ let score = 0;
1423
+
1424
+ if (analysis.hasInlineJS) score += 3;
1425
+ if (analysis.hasExternalJS) score += 2;
1426
+ if (analysis.eventHandlers > 10) score += 3;
1427
+ else if (analysis.eventHandlers > 5) score += 2;
1428
+ else if (analysis.eventHandlers > 0) score += 1;
1429
+ if (analysis.forms > 2) score += 2;
1430
+ else if (analysis.forms > 0) score += 1;
1431
+ if (analysis.hasInlineCSS) score += 1;
1432
+ if (analysis.dataAttributes > 10) score += 1;
1433
+
1434
+ if (score >= 7) complexity = 'High';
1435
+ else if (score >= 4) complexity = 'Medium';
1436
+
1437
+ const complexityColor = complexity === 'High' ? colorizer.red :
1438
+ complexity === 'Medium' ? colorizer.yellow :
1439
+ colorizer.green;
1440
+
1441
+ console.log(colorizer.cyan(' Complexity Score: ') + score + '/12');
1442
+ console.log(colorizer.cyan(' Complexity Level: ') + complexityColor(complexity));
1443
+
1444
+ console.log(colorizer.section('Conversion Recommendations'));
1445
+ const recommendations = [];
1446
+
1447
+ if (analysis.semanticTags.nav > 0) recommendations.push('Extract navigation as separate component');
1448
+ if (analysis.semanticTags.header > 0) recommendations.push('Extract header as separate component');
1449
+ if (analysis.semanticTags.footer > 0) recommendations.push('Extract footer as separate component');
1450
+ if (analysis.semanticTags.aside > 0) recommendations.push('Extract sidebar as separate component');
1451
+ if (analysis.forms > 0) recommendations.push('Consider using React Hook Form or Formik for form management');
1452
+ if (analysis.hasInlineJS) recommendations.push('JavaScript will be converted to React hooks and functions');
1453
+ if (analysis.eventHandlers > 5) recommendations.push('Multiple event handlers detected - will be converted to React event props');
1454
+ if (analysis.hasExternalJS) recommendations.push('External scripts will be converted to ES6 modules in utils folder');
1455
+ if (analysis.hasExternalCSS) recommendations.push('External stylesheets will be imported as CSS modules');
1456
+ if (analysis.dataAttributes > 0) recommendations.push('Data attributes detected - consider converting to props or state');
1457
+ if (complexity === 'High') recommendations.push('High complexity - expect significant manual adjustments needed');
1458
+
1459
+ if (recommendations.length > 0) {
1460
+ recommendations.forEach(rec => console.log(colorizer.bullet(rec)));
1461
+ } else {
1462
+ console.log(colorizer.dim(' No specific recommendations - straightforward conversion expected'));
1463
+ }
1464
+
1465
+ console.log();
1466
+ })
1467
+ .catch(err => {
1468
+ console.log(colorizer.error('Analysis failed: ' + err.message));
1469
+ if (err.stack) {
1470
+ console.log(colorizer.dim(err.stack));
1471
+ }
1472
+ console.log();
1473
+ });
1474
+ },
1475
+
1476
+ countTags(html) {
1477
+ const matches = html.match(/<[^>]+>/g);
1478
+ return matches ? matches.length : 0;
1479
+ },
1480
+
1481
+ countSemanticTags(html) {
1482
+ const tags = ['nav', 'header', 'footer', 'main', 'section', 'article', 'aside', 'figure'];
1483
+ const counts = {};
1484
+
1485
+ tags.forEach(tag => {
1486
+ const regex = new RegExp(`<${tag}[^>]*>`, 'gi');
1487
+ const matches = html.match(regex);
1488
+ counts[tag] = matches ? matches.length : 0;
1489
+ });
1490
+
1491
+ return counts;
1492
+ },
1493
+
1494
+ countEventHandlers(html) {
1495
+ const events = ['onclick', 'onchange', 'onsubmit', 'onload', 'onmouseover',
1496
+ 'onmouseout', 'onkeydown', 'onkeyup', 'onfocus', 'onblur',
1497
+ 'oninput', 'onscroll', 'ondblclick'];
1498
+ let count = 0;
1499
+
1500
+ events.forEach(event => {
1501
+ const regex = new RegExp(event + '=', 'gi');
1502
+ const matches = html.match(regex);
1503
+ if (matches) count += matches.length;
1504
+ });
1505
+
1506
+ return count;
1507
+ },
1508
+
1509
+ countExternalScripts(html) {
1510
+ const scripts = [];
1511
+ const scriptRegex = /<script[^>]+src=["']([^"']+)["'][^>]*>/gi;
1512
+ let match;
1513
+
1514
+ while ((match = scriptRegex.exec(html)) !== null) {
1515
+ scripts.push(match[1]);
1516
+ }
1517
+
1518
+ return scripts;
1519
+ },
1520
+
1521
+ toPascalCase(str) {
1522
+ return str
1523
+ .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
1524
+ .replace(/^(.)/, (_, c) => c.toUpperCase())
1525
+ .replace(/[^a-zA-Z0-9]/g, '');
1526
+ },
1527
+
1528
+ toCamelCase(str) {
1529
+ return str
1530
+ .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
1531
+ .replace(/^(.)/, (_, c) => c.toLowerCase())
1532
+ .replace(/[^a-zA-Z0-9]/g, '');
1533
+ }
1534
+ };
1535
+
1536
+ module.exports = HTMLToReact;