1use super::{LanguageFrameworkDetector, TechnologyRule, FrameworkDetectionUtils};
2use crate::analyzer::{DetectedTechnology, DetectedLanguage, TechnologyCategory, LibraryType};
3use crate::error::Result;
4use std::fs;
5use std::path::Path;
6
7pub struct JavaScriptFrameworkDetector;
8
9impl LanguageFrameworkDetector for JavaScriptFrameworkDetector {
10 fn detect_frameworks(&self, language: &DetectedLanguage) -> Result<Vec<DetectedTechnology>> {
11 let rules = get_js_technology_rules();
12
13 let mut technologies = detect_frameworks_from_files(language, &rules)?;
15
16 let all_deps: Vec<String> = language.main_dependencies.iter()
18 .chain(language.dev_dependencies.iter())
19 .cloned()
20 .collect();
21
22 if let Some(enhanced_techs) = detect_technologies_from_source_files(language, &rules) {
24 for enhanced_tech in enhanced_techs {
26 if let Some(existing) = technologies.iter_mut().find(|t| t.name == enhanced_tech.name) {
27 if enhanced_tech.confidence > existing.confidence {
29 existing.confidence = enhanced_tech.confidence;
30 }
31 } else {
32 technologies.push(enhanced_tech);
34 }
35 }
36 }
37
38 let dependency_based_techs = FrameworkDetectionUtils::detect_technologies_by_dependencies(
40 &rules, &all_deps, language.confidence
41 );
42
43 for dep_tech in dependency_based_techs {
45 if let Some(existing) = technologies.iter_mut().find(|t| t.name == dep_tech.name) {
46 if dep_tech.confidence > existing.confidence {
48 existing.confidence = dep_tech.confidence;
49 }
50 } else {
51 technologies.push(dep_tech);
53 }
54 }
55
56 Ok(technologies)
57 }
58
59 fn supported_languages(&self) -> Vec<&'static str> {
60 vec!["JavaScript", "TypeScript", "JavaScript/TypeScript"]
61 }
62}
63
64fn detect_frameworks_from_files(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Result<Vec<DetectedTechnology>> {
66 let mut detected = Vec::new();
67
68 if let Some(config_detections) = detect_by_config_files(language, rules) {
70 detected.extend(config_detections);
71 }
72
73 if detected.is_empty() {
75 if let Some(structure_detections) = detect_by_project_structure(language, rules) {
76 detected.extend(structure_detections);
77 }
78 }
79
80 if let Some(source_detections) = detect_by_source_patterns(language, rules) {
82 for source_tech in source_detections {
84 if let Some(existing_tech) = detected.iter_mut().find(|t| t.name == source_tech.name) {
85 if source_tech.confidence > existing_tech.confidence {
86 existing_tech.confidence = source_tech.confidence;
87 }
88 } else {
89 detected.push(source_tech);
90 }
91 }
92 }
93
94 Ok(detected)
95}
96
97fn detect_by_config_files(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Option<Vec<DetectedTechnology>> {
99 let mut detected = Vec::new();
100
101 for file_path in &language.files {
103 if let Some(file_name) = file_path.file_name().and_then(|n| n.to_str()) {
104 if file_name == "app.json" || file_name == "app.config.js" || file_name == "app.config.ts" {
106 if let Some(expo_rule) = rules.iter().find(|r| r.name == "Expo") {
107 detected.push(DetectedTechnology {
108 name: expo_rule.name.clone(),
109 version: None,
110 category: expo_rule.category.clone(),
111 confidence: 0.98, requires: expo_rule.requires.clone(),
113 conflicts_with: expo_rule.conflicts_with.clone(),
114 is_primary: expo_rule.is_primary_indicator,
115 file_indicators: expo_rule.file_indicators.clone(),
116 });
117 }
118 }
119 else if file_name == "next.config.js" || file_name == "next.config.ts" {
121 if let Some(nextjs_rule) = rules.iter().find(|r| r.name == "Next.js") {
122 detected.push(DetectedTechnology {
123 name: nextjs_rule.name.clone(),
124 version: None,
125 category: nextjs_rule.category.clone(),
126 confidence: 0.98, requires: nextjs_rule.requires.clone(),
128 conflicts_with: nextjs_rule.conflicts_with.clone(),
129 is_primary: nextjs_rule.is_primary_indicator,
130 file_indicators: nextjs_rule.file_indicators.clone(),
131 });
132 }
133 }
134 else if file_name == "app.config.ts" {
136 if let Ok(content) = std::fs::read_to_string(file_path) {
138 if content.contains("@tanstack/react-start") || content.contains("vinxi") {
139 if let Some(tanstack_rule) = rules.iter().find(|r| r.name == "Tanstack Start") {
140 detected.push(DetectedTechnology {
141 name: tanstack_rule.name.clone(),
142 version: None,
143 category: tanstack_rule.category.clone(),
144 confidence: 0.95, requires: tanstack_rule.requires.clone(),
146 conflicts_with: tanstack_rule.conflicts_with.clone(),
147 is_primary: tanstack_rule.is_primary_indicator,
148 file_indicators: tanstack_rule.file_indicators.clone(),
149 });
150 }
151 }
152 }
153 }
154 else if file_name == "react-native.config.js" {
156 if let Some(rn_rule) = rules.iter().find(|r| r.name == "React Native") {
157 detected.push(DetectedTechnology {
158 name: rn_rule.name.clone(),
159 version: None,
160 category: rn_rule.category.clone(),
161 confidence: 0.95, requires: rn_rule.requires.clone(),
163 conflicts_with: rn_rule.conflicts_with.clone(),
164 is_primary: rn_rule.is_primary_indicator,
165 file_indicators: rn_rule.file_indicators.clone(),
166 });
167 }
168 }
169 }
170 }
171
172 if detected.is_empty() {
173 None
174 } else {
175 Some(detected)
176 }
177}
178
179fn detect_by_project_structure(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Option<Vec<DetectedTechnology>> {
181 let mut detected = Vec::new();
182 let mut has_android_dir = false;
183 let mut has_ios_dir = false;
184 let mut has_pages_dir = false;
185 let mut has_app_dir = false;
186 let mut has_app_routes_dir = false;
187
188 for file_path in &language.files {
190 if let Some(parent) = file_path.parent() {
191 let path_str = parent.to_string_lossy();
192
193 if path_str.contains("android") {
195 has_android_dir = true;
196 } else if path_str.contains("ios") {
197 has_ios_dir = true;
198 }
199 else if path_str.contains("pages") {
201 has_pages_dir = true;
202 } else if path_str.contains("app") && !path_str.contains("app.config") {
203 has_app_dir = true;
204 }
205 else if path_str.contains("app/routes") {
207 has_app_routes_dir = true;
208 }
209 }
210 }
211
212 if has_app_routes_dir {
214 if let Some(tanstack_rule) = rules.iter().find(|r| r.name == "Tanstack Start") {
216 detected.push(DetectedTechnology {
217 name: tanstack_rule.name.clone(),
218 version: None,
219 category: tanstack_rule.category.clone(),
220 confidence: 0.8, requires: tanstack_rule.requires.clone(),
222 conflicts_with: tanstack_rule.conflicts_with.clone(),
223 is_primary: tanstack_rule.is_primary_indicator,
224 file_indicators: tanstack_rule.file_indicators.clone(),
225 });
226 }
227 } else if has_pages_dir || has_app_dir {
228 if let Some(nextjs_rule) = rules.iter().find(|r| r.name == "Next.js") {
230 detected.push(DetectedTechnology {
231 name: nextjs_rule.name.clone(),
232 version: None,
233 category: nextjs_rule.category.clone(),
234 confidence: 0.8, requires: nextjs_rule.requires.clone(),
236 conflicts_with: nextjs_rule.conflicts_with.clone(),
237 is_primary: nextjs_rule.is_primary_indicator,
238 file_indicators: nextjs_rule.file_indicators.clone(),
239 });
240 }
241 } else if has_android_dir && has_ios_dir {
242 if let Some(rn_rule) = rules.iter().find(|r| r.name == "React Native") {
244 detected.push(DetectedTechnology {
245 name: rn_rule.name.clone(),
246 version: None,
247 category: rn_rule.category.clone(),
248 confidence: 0.8, requires: rn_rule.requires.clone(),
250 conflicts_with: rn_rule.conflicts_with.clone(),
251 is_primary: rn_rule.is_primary_indicator,
252 file_indicators: rn_rule.file_indicators.clone(),
253 });
254 }
255 }
256
257 for file_path in &language.files {
259 if let Some(file_name) = file_path.file_name().and_then(|n| n.to_str()) {
260 if file_name == "App.js" || file_name == "App.tsx" {
261 if let Ok(content) = std::fs::read_to_string(file_path) {
262 if content.contains("expo") && content.contains("registerRootComponent") {
263 if let Some(expo_rule) = rules.iter().find(|r| r.name == "Expo") {
264 detected.push(DetectedTechnology {
265 name: expo_rule.name.clone(),
266 version: None,
267 category: expo_rule.category.clone(),
268 confidence: 0.85, requires: expo_rule.requires.clone(),
270 conflicts_with: expo_rule.conflicts_with.clone(),
271 is_primary: expo_rule.is_primary_indicator,
272 file_indicators: expo_rule.file_indicators.clone(),
273 });
274 }
275 }
276 }
277 }
278 }
279 }
280
281 if detected.is_empty() {
282 None
283 } else {
284 Some(detected)
285 }
286}
287
288fn detect_by_source_patterns(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Option<Vec<DetectedTechnology>> {
290 let mut detected = Vec::new();
291
292 for file_path in &language.files {
294 if let Ok(content) = std::fs::read_to_string(file_path) {
295 if content.contains("expo") && content.contains("from 'expo'") {
297 if let Some(expo_rule) = rules.iter().find(|r| r.name == "Expo") {
298 detected.push(DetectedTechnology {
299 name: expo_rule.name.clone(),
300 version: None,
301 category: expo_rule.category.clone(),
302 confidence: 0.7, requires: expo_rule.requires.clone(),
304 conflicts_with: expo_rule.conflicts_with.clone(),
305 is_primary: expo_rule.is_primary_indicator,
306 file_indicators: expo_rule.file_indicators.clone(),
307 });
308 }
309 }
310
311 if content.contains("next/") {
313 if let Some(nextjs_rule) = rules.iter().find(|r| r.name == "Next.js") {
314 detected.push(DetectedTechnology {
315 name: nextjs_rule.name.clone(),
316 version: None,
317 category: nextjs_rule.category.clone(),
318 confidence: 0.7, requires: nextjs_rule.requires.clone(),
320 conflicts_with: nextjs_rule.conflicts_with.clone(),
321 is_primary: nextjs_rule.is_primary_indicator,
322 file_indicators: nextjs_rule.file_indicators.clone(),
323 });
324 }
325 }
326
327 if content.contains("@tanstack/react-router") && content.contains("createFileRoute") {
329 if let Some(tanstack_rule) = rules.iter().find(|r| r.name == "Tanstack Start") {
330 detected.push(DetectedTechnology {
331 name: tanstack_rule.name.clone(),
332 version: None,
333 category: tanstack_rule.category.clone(),
334 confidence: 0.7, requires: tanstack_rule.requires.clone(),
336 conflicts_with: tanstack_rule.conflicts_with.clone(),
337 is_primary: tanstack_rule.is_primary_indicator,
338 file_indicators: tanstack_rule.file_indicators.clone(),
339 });
340 }
341 }
342
343 if content.contains("react-router") && content.contains("BrowserRouter") {
345 if let Some(rr_rule) = rules.iter().find(|r| r.name == "React Router v7") {
346 detected.push(DetectedTechnology {
347 name: rr_rule.name.clone(),
348 version: None,
349 category: rr_rule.category.clone(),
350 confidence: 0.7, requires: rr_rule.requires.clone(),
352 conflicts_with: rr_rule.conflicts_with.clone(),
353 is_primary: rr_rule.is_primary_indicator,
354 file_indicators: rr_rule.file_indicators.clone(),
355 });
356 }
357 }
358 }
359 }
360
361 if detected.is_empty() {
362 None
363 } else {
364 Some(detected)
365 }
366}
367
368fn detect_technologies_from_source_files(language: &DetectedLanguage, rules: &[TechnologyRule]) -> Option<Vec<DetectedTechnology>> {
370
371
372 let mut detected = Vec::new();
373
374 for file_path in &language.files {
376 if let Ok(content) = fs::read_to_string(file_path) {
377 if let Some(drizzle_confidence) = analyze_drizzle_usage(&content, file_path) {
379 if let Some(drizzle_rule) = rules.iter().find(|r| r.name == "Drizzle ORM") {
380 detected.push(DetectedTechnology {
381 name: "Drizzle ORM".to_string(),
382 version: None,
383 category: TechnologyCategory::Database,
384 confidence: drizzle_confidence,
385 requires: vec![],
386 conflicts_with: vec![],
387 is_primary: false,
388 file_indicators: drizzle_rule.file_indicators.clone(),
389 });
390 }
391 }
392
393 if let Some(prisma_confidence) = analyze_prisma_usage(&content, file_path) {
395 if let Some(prisma_rule) = rules.iter().find(|r| r.name == "Prisma") {
396 detected.push(DetectedTechnology {
397 name: "Prisma".to_string(),
398 version: None,
399 category: TechnologyCategory::Database,
400 confidence: prisma_confidence,
401 requires: vec![],
402 conflicts_with: vec![],
403 is_primary: false,
404 file_indicators: prisma_rule.file_indicators.clone(),
405 });
406 }
407 }
408
409 if let Some(encore_confidence) = analyze_encore_usage(&content, file_path) {
411 if let Some(encore_rule) = rules.iter().find(|r| r.name == "Encore") {
412 detected.push(DetectedTechnology {
413 name: "Encore".to_string(),
414 version: None,
415 category: TechnologyCategory::BackendFramework,
416 confidence: encore_confidence,
417 requires: vec![],
418 conflicts_with: vec![],
419 is_primary: true,
420 file_indicators: encore_rule.file_indicators.clone(),
421 });
422 }
423 }
424
425 if let Some(tanstack_confidence) = analyze_tanstack_start_usage(&content, file_path) {
427 if let Some(tanstack_rule) = rules.iter().find(|r| r.name == "Tanstack Start") {
428 detected.push(DetectedTechnology {
429 name: "Tanstack Start".to_string(),
430 version: None,
431 category: TechnologyCategory::MetaFramework,
432 confidence: tanstack_confidence,
433 requires: vec!["React".to_string()],
434 conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string()],
435 is_primary: true,
436 file_indicators: tanstack_rule.file_indicators.clone(),
437 });
438 }
439 }
440 }
441 }
442
443 if detected.is_empty() {
444 None
445 } else {
446 Some(detected)
447 }
448}
449
450fn analyze_drizzle_usage(content: &str, file_path: &Path) -> Option<f32> {
452 let file_name = file_path.file_name()?.to_string_lossy();
453 let mut confidence: f32 = 0.0;
454
455 if content.contains("drizzle-orm") {
457 confidence += 0.3;
458 }
459
460 if file_name.contains("schema") || file_name.contains("db.ts") || file_name.contains("database") {
462 if content.contains("pgTable") || content.contains("mysqlTable") || content.contains("sqliteTable") {
463 confidence += 0.4;
464 }
465 if content.contains("pgEnum") || content.contains("relations") {
466 confidence += 0.3;
467 }
468 }
469
470 if content.contains("from 'drizzle-orm/pg-core'") ||
472 content.contains("from 'drizzle-orm/mysql-core'") ||
473 content.contains("from 'drizzle-orm/sqlite-core'") {
474 confidence += 0.3;
475 }
476
477 if content.contains("db.select()") || content.contains("db.insert()") ||
479 content.contains("db.update()") || content.contains("db.delete()") {
480 confidence += 0.2;
481 }
482
483 if content.contains("drizzle(") && (content.contains("connectionString") || content.contains("postgres(")) {
485 confidence += 0.2;
486 }
487
488 if content.contains("drizzle.config") || file_name.contains("migrate") {
490 confidence += 0.2;
491 }
492
493 if content.contains(".prepare()") && content.contains("drizzle") {
495 confidence += 0.1;
496 }
497
498 if confidence > 0.0 {
499 Some(confidence.min(1.0_f32))
500 } else {
501 None
502 }
503}
504
505fn analyze_prisma_usage(content: &str, file_path: &Path) -> Option<f32> {
507 let file_name = file_path.file_name()?.to_string_lossy();
508 let mut confidence: f32 = 0.0;
509 let mut has_prisma_import = false;
510
511 if content.contains("@prisma/client") || content.contains("from '@prisma/client'") {
513 confidence += 0.4;
514 has_prisma_import = true;
515 }
516
517 if file_name == "schema.prisma" {
519 if content.contains("model ") || content.contains("generator ") || content.contains("datasource ") {
520 confidence += 0.6;
521 has_prisma_import = true;
522 }
523 }
524
525 if has_prisma_import {
527 if content.contains("new PrismaClient") || content.contains("PrismaClient()") {
529 confidence += 0.3;
530 }
531
532 if content.contains("prisma.") && (
534 content.contains(".findUnique(") ||
535 content.contains(".findFirst(") ||
536 content.contains(".upsert(") ||
537 content.contains(".$connect()") ||
538 content.contains(".$disconnect()")
539 ) {
540 confidence += 0.2;
541 }
542 }
543
544 if confidence > 0.0 && has_prisma_import {
546 Some(confidence.min(1.0_f32))
547 } else {
548 None
549 }
550}
551
552fn analyze_encore_usage(content: &str, file_path: &Path) -> Option<f32> {
554 let file_name = file_path.file_name()?.to_string_lossy();
555 let mut confidence: f32 = 0.0;
556
557 if content.contains("// Code generated by the Encore") || content.contains("DO NOT EDIT") {
559 return None;
560 }
561
562 if file_name.contains("client.ts") || file_name.contains("client.js") {
564 return None;
565 }
566
567 let mut has_service_patterns = false;
569
570 if file_name.contains("encore.service") || file_name.contains("service.ts") {
572 confidence += 0.4;
573 has_service_patterns = true;
574 }
575
576 if content.contains("encore.dev/api") && (content.contains("export") || content.contains("api.")) {
578 confidence += 0.4;
579 has_service_patterns = true;
580 }
581
582 if content.contains("SQLDatabase") && content.contains("encore.dev") {
584 confidence += 0.3;
585 has_service_patterns = true;
586 }
587
588 if content.contains("secret(") && content.contains("encore.dev/config") {
590 confidence += 0.3;
591 has_service_patterns = true;
592 }
593
594 if content.contains("Topic") && content.contains("encore.dev/pubsub") {
596 confidence += 0.3;
597 has_service_patterns = true;
598 }
599
600 if content.contains("cron") && content.contains("encore.dev") {
602 confidence += 0.2;
603 has_service_patterns = true;
604 }
605
606 if confidence > 0.0 && has_service_patterns {
608 Some(confidence.min(1.0_f32))
609 } else {
610 None
611 }
612}
613
614fn analyze_tanstack_start_usage(content: &str, file_path: &Path) -> Option<f32> {
616 let file_name = file_path.file_name()?.to_string_lossy();
617 let mut confidence: f32 = 0.0;
618 let mut has_start_patterns = false;
619
620 if file_name == "app.config.ts" || file_name == "app.config.js" {
622 if content.contains("@tanstack/react-start") || content.contains("tanstack") {
623 confidence += 0.5;
624 has_start_patterns = true;
625 }
626 }
627
628 if file_name.contains("router.") && (file_name.ends_with(".ts") || file_name.ends_with(".tsx")) {
630 if content.contains("createRouter") && content.contains("@tanstack/react-router") {
631 confidence += 0.4;
632 has_start_patterns = true;
633 }
634 if content.contains("routeTree") {
635 confidence += 0.2;
636 has_start_patterns = true;
637 }
638 }
639
640 if file_name == "ssr.tsx" || file_name == "ssr.ts" {
642 if content.contains("createStartHandler") || content.contains("@tanstack/react-start/server") {
643 confidence += 0.5;
644 has_start_patterns = true;
645 }
646 }
647
648 if file_name == "client.tsx" || file_name == "client.ts" {
650 if content.contains("StartClient") && content.contains("@tanstack/react-start") {
651 confidence += 0.5;
652 has_start_patterns = true;
653 }
654 if content.contains("hydrateRoot") && content.contains("createRouter") {
655 confidence += 0.3;
656 has_start_patterns = true;
657 }
658 }
659
660 if file_name == "__root.tsx" || file_name == "__root.ts" {
662 if content.contains("createRootRoute") && content.contains("@tanstack/react-router") {
663 confidence += 0.4;
664 has_start_patterns = true;
665 }
666 if content.contains("HeadContent") && content.contains("Scripts") {
667 confidence += 0.3;
668 has_start_patterns = true;
669 }
670 }
671
672 if file_path.to_string_lossy().contains("routes/") {
674 if content.contains("createFileRoute") && content.contains("@tanstack/react-router") {
675 confidence += 0.3;
676 has_start_patterns = true;
677 }
678 }
679
680 if content.contains("createServerFn") && content.contains("@tanstack/react-start") {
682 confidence += 0.4;
683 has_start_patterns = true;
684 }
685
686 if content.contains("from '@tanstack/react-start'") {
688 confidence += 0.3;
689 has_start_patterns = true;
690 }
691
692 if file_name == "vinxi.config.ts" || file_name == "vinxi.config.js" {
694 confidence += 0.2;
695 has_start_patterns = true;
696 }
697
698 if confidence > 0.0 && has_start_patterns {
700 Some(confidence.min(1.0_f32))
701 } else {
702 None
703 }
704}
705
706fn get_js_technology_rules() -> Vec<TechnologyRule> {
708 vec![
709 TechnologyRule {
711 name: "Next.js".to_string(),
712 category: TechnologyCategory::MetaFramework,
713 confidence: 0.95,
714 dependency_patterns: vec!["next".to_string()],
715 requires: vec!["React".to_string()],
716 conflicts_with: vec!["Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string()],
717 is_primary_indicator: true,
718 alternative_names: vec!["nextjs".to_string()],
719 file_indicators: vec!["next.config.js".to_string(), "next.config.ts".to_string(), "pages/".to_string(), "app/".to_string()],
720 },
721 TechnologyRule {
722 name: "Tanstack Start".to_string(),
723 category: TechnologyCategory::MetaFramework,
724 confidence: 0.95,
725 dependency_patterns: vec!["@tanstack/react-start".to_string()],
726 requires: vec!["React".to_string()],
727 conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string()],
728 is_primary_indicator: true,
729 alternative_names: vec!["tanstack-start".to_string(), "TanStack Start".to_string()],
730 file_indicators: vec!["app.config.ts".to_string(), "app.config.js".to_string(), "app/routes/".to_string(), "vite.config.ts".to_string()],
731 },
732 TechnologyRule {
733 name: "React Router v7".to_string(),
734 category: TechnologyCategory::MetaFramework,
735 confidence: 0.95,
736 dependency_patterns: vec!["react-router".to_string(), "react-dom".to_string(), "react-router-dom".to_string()],
737 requires: vec!["React".to_string()],
738 conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "React Native".to_string(), "Expo".to_string()],
739 is_primary_indicator: true,
740 alternative_names: vec!["remix".to_string(), "react-router".to_string()],
741 file_indicators: vec![],
742 },
743 TechnologyRule {
744 name: "SvelteKit".to_string(),
745 category: TechnologyCategory::MetaFramework,
746 confidence: 0.95,
747 dependency_patterns: vec!["@sveltejs/kit".to_string()],
748 requires: vec!["Svelte".to_string()],
749 conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "Nuxt.js".to_string()],
750 is_primary_indicator: true,
751 alternative_names: vec!["svelte-kit".to_string()],
752 file_indicators: vec![],
753 },
754 TechnologyRule {
755 name: "Nuxt.js".to_string(),
756 category: TechnologyCategory::MetaFramework,
757 confidence: 0.95,
758 dependency_patterns: vec!["nuxt".to_string(), "@nuxt/core".to_string()],
759 requires: vec!["Vue.js".to_string()],
760 conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string()],
761 is_primary_indicator: true,
762 alternative_names: vec!["nuxtjs".to_string()],
763 file_indicators: vec![],
764 },
765 TechnologyRule {
766 name: "Astro".to_string(),
767 category: TechnologyCategory::MetaFramework,
768 confidence: 0.95,
769 dependency_patterns: vec!["astro".to_string()],
770 requires: vec![],
771 conflicts_with: vec![],
772 is_primary_indicator: true,
773 alternative_names: vec![],
774 file_indicators: vec![],
775 },
776 TechnologyRule {
777 name: "SolidStart".to_string(),
778 category: TechnologyCategory::MetaFramework,
779 confidence: 0.95,
780 dependency_patterns: vec!["solid-start".to_string()],
781 requires: vec!["SolidJS".to_string()],
782 conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string()],
783 is_primary_indicator: true,
784 alternative_names: vec![],
785 file_indicators: vec![],
786 },
787
788 TechnologyRule {
790 name: "React Native".to_string(),
791 category: TechnologyCategory::FrontendFramework,
792 confidence: 0.95,
793 dependency_patterns: vec!["react-native".to_string()],
794 requires: vec!["React".to_string()],
795 conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "Tanstack Start".to_string()],
796 is_primary_indicator: true,
797 alternative_names: vec!["reactnative".to_string()],
798 file_indicators: vec!["react-native.config.js".to_string(), "android/".to_string(), "ios/".to_string()],
799 },
800 TechnologyRule {
801 name: "Expo".to_string(),
802 category: TechnologyCategory::MetaFramework,
803 confidence: 0.98,
804 dependency_patterns: vec!["expo".to_string(), "expo-router".to_string(), "@expo/vector-icons".to_string()],
805 requires: vec!["React Native".to_string()],
806 conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "Tanstack Start".to_string()],
807 is_primary_indicator: true,
808 alternative_names: vec![],
809 file_indicators: vec!["app.json".to_string(), "app.config.js".to_string(), "app.config.ts".to_string()],
810 },
811
812 TechnologyRule {
814 name: "Angular".to_string(),
815 category: TechnologyCategory::FrontendFramework,
816 confidence: 0.90,
817 dependency_patterns: vec!["@angular/core".to_string()],
818 requires: vec![],
819 conflicts_with: vec![],
820 is_primary_indicator: true,
821 alternative_names: vec!["angular".to_string()],
822 file_indicators: vec![],
823 },
824 TechnologyRule {
825 name: "Svelte".to_string(),
826 category: TechnologyCategory::FrontendFramework,
827 confidence: 0.95,
828 dependency_patterns: vec!["svelte".to_string()],
829 requires: vec![],
830 conflicts_with: vec![],
831 is_primary_indicator: false, alternative_names: vec![],
833 file_indicators: vec![],
834 },
835
836 TechnologyRule {
838 name: "React".to_string(),
839 category: TechnologyCategory::Library(LibraryType::UI),
840 confidence: 0.90,
841 dependency_patterns: vec!["react".to_string()],
842 requires: vec![],
843 conflicts_with: vec![],
844 is_primary_indicator: false, alternative_names: vec!["reactjs".to_string()],
846 file_indicators: vec![],
847 },
848 TechnologyRule {
849 name: "Vue.js".to_string(),
850 category: TechnologyCategory::Library(LibraryType::UI),
851 confidence: 0.90,
852 dependency_patterns: vec!["vue".to_string()],
853 requires: vec![],
854 conflicts_with: vec![],
855 is_primary_indicator: false,
856 alternative_names: vec!["vuejs".to_string()],
857 file_indicators: vec![],
858 },
859 TechnologyRule {
860 name: "SolidJS".to_string(),
861 category: TechnologyCategory::Library(LibraryType::UI),
862 confidence: 0.95,
863 dependency_patterns: vec!["solid-js".to_string()],
864 requires: vec![],
865 conflicts_with: vec![],
866 is_primary_indicator: false,
867 alternative_names: vec!["solid".to_string()],
868 file_indicators: vec![],
869 },
870 TechnologyRule {
871 name: "HTMX".to_string(),
872 category: TechnologyCategory::Library(LibraryType::UI),
873 confidence: 0.95,
874 dependency_patterns: vec!["htmx.org".to_string()],
875 requires: vec![],
876 conflicts_with: vec![],
877 is_primary_indicator: false,
878 alternative_names: vec!["htmx".to_string()],
879 file_indicators: vec![],
880 },
881
882 TechnologyRule {
884 name: "Express.js".to_string(),
885 category: TechnologyCategory::BackendFramework,
886 confidence: 0.95,
887 dependency_patterns: vec!["express".to_string()],
888 requires: vec![],
889 conflicts_with: vec![],
890 is_primary_indicator: true,
891 alternative_names: vec!["express".to_string()],
892 file_indicators: vec![],
893 },
894 TechnologyRule {
895 name: "Fastify".to_string(),
896 category: TechnologyCategory::BackendFramework,
897 confidence: 0.95,
898 dependency_patterns: vec!["fastify".to_string()],
899 requires: vec![],
900 conflicts_with: vec![],
901 is_primary_indicator: true,
902 alternative_names: vec![],
903 file_indicators: vec![],
904 },
905 TechnologyRule {
906 name: "Nest.js".to_string(),
907 category: TechnologyCategory::BackendFramework,
908 confidence: 0.95,
909 dependency_patterns: vec!["@nestjs/core".to_string()],
910 requires: vec![],
911 conflicts_with: vec![],
912 is_primary_indicator: true,
913 alternative_names: vec!["nestjs".to_string()],
914 file_indicators: vec![],
915 },
916 TechnologyRule {
917 name: "Hono".to_string(),
918 category: TechnologyCategory::BackendFramework,
919 confidence: 0.95,
920 dependency_patterns: vec!["hono".to_string()],
921 requires: vec![],
922 conflicts_with: vec![],
923 is_primary_indicator: true,
924 alternative_names: vec![],
925 file_indicators: vec![],
926 },
927 TechnologyRule {
928 name: "Elysia".to_string(),
929 category: TechnologyCategory::BackendFramework,
930 confidence: 0.95,
931 dependency_patterns: vec!["elysia".to_string()],
932 requires: vec![],
933 conflicts_with: vec![],
934 is_primary_indicator: true,
935 alternative_names: vec![],
936 file_indicators: vec![],
937 },
938 TechnologyRule {
939 name: "Encore".to_string(),
940 category: TechnologyCategory::BackendFramework,
941 confidence: 0.95,
942 dependency_patterns: vec!["encore.dev".to_string(), "encore".to_string()],
943 requires: vec![],
944 conflicts_with: vec![],
945 is_primary_indicator: true,
946 alternative_names: vec!["encore-ts-starter".to_string()],
947 file_indicators: vec![],
948 },
949
950 TechnologyRule {
952 name: "Vite".to_string(),
953 category: TechnologyCategory::BuildTool,
954 confidence: 0.80,
955 dependency_patterns: vec!["vite".to_string()],
956 requires: vec![],
957 conflicts_with: vec![],
958 is_primary_indicator: false,
959 alternative_names: vec![],
960 file_indicators: vec![],
961 },
962 TechnologyRule {
963 name: "Webpack".to_string(),
964 category: TechnologyCategory::BuildTool,
965 confidence: 0.80,
966 dependency_patterns: vec!["webpack".to_string()],
967 requires: vec![],
968 conflicts_with: vec![],
969 is_primary_indicator: false,
970 alternative_names: vec![],
971 file_indicators: vec![],
972 },
973
974 TechnologyRule {
976 name: "Prisma".to_string(),
977 category: TechnologyCategory::Database,
978 confidence: 0.90,
979 dependency_patterns: vec!["prisma".to_string(), "@prisma/client".to_string()],
980 requires: vec![],
981 conflicts_with: vec![],
982 is_primary_indicator: false,
983 alternative_names: vec![],
984 file_indicators: vec![],
985 },
986 TechnologyRule {
987 name: "Drizzle ORM".to_string(),
988 category: TechnologyCategory::Database,
989 confidence: 0.90,
990 dependency_patterns: vec!["drizzle-orm".to_string(), "drizzle-kit".to_string()],
991 requires: vec![],
992 conflicts_with: vec![],
993 is_primary_indicator: false,
994 alternative_names: vec!["drizzle".to_string()],
995 file_indicators: vec![],
996 },
997 TechnologyRule {
998 name: "Sequelize".to_string(),
999 category: TechnologyCategory::Database,
1000 confidence: 0.90,
1001 dependency_patterns: vec!["sequelize".to_string()],
1002 requires: vec![],
1003 conflicts_with: vec![],
1004 is_primary_indicator: false,
1005 alternative_names: vec![],
1006 file_indicators: vec![],
1007 },
1008 TechnologyRule {
1009 name: "TypeORM".to_string(),
1010 category: TechnologyCategory::Database,
1011 confidence: 0.90,
1012 dependency_patterns: vec!["typeorm".to_string()],
1013 requires: vec![],
1014 conflicts_with: vec![],
1015 is_primary_indicator: false,
1016 alternative_names: vec![],
1017 file_indicators: vec![],
1018 },
1019 TechnologyRule {
1020 name: "MikroORM".to_string(),
1021 category: TechnologyCategory::Database,
1022 confidence: 0.90,
1023 dependency_patterns: vec!["@mikro-orm/core".to_string(), "@mikro-orm/postgresql".to_string(), "@mikro-orm/mysql".to_string(), "@mikro-orm/sqlite".to_string(), "@mikro-orm/mongodb".to_string()],
1024 requires: vec![],
1025 conflicts_with: vec![],
1026 is_primary_indicator: false,
1027 alternative_names: vec!["mikro-orm".to_string()],
1028 file_indicators: vec![],
1029 },
1030 TechnologyRule {
1031 name: "Mongoose".to_string(),
1032 category: TechnologyCategory::Database,
1033 confidence: 0.95,
1034 dependency_patterns: vec!["mongoose".to_string()],
1035 requires: vec![],
1036 conflicts_with: vec![],
1037 is_primary_indicator: false,
1038 alternative_names: vec![],
1039 file_indicators: vec![],
1040 },
1041 TechnologyRule {
1042 name: "Typegoose".to_string(),
1043 category: TechnologyCategory::Database,
1044 confidence: 0.90,
1045 dependency_patterns: vec!["@typegoose/typegoose".to_string()],
1046 requires: vec!["Mongoose".to_string()],
1047 conflicts_with: vec![],
1048 is_primary_indicator: false,
1049 alternative_names: vec![],
1050 file_indicators: vec![],
1051 },
1052 TechnologyRule {
1053 name: "Objection.js".to_string(),
1054 category: TechnologyCategory::Database,
1055 confidence: 0.90,
1056 dependency_patterns: vec!["objection".to_string()],
1057 requires: vec!["Knex.js".to_string()],
1058 conflicts_with: vec![],
1059 is_primary_indicator: false,
1060 alternative_names: vec!["objectionjs".to_string()],
1061 file_indicators: vec![],
1062 },
1063 TechnologyRule {
1064 name: "Bookshelf".to_string(),
1065 category: TechnologyCategory::Database,
1066 confidence: 0.85,
1067 dependency_patterns: vec!["bookshelf".to_string()],
1068 requires: vec!["Knex.js".to_string()],
1069 conflicts_with: vec![],
1070 is_primary_indicator: false,
1071 alternative_names: vec![],
1072 file_indicators: vec![],
1073 },
1074 TechnologyRule {
1075 name: "Waterline".to_string(),
1076 category: TechnologyCategory::Database,
1077 confidence: 0.85,
1078 dependency_patterns: vec!["waterline".to_string(), "sails-mysql".to_string(), "sails-postgresql".to_string(), "sails-disk".to_string()],
1079 requires: vec![],
1080 conflicts_with: vec![],
1081 is_primary_indicator: false,
1082 alternative_names: vec![],
1083 file_indicators: vec![],
1084 },
1085 TechnologyRule {
1086 name: "Knex.js".to_string(),
1087 category: TechnologyCategory::Database,
1088 confidence: 0.85,
1089 dependency_patterns: vec!["knex".to_string()],
1090 requires: vec![],
1091 conflicts_with: vec![],
1092 is_primary_indicator: false,
1093 alternative_names: vec!["knexjs".to_string()],
1094 file_indicators: vec![],
1095 },
1096
1097 TechnologyRule {
1099 name: "Node.js".to_string(),
1100 category: TechnologyCategory::Runtime,
1101 confidence: 0.90,
1102 dependency_patterns: vec!["node".to_string()], requires: vec![],
1104 conflicts_with: vec![],
1105 is_primary_indicator: false,
1106 alternative_names: vec!["nodejs".to_string()],
1107 file_indicators: vec![],
1108 },
1109 TechnologyRule {
1110 name: "Bun".to_string(),
1111 category: TechnologyCategory::Runtime,
1112 confidence: 0.95,
1113 dependency_patterns: vec!["bun".to_string()], requires: vec![],
1115 conflicts_with: vec![],
1116 is_primary_indicator: false,
1117 alternative_names: vec![],
1118 file_indicators: vec![],
1119 },
1120 TechnologyRule {
1121 name: "Deno".to_string(),
1122 category: TechnologyCategory::Runtime,
1123 confidence: 0.95,
1124 dependency_patterns: vec!["@deno/core".to_string(), "deno".to_string()],
1125 requires: vec![],
1126 conflicts_with: vec![],
1127 is_primary_indicator: false,
1128 alternative_names: vec![],
1129 file_indicators: vec![],
1130 },
1131 TechnologyRule {
1132 name: "WinterJS".to_string(),
1133 category: TechnologyCategory::Runtime,
1134 confidence: 0.95,
1135 dependency_patterns: vec!["winterjs".to_string(), "winter-js".to_string()],
1136 requires: vec![],
1137 conflicts_with: vec![],
1138 is_primary_indicator: false,
1139 alternative_names: vec!["winter.js".to_string()],
1140 file_indicators: vec![],
1141 },
1142 TechnologyRule {
1143 name: "Cloudflare Workers".to_string(),
1144 category: TechnologyCategory::Runtime,
1145 confidence: 0.90,
1146 dependency_patterns: vec!["@cloudflare/workers-types".to_string(), "@cloudflare/vitest-pool-workers".to_string(), "wrangler".to_string()],
1147 requires: vec![],
1148 conflicts_with: vec![],
1149 is_primary_indicator: false,
1150 alternative_names: vec!["cloudflare-workers".to_string()],
1151 file_indicators: vec![],
1152 },
1153 TechnologyRule {
1154 name: "Vercel Edge Runtime".to_string(),
1155 category: TechnologyCategory::Runtime,
1156 confidence: 0.90,
1157 dependency_patterns: vec!["@vercel/edge-runtime".to_string(), "@edge-runtime/vm".to_string()],
1158 requires: vec![],
1159 conflicts_with: vec![],
1160 is_primary_indicator: false,
1161 alternative_names: vec!["vercel-edge".to_string()],
1162 file_indicators: vec![],
1163 },
1164 TechnologyRule {
1165 name: "Hermes".to_string(),
1166 category: TechnologyCategory::Runtime,
1167 confidence: 0.85,
1168 dependency_patterns: vec!["hermes-engine".to_string()],
1169 requires: vec!["React Native".to_string()],
1170 conflicts_with: vec![],
1171 is_primary_indicator: false,
1172 alternative_names: vec![],
1173 file_indicators: vec![],
1174 },
1175 TechnologyRule {
1176 name: "Electron".to_string(),
1177 category: TechnologyCategory::Runtime,
1178 confidence: 0.95,
1179 dependency_patterns: vec!["electron".to_string()],
1180 requires: vec![],
1181 conflicts_with: vec![],
1182 is_primary_indicator: false,
1183 alternative_names: vec![],
1184 file_indicators: vec![],
1185 },
1186 TechnologyRule {
1187 name: "Tauri".to_string(),
1188 category: TechnologyCategory::Runtime,
1189 confidence: 0.95,
1190 dependency_patterns: vec!["@tauri-apps/cli".to_string(), "@tauri-apps/api".to_string()],
1191 requires: vec![],
1192 conflicts_with: vec!["Electron".to_string()],
1193 is_primary_indicator: false,
1194 alternative_names: vec![],
1195 file_indicators: vec![],
1196 },
1197 TechnologyRule {
1198 name: "QuickJS".to_string(),
1199 category: TechnologyCategory::Runtime,
1200 confidence: 0.85,
1201 dependency_patterns: vec!["quickjs".to_string(), "quickjs-emscripten".to_string()],
1202 requires: vec![],
1203 conflicts_with: vec![],
1204 is_primary_indicator: false,
1205 alternative_names: vec![],
1206 file_indicators: vec![],
1207 },
1208
1209 TechnologyRule {
1211 name: "Jest".to_string(),
1212 category: TechnologyCategory::Testing,
1213 confidence: 0.85,
1214 dependency_patterns: vec!["jest".to_string()],
1215 requires: vec![],
1216 conflicts_with: vec![],
1217 is_primary_indicator: false,
1218 alternative_names: vec![],
1219 file_indicators: vec![],
1220 },
1221 TechnologyRule {
1222 name: "Vitest".to_string(),
1223 category: TechnologyCategory::Testing,
1224 confidence: 0.85,
1225 dependency_patterns: vec!["vitest".to_string()],
1226 requires: vec![],
1227 conflicts_with: vec![],
1228 is_primary_indicator: false,
1229 alternative_names: vec![],
1230 file_indicators: vec![],
1231 },
1232 ]
1233}