1use super::{LanguageFrameworkDetector, TechnologyRule, FrameworkDetectionUtils};
2use crate::analyzer::{DetectedTechnology, DetectedLanguage, TechnologyCategory, LibraryType};
3use crate::error::Result;
4use std::path::Path;
5
6pub struct JavaScriptFrameworkDetector;
7
8impl LanguageFrameworkDetector for JavaScriptFrameworkDetector {
9 fn detect_frameworks(&self, language: &DetectedLanguage) -> Result<Vec<DetectedTechnology>> {
10 let rules = get_js_technology_rules();
11
12 let all_deps: Vec<String> = language.main_dependencies.iter()
14 .chain(language.dev_dependencies.iter())
15 .cloned()
16 .collect();
17
18 let mut technologies = FrameworkDetectionUtils::detect_technologies_by_dependencies(
19 &rules, &all_deps, language.confidence
20 );
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 Ok(technologies)
39 }
40
41 fn supported_languages(&self) -> Vec<&'static str> {
42 vec!["JavaScript", "TypeScript", "JavaScript/TypeScript"]
43 }
44}
45
46fn detect_technologies_from_source_files(language: &DetectedLanguage, _rules: &[TechnologyRule]) -> Option<Vec<DetectedTechnology>> {
48 use std::fs;
49
50 let mut detected = Vec::new();
51
52 for file_path in &language.files {
54 if let Ok(content) = fs::read_to_string(file_path) {
55 if let Some(drizzle_confidence) = analyze_drizzle_usage(&content, file_path) {
57 detected.push(DetectedTechnology {
58 name: "Drizzle ORM".to_string(),
59 version: None,
60 category: TechnologyCategory::Database,
61 confidence: drizzle_confidence,
62 requires: vec![],
63 conflicts_with: vec![],
64 is_primary: false,
65 });
66 }
67
68 if let Some(prisma_confidence) = analyze_prisma_usage(&content, file_path) {
70 detected.push(DetectedTechnology {
71 name: "Prisma".to_string(),
72 version: None,
73 category: TechnologyCategory::Database,
74 confidence: prisma_confidence,
75 requires: vec![],
76 conflicts_with: vec![],
77 is_primary: false,
78 });
79 }
80
81 if let Some(encore_confidence) = analyze_encore_usage(&content, file_path) {
83 detected.push(DetectedTechnology {
84 name: "Encore".to_string(),
85 version: None,
86 category: TechnologyCategory::BackendFramework,
87 confidence: encore_confidence,
88 requires: vec![],
89 conflicts_with: vec![],
90 is_primary: true,
91 });
92 }
93
94 if let Some(tanstack_confidence) = analyze_tanstack_start_usage(&content, file_path) {
96 detected.push(DetectedTechnology {
97 name: "Tanstack Start".to_string(),
98 version: None,
99 category: TechnologyCategory::MetaFramework,
100 confidence: tanstack_confidence,
101 requires: vec!["React".to_string()],
102 conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string()],
103 is_primary: true,
104 });
105 }
106 }
107 }
108
109 if detected.is_empty() {
110 None
111 } else {
112 Some(detected)
113 }
114}
115
116fn analyze_drizzle_usage(content: &str, file_path: &Path) -> Option<f32> {
118 let file_name = file_path.file_name()?.to_string_lossy();
119 let mut confidence: f32 = 0.0;
120
121 if content.contains("drizzle-orm") {
123 confidence += 0.3;
124 }
125
126 if file_name.contains("schema") || file_name.contains("db.ts") || file_name.contains("database") {
128 if content.contains("pgTable") || content.contains("mysqlTable") || content.contains("sqliteTable") {
129 confidence += 0.4;
130 }
131 if content.contains("pgEnum") || content.contains("relations") {
132 confidence += 0.3;
133 }
134 }
135
136 if content.contains("from 'drizzle-orm/pg-core'") ||
138 content.contains("from 'drizzle-orm/mysql-core'") ||
139 content.contains("from 'drizzle-orm/sqlite-core'") {
140 confidence += 0.3;
141 }
142
143 if content.contains("db.select()") || content.contains("db.insert()") ||
145 content.contains("db.update()") || content.contains("db.delete()") {
146 confidence += 0.2;
147 }
148
149 if content.contains("drizzle(") && (content.contains("connectionString") || content.contains("postgres(")) {
151 confidence += 0.2;
152 }
153
154 if content.contains("drizzle.config") || file_name.contains("migrate") {
156 confidence += 0.2;
157 }
158
159 if content.contains(".prepare()") && content.contains("drizzle") {
161 confidence += 0.1;
162 }
163
164 if confidence > 0.0 {
165 Some(confidence.min(1.0_f32))
166 } else {
167 None
168 }
169}
170
171fn analyze_prisma_usage(content: &str, file_path: &Path) -> Option<f32> {
173 let file_name = file_path.file_name()?.to_string_lossy();
174 let mut confidence: f32 = 0.0;
175 let mut has_prisma_import = false;
176
177 if content.contains("@prisma/client") || content.contains("from '@prisma/client'") {
179 confidence += 0.4;
180 has_prisma_import = true;
181 }
182
183 if file_name == "schema.prisma" {
185 if content.contains("model ") || content.contains("generator ") || content.contains("datasource ") {
186 confidence += 0.6;
187 has_prisma_import = true;
188 }
189 }
190
191 if has_prisma_import {
193 if content.contains("new PrismaClient") || content.contains("PrismaClient()") {
195 confidence += 0.3;
196 }
197
198 if content.contains("prisma.") && (
200 content.contains(".findUnique(") ||
201 content.contains(".findFirst(") ||
202 content.contains(".upsert(") ||
203 content.contains(".$connect()") ||
204 content.contains(".$disconnect()")
205 ) {
206 confidence += 0.2;
207 }
208 }
209
210 if confidence > 0.0 && has_prisma_import {
212 Some(confidence.min(1.0_f32))
213 } else {
214 None
215 }
216}
217
218fn analyze_encore_usage(content: &str, file_path: &Path) -> Option<f32> {
220 let file_name = file_path.file_name()?.to_string_lossy();
221 let mut confidence: f32 = 0.0;
222
223 if content.contains("// Code generated by the Encore") || content.contains("DO NOT EDIT") {
225 return None;
226 }
227
228 if file_name.contains("client.ts") || file_name.contains("client.js") {
230 return None;
231 }
232
233 let mut has_service_patterns = false;
235
236 if file_name.contains("encore.service") || file_name.contains("service.ts") {
238 confidence += 0.4;
239 has_service_patterns = true;
240 }
241
242 if content.contains("encore.dev/api") && (content.contains("export") || content.contains("api.")) {
244 confidence += 0.4;
245 has_service_patterns = true;
246 }
247
248 if content.contains("SQLDatabase") && content.contains("encore.dev") {
250 confidence += 0.3;
251 has_service_patterns = true;
252 }
253
254 if content.contains("secret(") && content.contains("encore.dev/config") {
256 confidence += 0.3;
257 has_service_patterns = true;
258 }
259
260 if content.contains("Topic") && content.contains("encore.dev/pubsub") {
262 confidence += 0.3;
263 has_service_patterns = true;
264 }
265
266 if content.contains("cron") && content.contains("encore.dev") {
268 confidence += 0.2;
269 has_service_patterns = true;
270 }
271
272 if confidence > 0.0 && has_service_patterns {
274 Some(confidence.min(1.0_f32))
275 } else {
276 None
277 }
278}
279
280fn analyze_tanstack_start_usage(content: &str, file_path: &Path) -> Option<f32> {
282 let file_name = file_path.file_name()?.to_string_lossy();
283 let mut confidence: f32 = 0.0;
284 let mut has_start_patterns = false;
285
286 if file_name == "app.config.ts" || file_name == "app.config.js" {
288 if content.contains("@tanstack/react-start") || content.contains("tanstack") {
289 confidence += 0.5;
290 has_start_patterns = true;
291 }
292 }
293
294 if file_name.contains("router.") && (file_name.ends_with(".ts") || file_name.ends_with(".tsx")) {
296 if content.contains("createRouter") && content.contains("@tanstack/react-router") {
297 confidence += 0.4;
298 has_start_patterns = true;
299 }
300 if content.contains("routeTree") {
301 confidence += 0.2;
302 has_start_patterns = true;
303 }
304 }
305
306 if file_name == "ssr.tsx" || file_name == "ssr.ts" {
308 if content.contains("createStartHandler") || content.contains("@tanstack/react-start/server") {
309 confidence += 0.5;
310 has_start_patterns = true;
311 }
312 }
313
314 if file_name == "client.tsx" || file_name == "client.ts" {
316 if content.contains("StartClient") && content.contains("@tanstack/react-start") {
317 confidence += 0.5;
318 has_start_patterns = true;
319 }
320 if content.contains("hydrateRoot") && content.contains("createRouter") {
321 confidence += 0.3;
322 has_start_patterns = true;
323 }
324 }
325
326 if file_name == "__root.tsx" || file_name == "__root.ts" {
328 if content.contains("createRootRoute") && content.contains("@tanstack/react-router") {
329 confidence += 0.4;
330 has_start_patterns = true;
331 }
332 if content.contains("HeadContent") && content.contains("Scripts") {
333 confidence += 0.3;
334 has_start_patterns = true;
335 }
336 }
337
338 if file_path.to_string_lossy().contains("routes/") {
340 if content.contains("createFileRoute") && content.contains("@tanstack/react-router") {
341 confidence += 0.3;
342 has_start_patterns = true;
343 }
344 }
345
346 if content.contains("createServerFn") && content.contains("@tanstack/react-start") {
348 confidence += 0.4;
349 has_start_patterns = true;
350 }
351
352 if content.contains("from '@tanstack/react-start'") {
354 confidence += 0.3;
355 has_start_patterns = true;
356 }
357
358 if file_name == "vinxi.config.ts" || file_name == "vinxi.config.js" {
360 confidence += 0.2;
361 has_start_patterns = true;
362 }
363
364 if confidence > 0.0 && has_start_patterns {
366 Some(confidence.min(1.0_f32))
367 } else {
368 None
369 }
370}
371
372fn get_js_technology_rules() -> Vec<TechnologyRule> {
374 vec![
375 TechnologyRule {
377 name: "Next.js".to_string(),
378 category: TechnologyCategory::MetaFramework,
379 confidence: 0.95,
380 dependency_patterns: vec!["next".to_string()],
381 requires: vec!["React".to_string()],
382 conflicts_with: vec!["Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string()],
383 is_primary_indicator: true,
384 alternative_names: vec!["nextjs".to_string()],
385 },
386 TechnologyRule {
387 name: "Tanstack Start".to_string(),
388 category: TechnologyCategory::MetaFramework,
389 confidence: 0.95,
390 dependency_patterns: vec!["@tanstack/react-start".to_string()],
391 requires: vec!["React".to_string()],
392 conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string()],
393 is_primary_indicator: true,
394 alternative_names: vec!["tanstack-start".to_string(), "TanStack Start".to_string()],
395 },
396 TechnologyRule {
397 name: "React Router v7".to_string(),
398 category: TechnologyCategory::MetaFramework,
399 confidence: 0.95,
400 dependency_patterns: vec!["react-router".to_string(), "react-dom".to_string(), "react-router-dom".to_string()],
401 requires: vec!["React".to_string()],
402 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()],
403 is_primary_indicator: true,
404 alternative_names: vec!["remix".to_string(), "react-router".to_string()],
405 },
406 TechnologyRule {
407 name: "SvelteKit".to_string(),
408 category: TechnologyCategory::MetaFramework,
409 confidence: 0.95,
410 dependency_patterns: vec!["@sveltejs/kit".to_string()],
411 requires: vec!["Svelte".to_string()],
412 conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "Nuxt.js".to_string()],
413 is_primary_indicator: true,
414 alternative_names: vec!["svelte-kit".to_string()],
415 },
416 TechnologyRule {
417 name: "Nuxt.js".to_string(),
418 category: TechnologyCategory::MetaFramework,
419 confidence: 0.95,
420 dependency_patterns: vec!["nuxt".to_string(), "@nuxt/core".to_string()],
421 requires: vec!["Vue.js".to_string()],
422 conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string()],
423 is_primary_indicator: true,
424 alternative_names: vec!["nuxtjs".to_string()],
425 },
426 TechnologyRule {
427 name: "Astro".to_string(),
428 category: TechnologyCategory::MetaFramework,
429 confidence: 0.95,
430 dependency_patterns: vec!["astro".to_string()],
431 requires: vec![],
432 conflicts_with: vec![],
433 is_primary_indicator: true,
434 alternative_names: vec![],
435 },
436 TechnologyRule {
437 name: "SolidStart".to_string(),
438 category: TechnologyCategory::MetaFramework,
439 confidence: 0.95,
440 dependency_patterns: vec!["solid-start".to_string()],
441 requires: vec!["SolidJS".to_string()],
442 conflicts_with: vec!["Next.js".to_string(), "Tanstack Start".to_string(), "React Router v7".to_string(), "SvelteKit".to_string()],
443 is_primary_indicator: true,
444 alternative_names: vec![],
445 },
446
447 TechnologyRule {
449 name: "React Native".to_string(),
450 category: TechnologyCategory::FrontendFramework,
451 confidence: 0.95,
452 dependency_patterns: vec!["react-native".to_string()],
453 requires: vec!["React".to_string()],
454 conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "Tanstack Start".to_string()],
455 is_primary_indicator: true,
456 alternative_names: vec!["reactnative".to_string()],
457 },
458 TechnologyRule {
459 name: "Expo".to_string(),
460 category: TechnologyCategory::MetaFramework,
461 confidence: 0.98,
462 dependency_patterns: vec!["expo".to_string(), "expo-router".to_string(), "@expo/vector-icons".to_string()],
463 requires: vec!["React Native".to_string()],
464 conflicts_with: vec!["Next.js".to_string(), "React Router v7".to_string(), "SvelteKit".to_string(), "Nuxt.js".to_string(), "Tanstack Start".to_string()],
465 is_primary_indicator: true,
466 alternative_names: vec![],
467 },
468
469 TechnologyRule {
471 name: "Angular".to_string(),
472 category: TechnologyCategory::FrontendFramework,
473 confidence: 0.90,
474 dependency_patterns: vec!["@angular/core".to_string()],
475 requires: vec![],
476 conflicts_with: vec![],
477 is_primary_indicator: true,
478 alternative_names: vec!["angular".to_string()],
479 },
480 TechnologyRule {
481 name: "Svelte".to_string(),
482 category: TechnologyCategory::FrontendFramework,
483 confidence: 0.95,
484 dependency_patterns: vec!["svelte".to_string()],
485 requires: vec![],
486 conflicts_with: vec![],
487 is_primary_indicator: false, alternative_names: vec![],
489 },
490
491 TechnologyRule {
493 name: "React".to_string(),
494 category: TechnologyCategory::Library(LibraryType::UI),
495 confidence: 0.90,
496 dependency_patterns: vec!["react".to_string()],
497 requires: vec![],
498 conflicts_with: vec![],
499 is_primary_indicator: false, alternative_names: vec!["reactjs".to_string()],
501 },
502 TechnologyRule {
503 name: "Vue.js".to_string(),
504 category: TechnologyCategory::Library(LibraryType::UI),
505 confidence: 0.90,
506 dependency_patterns: vec!["vue".to_string()],
507 requires: vec![],
508 conflicts_with: vec![],
509 is_primary_indicator: false,
510 alternative_names: vec!["vuejs".to_string()],
511 },
512 TechnologyRule {
513 name: "SolidJS".to_string(),
514 category: TechnologyCategory::Library(LibraryType::UI),
515 confidence: 0.95,
516 dependency_patterns: vec!["solid-js".to_string()],
517 requires: vec![],
518 conflicts_with: vec![],
519 is_primary_indicator: false,
520 alternative_names: vec!["solid".to_string()],
521 },
522 TechnologyRule {
523 name: "HTMX".to_string(),
524 category: TechnologyCategory::Library(LibraryType::UI),
525 confidence: 0.95,
526 dependency_patterns: vec!["htmx.org".to_string()],
527 requires: vec![],
528 conflicts_with: vec![],
529 is_primary_indicator: false,
530 alternative_names: vec!["htmx".to_string()],
531 },
532
533 TechnologyRule {
535 name: "Express.js".to_string(),
536 category: TechnologyCategory::BackendFramework,
537 confidence: 0.95,
538 dependency_patterns: vec!["express".to_string()],
539 requires: vec![],
540 conflicts_with: vec![],
541 is_primary_indicator: true,
542 alternative_names: vec!["express".to_string()],
543 },
544 TechnologyRule {
545 name: "Fastify".to_string(),
546 category: TechnologyCategory::BackendFramework,
547 confidence: 0.95,
548 dependency_patterns: vec!["fastify".to_string()],
549 requires: vec![],
550 conflicts_with: vec![],
551 is_primary_indicator: true,
552 alternative_names: vec![],
553 },
554 TechnologyRule {
555 name: "Nest.js".to_string(),
556 category: TechnologyCategory::BackendFramework,
557 confidence: 0.95,
558 dependency_patterns: vec!["@nestjs/core".to_string()],
559 requires: vec![],
560 conflicts_with: vec![],
561 is_primary_indicator: true,
562 alternative_names: vec!["nestjs".to_string()],
563 },
564 TechnologyRule {
565 name: "Hono".to_string(),
566 category: TechnologyCategory::BackendFramework,
567 confidence: 0.95,
568 dependency_patterns: vec!["hono".to_string()],
569 requires: vec![],
570 conflicts_with: vec![],
571 is_primary_indicator: true,
572 alternative_names: vec![],
573 },
574 TechnologyRule {
575 name: "Elysia".to_string(),
576 category: TechnologyCategory::BackendFramework,
577 confidence: 0.95,
578 dependency_patterns: vec!["elysia".to_string()],
579 requires: vec![],
580 conflicts_with: vec![],
581 is_primary_indicator: true,
582 alternative_names: vec![],
583 },
584 TechnologyRule {
585 name: "Encore".to_string(),
586 category: TechnologyCategory::BackendFramework,
587 confidence: 0.95,
588 dependency_patterns: vec!["encore.dev".to_string(), "encore".to_string()],
589 requires: vec![],
590 conflicts_with: vec![],
591 is_primary_indicator: true,
592 alternative_names: vec!["encore-ts-starter".to_string()],
593 },
594
595 TechnologyRule {
597 name: "Vite".to_string(),
598 category: TechnologyCategory::BuildTool,
599 confidence: 0.80,
600 dependency_patterns: vec!["vite".to_string()],
601 requires: vec![],
602 conflicts_with: vec![],
603 is_primary_indicator: false,
604 alternative_names: vec![],
605 },
606 TechnologyRule {
607 name: "Webpack".to_string(),
608 category: TechnologyCategory::BuildTool,
609 confidence: 0.80,
610 dependency_patterns: vec!["webpack".to_string()],
611 requires: vec![],
612 conflicts_with: vec![],
613 is_primary_indicator: false,
614 alternative_names: vec![],
615 },
616
617 TechnologyRule {
619 name: "Prisma".to_string(),
620 category: TechnologyCategory::Database,
621 confidence: 0.90,
622 dependency_patterns: vec!["prisma".to_string(), "@prisma/client".to_string()],
623 requires: vec![],
624 conflicts_with: vec![],
625 is_primary_indicator: false,
626 alternative_names: vec![],
627 },
628 TechnologyRule {
629 name: "Drizzle ORM".to_string(),
630 category: TechnologyCategory::Database,
631 confidence: 0.90,
632 dependency_patterns: vec!["drizzle-orm".to_string(), "drizzle-kit".to_string()],
633 requires: vec![],
634 conflicts_with: vec![],
635 is_primary_indicator: false,
636 alternative_names: vec!["drizzle".to_string()],
637 },
638 TechnologyRule {
639 name: "Sequelize".to_string(),
640 category: TechnologyCategory::Database,
641 confidence: 0.90,
642 dependency_patterns: vec!["sequelize".to_string()],
643 requires: vec![],
644 conflicts_with: vec![],
645 is_primary_indicator: false,
646 alternative_names: vec![],
647 },
648 TechnologyRule {
649 name: "TypeORM".to_string(),
650 category: TechnologyCategory::Database,
651 confidence: 0.90,
652 dependency_patterns: vec!["typeorm".to_string()],
653 requires: vec![],
654 conflicts_with: vec![],
655 is_primary_indicator: false,
656 alternative_names: vec![],
657 },
658 TechnologyRule {
659 name: "MikroORM".to_string(),
660 category: TechnologyCategory::Database,
661 confidence: 0.90,
662 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()],
663 requires: vec![],
664 conflicts_with: vec![],
665 is_primary_indicator: false,
666 alternative_names: vec!["mikro-orm".to_string()],
667 },
668 TechnologyRule {
669 name: "Mongoose".to_string(),
670 category: TechnologyCategory::Database,
671 confidence: 0.95,
672 dependency_patterns: vec!["mongoose".to_string()],
673 requires: vec![],
674 conflicts_with: vec![],
675 is_primary_indicator: false,
676 alternative_names: vec![],
677 },
678 TechnologyRule {
679 name: "Typegoose".to_string(),
680 category: TechnologyCategory::Database,
681 confidence: 0.90,
682 dependency_patterns: vec!["@typegoose/typegoose".to_string()],
683 requires: vec!["Mongoose".to_string()],
684 conflicts_with: vec![],
685 is_primary_indicator: false,
686 alternative_names: vec![],
687 },
688 TechnologyRule {
689 name: "Objection.js".to_string(),
690 category: TechnologyCategory::Database,
691 confidence: 0.90,
692 dependency_patterns: vec!["objection".to_string()],
693 requires: vec!["Knex.js".to_string()],
694 conflicts_with: vec![],
695 is_primary_indicator: false,
696 alternative_names: vec!["objectionjs".to_string()],
697 },
698 TechnologyRule {
699 name: "Bookshelf".to_string(),
700 category: TechnologyCategory::Database,
701 confidence: 0.85,
702 dependency_patterns: vec!["bookshelf".to_string()],
703 requires: vec!["Knex.js".to_string()],
704 conflicts_with: vec![],
705 is_primary_indicator: false,
706 alternative_names: vec![],
707 },
708 TechnologyRule {
709 name: "Waterline".to_string(),
710 category: TechnologyCategory::Database,
711 confidence: 0.85,
712 dependency_patterns: vec!["waterline".to_string(), "sails-mysql".to_string(), "sails-postgresql".to_string(), "sails-disk".to_string()],
713 requires: vec![],
714 conflicts_with: vec![],
715 is_primary_indicator: false,
716 alternative_names: vec![],
717 },
718 TechnologyRule {
719 name: "Knex.js".to_string(),
720 category: TechnologyCategory::Database,
721 confidence: 0.85,
722 dependency_patterns: vec!["knex".to_string()],
723 requires: vec![],
724 conflicts_with: vec![],
725 is_primary_indicator: false,
726 alternative_names: vec!["knexjs".to_string()],
727 },
728
729 TechnologyRule {
731 name: "Node.js".to_string(),
732 category: TechnologyCategory::Runtime,
733 confidence: 0.90,
734 dependency_patterns: vec!["node".to_string()], requires: vec![],
736 conflicts_with: vec![],
737 is_primary_indicator: false,
738 alternative_names: vec!["nodejs".to_string()],
739 },
740 TechnologyRule {
741 name: "Bun".to_string(),
742 category: TechnologyCategory::Runtime,
743 confidence: 0.95,
744 dependency_patterns: vec!["bun".to_string()], requires: vec![],
746 conflicts_with: vec![],
747 is_primary_indicator: false,
748 alternative_names: vec![],
749 },
750 TechnologyRule {
751 name: "Deno".to_string(),
752 category: TechnologyCategory::Runtime,
753 confidence: 0.95,
754 dependency_patterns: vec!["@deno/core".to_string(), "deno".to_string()],
755 requires: vec![],
756 conflicts_with: vec![],
757 is_primary_indicator: false,
758 alternative_names: vec![],
759 },
760 TechnologyRule {
761 name: "WinterJS".to_string(),
762 category: TechnologyCategory::Runtime,
763 confidence: 0.95,
764 dependency_patterns: vec!["winterjs".to_string(), "winter-js".to_string()],
765 requires: vec![],
766 conflicts_with: vec![],
767 is_primary_indicator: false,
768 alternative_names: vec!["winter.js".to_string()],
769 },
770 TechnologyRule {
771 name: "Cloudflare Workers".to_string(),
772 category: TechnologyCategory::Runtime,
773 confidence: 0.90,
774 dependency_patterns: vec!["@cloudflare/workers-types".to_string(), "@cloudflare/vitest-pool-workers".to_string(), "wrangler".to_string()],
775 requires: vec![],
776 conflicts_with: vec![],
777 is_primary_indicator: false,
778 alternative_names: vec!["cloudflare-workers".to_string()],
779 },
780 TechnologyRule {
781 name: "Vercel Edge Runtime".to_string(),
782 category: TechnologyCategory::Runtime,
783 confidence: 0.90,
784 dependency_patterns: vec!["@vercel/edge-runtime".to_string(), "@edge-runtime/vm".to_string()],
785 requires: vec![],
786 conflicts_with: vec![],
787 is_primary_indicator: false,
788 alternative_names: vec!["vercel-edge".to_string()],
789 },
790 TechnologyRule {
791 name: "Hermes".to_string(),
792 category: TechnologyCategory::Runtime,
793 confidence: 0.85,
794 dependency_patterns: vec!["hermes-engine".to_string()],
795 requires: vec!["React Native".to_string()],
796 conflicts_with: vec![],
797 is_primary_indicator: false,
798 alternative_names: vec![],
799 },
800 TechnologyRule {
801 name: "Electron".to_string(),
802 category: TechnologyCategory::Runtime,
803 confidence: 0.95,
804 dependency_patterns: vec!["electron".to_string()],
805 requires: vec![],
806 conflicts_with: vec![],
807 is_primary_indicator: false,
808 alternative_names: vec![],
809 },
810 TechnologyRule {
811 name: "Tauri".to_string(),
812 category: TechnologyCategory::Runtime,
813 confidence: 0.95,
814 dependency_patterns: vec!["@tauri-apps/cli".to_string(), "@tauri-apps/api".to_string()],
815 requires: vec![],
816 conflicts_with: vec!["Electron".to_string()],
817 is_primary_indicator: false,
818 alternative_names: vec![],
819 },
820 TechnologyRule {
821 name: "QuickJS".to_string(),
822 category: TechnologyCategory::Runtime,
823 confidence: 0.85,
824 dependency_patterns: vec!["quickjs".to_string(), "quickjs-emscripten".to_string()],
825 requires: vec![],
826 conflicts_with: vec![],
827 is_primary_indicator: false,
828 alternative_names: vec![],
829 },
830
831 TechnologyRule {
833 name: "Jest".to_string(),
834 category: TechnologyCategory::Testing,
835 confidence: 0.85,
836 dependency_patterns: vec!["jest".to_string()],
837 requires: vec![],
838 conflicts_with: vec![],
839 is_primary_indicator: false,
840 alternative_names: vec![],
841 },
842 TechnologyRule {
843 name: "Vitest".to_string(),
844 category: TechnologyCategory::Testing,
845 confidence: 0.85,
846 dependency_patterns: vec!["vitest".to_string()],
847 requires: vec![],
848 conflicts_with: vec![],
849 is_primary_indicator: false,
850 alternative_names: vec![],
851 },
852 ]
853}