@buoy-design/cli 0.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 (101) hide show
  1. package/dist/bin.d.ts +3 -0
  2. package/dist/bin.d.ts.map +1 -0
  3. package/dist/bin.js +5 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/commands/__tests__/ci.test.d.ts +2 -0
  6. package/dist/commands/__tests__/ci.test.d.ts.map +1 -0
  7. package/dist/commands/__tests__/ci.test.js +33 -0
  8. package/dist/commands/__tests__/ci.test.js.map +1 -0
  9. package/dist/commands/bootstrap.d.ts +3 -0
  10. package/dist/commands/bootstrap.d.ts.map +1 -0
  11. package/dist/commands/bootstrap.js +458 -0
  12. package/dist/commands/bootstrap.js.map +1 -0
  13. package/dist/commands/build.d.ts +3 -0
  14. package/dist/commands/build.d.ts.map +1 -0
  15. package/dist/commands/build.js +314 -0
  16. package/dist/commands/build.js.map +1 -0
  17. package/dist/commands/ci.d.ts +24 -0
  18. package/dist/commands/ci.d.ts.map +1 -0
  19. package/dist/commands/ci.js +273 -0
  20. package/dist/commands/ci.js.map +1 -0
  21. package/dist/commands/ci.logic.d.ts +20 -0
  22. package/dist/commands/ci.logic.d.ts.map +1 -0
  23. package/dist/commands/ci.logic.js +28 -0
  24. package/dist/commands/ci.logic.js.map +1 -0
  25. package/dist/commands/drift.d.ts +3 -0
  26. package/dist/commands/drift.d.ts.map +1 -0
  27. package/dist/commands/drift.js +178 -0
  28. package/dist/commands/drift.js.map +1 -0
  29. package/dist/commands/index.d.ts +9 -0
  30. package/dist/commands/index.d.ts.map +1 -0
  31. package/dist/commands/index.js +9 -0
  32. package/dist/commands/index.js.map +1 -0
  33. package/dist/commands/init.d.ts +3 -0
  34. package/dist/commands/init.d.ts.map +1 -0
  35. package/dist/commands/init.js +490 -0
  36. package/dist/commands/init.js.map +1 -0
  37. package/dist/commands/plugins.d.ts +3 -0
  38. package/dist/commands/plugins.d.ts.map +1 -0
  39. package/dist/commands/plugins.js +72 -0
  40. package/dist/commands/plugins.js.map +1 -0
  41. package/dist/commands/scan.d.ts +3 -0
  42. package/dist/commands/scan.d.ts.map +1 -0
  43. package/dist/commands/scan.js +266 -0
  44. package/dist/commands/scan.js.map +1 -0
  45. package/dist/commands/status.d.ts +3 -0
  46. package/dist/commands/status.d.ts.map +1 -0
  47. package/dist/commands/status.js +205 -0
  48. package/dist/commands/status.js.map +1 -0
  49. package/dist/config/index.d.ts +3 -0
  50. package/dist/config/index.d.ts.map +1 -0
  51. package/dist/config/index.js +3 -0
  52. package/dist/config/index.js.map +1 -0
  53. package/dist/config/loader.d.ts +8 -0
  54. package/dist/config/loader.d.ts.map +1 -0
  55. package/dist/config/loader.js +67 -0
  56. package/dist/config/loader.js.map +1 -0
  57. package/dist/config/schema.d.ts +1040 -0
  58. package/dist/config/schema.d.ts.map +1 -0
  59. package/dist/config/schema.js +116 -0
  60. package/dist/config/schema.js.map +1 -0
  61. package/dist/detect/frameworks.d.ts +18 -0
  62. package/dist/detect/frameworks.d.ts.map +1 -0
  63. package/dist/detect/frameworks.js +168 -0
  64. package/dist/detect/frameworks.js.map +1 -0
  65. package/dist/detect/index.d.ts +3 -0
  66. package/dist/detect/index.d.ts.map +1 -0
  67. package/dist/detect/index.js +3 -0
  68. package/dist/detect/index.js.map +1 -0
  69. package/dist/detect/project-detector.d.ts +61 -0
  70. package/dist/detect/project-detector.d.ts.map +1 -0
  71. package/dist/detect/project-detector.js +849 -0
  72. package/dist/detect/project-detector.js.map +1 -0
  73. package/dist/index.d.ts +5 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +22 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/output/formatters.d.ts +21 -0
  78. package/dist/output/formatters.d.ts.map +1 -0
  79. package/dist/output/formatters.js +421 -0
  80. package/dist/output/formatters.js.map +1 -0
  81. package/dist/output/index.d.ts +3 -0
  82. package/dist/output/index.d.ts.map +1 -0
  83. package/dist/output/index.js +3 -0
  84. package/dist/output/index.js.map +1 -0
  85. package/dist/output/reporters.d.ts +23 -0
  86. package/dist/output/reporters.d.ts.map +1 -0
  87. package/dist/output/reporters.js +147 -0
  88. package/dist/output/reporters.js.map +1 -0
  89. package/dist/plugins/index.d.ts +3 -0
  90. package/dist/plugins/index.d.ts.map +1 -0
  91. package/dist/plugins/index.js +3 -0
  92. package/dist/plugins/index.js.map +1 -0
  93. package/dist/plugins/loader.d.ts +11 -0
  94. package/dist/plugins/loader.d.ts.map +1 -0
  95. package/dist/plugins/loader.js +77 -0
  96. package/dist/plugins/loader.js.map +1 -0
  97. package/dist/plugins/registry.d.ts +15 -0
  98. package/dist/plugins/registry.d.ts.map +1 -0
  99. package/dist/plugins/registry.js +32 -0
  100. package/dist/plugins/registry.js.map +1 -0
  101. package/package.json +48 -0
