codemeld 2.1.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.
Files changed (243) hide show
  1. package/README.md +514 -0
  2. package/bin/cli.js +2 -0
  3. package/dist/ai/agent.d.ts +124 -0
  4. package/dist/ai/agent.d.ts.map +1 -0
  5. package/dist/ai/agent.js +289 -0
  6. package/dist/ai/agent.js.map +1 -0
  7. package/dist/ai/index.d.ts +10 -0
  8. package/dist/ai/index.d.ts.map +1 -0
  9. package/dist/ai/index.js +10 -0
  10. package/dist/ai/index.js.map +1 -0
  11. package/dist/ai/prompts.d.ts +35 -0
  12. package/dist/ai/prompts.d.ts.map +1 -0
  13. package/dist/ai/prompts.js +166 -0
  14. package/dist/ai/prompts.js.map +1 -0
  15. package/dist/ai/refinement-loop.d.ts +29 -0
  16. package/dist/ai/refinement-loop.d.ts.map +1 -0
  17. package/dist/ai/refinement-loop.js +180 -0
  18. package/dist/ai/refinement-loop.js.map +1 -0
  19. package/dist/ai/tools.d.ts +17 -0
  20. package/dist/ai/tools.d.ts.map +1 -0
  21. package/dist/ai/tools.js +353 -0
  22. package/dist/ai/tools.js.map +1 -0
  23. package/dist/ai/visual-compare.d.ts +43 -0
  24. package/dist/ai/visual-compare.d.ts.map +1 -0
  25. package/dist/ai/visual-compare.js +176 -0
  26. package/dist/ai/visual-compare.js.map +1 -0
  27. package/dist/cli.d.ts +3 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +179 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/converter.d.ts +10 -0
  32. package/dist/converter.d.ts.map +1 -0
  33. package/dist/converter.js +836 -0
  34. package/dist/converter.js.map +1 -0
  35. package/dist/deconverter.d.ts +19 -0
  36. package/dist/deconverter.d.ts.map +1 -0
  37. package/dist/deconverter.js +188 -0
  38. package/dist/deconverter.js.map +1 -0
  39. package/dist/frameworks/angular-adapter.d.ts +27 -0
  40. package/dist/frameworks/angular-adapter.d.ts.map +1 -0
  41. package/dist/frameworks/angular-adapter.js +617 -0
  42. package/dist/frameworks/angular-adapter.js.map +1 -0
  43. package/dist/frameworks/index.d.ts +10 -0
  44. package/dist/frameworks/index.d.ts.map +1 -0
  45. package/dist/frameworks/index.js +21 -0
  46. package/dist/frameworks/index.js.map +1 -0
  47. package/dist/frameworks/nextjs-adapter.d.ts +22 -0
  48. package/dist/frameworks/nextjs-adapter.d.ts.map +1 -0
  49. package/dist/frameworks/nextjs-adapter.js +392 -0
  50. package/dist/frameworks/nextjs-adapter.js.map +1 -0
  51. package/dist/frameworks/react-adapter.d.ts +21 -0
  52. package/dist/frameworks/react-adapter.d.ts.map +1 -0
  53. package/dist/frameworks/react-adapter.js +71 -0
  54. package/dist/frameworks/react-adapter.js.map +1 -0
  55. package/dist/frameworks/svelte-adapter.d.ts +27 -0
  56. package/dist/frameworks/svelte-adapter.d.ts.map +1 -0
  57. package/dist/frameworks/svelte-adapter.js +519 -0
  58. package/dist/frameworks/svelte-adapter.js.map +1 -0
  59. package/dist/frameworks/types.d.ts +78 -0
  60. package/dist/frameworks/types.d.ts.map +1 -0
  61. package/dist/frameworks/types.js +2 -0
  62. package/dist/frameworks/types.js.map +1 -0
  63. package/dist/frameworks/vue-adapter.d.ts +34 -0
  64. package/dist/frameworks/vue-adapter.d.ts.map +1 -0
  65. package/dist/frameworks/vue-adapter.js +632 -0
  66. package/dist/frameworks/vue-adapter.js.map +1 -0
  67. package/dist/generators/accessibility-generator.d.ts +43 -0
  68. package/dist/generators/accessibility-generator.d.ts.map +1 -0
  69. package/dist/generators/accessibility-generator.js +507 -0
  70. package/dist/generators/accessibility-generator.js.map +1 -0
  71. package/dist/generators/asset-handler.d.ts +14 -0
  72. package/dist/generators/asset-handler.d.ts.map +1 -0
  73. package/dist/generators/asset-handler.js +79 -0
  74. package/dist/generators/asset-handler.js.map +1 -0
  75. package/dist/generators/build-verifier.d.ts +8 -0
  76. package/dist/generators/build-verifier.d.ts.map +1 -0
  77. package/dist/generators/build-verifier.js +64 -0
  78. package/dist/generators/build-verifier.js.map +1 -0
  79. package/dist/generators/component-extractor.d.ts +25 -0
  80. package/dist/generators/component-extractor.d.ts.map +1 -0
  81. package/dist/generators/component-extractor.js +146 -0
  82. package/dist/generators/component-extractor.js.map +1 -0
  83. package/dist/generators/component-generator.d.ts +12 -0
  84. package/dist/generators/component-generator.d.ts.map +1 -0
  85. package/dist/generators/component-generator.js +724 -0
  86. package/dist/generators/component-generator.js.map +1 -0
  87. package/dist/generators/deploy-generator.d.ts +9 -0
  88. package/dist/generators/deploy-generator.d.ts.map +1 -0
  89. package/dist/generators/deploy-generator.js +409 -0
  90. package/dist/generators/deploy-generator.js.map +1 -0
  91. package/dist/generators/error-boundary.d.ts +5 -0
  92. package/dist/generators/error-boundary.d.ts.map +1 -0
  93. package/dist/generators/error-boundary.js +59 -0
  94. package/dist/generators/error-boundary.js.map +1 -0
  95. package/dist/generators/form-generator.d.ts +42 -0
  96. package/dist/generators/form-generator.d.ts.map +1 -0
  97. package/dist/generators/form-generator.js +662 -0
  98. package/dist/generators/form-generator.js.map +1 -0
  99. package/dist/generators/hooks-generator.d.ts +40 -0
  100. package/dist/generators/hooks-generator.d.ts.map +1 -0
  101. package/dist/generators/hooks-generator.js +297 -0
  102. package/dist/generators/hooks-generator.js.map +1 -0
  103. package/dist/generators/html-generator.d.ts +27 -0
  104. package/dist/generators/html-generator.d.ts.map +1 -0
  105. package/dist/generators/html-generator.js +772 -0
  106. package/dist/generators/html-generator.js.map +1 -0
  107. package/dist/generators/jquery-converter.d.ts +41 -0
  108. package/dist/generators/jquery-converter.d.ts.map +1 -0
  109. package/dist/generators/jquery-converter.js +594 -0
  110. package/dist/generators/jquery-converter.js.map +1 -0
  111. package/dist/generators/pattern-implementer.d.ts +26 -0
  112. package/dist/generators/pattern-implementer.d.ts.map +1 -0
  113. package/dist/generators/pattern-implementer.js +336 -0
  114. package/dist/generators/pattern-implementer.js.map +1 -0
  115. package/dist/generators/performance-generator.d.ts +51 -0
  116. package/dist/generators/performance-generator.d.ts.map +1 -0
  117. package/dist/generators/performance-generator.js +428 -0
  118. package/dist/generators/performance-generator.js.map +1 -0
  119. package/dist/generators/router-generator.d.ts +21 -0
  120. package/dist/generators/router-generator.d.ts.map +1 -0
  121. package/dist/generators/router-generator.js +178 -0
  122. package/dist/generators/router-generator.js.map +1 -0
  123. package/dist/generators/scaffolder.d.ts +28 -0
  124. package/dist/generators/scaffolder.d.ts.map +1 -0
  125. package/dist/generators/scaffolder.js +266 -0
  126. package/dist/generators/scaffolder.js.map +1 -0
  127. package/dist/generators/seo-generator.d.ts +29 -0
  128. package/dist/generators/seo-generator.d.ts.map +1 -0
  129. package/dist/generators/seo-generator.js +223 -0
  130. package/dist/generators/seo-generator.js.map +1 -0
  131. package/dist/generators/test-generator.d.ts +19 -0
  132. package/dist/generators/test-generator.d.ts.map +1 -0
  133. package/dist/generators/test-generator.js +398 -0
  134. package/dist/generators/test-generator.js.map +1 -0
  135. package/dist/generators/type-generator.d.ts +33 -0
  136. package/dist/generators/type-generator.d.ts.map +1 -0
  137. package/dist/generators/type-generator.js +663 -0
  138. package/dist/generators/type-generator.js.map +1 -0
  139. package/dist/index.d.ts +23 -0
  140. package/dist/index.d.ts.map +1 -0
  141. package/dist/index.js +12 -0
  142. package/dist/index.js.map +1 -0
  143. package/dist/parsers/css-processor.d.ts +23 -0
  144. package/dist/parsers/css-processor.d.ts.map +1 -0
  145. package/dist/parsers/css-processor.js +129 -0
  146. package/dist/parsers/css-processor.js.map +1 -0
  147. package/dist/parsers/framework-parser.d.ts +48 -0
  148. package/dist/parsers/framework-parser.d.ts.map +1 -0
  149. package/dist/parsers/framework-parser.js +770 -0
  150. package/dist/parsers/framework-parser.js.map +1 -0
  151. package/dist/parsers/html-parser.d.ts +12 -0
  152. package/dist/parsers/html-parser.d.ts.map +1 -0
  153. package/dist/parsers/html-parser.js +444 -0
  154. package/dist/parsers/html-parser.js.map +1 -0
  155. package/dist/parsers/js-analyzer.d.ts +199 -0
  156. package/dist/parsers/js-analyzer.d.ts.map +1 -0
  157. package/dist/parsers/js-analyzer.js +680 -0
  158. package/dist/parsers/js-analyzer.js.map +1 -0
  159. package/dist/parsers/js-resolver.d.ts +8 -0
  160. package/dist/parsers/js-resolver.d.ts.map +1 -0
  161. package/dist/parsers/js-resolver.js +45 -0
  162. package/dist/parsers/js-resolver.js.map +1 -0
  163. package/dist/parsers/tailwind-detector.d.ts +23 -0
  164. package/dist/parsers/tailwind-detector.d.ts.map +1 -0
  165. package/dist/parsers/tailwind-detector.js +104 -0
  166. package/dist/parsers/tailwind-detector.js.map +1 -0
  167. package/dist/tests/advanced-features.test.d.ts +2 -0
  168. package/dist/tests/advanced-features.test.d.ts.map +1 -0
  169. package/dist/tests/advanced-features.test.js +235 -0
  170. package/dist/tests/advanced-features.test.js.map +1 -0
  171. package/dist/tests/css-modules.test.d.ts +2 -0
  172. package/dist/tests/css-modules.test.d.ts.map +1 -0
  173. package/dist/tests/css-modules.test.js +61 -0
  174. package/dist/tests/css-modules.test.js.map +1 -0
  175. package/dist/tests/css-processor.test.d.ts +2 -0
  176. package/dist/tests/css-processor.test.d.ts.map +1 -0
  177. package/dist/tests/css-processor.test.js +48 -0
  178. package/dist/tests/css-processor.test.js.map +1 -0
  179. package/dist/tests/html-parser.test.d.ts +2 -0
  180. package/dist/tests/html-parser.test.d.ts.map +1 -0
  181. package/dist/tests/html-parser.test.js +78 -0
  182. package/dist/tests/html-parser.test.js.map +1 -0
  183. package/dist/tests/integration.test.d.ts +2 -0
  184. package/dist/tests/integration.test.d.ts.map +1 -0
  185. package/dist/tests/integration.test.js +65 -0
  186. package/dist/tests/integration.test.js.map +1 -0
  187. package/dist/tests/js-analyzer.test.d.ts +2 -0
  188. package/dist/tests/js-analyzer.test.d.ts.map +1 -0
  189. package/dist/tests/js-analyzer.test.js +58 -0
  190. package/dist/tests/js-analyzer.test.js.map +1 -0
  191. package/dist/tests/naming.test.d.ts +2 -0
  192. package/dist/tests/naming.test.d.ts.map +1 -0
  193. package/dist/tests/naming.test.js +43 -0
  194. package/dist/tests/naming.test.js.map +1 -0
  195. package/dist/tests/router-generator.test.d.ts +2 -0
  196. package/dist/tests/router-generator.test.d.ts.map +1 -0
  197. package/dist/tests/router-generator.test.js +60 -0
  198. package/dist/tests/router-generator.test.js.map +1 -0
  199. package/dist/tui/chat.d.ts +13 -0
  200. package/dist/tui/chat.d.ts.map +1 -0
  201. package/dist/tui/chat.js +499 -0
  202. package/dist/tui/chat.js.map +1 -0
  203. package/dist/tui/design-guide.d.ts +41 -0
  204. package/dist/tui/design-guide.d.ts.map +1 -0
  205. package/dist/tui/design-guide.js +184 -0
  206. package/dist/tui/design-guide.js.map +1 -0
  207. package/dist/tui/input.d.ts +30 -0
  208. package/dist/tui/input.d.ts.map +1 -0
  209. package/dist/tui/input.js +239 -0
  210. package/dist/tui/input.js.map +1 -0
  211. package/dist/tui/renderer.d.ts +48 -0
  212. package/dist/tui/renderer.d.ts.map +1 -0
  213. package/dist/tui/renderer.js +212 -0
  214. package/dist/tui/renderer.js.map +1 -0
  215. package/dist/tui/tools.d.ts +14 -0
  216. package/dist/tui/tools.d.ts.map +1 -0
  217. package/dist/tui/tools.js +1370 -0
  218. package/dist/tui/tools.js.map +1 -0
  219. package/dist/types.d.ts +93 -0
  220. package/dist/types.d.ts.map +1 -0
  221. package/dist/types.js +2 -0
  222. package/dist/types.js.map +1 -0
  223. package/dist/utils/config.d.ts +20 -0
  224. package/dist/utils/config.d.ts.map +1 -0
  225. package/dist/utils/config.js +33 -0
  226. package/dist/utils/config.js.map +1 -0
  227. package/dist/utils/formatter.d.ts +5 -0
  228. package/dist/utils/formatter.d.ts.map +1 -0
  229. package/dist/utils/formatter.js +68 -0
  230. package/dist/utils/formatter.js.map +1 -0
  231. package/dist/utils/logger.d.ts +8 -0
  232. package/dist/utils/logger.d.ts.map +1 -0
  233. package/dist/utils/logger.js +19 -0
  234. package/dist/utils/logger.js.map +1 -0
  235. package/dist/utils/naming.d.ts +17 -0
  236. package/dist/utils/naming.d.ts.map +1 -0
  237. package/dist/utils/naming.js +48 -0
  238. package/dist/utils/naming.js.map +1 -0
  239. package/dist/utils/report.d.ts +56 -0
  240. package/dist/utils/report.d.ts.map +1 -0
  241. package/dist/utils/report.js +339 -0
  242. package/dist/utils/report.js.map +1 -0
  243. package/package.json +61 -0
