@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.
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +5 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/__tests__/ci.test.d.ts +2 -0
- package/dist/commands/__tests__/ci.test.d.ts.map +1 -0
- package/dist/commands/__tests__/ci.test.js +33 -0
- package/dist/commands/__tests__/ci.test.js.map +1 -0
- package/dist/commands/bootstrap.d.ts +3 -0
- package/dist/commands/bootstrap.d.ts.map +1 -0
- package/dist/commands/bootstrap.js +458 -0
- package/dist/commands/bootstrap.js.map +1 -0
- package/dist/commands/build.d.ts +3 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +314 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/ci.d.ts +24 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +273 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/ci.logic.d.ts +20 -0
- package/dist/commands/ci.logic.d.ts.map +1 -0
- package/dist/commands/ci.logic.js +28 -0
- package/dist/commands/ci.logic.js.map +1 -0
- package/dist/commands/drift.d.ts +3 -0
- package/dist/commands/drift.d.ts.map +1 -0
- package/dist/commands/drift.js +178 -0
- package/dist/commands/drift.js.map +1 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +9 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +490 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/plugins.d.ts +3 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +72 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/scan.d.ts +3 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +266 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +205 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +67 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +1040 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +116 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/detect/frameworks.d.ts +18 -0
- package/dist/detect/frameworks.d.ts.map +1 -0
- package/dist/detect/frameworks.js +168 -0
- package/dist/detect/frameworks.js.map +1 -0
- package/dist/detect/index.d.ts +3 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +3 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/detect/project-detector.d.ts +61 -0
- package/dist/detect/project-detector.d.ts.map +1 -0
- package/dist/detect/project-detector.js +849 -0
- package/dist/detect/project-detector.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/output/formatters.d.ts +21 -0
- package/dist/output/formatters.d.ts.map +1 -0
- package/dist/output/formatters.js +421 -0
- package/dist/output/formatters.js.map +1 -0
- package/dist/output/index.d.ts +3 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/index.js +3 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/reporters.d.ts +23 -0
- package/dist/output/reporters.d.ts.map +1 -0
- package/dist/output/reporters.js +147 -0
- package/dist/output/reporters.js.map +1 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +3 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/loader.d.ts +11 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +77 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/registry.d.ts +15 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +32 -0
- package/dist/plugins/registry.js.map +1 -0
- 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
|