@@ -0,0 +1,849 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
2
+ import { resolve, basename } from 'path';
3
+ import { glob } from 'glob';
4
+ // Common component directory names (JS frameworks)
5
+ const COMPONENT_DIRS = [
6
+ 'src/components',
7
+ 'components',
8
+ 'src/ui',
9
+ 'ui',
10
+ 'lib/components',
11
+ 'lib/ui',
12
+ 'app/components',
13
+ 'packages/ui/src',
14
+ 'packages/components/src',
15
+ ];
16
+ // Template directories for server-side frameworks
17
+ const TEMPLATE_DIRS = [
18
+ // PHP
19
+ { dir: 'templates', ext: 'php', type: 'php' },
20
+ { dir: 'views', ext: 'php', type: 'php' },
21
+ { dir: 'includes', ext: 'php', type: 'php' },
22
+ { dir: 'partials', ext: 'php', type: 'php' },
23
+ // Laravel Blade
24
+ { dir: 'resources/views', ext: 'blade.php', type: 'blade' },
25
+ // Ruby/Rails ERB
26
+ { dir: 'app/views', ext: 'erb', type: 'erb' },
27
+ { dir: 'app/views', ext: 'html.erb', type: 'erb' },
28
+ // Twig (Symfony)
29
+ { dir: 'templates', ext: 'html.twig', type: 'twig' },
30
+ // Django/Jinja/Go/Generic HTML templates
31
+ { dir: 'templates', ext: 'html', type: 'html' },
32
+ // Hugo
33
+ { dir: 'layouts', ext: 'html', type: 'html' },
34
+ // Jekyll
35
+ { dir: '_layouts', ext: 'html', type: 'html' },
36
+ { dir: '_includes', ext: 'html', type: 'html' },
37
+ // Eleventy
38
+ { dir: 'src', ext: 'njk', type: 'njk' },
39
+ { dir: '_includes', ext: 'njk', type: 'njk' },
40
+ ];
41
+ // Token file patterns
42
+ const TOKEN_PATTERNS = [
43
+ // Standard token files
44
+ { pattern: '**/tokens.json', type: 'json', name: 'Design tokens (JSON)' },
45
+ { pattern: '**/tokens/*.json', type: 'json', name: 'Design tokens (JSON)' },
46
+ { pattern: '**/design-tokens.json', type: 'json', name: 'Design tokens (JSON)' },
47
+ { pattern: '**/design-tokens/*.json', type: 'json', name: 'Design tokens (JSON)' },
48
+ // Style Dictionary
49
+ { pattern: '**/style-dictionary.config.json', type: 'json', name: 'Style Dictionary config' },
50
+ { pattern: '**/style-dictionary.config.js', type: 'js', name: 'Style Dictionary config' },
51
+ { pattern: '**/tokens/**/**.json', type: 'json', name: 'Style Dictionary tokens' },
52
+ // Tokens Studio (Figma plugin)
53
+ { pattern: '**/tokens.json', type: 'json', name: 'Tokens Studio' },
54
+ { pattern: '**/$metadata.json', type: 'json', name: 'Tokens Studio metadata' },
55
+ // W3C Design Token format
56
+ { pattern: '**/*.tokens.json', type: 'json', name: 'W3C Design Tokens' },
57
+ // CSS
58
+ { pattern: '**/variables.css', type: 'css', name: 'CSS variables' },
59
+ { pattern: '**/theme.css', type: 'css', name: 'Theme CSS' },
60
+ { pattern: '**/custom-properties.css', type: 'css', name: 'CSS custom properties' },
61
+ // SCSS/Sass
62
+ { pattern: '**/_variables.scss', type: 'scss', name: 'SCSS variables' },
63
+ { pattern: '**/_tokens.scss', type: 'scss', name: 'SCSS tokens' },
64
+ { pattern: '**/variables.scss', type: 'scss', name: 'SCSS variables' },
65
+ { pattern: '**/_colors.scss', type: 'scss', name: 'SCSS colors' },
66
+ { pattern: '**/_typography.scss', type: 'scss', name: 'SCSS typography' },
67
+ // JS/TS theme files
68
+ { pattern: '**/theme.ts', type: 'js', name: 'Theme config' },
69
+ { pattern: '**/theme.js', type: 'js', name: 'Theme config' },
70
+ { pattern: '**/tokens.ts', type: 'js', name: 'Token definitions' },
71
+ { pattern: '**/tokens.js', type: 'js', name: 'Token definitions' },
72
+ { pattern: '**/theme/index.ts', type: 'js', name: 'Theme index' },
73
+ { pattern: '**/theme/index.js', type: 'js', name: 'Theme index' },
74
+ // Tailwind
75
+ { pattern: 'tailwind.config.js', type: 'tailwind', name: 'Tailwind config' },
76
+ { pattern: 'tailwind.config.ts', type: 'tailwind', name: 'Tailwind config' },
77
+ { pattern: 'tailwind.config.mjs', type: 'tailwind', name: 'Tailwind config' },
78
+ { pattern: 'tailwind.config.cjs', type: 'tailwind', name: 'Tailwind config' },
79
+ ];
80
+ // Design system packages to detect
81
+ const DESIGN_SYSTEMS = [
82
+ // React design systems
83
+ { package: '@chakra-ui/react', type: 'chakra' },
84
+ { package: '@mui/material', type: 'mui' },
85
+ { package: '@material-ui/core', type: 'mui' },
86
+ { package: 'antd', type: 'antd' },
87
+ { package: '@radix-ui/react-', type: 'radix' },
88
+ { package: '@shadcn/ui', type: 'shadcn' },
89
+ { package: '@mantine/core', type: 'mantine' },
90
+ { package: '@nextui-org/react', type: 'nextui' },
91
+ { package: 'primereact', type: 'primereact' },
92
+ { package: '@carbon/react', type: 'carbon' },
93
+ // CSS frameworks
94
+ { package: 'bootstrap', type: 'bootstrap' },
95
+ { package: 'react-bootstrap', type: 'bootstrap' },
96
+ { package: '@ng-bootstrap/ng-bootstrap', type: 'bootstrap' },
97
+ { package: 'bulma', type: 'bulma' },
98
+ { package: 'foundation-sites', type: 'foundation' },
99
+ { package: 'tailwindcss', type: 'tailwind' },
100
+ ];
101
+ export class ProjectDetector {
102
+ root;
103
+ packageJson = null;
104
+ constructor(root = process.cwd()) {
105
+ this.root = root;
106
+ this.loadPackageJson();
107
+ }
108
+ loadPackageJson() {
109
+ const pkgPath = resolve(this.root, 'package.json');
110
+ if (existsSync(pkgPath)) {
111
+ try {
112
+ this.packageJson = JSON.parse(readFileSync(pkgPath, 'utf-8'));
113
+ }
114
+ catch {
115
+ this.packageJson = null;
116
+ }
117
+ }
118
+ }
119
+ async detect() {
120
+ const [frameworks, components, tokens, storybook, designSystem, monorepo] = await Promise.all([
121
+ this.detectFrameworks(),
122
+ this.detectComponents(),
123
+ this.detectTokens(),
124
+ this.detectStorybook(),
125
+ this.detectDesignSystem(),
126
+ this.detectMonorepo(),
127
+ ]);
128
+ return {
129
+ name: this.getProjectName(),
130
+ root: this.root,
131
+ frameworks,
132
+ primaryFramework: frameworks[0] || null,
133
+ components,
134
+ tokens,
135
+ storybook,
136
+ designSystem,
137
+ monorepo,
138
+ };
139
+ }
140
+ getProjectName() {
141
+ if (this.packageJson && typeof this.packageJson.name === 'string') {
142
+ return this.packageJson.name;
143
+ }
144
+ return basename(this.root);
145
+ }
146
+ getAllDeps() {
147
+ if (!this.packageJson)
148
+ return {};
149
+ const deps = (this.packageJson.dependencies || {});
150
+ const devDeps = (this.packageJson.devDependencies || {});
151
+ return { ...deps, ...devDeps };
152
+ }
153
+ async detectFrameworks() {
154
+ const deps = this.getAllDeps();
155
+ const hasTypescript = 'typescript' in deps || existsSync(resolve(this.root, 'tsconfig.json'));
156
+ const frameworks = [];
157
+ const addedNames = new Set();
158
+ const addFramework = (fw) => {
159
+ // Avoid duplicates (e.g., don't add both 'react' and 'nextjs' as separate react entries)
160
+ if (!addedNames.has(fw.name)) {
161
+ addedNames.add(fw.name);
162
+ frameworks.push(fw);
163
+ }
164
+ };
165
+ // ============================================
166
+ // Meta-frameworks (check first - more specific)
167
+ // ============================================
168
+ // Next.js
169
+ if ('next' in deps) {
170
+ addFramework({
171
+ name: 'nextjs',
172
+ version: deps['next'] || 'unknown',
173
+ typescript: hasTypescript,
174
+ meta: 'React',
175
+ });
176
+ }
177
+ // Nuxt
178
+ if ('nuxt' in deps || 'nuxt3' in deps) {
179
+ addFramework({
180
+ name: 'nuxt',
181
+ version: deps['nuxt'] || deps['nuxt3'] || 'unknown',
182
+ typescript: hasTypescript,
183
+ meta: 'Vue',
184
+ });
185
+ }
186
+ // Astro
187
+ if ('astro' in deps) {
188
+ addFramework({
189
+ name: 'astro',
190
+ version: deps['astro'] || 'unknown',
191
+ typescript: hasTypescript,
192
+ });
193
+ }
194
+ // Remix
195
+ if ('@remix-run/react' in deps || '@remix-run/node' in deps) {
196
+ addFramework({
197
+ name: 'remix',
198
+ version: deps['@remix-run/react'] || deps['@remix-run/node'] || 'unknown',
199
+ typescript: hasTypescript,
200
+ meta: 'React',
201
+ });
202
+ }
203
+ // SvelteKit
204
+ if ('@sveltejs/kit' in deps) {
205
+ addFramework({
206
+ name: 'sveltekit',
207
+ version: deps['@sveltejs/kit'] || 'unknown',
208
+ typescript: hasTypescript,
209
+ meta: 'Svelte',
210
+ });
211
+ }
212
+ // Gatsby
213
+ if ('gatsby' in deps) {
214
+ addFramework({
215
+ name: 'gatsby',
216
+ version: deps['gatsby'] || 'unknown',
217
+ typescript: hasTypescript,
218
+ meta: 'React',
219
+ });
220
+ }
221
+ // ============================================
222
+ // Mobile frameworks
223
+ // ============================================
224
+ // Expo (check before React Native)
225
+ if ('expo' in deps) {
226
+ addFramework({
227
+ name: 'expo',
228
+ version: deps['expo'] || 'unknown',
229
+ typescript: hasTypescript,
230
+ meta: 'React Native',
231
+ });
232
+ }
233
+ // React Native (only if not already added via Expo)
234
+ if ('react-native' in deps && !addedNames.has('expo')) {
235
+ addFramework({
236
+ name: 'react-native',
237
+ version: deps['react-native'] || 'unknown',
238
+ typescript: hasTypescript,
239
+ });
240
+ }
241
+ // Flutter (check for pubspec.yaml)
242
+ if (existsSync(resolve(this.root, 'pubspec.yaml'))) {
243
+ try {
244
+ const pubspec = readFileSync(resolve(this.root, 'pubspec.yaml'), 'utf-8');
245
+ if (pubspec.includes('flutter:')) {
246
+ addFramework({
247
+ name: 'flutter',
248
+ version: 'unknown',
249
+ typescript: false,
250
+ });
251
+ }
252
+ }
253
+ catch {
254
+ // ignore
255
+ }
256
+ }
257
+ // ============================================
258
+ // Web Components
259
+ // ============================================
260
+ // Lit
261
+ if ('lit' in deps || 'lit-element' in deps) {
262
+ addFramework({
263
+ name: 'lit',
264
+ version: deps['lit'] || deps['lit-element'] || 'unknown',
265
+ typescript: hasTypescript,
266
+ });
267
+ }
268
+ // Stencil
269
+ if ('@stencil/core' in deps) {
270
+ addFramework({
271
+ name: 'stencil',
272
+ version: deps['@stencil/core'] || 'unknown',
273
+ typescript: true,
274
+ });
275
+ }
276
+ // ============================================
277
+ // Base JS frameworks (only add if meta-framework not already added)
278
+ // ============================================
279
+ // Preact
280
+ if ('preact' in deps) {
281
+ addFramework({
282
+ name: 'preact',
283
+ version: deps['preact'] || 'unknown',
284
+ typescript: hasTypescript,
285
+ });
286
+ }
287
+ // React (skip if Next.js, Remix, Gatsby, or Expo already added)
288
+ const hasReactMeta = addedNames.has('nextjs') || addedNames.has('remix') || addedNames.has('gatsby') || addedNames.has('expo') || addedNames.has('react-native');
289
+ if (('react' in deps || 'react-dom' in deps) && !hasReactMeta) {
290
+ addFramework({
291
+ name: 'react',
292
+ version: deps['react'] || deps['react-dom'] || 'unknown',
293
+ typescript: hasTypescript,
294
+ });
295
+ }
296
+ // Vue (skip if Nuxt already added)
297
+ if ('vue' in deps && !addedNames.has('nuxt')) {
298
+ addFramework({
299
+ name: 'vue',
300
+ version: deps['vue'] || 'unknown',
301
+ typescript: hasTypescript,
302
+ });
303
+ }
304
+ // Svelte (skip if SvelteKit already added)
305
+ if ('svelte' in deps && !addedNames.has('sveltekit')) {
306
+ addFramework({
307
+ name: 'svelte',
308
+ version: deps['svelte'] || 'unknown',
309
+ typescript: hasTypescript,
310
+ });
311
+ }
312
+ // Angular
313
+ if ('@angular/core' in deps) {
314
+ addFramework({
315
+ name: 'angular',
316
+ version: deps['@angular/core'] || 'unknown',
317
+ typescript: true,
318
+ });
319
+ }
320
+ // Solid
321
+ if ('solid-js' in deps) {
322
+ addFramework({
323
+ name: 'solid',
324
+ version: deps['solid-js'] || 'unknown',
325
+ typescript: hasTypescript,
326
+ });
327
+ }
328
+ // ============================================
329
+ // Node.js server frameworks
330
+ // ============================================
331
+ // NestJS
332
+ if ('@nestjs/core' in deps) {
333
+ addFramework({
334
+ name: 'nestjs',
335
+ version: deps['@nestjs/core'] || 'unknown',
336
+ typescript: true,
337
+ });
338
+ }
339
+ // Express
340
+ if ('express' in deps) {
341
+ addFramework({
342
+ name: 'express',
343
+ version: deps['express'] || 'unknown',
344
+ typescript: hasTypescript,
345
+ });
346
+ }
347
+ // ============================================
348
+ // Static site generators (file-based detection)
349
+ // ============================================
350
+ // Eleventy
351
+ if ('@11ty/eleventy' in deps) {
352
+ addFramework({
353
+ name: 'eleventy',
354
+ version: deps['@11ty/eleventy'] || 'unknown',
355
+ typescript: false,
356
+ });
357
+ }
358
+ // Hugo (look for hugo.toml or config.toml with hugo)
359
+ if (existsSync(resolve(this.root, 'hugo.toml')) ||
360
+ existsSync(resolve(this.root, 'hugo.yaml')) ||
361
+ existsSync(resolve(this.root, 'config.toml'))) {
362
+ addFramework({
363
+ name: 'hugo',
364
+ version: 'unknown',
365
+ typescript: false,
366
+ });
367
+ }
368
+ // Jekyll (look for _config.yml with jekyll patterns)
369
+ if (existsSync(resolve(this.root, '_config.yml'))) {
370
+ try {
371
+ const config = readFileSync(resolve(this.root, '_config.yml'), 'utf-8');
372
+ if (config.includes('jekyll') || existsSync(resolve(this.root, '_posts'))) {
373
+ addFramework({
374
+ name: 'jekyll',
375
+ version: 'unknown',
376
+ typescript: false,
377
+ });
378
+ }
379
+ }
380
+ catch {
381
+ // ignore
382
+ }
383
+ }
384
+ // ============================================
385
+ // PHP frameworks
386
+ // ============================================
387
+ // Check composer.json for PHP frameworks
388
+ const composerPath = resolve(this.root, 'composer.json');
389
+ if (existsSync(composerPath)) {
390
+ try {
391
+ const composer = JSON.parse(readFileSync(composerPath, 'utf-8'));
392
+ const composerDeps = {
393
+ ...(composer.require || {}),
394
+ ...(composer['require-dev'] || {}),
395
+ };
396
+ // Laravel
397
+ if ('laravel/framework' in composerDeps) {
398
+ addFramework({
399
+ name: 'laravel',
400
+ version: composerDeps['laravel/framework'] || 'unknown',
401
+ typescript: false,
402
+ });
403
+ }
404
+ // Symfony
405
+ if ('symfony/framework-bundle' in composerDeps || 'symfony/symfony' in composerDeps) {
406
+ addFramework({
407
+ name: 'symfony',
408
+ version: composerDeps['symfony/framework-bundle'] || composerDeps['symfony/symfony'] || 'unknown',
409
+ typescript: false,
410
+ });
411
+ }
412
+ }
413
+ catch {
414
+ // ignore
415
+ }
416
+ }
417
+ // Generic PHP (only if no specific PHP framework found)
418
+ if ((existsSync(resolve(this.root, 'index.php')) || existsSync(composerPath)) &&
419
+ !addedNames.has('laravel') && !addedNames.has('symfony')) {
420
+ addFramework({
421
+ name: 'php',
422
+ version: 'unknown',
423
+ typescript: false,
424
+ });
425
+ }
426
+ // ============================================
427
+ // Ruby frameworks
428
+ // ============================================
429
+ const gemfilePath = resolve(this.root, 'Gemfile');
430
+ if (existsSync(gemfilePath)) {
431
+ try {
432
+ const gemfile = readFileSync(gemfilePath, 'utf-8');
433
+ if (gemfile.includes('rails')) {
434
+ addFramework({
435
+ name: 'rails',
436
+ version: 'unknown',
437
+ typescript: false,
438
+ });
439
+ }
440
+ }
441
+ catch {
442
+ // ignore
443
+ }
444
+ }
445
+ // ============================================
446
+ // Python frameworks
447
+ // ============================================
448
+ // Check requirements.txt or pyproject.toml
449
+ const requirementsPath = resolve(this.root, 'requirements.txt');
450
+ const pyprojectPath = resolve(this.root, 'pyproject.toml');
451
+ if (existsSync(requirementsPath)) {
452
+ try {
453
+ const requirements = readFileSync(requirementsPath, 'utf-8').toLowerCase();
454
+ if (requirements.includes('fastapi')) {
455
+ addFramework({ name: 'fastapi', version: 'unknown', typescript: false });
456
+ }
457
+ if (requirements.includes('flask')) {
458
+ addFramework({ name: 'flask', version: 'unknown', typescript: false });
459
+ }
460
+ if (requirements.includes('django')) {
461
+ addFramework({ name: 'django', version: 'unknown', typescript: false });
462
+ }
463
+ }
464
+ catch {
465
+ // ignore
466
+ }
467
+ }
468
+ if (existsSync(pyprojectPath)) {
469
+ try {
470
+ const pyproject = readFileSync(pyprojectPath, 'utf-8').toLowerCase();
471
+ if (pyproject.includes('fastapi') && !addedNames.has('fastapi')) {
472
+ addFramework({ name: 'fastapi', version: 'unknown', typescript: false });
473
+ }
474
+ if (pyproject.includes('flask') && !addedNames.has('flask')) {
475
+ addFramework({ name: 'flask', version: 'unknown', typescript: false });
476
+ }
477
+ if (pyproject.includes('django') && !addedNames.has('django')) {
478
+ addFramework({ name: 'django', version: 'unknown', typescript: false });
479
+ }
480
+ }
481
+ catch {
482
+ // ignore
483
+ }
484
+ }
485
+ // Django fallback - look for manage.py
486
+ if (existsSync(resolve(this.root, 'manage.py')) && !addedNames.has('django')) {
487
+ addFramework({
488
+ name: 'django',
489
+ version: 'unknown',
490
+ typescript: false,
491
+ });
492
+ }
493
+ // ============================================
494
+ // Go
495
+ // ============================================
496
+ if (existsSync(resolve(this.root, 'go.mod'))) {
497
+ addFramework({
498
+ name: 'go',
499
+ version: 'unknown',
500
+ typescript: false,
501
+ });
502
+ }
503
+ // ============================================
504
+ // Java/Spring
505
+ // ============================================
506
+ if (existsSync(resolve(this.root, 'pom.xml'))) {
507
+ try {
508
+ const pom = readFileSync(resolve(this.root, 'pom.xml'), 'utf-8');
509
+ if (pom.includes('spring-boot') || pom.includes('springframework')) {
510
+ addFramework({
511
+ name: 'spring',
512
+ version: 'unknown',
513
+ typescript: false,
514
+ });
515
+ }
516
+ }
517
+ catch {
518
+ // ignore
519
+ }
520
+ }
521
+ if (existsSync(resolve(this.root, 'build.gradle')) || existsSync(resolve(this.root, 'build.gradle.kts'))) {
522
+ try {
523
+ const gradlePath = existsSync(resolve(this.root, 'build.gradle'))
524
+ ? resolve(this.root, 'build.gradle')
525
+ : resolve(this.root, 'build.gradle.kts');
526
+ const gradle = readFileSync(gradlePath, 'utf-8');
527
+ if ((gradle.includes('spring-boot') || gradle.includes('springframework')) && !addedNames.has('spring')) {
528
+ addFramework({
529
+ name: 'spring',
530
+ version: 'unknown',
531
+ typescript: false,
532
+ });
533
+ }
534
+ }
535
+ catch {
536
+ // ignore
537
+ }
538
+ }
539
+ // ============================================
540
+ // .NET/ASP.NET
541
+ // ============================================
542
+ const csprojFiles = await glob('*.csproj', { cwd: this.root });
543
+ const firstCsproj = csprojFiles[0];
544
+ if (firstCsproj) {
545
+ try {
546
+ const csproj = readFileSync(resolve(this.root, firstCsproj), 'utf-8');
547
+ if (csproj.includes('Microsoft.AspNetCore') || csproj.includes('Microsoft.NET.Sdk.Web')) {
548
+ addFramework({
549
+ name: 'aspnet',
550
+ version: 'unknown',
551
+ typescript: false,
552
+ });
553
+ }
554
+ }
555
+ catch {
556
+ // ignore
557
+ }
558
+ }
559
+ return frameworks;
560
+ }
561
+ async detectComponents() {
562
+ const locations = [];
563
+ // Check JS component directories
564
+ for (const dir of COMPONENT_DIRS) {
565
+ const fullPath = resolve(this.root, dir);
566
+ if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
567
+ // Count component files
568
+ const extensions = ['tsx', 'jsx', 'vue', 'svelte'];
569
+ let fileCount = 0;
570
+ for (const ext of extensions) {
571
+ const files = await glob(`**/*.${ext}`, {
572
+ cwd: fullPath,
573
+ ignore: ['**/*.test.*', '**/*.spec.*', '**/*.stories.*', '**/node_modules/**'],
574
+ });
575
+ fileCount += files.length;
576
+ }
577
+ if (fileCount > 0) {
578
+ locations.push({
579
+ path: dir,
580
+ fileCount,
581
+ pattern: `${dir}/**/*.{tsx,jsx,vue,svelte}`,
582
+ type: 'jsx',
583
+ });
584
+ }
585
+ }
586
+ }
587
+ // If no standard JS dirs found, check src for any component files
588
+ if (locations.length === 0) {
589
+ const srcPath = resolve(this.root, 'src');
590
+ if (existsSync(srcPath)) {
591
+ const files = await glob('**/*.{tsx,jsx,vue,svelte}', {
592
+ cwd: srcPath,
593
+ ignore: ['**/*.test.*', '**/*.spec.*', '**/*.stories.*', '**/node_modules/**'],
594
+ });
595
+ if (files.length > 0) {
596
+ locations.push({
597
+ path: 'src',
598
+ fileCount: files.length,
599
+ pattern: 'src/**/*.{tsx,jsx,vue,svelte}',
600
+ type: 'jsx',
601
+ });
602
+ }
603
+ }
604
+ }
605
+ // Check template directories for server-side frameworks
606
+ for (const { dir, ext, type } of TEMPLATE_DIRS) {
607
+ const fullPath = resolve(this.root, dir);
608
+ if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
609
+ const files = await glob(`**/*.${ext}`, {
610
+ cwd: fullPath,
611
+ ignore: ['**/node_modules/**', '**/vendor/**', '**/cache/**'],
612
+ });
613
+ if (files.length > 0) {
614
+ locations.push({
615
+ path: dir,
616
+ fileCount: files.length,
617
+ pattern: `${dir}/**/*.${ext}`,
618
+ type,
619
+ });
620
+ }
621
+ }
622
+ }
623
+ return locations;
624
+ }
625
+ async detectTokens() {
626
+ const tokens = [];
627
+ const foundPaths = new Set();
628
+ // Check predefined token patterns first
629
+ for (const { pattern, type, name } of TOKEN_PATTERNS) {
630
+ const files = await glob(pattern, {
631
+ cwd: this.root,
632
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/vendor/**'],
633
+ });
634
+ for (const file of files) {
635
+ if (!foundPaths.has(file)) {
636
+ foundPaths.add(file);
637
+ tokens.push({
638
+ path: file,
639
+ type,
640
+ name,
641
+ });
642
+ }
643
+ }
644
+ }
645
+ // Scan ALL CSS files for :root with CSS variables
646
+ const allCssFiles = await glob('**/*.css', {
647
+ cwd: this.root,
648
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/vendor/**', '**/*.min.css'],
649
+ });
650
+ for (const file of allCssFiles) {
651
+ if (foundPaths.has(file))
652
+ continue;
653
+ try {
654
+ const content = readFileSync(resolve(this.root, file), 'utf-8');
655
+ // Check if file has :root with CSS variables
656
+ if (content.includes(':root') && content.includes('--')) {
657
+ // Count how many CSS variables are defined
658
+ const varMatches = content.match(/--[\w-]+\s*:/g);
659
+ const varCount = varMatches ? varMatches.length : 0;
660
+ if (varCount > 0) {
661
+ foundPaths.add(file);
662
+ tokens.push({
663
+ path: file,
664
+ type: 'css',
665
+ name: `CSS variables (${varCount} tokens)`,
666
+ });
667
+ }
668
+ }
669
+ }
670
+ catch {
671
+ // ignore unreadable files
672
+ }
673
+ }
674
+ // Also scan SCSS files for variables
675
+ const allScssFiles = await glob('**/*.scss', {
676
+ cwd: this.root,
677
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/vendor/**'],
678
+ });
679
+ for (const file of allScssFiles) {
680
+ if (foundPaths.has(file))
681
+ continue;
682
+ try {
683
+ const content = readFileSync(resolve(this.root, file), 'utf-8');
684
+ // Check if file has SCSS variables ($ prefix)
685
+ const varMatches = content.match(/\$[\w-]+\s*:/g);
686
+ const varCount = varMatches ? varMatches.length : 0;
687
+ if (varCount >= 5) { // Only include if it has multiple variables (likely a token file)
688
+ foundPaths.add(file);
689
+ tokens.push({
690
+ path: file,
691
+ type: 'scss',
692
+ name: `SCSS variables (${varCount} tokens)`,
693
+ });
694
+ }
695
+ }
696
+ catch {
697
+ // ignore unreadable files
698
+ }
699
+ }
700
+ return tokens;
701
+ }
702
+ async detectStorybook() {
703
+ const storybookDir = resolve(this.root, '.storybook');
704
+ if (existsSync(storybookDir)) {
705
+ const deps = this.getAllDeps();
706
+ let version = null;
707
+ // Find storybook version
708
+ for (const [pkg, ver] of Object.entries(deps)) {
709
+ if (pkg.startsWith('@storybook/') || pkg === 'storybook') {
710
+ version = ver;
711
+ break;
712
+ }
713
+ }
714
+ return {
715
+ configPath: '.storybook',
716
+ version,
717
+ };
718
+ }
719
+ return null;
720
+ }
721
+ async detectDesignSystem() {
722
+ const deps = this.getAllDeps();
723
+ for (const { package: pkg, type } of DESIGN_SYSTEMS) {
724
+ // Handle prefix matching (like @radix-ui/react-)
725
+ if (pkg.endsWith('-')) {
726
+ for (const [depName, version] of Object.entries(deps)) {
727
+ if (depName.startsWith(pkg)) {
728
+ return {
729
+ package: depName,
730
+ version: version,
731
+ type,
732
+ };
733
+ }
734
+ }
735
+ }
736
+ else if (pkg in deps) {
737
+ return {
738
+ package: pkg,
739
+ version: deps[pkg],
740
+ type,
741
+ };
742
+ }
743
+ }
744
+ return null;
745
+ }
746
+ async detectMonorepo() {
747
+ // Check for pnpm workspaces
748
+ const pnpmWorkspace = resolve(this.root, 'pnpm-workspace.yaml');
749
+ if (existsSync(pnpmWorkspace)) {
750
+ return {
751
+ type: 'pnpm',
752
+ packages: await this.findWorkspacePackages(),
753
+ };
754
+ }
755
+ // Check for Turborepo
756
+ const turboJson = resolve(this.root, 'turbo.json');
757
+ if (existsSync(turboJson)) {
758
+ return {
759
+ type: 'turborepo',
760
+ packages: await this.findWorkspacePackages(),
761
+ };
762
+ }
763
+ // Check for Nx
764
+ const nxJson = resolve(this.root, 'nx.json');
765
+ if (existsSync(nxJson)) {
766
+ return {
767
+ type: 'nx',
768
+ packages: await this.findWorkspacePackages(),
769
+ };
770
+ }
771
+ // Check package.json workspaces (yarn/npm)
772
+ if (this.packageJson && this.packageJson.workspaces) {
773
+ return {
774
+ type: 'yarn',
775
+ packages: await this.findWorkspacePackages(),
776
+ };
777
+ }
778
+ return null;
779
+ }
780
+ async findWorkspacePackages() {
781
+ const packages = [];
782
+ const packagesDir = resolve(this.root, 'packages');
783
+ const appsDir = resolve(this.root, 'apps');
784
+ for (const dir of [packagesDir, appsDir]) {
785
+ if (existsSync(dir) && statSync(dir).isDirectory()) {
786
+ const entries = readdirSync(dir);
787
+ for (const entry of entries) {
788
+ const pkgJson = resolve(dir, entry, 'package.json');
789
+ if (existsSync(pkgJson)) {
790
+ packages.push(`${basename(dir)}/${entry}`);
791
+ }
792
+ }
793
+ }
794
+ }
795
+ return packages;
796
+ }
797
+ }
798
+ // Helper to get a summary string
799
+ export function getDetectionSummary(project) {
800
+ const summary = [];
801
+ if (project.frameworks.length > 0) {
802
+ if (project.frameworks.length === 1) {
803
+ const fw = project.frameworks[0];
804
+ const ts = fw.typescript ? ' + TypeScript' : '';
805
+ summary.push(`${capitalize(fw.name)}${ts} project`);
806
+ }
807
+ else {
808
+ // Multiple frameworks detected - framework sprawl
809
+ const names = project.frameworks.map(f => capitalize(f.name)).join(', ');
810
+ summary.push(`⚠️ Multiple frameworks: ${names}`);
811
+ }
812
+ }
813
+ if (project.components.length > 0) {
814
+ const total = project.components.reduce((sum, c) => sum + c.fileCount, 0);
815
+ const paths = project.components.map(c => c.path).join(', ');
816
+ summary.push(`${total} component files in ${paths}`);
817
+ }
818
+ if (project.tokens.length > 0) {
819
+ summary.push(`${project.tokens.length} token source(s) found`);
820
+ }
821
+ if (project.storybook) {
822
+ summary.push(`Storybook detected`);
823
+ }
824
+ if (project.designSystem) {
825
+ summary.push(`Uses ${project.designSystem.package}`);
826
+ }
827
+ if (project.monorepo) {
828
+ summary.push(`${capitalize(project.monorepo.type)} monorepo with ${project.monorepo.packages.length} packages`);
829
+ }
830
+ return summary;
831
+ }
832
+ // Helper to check if project has framework sprawl (multiple UI frameworks)
833
+ export function hasFrameworkSprawl(project) {
834
+ // Only count UI/component frameworks, not backend frameworks
835
+ const uiFrameworks = ['react', 'vue', 'svelte', 'angular', 'solid', 'preact', 'lit', 'stencil',
836
+ 'nextjs', 'nuxt', 'astro', 'remix', 'sveltekit', 'gatsby', 'react-native', 'expo', 'flutter'];
837
+ const uiCount = project.frameworks.filter(f => uiFrameworks.includes(f.name)).length;
838
+ return uiCount > 1;
839
+ }
840
+ // Get UI frameworks only (for sprawl detection)
841
+ export function getUIFrameworks(project) {
842
+ const uiFrameworks = ['react', 'vue', 'svelte', 'angular', 'solid', 'preact', 'lit', 'stencil',
843
+ 'nextjs', 'nuxt', 'astro', 'remix', 'sveltekit', 'gatsby', 'react-native', 'expo', 'flutter'];
844
+ return project.frameworks.filter(f => uiFrameworks.includes(f.name));
845
+ }
846
+ function capitalize(s) {
847
+ return s.charAt(0).toUpperCase() + s.slice(1);
848
+ }
849
+ //# sourceMappingURL=project-detector.js.map