@@ -0,0 +1,770 @@
1
+ /**
2
+ * Framework Component Parser
3
+ *
4
+ * Parses React (.tsx/.jsx), Vue (.vue SFC), Svelte (.svelte), Angular (.ts @Component),
5
+ * and Next.js components back into structured data for reverse conversion to HTML/CSS/JS.
6
+ */
7
+ import { readFile, readdir, stat } from 'fs/promises';
8
+ import { join, extname, basename, dirname, relative } from 'path';
9
+ // ─── Framework Detection ────────────────────────────────────────────
10
+ export async function detectFramework(projectDir) {
11
+ try {
12
+ const pkgJson = JSON.parse(await readFile(join(projectDir, 'package.json'), 'utf-8'));
13
+ const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
14
+ if (allDeps['next'])
15
+ return 'nextjs';
16
+ if (allDeps['@angular/core'])
17
+ return 'angular';
18
+ if (allDeps['svelte'] || allDeps['@sveltejs/kit'])
19
+ return 'svelte';
20
+ if (allDeps['vue'])
21
+ return 'vue';
22
+ if (allDeps['react'])
23
+ return 'react';
24
+ }
25
+ catch { /* no package.json */ }
26
+ // Fallback: check for framework-specific config files
27
+ try {
28
+ await stat(join(projectDir, 'next.config.ts'));
29
+ return 'nextjs';
30
+ }
31
+ catch { }
32
+ try {
33
+ await stat(join(projectDir, 'next.config.js'));
34
+ return 'nextjs';
35
+ }
36
+ catch { }
37
+ try {
38
+ await stat(join(projectDir, 'angular.json'));
39
+ return 'angular';
40
+ }
41
+ catch { }
42
+ try {
43
+ await stat(join(projectDir, 'svelte.config.js'));
44
+ return 'svelte';
45
+ }
46
+ catch { }
47
+ try {
48
+ await stat(join(projectDir, 'vite.config.ts'));
49
+ // Could be React or Vue — check for vue plugin
50
+ const config = await readFile(join(projectDir, 'vite.config.ts'), 'utf-8');
51
+ if (config.includes('@vitejs/plugin-vue'))
52
+ return 'vue';
53
+ return 'react';
54
+ }
55
+ catch { }
56
+ return 'react'; // default
57
+ }
58
+ // ─── Main Project Parser ────────────────────────────────────────────
59
+ export async function parseFrameworkProject(projectDir) {
60
+ const framework = await detectFramework(projectDir);
61
+ const result = {
62
+ framework,
63
+ projectName: basename(projectDir),
64
+ components: [],
65
+ routes: [],
66
+ globalCSS: [],
67
+ externalStyles: [],
68
+ externalScripts: [],
69
+ preconnectLinks: [],
70
+ title: '',
71
+ meta: {},
72
+ };
73
+ // Parse project title and meta from index.html or equivalent
74
+ await parseProjectMeta(projectDir, framework, result);
75
+ // Discover and parse components
76
+ const componentFiles = await discoverComponentFiles(projectDir, framework);
77
+ for (const file of componentFiles) {
78
+ try {
79
+ const content = await readFile(file, 'utf-8');
80
+ const relPath = relative(projectDir, file);
81
+ const component = parseComponentFile(content, relPath, framework);
82
+ if (component) {
83
+ result.components.push(component);
84
+ }
85
+ }
86
+ catch { /* skip unparseable files */ }
87
+ }
88
+ // Parse routes
89
+ result.routes = await parseRoutes(projectDir, framework, result.components);
90
+ // Collect global CSS
91
+ await collectGlobalCSS(projectDir, framework, result);
92
+ return result;
93
+ }
94
+ // ─── Component File Discovery ───────────────────────────────────────
95
+ async function discoverComponentFiles(projectDir, framework) {
96
+ const files = [];
97
+ const extensions = getComponentExtensions(framework);
98
+ let searchDirs;
99
+ switch (framework) {
100
+ case 'nextjs':
101
+ searchDirs = [join(projectDir, 'app'), join(projectDir, 'src')];
102
+ break;
103
+ case 'svelte':
104
+ searchDirs = [join(projectDir, 'src')];
105
+ break;
106
+ case 'angular':
107
+ searchDirs = [join(projectDir, 'src', 'app')];
108
+ break;
109
+ case 'vue':
110
+ searchDirs = [join(projectDir, 'src')];
111
+ break;
112
+ default: // react
113
+ searchDirs = [join(projectDir, 'src')];
114
+ }
115
+ for (const dir of searchDirs) {
116
+ await walkDir(dir, extensions, files);
117
+ }
118
+ return files;
119
+ }
120
+ function getComponentExtensions(framework) {
121
+ switch (framework) {
122
+ case 'vue': return ['.vue'];
123
+ case 'svelte': return ['.svelte'];
124
+ case 'angular': return ['.ts']; // Angular components are .ts
125
+ default: return ['.tsx', '.jsx'];
126
+ }
127
+ }
128
+ async function walkDir(dir, extensions, result) {
129
+ let entries;
130
+ try {
131
+ entries = await readdir(dir, { withFileTypes: true });
132
+ }
133
+ catch {
134
+ return;
135
+ }
136
+ for (const entry of entries) {
137
+ const fullPath = join(dir, entry.name);
138
+ if (entry.isDirectory()) {
139
+ if (['node_modules', '.git', 'dist', 'build', '.next', '.svelte-kit', '.angular'].includes(entry.name))
140
+ continue;
141
+ await walkDir(fullPath, extensions, result);
142
+ }
143
+ else if (entry.isFile()) {
144
+ const ext = extname(entry.name);
145
+ if (extensions.includes(ext)) {
146
+ // Skip config/utility files
147
+ if (entry.name.startsWith('vite.config') || entry.name.startsWith('next.config') ||
148
+ entry.name === 'main.ts' || entry.name === 'main.tsx' ||
149
+ entry.name.endsWith('.d.ts') || entry.name.endsWith('.spec.ts') ||
150
+ entry.name.endsWith('.test.ts') || entry.name.endsWith('.test.tsx'))
151
+ continue;
152
+ result.push(fullPath);
153
+ }
154
+ }
155
+ }
156
+ }
157
+ // ─── Component Parsing ──────────────────────────────────────────────
158
+ function parseComponentFile(content, relPath, framework) {
159
+ switch (framework) {
160
+ case 'react':
161
+ case 'nextjs':
162
+ return parseReactComponent(content, relPath, framework);
163
+ case 'vue':
164
+ return parseVueComponent(content, relPath);
165
+ case 'svelte':
166
+ return parseSvelteComponent(content, relPath);
167
+ case 'angular':
168
+ return parseAngularComponent(content, relPath);
169
+ default:
170
+ return null;
171
+ }
172
+ }
173
+ // ─── React/Next.js Parser ───────────────────────────────────────────
174
+ function parseReactComponent(content, relPath, framework) {
175
+ const name = extractComponentName(content, relPath);
176
+ if (!name)
177
+ return null;
178
+ const stateVars = new Map();
179
+ const eventHandlers = new Map();
180
+ const effects = [];
181
+ const childComponents = [];
182
+ // Extract useState declarations
183
+ const statePattern = /const\s+\[(\w+),\s*(\w+)\]\s*=\s*useState(?:<[^>]*>)?\(([^)]*)\)/g;
184
+ let match;
185
+ while ((match = statePattern.exec(content)) !== null) {
186
+ stateVars.set(match[1], match[3] || "''");
187
+ }
188
+ // Extract useEffect
189
+ const effectPattern = /useEffect\(\(\)\s*=>\s*\{([\s\S]*?)\},\s*\[[^\]]*\]\)/g;
190
+ while ((match = effectPattern.exec(content)) !== null) {
191
+ effects.push(match[1].trim());
192
+ }
193
+ // Extract event handlers: const handleX/toggleX/onX = (...) => { ... }
194
+ const handlerPattern = /const\s+(handle\w+|toggle\w+|on[A-Z]\w+)\s*=\s*(?:\([^)]*\)\s*=>|\(\)\s*=>)\s*\{([\s\S]*?)\n\s*\};/g;
195
+ while ((match = handlerPattern.exec(content)) !== null) {
196
+ eventHandlers.set(match[1], match[2].trim());
197
+ }
198
+ // Also extract function declaration handlers
199
+ const fnHandlerPattern = /function\s+(handle\w+|toggle\w+|on[A-Z]\w+)\s*\([^)]*\)\s*\{([\s\S]*?)\n\s*\}/g;
200
+ while ((match = fnHandlerPattern.exec(content)) !== null) {
201
+ if (!eventHandlers.has(match[1])) {
202
+ eventHandlers.set(match[1], match[2].trim());
203
+ }
204
+ }
205
+ // Extract JSX from return statement
206
+ const template = extractJSXReturn(content);
207
+ // Extract CSS imports
208
+ let css = '';
209
+ const cssImportPattern = /import\s+['"]\.\/([^'"]+\.css)['"]/g;
210
+ while ((match = cssImportPattern.exec(content)) !== null) {
211
+ // CSS will be collected separately
212
+ }
213
+ // Detect child component imports
214
+ const importPattern = /import\s+(\w+)\s+from\s+['"]\.\/([^'"]+)['"]/g;
215
+ while ((match = importPattern.exec(content)) !== null) {
216
+ if (/^[A-Z]/.test(match[1])) {
217
+ childComponents.push(match[1]);
218
+ }
219
+ }
220
+ // Detect Link imports (react-router or next/link)
221
+ const usesLink = /import.*Link.*from\s+['"](?:react-router-dom|next\/link)['"]/.test(content);
222
+ return {
223
+ filePath: relPath,
224
+ name,
225
+ framework,
226
+ template: template || '',
227
+ css,
228
+ script: '',
229
+ stateVars,
230
+ eventHandlers,
231
+ effects,
232
+ isInteractive: stateVars.size > 0 || eventHandlers.size > 0 || effects.length > 0,
233
+ childComponents,
234
+ };
235
+ }
236
+ function extractComponentName(content, relPath) {
237
+ // export default function ComponentName
238
+ let match = content.match(/(?:export\s+default\s+)?function\s+([A-Z]\w+)\s*\(/);
239
+ if (match)
240
+ return match[1];
241
+ // const ComponentName = ...
242
+ match = content.match(/(?:export\s+default\s+)?const\s+([A-Z]\w+)\s*[=:]/);
243
+ if (match)
244
+ return match[1];
245
+ // export default ComponentName
246
+ match = content.match(/export\s+default\s+([A-Z]\w+)/);
247
+ if (match)
248
+ return match[1];
249
+ // Derive from filename
250
+ const base = basename(relPath).replace(/\.\w+$/, '');
251
+ if (/^[A-Z]/.test(base))
252
+ return base;
253
+ return base.charAt(0).toUpperCase() + base.slice(1).replace(/-(\w)/g, (_, c) => c.toUpperCase());
254
+ }
255
+ function extractJSXReturn(content) {
256
+ // Find the LAST `return (` that contains JSX (the component's main return)
257
+ // Search from the end of the file backwards to skip handler returns
258
+ const returnPattern = /return\s*\(/g;
259
+ const candidates = [];
260
+ let match;
261
+ while ((match = returnPattern.exec(content)) !== null) {
262
+ candidates.push(match.index);
263
+ }
264
+ // Try each candidate from LAST to FIRST — the component's main return
265
+ // is usually the last one (closest to the end of the file)
266
+ for (let ci = candidates.length - 1; ci >= 0; ci--) {
267
+ const returnIdx = candidates[ci];
268
+ const start = content.indexOf('(', returnIdx);
269
+ if (start === -1)
270
+ continue;
271
+ let depth = 1;
272
+ let i = start + 1;
273
+ // Track JSX brace depth to know when we're in a JS expression vs text
274
+ let braceDepth = 0;
275
+ // Track whether we're inside an HTML tag definition (between < and >)
276
+ let inTag = false;
277
+ while (i < content.length && depth > 0) {
278
+ // Skip /* ... */ comments
279
+ if (content[i] === '/' && content[i + 1] === '*') {
280
+ i += 2;
281
+ while (i < content.length - 1 && !(content[i] === '*' && content[i + 1] === '/')) {
282
+ i++;
283
+ }
284
+ i += 2;
285
+ continue;
286
+ }
287
+ // Skip // line comments
288
+ if (content[i] === '/' && content[i + 1] === '/') {
289
+ while (i < content.length && content[i] !== '\n')
290
+ i++;
291
+ i++;
292
+ continue;
293
+ }
294
+ // Track JSX braces for expression context
295
+ if (content[i] === '{') {
296
+ braceDepth++;
297
+ i++;
298
+ continue;
299
+ }
300
+ if (content[i] === '}' && braceDepth > 0) {
301
+ braceDepth--;
302
+ i++;
303
+ continue;
304
+ }
305
+ // Track HTML tag context
306
+ if (content[i] === '<' && braceDepth === 0) {
307
+ inTag = true;
308
+ }
309
+ if (content[i] === '>' && inTag && braceDepth === 0) {
310
+ inTag = false;
311
+ }
312
+ // Handle string literals — only when inside a JS expression ({...}) or tag attribute
313
+ // In JSX text content (between > and <), quotes are just text, not string delimiters
314
+ if ((content[i] === '"' || content[i] === "'" || content[i] === '`') && (braceDepth > 0 || inTag)) {
315
+ const quote = content[i];
316
+ i++;
317
+ while (i < content.length && content[i] !== quote) {
318
+ if (content[i] === '\\')
319
+ i++; // skip escaped chars
320
+ i++;
321
+ }
322
+ }
323
+ else if (content[i] === '(') {
324
+ depth++;
325
+ }
326
+ else if (content[i] === ')') {
327
+ depth--;
328
+ }
329
+ i++;
330
+ }
331
+ if (depth !== 0)
332
+ continue;
333
+ let jsx = content.slice(start + 1, i - 1).trim();
334
+ // Check if this actually contains JSX (has HTML-like tags)
335
+ if (/<\w/.test(jsx) || /^\s*</.test(jsx)) {
336
+ // Remove wrapping fragments
337
+ jsx = jsx
338
+ .replace(/^\s*<>\s*/, '')
339
+ .replace(/\s*<\/>\s*$/, '')
340
+ .replace(/^\s*<React\.Fragment>\s*/, '')
341
+ .replace(/\s*<\/React\.Fragment>\s*$/, '')
342
+ .trim();
343
+ // Remove any trailing non-HTML code leaked after the template
344
+ // (catches export statements, multi-line comments, etc.)
345
+ // Split by lines and find where non-HTML content starts
346
+ const jsxLines = jsx.split('\n');
347
+ let truncateAt = jsxLines.length;
348
+ for (let li = jsxLines.length - 1; li >= 0; li--) {
349
+ const line = jsxLines[li].trim();
350
+ if (!line)
351
+ continue; // skip blank lines
352
+ // If line looks like JS code (export, function, var, const, /*, etc.) not HTML
353
+ if (/^(export\s|function\s|const\s|let\s|var\s|\/\*|\/\/|\*\s*$|\*\s+|^\*\/|import\s|}\s*;?\s*$)/.test(line) || line.startsWith('*')) {
354
+ truncateAt = li;
355
+ }
356
+ else {
357
+ break; // Found a line that looks like HTML, stop
358
+ }
359
+ }
360
+ if (truncateAt < jsxLines.length) {
361
+ jsx = jsxLines.slice(0, truncateAt).join('\n').trim();
362
+ }
363
+ return jsx;
364
+ }
365
+ }
366
+ return null;
367
+ }
368
+ // ─── Vue SFC Parser ─────────────────────────────────────────────────
369
+ function parseVueComponent(content, relPath) {
370
+ const name = basename(relPath).replace(/\.vue$/, '');
371
+ const pascalName = name.charAt(0).toUpperCase() + name.slice(1).replace(/-(\w)/g, (_, c) => c.toUpperCase());
372
+ const stateVars = new Map();
373
+ const eventHandlers = new Map();
374
+ const effects = [];
375
+ const childComponents = [];
376
+ // Extract <template> content
377
+ const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
378
+ const template = templateMatch ? templateMatch[1].trim() : '';
379
+ // Extract <script setup> content
380
+ const scriptMatch = content.match(/<script\s+setup[^>]*>([\s\S]*?)<\/script>/);
381
+ const script = scriptMatch ? scriptMatch[1].trim() : '';
382
+ // Extract <style> content
383
+ const styleMatch = content.match(/<style[^>]*>([\s\S]*?)<\/style>/);
384
+ const css = styleMatch ? styleMatch[1].trim() : '';
385
+ // Parse ref() declarations from script
386
+ const refPattern = /const\s+(\w+)\s*=\s*ref(?:<[^>]*>)?\(([^)]*)\)/g;
387
+ let match;
388
+ while ((match = refPattern.exec(script)) !== null) {
389
+ stateVars.set(match[1], match[2] || "''");
390
+ }
391
+ // Parse event handlers
392
+ const handlerPattern = /const\s+(\w+)\s*=\s*(?:\([^)]*\)\s*=>|\(\)\s*=>)\s*\{([\s\S]*?)\n\s*\};?/g;
393
+ while ((match = handlerPattern.exec(script)) !== null) {
394
+ eventHandlers.set(match[1], match[2].trim());
395
+ }
396
+ // Parse function handlers
397
+ const fnHandlerPattern = /function\s+(\w+)\s*\([^)]*\)\s*\{([\s\S]*?)\n\s*\}/g;
398
+ while ((match = fnHandlerPattern.exec(script)) !== null) {
399
+ eventHandlers.set(match[1], match[2].trim());
400
+ }
401
+ // Parse onMounted effects
402
+ const mountedPattern = /onMounted\(\(\)\s*=>\s*\{([\s\S]*?)\}\)/g;
403
+ while ((match = mountedPattern.exec(script)) !== null) {
404
+ effects.push(match[1].trim());
405
+ }
406
+ // Import detection
407
+ const importPattern = /import\s+(\w+)\s+from\s+['"][^'"]*['"]/g;
408
+ while ((match = importPattern.exec(script)) !== null) {
409
+ if (/^[A-Z]/.test(match[1]))
410
+ childComponents.push(match[1]);
411
+ }
412
+ return {
413
+ filePath: relPath,
414
+ name: pascalName,
415
+ framework: 'vue',
416
+ template,
417
+ css,
418
+ script,
419
+ stateVars,
420
+ eventHandlers,
421
+ effects,
422
+ isInteractive: stateVars.size > 0 || eventHandlers.size > 0 || effects.length > 0,
423
+ childComponents,
424
+ };
425
+ }
426
+ // ─── Svelte Parser ──────────────────────────────────────────────────
427
+ function parseSvelteComponent(content, relPath) {
428
+ const name = basename(relPath).replace(/\.svelte$/, '');
429
+ const pascalName = name.charAt(0).toUpperCase() + name.slice(1).replace(/-(\w)/g, (_, c) => c.toUpperCase());
430
+ const stateVars = new Map();
431
+ const eventHandlers = new Map();
432
+ const effects = [];
433
+ const childComponents = [];
434
+ // Extract <script> content
435
+ const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
436
+ const script = scriptMatch ? scriptMatch[1].trim() : '';
437
+ // Extract <style> content
438
+ const styleMatch = content.match(/<style[^>]*>([\s\S]*?)<\/style>/);
439
+ const css = styleMatch ? styleMatch[1].trim() : '';
440
+ // Template is everything outside script and style tags
441
+ let template = content
442
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/g, '')
443
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/g, '')
444
+ .trim();
445
+ // Parse $state() declarations
446
+ const statePattern = /let\s+(\w+)\s*=\s*\$state\(([^)]*)\)/g;
447
+ let match;
448
+ while ((match = statePattern.exec(script)) !== null) {
449
+ stateVars.set(match[1], match[2] || "''");
450
+ }
451
+ // Parse regular let declarations (Svelte 4 reactive)
452
+ const letPattern = /let\s+(\w+)\s*=\s*([^;]+);/g;
453
+ while ((match = letPattern.exec(script)) !== null) {
454
+ if (!match[2].includes('$state') && !match[2].includes('import')) {
455
+ stateVars.set(match[1], match[2].trim());
456
+ }
457
+ }
458
+ // Parse function handlers
459
+ const fnPattern = /function\s+(\w+)\s*\([^)]*\)\s*\{([\s\S]*?)\n\s*\}/g;
460
+ while ((match = fnPattern.exec(script)) !== null) {
461
+ eventHandlers.set(match[1], match[2].trim());
462
+ }
463
+ // Parse $effect()
464
+ const effectPattern = /\$effect\(\(\)\s*=>\s*\{([\s\S]*?)\}\)/g;
465
+ while ((match = effectPattern.exec(script)) !== null) {
466
+ effects.push(match[1].trim());
467
+ }
468
+ // Parse onMount
469
+ const mountPattern = /onMount\(\(\)\s*=>\s*\{([\s\S]*?)\}\)/g;
470
+ while ((match = mountPattern.exec(script)) !== null) {
471
+ effects.push(match[1].trim());
472
+ }
473
+ return {
474
+ filePath: relPath,
475
+ name: pascalName,
476
+ framework: 'svelte',
477
+ template,
478
+ css,
479
+ script,
480
+ stateVars,
481
+ eventHandlers,
482
+ effects,
483
+ isInteractive: stateVars.size > 0 || eventHandlers.size > 0 || effects.length > 0,
484
+ childComponents,
485
+ };
486
+ }
487
+ // ─── Angular Parser ─────────────────────────────────────────────────
488
+ function parseAngularComponent(content, relPath) {
489
+ // Must have @Component decorator
490
+ if (!content.includes('@Component'))
491
+ return null;
492
+ const name = basename(relPath).replace(/\.component\.ts$/, '').replace(/\.ts$/, '');
493
+ const pascalName = name.charAt(0).toUpperCase() + name.slice(1).replace(/-(\w)/g, (_, c) => c.toUpperCase());
494
+ const stateVars = new Map();
495
+ const eventHandlers = new Map();
496
+ const effects = [];
497
+ const childComponents = [];
498
+ // Extract inline template
499
+ const templateMatch = content.match(/template:\s*`([\s\S]*?)`/);
500
+ const template = templateMatch ? templateMatch[1].trim() : '';
501
+ // Extract inline styles
502
+ const stylesMatch = content.match(/styles:\s*\[\s*`([\s\S]*?)`\s*\]/);
503
+ const css = stylesMatch ? stylesMatch[1].trim() : '';
504
+ // Parse signal() declarations
505
+ const signalPattern = /(\w+)\s*=\s*signal(?:<[^>]*>)?\(([^)]*)\)/g;
506
+ let match;
507
+ while ((match = signalPattern.exec(content)) !== null) {
508
+ stateVars.set(match[1], match[2] || "''");
509
+ }
510
+ // Parse methods (event handlers)
511
+ const methodPattern = /(\w+)\s*\([^)]*\)\s*\{([\s\S]*?)\n\s*\}/g;
512
+ while ((match = methodPattern.exec(content)) !== null) {
513
+ const methodName = match[1];
514
+ if (['constructor', 'ngOnInit', 'ngOnDestroy', 'ngAfterViewInit'].includes(methodName)) {
515
+ if (methodName === 'ngOnInit')
516
+ effects.push(match[2].trim());
517
+ continue;
518
+ }
519
+ eventHandlers.set(methodName, match[2].trim());
520
+ }
521
+ return {
522
+ filePath: relPath,
523
+ name: pascalName + 'Component',
524
+ framework: 'angular',
525
+ template,
526
+ css,
527
+ script: '',
528
+ stateVars,
529
+ eventHandlers,
530
+ effects,
531
+ isInteractive: stateVars.size > 0 || eventHandlers.size > 0 || effects.length > 0,
532
+ childComponents,
533
+ };
534
+ }
535
+ // ─── Route Parsing ──────────────────────────────────────────────────
536
+ async function parseRoutes(projectDir, framework, components) {
537
+ const routes = [];
538
+ switch (framework) {
539
+ case 'react': {
540
+ // Parse App.tsx for React Router routes
541
+ try {
542
+ const appContent = await readFile(join(projectDir, 'src', 'App.tsx'), 'utf-8');
543
+ const routePattern = /<Route\s+path="([^"]+)"\s+element=\{<(\w+)\s*\/>\}/g;
544
+ let match;
545
+ while ((match = routePattern.exec(appContent)) !== null) {
546
+ routes.push({
547
+ path: match[1],
548
+ componentName: match[2],
549
+ componentFile: '',
550
+ isIndex: match[1] === '/',
551
+ });
552
+ }
553
+ }
554
+ catch { /* no App.tsx or no routes */ }
555
+ // If no routes found, treat it as single-page
556
+ if (routes.length === 0 && components.length > 0) {
557
+ routes.push({
558
+ path: '/',
559
+ componentName: components[0].name,
560
+ componentFile: components[0].filePath,
561
+ isIndex: true,
562
+ });
563
+ }
564
+ break;
565
+ }
566
+ case 'nextjs': {
567
+ // File-based routing: app/page.tsx, app/about/page.tsx
568
+ const appDir = join(projectDir, 'app');
569
+ const pageFiles = await findFiles(appDir, 'page.tsx');
570
+ for (const file of pageFiles) {
571
+ const relDir = relative(appDir, dirname(file));
572
+ const path = relDir === '' || relDir === '.' ? '/' : `/${relDir}`;
573
+ const content = await readFile(file, 'utf-8');
574
+ const nameMatch = content.match(/import\s+(\w+)\s+from/);
575
+ routes.push({
576
+ path,
577
+ componentName: nameMatch?.[1] || 'Page',
578
+ componentFile: relative(projectDir, file),
579
+ isIndex: path === '/',
580
+ });
581
+ }
582
+ break;
583
+ }
584
+ case 'vue': {
585
+ // Parse router/index.ts
586
+ try {
587
+ const routerContent = await readFile(join(projectDir, 'src', 'router', 'index.ts'), 'utf-8');
588
+ const routePattern = /path:\s*'([^']+)',\s*name:\s*'([^']+)'/g;
589
+ let match;
590
+ while ((match = routePattern.exec(routerContent)) !== null) {
591
+ routes.push({
592
+ path: match[1],
593
+ componentName: match[2],
594
+ componentFile: '',
595
+ isIndex: match[1] === '/',
596
+ });
597
+ }
598
+ }
599
+ catch { /* no router config */ }
600
+ break;
601
+ }
602
+ case 'svelte': {
603
+ // File-based routing: src/routes/+page.svelte, src/routes/about/+page.svelte
604
+ const routesDir = join(projectDir, 'src', 'routes');
605
+ const pageFiles = await findFiles(routesDir, '+page.svelte');
606
+ for (const file of pageFiles) {
607
+ const relDir = relative(routesDir, dirname(file));
608
+ const path = relDir === '' || relDir === '.' ? '/' : `/${relDir}`;
609
+ const content = await readFile(file, 'utf-8');
610
+ const nameMatch = content.match(/import\s+(\w+)\s+from/);
611
+ routes.push({
612
+ path,
613
+ componentName: nameMatch?.[1] || basename(dirname(file)),
614
+ componentFile: relative(projectDir, file),
615
+ isIndex: path === '/',
616
+ });
617
+ }
618
+ break;
619
+ }
620
+ case 'angular': {
621
+ // Parse app.routes.ts
622
+ try {
623
+ const routesContent = await readFile(join(projectDir, 'src', 'app', 'app.routes.ts'), 'utf-8');
624
+ // Match route blocks: { path: '...', loadComponent: ... .then((m) => m.ComponentName) }
625
+ const routePattern = /path:\s*'([^']*)'[\s\S]*?\.then\(\s*\(?m\)?\s*=>\s*m\.(\w+)/g;
626
+ let match;
627
+ while ((match = routePattern.exec(routesContent)) !== null) {
628
+ routes.push({
629
+ path: match[1] === '' ? '/' : `/${match[1]}`,
630
+ componentName: match[2],
631
+ componentFile: '',
632
+ isIndex: match[1] === '',
633
+ });
634
+ }
635
+ }
636
+ catch { /* no routes config */ }
637
+ break;
638
+ }
639
+ }
640
+ return routes;
641
+ }
642
+ async function findFiles(dir, fileName) {
643
+ const results = [];
644
+ let entries;
645
+ try {
646
+ entries = await readdir(dir, { withFileTypes: true });
647
+ }
648
+ catch {
649
+ return results;
650
+ }
651
+ for (const entry of entries) {
652
+ const fullPath = join(dir, entry.name);
653
+ if (entry.isDirectory()) {
654
+ if (entry.name === 'node_modules')
655
+ continue;
656
+ results.push(...await findFiles(fullPath, fileName));
657
+ }
658
+ else if (entry.name === fileName) {
659
+ results.push(fullPath);
660
+ }
661
+ }
662
+ return results;
663
+ }
664
+ // ─── Project Meta Parsing ───────────────────────────────────────────
665
+ async function parseProjectMeta(projectDir, framework, result) {
666
+ // Try reading index.html
667
+ const indexPaths = [
668
+ join(projectDir, 'index.html'),
669
+ join(projectDir, 'public', 'index.html'),
670
+ join(projectDir, 'src', 'index.html'),
671
+ join(projectDir, 'src', 'app.html'), // SvelteKit
672
+ ];
673
+ for (const indexPath of indexPaths) {
674
+ try {
675
+ const html = await readFile(indexPath, 'utf-8');
676
+ const titleMatch = html.match(/<title>([^<]*)<\/title>/);
677
+ if (titleMatch)
678
+ result.title = titleMatch[1];
679
+ // Extract external stylesheets (both href="..." and rel="stylesheet")
680
+ const linkPattern = /<link[^>]*href="(https?:\/\/[^"]+)"[^>]*>/g;
681
+ let match;
682
+ while ((match = linkPattern.exec(html)) !== null) {
683
+ // Skip preconnect links (they're not stylesheets)
684
+ if (match[0].includes('rel="preconnect"'))
685
+ continue;
686
+ // Only include if it's a stylesheet (has rel="stylesheet" or is a font link)
687
+ if (match[0].includes('stylesheet') || match[0].includes('fonts.googleapis')) {
688
+ result.externalStyles.push(match[1]);
689
+ }
690
+ }
691
+ // Also match: <link rel="stylesheet" href="...">
692
+ const linkPattern2 = /<link[^>]*rel="stylesheet"[^>]*href="(https?:\/\/[^"]+)"/g;
693
+ while ((match = linkPattern2.exec(html)) !== null) {
694
+ if (!result.externalStyles.includes(match[1])) {
695
+ result.externalStyles.push(match[1]);
696
+ }
697
+ }
698
+ // Match preconnect links (for fonts)
699
+ const preconnectPattern = /<link[^>]*rel="preconnect"[^>]*>/g;
700
+ while ((match = preconnectPattern.exec(html)) !== null) {
701
+ if (!result.preconnectLinks.includes(match[0])) {
702
+ result.preconnectLinks.push(match[0]);
703
+ }
704
+ }
705
+ // Extract external scripts
706
+ const scriptPattern = /<script[^>]*src="(https?:\/\/[^"]+)"[^>]*>/g;
707
+ while ((match = scriptPattern.exec(html)) !== null) {
708
+ result.externalScripts.push(match[1]);
709
+ }
710
+ break;
711
+ }
712
+ catch {
713
+ continue;
714
+ }
715
+ }
716
+ // Get project name from package.json
717
+ try {
718
+ const pkg = JSON.parse(await readFile(join(projectDir, 'package.json'), 'utf-8'));
719
+ result.projectName = pkg.name || result.projectName;
720
+ }
721
+ catch { }
722
+ }
723
+ // ─── Global CSS Collection ──────────────────────────────────────────
724
+ async function collectGlobalCSS(projectDir, framework, result) {
725
+ const cssPaths = [];
726
+ switch (framework) {
727
+ case 'react':
728
+ cssPaths.push(join(projectDir, 'src', 'styles', 'global.css'), join(projectDir, 'src', 'styles', 'tailwind.css'), join(projectDir, 'src', 'index.css'), join(projectDir, 'src', 'App.css'));
729
+ break;
730
+ case 'nextjs':
731
+ cssPaths.push(join(projectDir, 'app', 'styles', 'global.css'), join(projectDir, 'app', 'styles', 'tailwind.css'), join(projectDir, 'app', 'globals.css'));
732
+ break;
733
+ case 'vue':
734
+ cssPaths.push(join(projectDir, 'src', 'styles', 'global.css'), join(projectDir, 'src', 'styles', 'tailwind.css'), join(projectDir, 'src', 'assets', 'main.css'));
735
+ break;
736
+ case 'svelte':
737
+ cssPaths.push(join(projectDir, 'src', 'lib', 'styles', 'global.css'), join(projectDir, 'src', 'lib', 'styles', 'tailwind.css'), join(projectDir, 'src', 'app.css'));
738
+ break;
739
+ case 'angular':
740
+ cssPaths.push(join(projectDir, 'src', 'styles', 'global.css'), join(projectDir, 'src', 'styles.css'));
741
+ break;
742
+ }
743
+ for (const cssPath of cssPaths) {
744
+ try {
745
+ const css = await readFile(cssPath, 'utf-8');
746
+ if (css.trim())
747
+ result.globalCSS.push(css);
748
+ }
749
+ catch { /* file doesn't exist */ }
750
+ }
751
+ // Also collect component-level CSS files
752
+ const componentDir = framework === 'svelte'
753
+ ? join(projectDir, 'src', 'lib', 'components')
754
+ : framework === 'angular'
755
+ ? join(projectDir, 'src', 'app', 'components')
756
+ : framework === 'nextjs'
757
+ ? join(projectDir, 'app', 'components')
758
+ : join(projectDir, 'src', 'components');
759
+ try {
760
+ const cssFiles = [];
761
+ await walkDir(componentDir, ['.css'], cssFiles);
762
+ for (const file of cssFiles) {
763
+ const css = await readFile(file, 'utf-8');
764
+ if (css.trim())
765
+ result.globalCSS.push(css);
766
+ }
767
+ }
768
+ catch { }
769
+ }
770
+ //# sourceMappingURL=framework-parser.js.map