@codexa/cli 8.5.0 → 8.6.9

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/detectors/jvm.ts CHANGED
@@ -1,434 +1,434 @@
1
- /**
2
- * JVM Ecosystem Detector (Java/Kotlin/Scala)
3
- *
4
- * Detects JVM projects including:
5
- * - Build tools: Maven, Gradle, sbt
6
- * - Frameworks: Spring Boot, Quarkus, Micronaut, Ktor
7
- * - ORMs: Hibernate, JPA, Exposed, jOOQ
8
- * - Testing: JUnit, TestNG, Kotest, Mockito
9
- */
10
-
11
- import { join } from "path";
12
- import {
13
- registerDetector,
14
- Detector,
15
- DetectorResult,
16
- DetectedTechnology,
17
- fileExists,
18
- dirExists,
19
- findFiles,
20
- readText,
21
- } from "./index";
22
-
23
- interface MavenDependency {
24
- groupId: string;
25
- artifactId: string;
26
- version?: string;
27
- }
28
-
29
- function parsePomXml(content: string): { dependencies: MavenDependency[]; parent?: MavenDependency } {
30
- const result: { dependencies: MavenDependency[]; parent?: MavenDependency } = { dependencies: [] };
31
-
32
- // Parse parent
33
- const parentMatch = content.match(/<parent>([\s\S]*?)<\/parent>/);
34
- if (parentMatch) {
35
- const groupIdMatch = parentMatch[1].match(/<groupId>([^<]+)<\/groupId>/);
36
- const artifactIdMatch = parentMatch[1].match(/<artifactId>([^<]+)<\/artifactId>/);
37
- const versionMatch = parentMatch[1].match(/<version>([^<]+)<\/version>/);
38
- if (groupIdMatch && artifactIdMatch) {
39
- result.parent = {
40
- groupId: groupIdMatch[1],
41
- artifactId: artifactIdMatch[1],
42
- version: versionMatch?.[1],
43
- };
44
- }
45
- }
46
-
47
- // Parse dependencies
48
- const depsMatch = content.match(/<dependencies>([\s\S]*?)<\/dependencies>/);
49
- if (depsMatch) {
50
- const depRegex = /<dependency>([\s\S]*?)<\/dependency>/g;
51
- let match;
52
- while ((match = depRegex.exec(depsMatch[1])) !== null) {
53
- const groupIdMatch = match[1].match(/<groupId>([^<]+)<\/groupId>/);
54
- const artifactIdMatch = match[1].match(/<artifactId>([^<]+)<\/artifactId>/);
55
- const versionMatch = match[1].match(/<version>([^<]+)<\/version>/);
56
- if (groupIdMatch && artifactIdMatch) {
57
- result.dependencies.push({
58
- groupId: groupIdMatch[1],
59
- artifactId: artifactIdMatch[1],
60
- version: versionMatch?.[1],
61
- });
62
- }
63
- }
64
- }
65
-
66
- return result;
67
- }
68
-
69
- function parseGradleDependencies(content: string): string[] {
70
- const deps: string[] = [];
71
-
72
- // Match various dependency patterns
73
- const patterns = [
74
- /implementation\s*['"(]([^'")\s]+)['")\s]/g,
75
- /api\s*['"(]([^'")\s]+)['")\s]/g,
76
- /compileOnly\s*['"(]([^'")\s]+)['")\s]/g,
77
- /runtimeOnly\s*['"(]([^'")\s]+)['")\s]/g,
78
- /testImplementation\s*['"(]([^'")\s]+)['")\s]/g,
79
- /implementation\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
80
- ];
81
-
82
- for (const pattern of patterns) {
83
- let match;
84
- while ((match = pattern.exec(content)) !== null) {
85
- deps.push(match[1]);
86
- }
87
- }
88
-
89
- return deps;
90
- }
91
-
92
- const jvmDetector: Detector = {
93
- name: "jvm",
94
- ecosystem: "java",
95
- priority: 70,
96
- markers: [
97
- { type: "file", pattern: "pom.xml", weight: 1.0 },
98
- { type: "file", pattern: "build.gradle", weight: 1.0 },
99
- { type: "file", pattern: "build.gradle.kts", weight: 1.0 },
100
- { type: "file", pattern: "settings.gradle", weight: 0.9 },
101
- { type: "file", pattern: "settings.gradle.kts", weight: 0.9 },
102
- { type: "file", pattern: "build.sbt", weight: 1.0 },
103
- { type: "file", pattern: "gradlew", weight: 0.8 },
104
- { type: "file", pattern: "mvnw", weight: 0.8 },
105
- { type: "glob", pattern: "**/*.java", weight: 0.7 },
106
- { type: "glob", pattern: "**/*.kt", weight: 0.7 },
107
- { type: "glob", pattern: "**/*.scala", weight: 0.7 },
108
- { type: "directory", pattern: "src/main/java", weight: 0.8 },
109
- { type: "directory", pattern: "src/main/kotlin", weight: 0.8 },
110
- ],
111
-
112
- async detect(cwd: string): Promise<DetectorResult | null> {
113
- const technologies: DetectedTechnology[] = [];
114
- const structure: Record<string, string> = {};
115
- const configFiles: string[] = [];
116
-
117
- // Detect build tool and collect dependencies
118
- const allDeps = new Set<string>();
119
- let buildTool = "";
120
- let language = "Java";
121
-
122
- // Check for Maven
123
- if (fileExists(join(cwd, "pom.xml"))) {
124
- buildTool = "Maven";
125
- configFiles.push("pom.xml");
126
- if (fileExists(join(cwd, "mvnw"))) configFiles.push("mvnw");
127
-
128
- const pomContent = readText(join(cwd, "pom.xml"));
129
- if (pomContent) {
130
- const pom = parsePomXml(pomContent);
131
-
132
- // Check parent for Spring Boot
133
- if (pom.parent?.artifactId === "spring-boot-starter-parent") {
134
- allDeps.add("spring-boot");
135
- }
136
-
137
- for (const dep of pom.dependencies) {
138
- allDeps.add(`${dep.groupId}:${dep.artifactId}`);
139
- allDeps.add(dep.artifactId);
140
- }
141
- }
142
-
143
- technologies.push({
144
- name: "Maven",
145
- confidence: 1.0,
146
- source: "pom.xml",
147
- category: "build",
148
- });
149
- }
150
-
151
- // Check for Gradle
152
- const gradleFile = fileExists(join(cwd, "build.gradle.kts"))
153
- ? "build.gradle.kts"
154
- : fileExists(join(cwd, "build.gradle"))
155
- ? "build.gradle"
156
- : null;
157
-
158
- if (gradleFile) {
159
- buildTool = "Gradle";
160
- configFiles.push(gradleFile);
161
- if (fileExists(join(cwd, "gradlew"))) configFiles.push("gradlew");
162
- if (fileExists(join(cwd, "settings.gradle.kts"))) configFiles.push("settings.gradle.kts");
163
- else if (fileExists(join(cwd, "settings.gradle"))) configFiles.push("settings.gradle");
164
-
165
- const gradleContent = readText(join(cwd, gradleFile));
166
- if (gradleContent) {
167
- const deps = parseGradleDependencies(gradleContent);
168
- for (const dep of deps) {
169
- allDeps.add(dep);
170
- // Also add artifact name
171
- const parts = dep.split(":");
172
- if (parts.length >= 2) {
173
- allDeps.add(parts[1]);
174
- }
175
- }
176
-
177
- // Check for Kotlin
178
- if (gradleContent.includes("kotlin(") || gradleContent.includes("org.jetbrains.kotlin")) {
179
- language = "Kotlin";
180
- }
181
- }
182
-
183
- technologies.push({
184
- name: "Gradle",
185
- confidence: 1.0,
186
- source: gradleFile,
187
- category: "build",
188
- });
189
- }
190
-
191
- // Check for sbt (Scala)
192
- if (fileExists(join(cwd, "build.sbt"))) {
193
- buildTool = "sbt";
194
- language = "Scala";
195
- configFiles.push("build.sbt");
196
-
197
- technologies.push({
198
- name: "sbt",
199
- confidence: 1.0,
200
- source: "build.sbt",
201
- category: "build",
202
- });
203
- }
204
-
205
- // No build tool found - check for source files
206
- if (!buildTool) {
207
- const javaFiles = findFiles(cwd, "**/*.java");
208
- const ktFiles = findFiles(cwd, "**/*.kt");
209
- const scalaFiles = findFiles(cwd, "**/*.scala");
210
-
211
- if (javaFiles.length === 0 && ktFiles.length === 0 && scalaFiles.length === 0) {
212
- return null;
213
- }
214
-
215
- if (ktFiles.length > javaFiles.length) language = "Kotlin";
216
- else if (scalaFiles.length > javaFiles.length) language = "Scala";
217
- }
218
-
219
- // Detect language from files
220
- if (dirExists(join(cwd, "src/main/kotlin")) || findFiles(cwd, "**/*.kt").length > 0) {
221
- if (language !== "Scala") language = "Kotlin";
222
- }
223
- if (dirExists(join(cwd, "src/main/scala")) || findFiles(cwd, "**/*.scala").length > 0) {
224
- language = "Scala";
225
- }
226
-
227
- // Add language as runtime
228
- technologies.push({
229
- name: language,
230
- confidence: 1.0,
231
- source: "Source files",
232
- category: "runtime",
233
- });
234
-
235
- // Web Framework Detection
236
- const webFrameworks = [
237
- { deps: ["spring-boot", "spring-boot-starter-web", "org.springframework.boot:spring-boot-starter-web"], name: "Spring Boot", confidence: 1.0 },
238
- { deps: ["spring-webflux", "spring-boot-starter-webflux"], name: "Spring WebFlux", confidence: 1.0 },
239
- { deps: ["io.quarkus", "quarkus-core", "quarkus-resteasy"], name: "Quarkus", confidence: 1.0 },
240
- { deps: ["io.micronaut", "micronaut-core", "micronaut-http-server"], name: "Micronaut", confidence: 1.0 },
241
- { deps: ["io.ktor", "ktor-server-core", "ktor-server"], name: "Ktor", confidence: 1.0 },
242
- { deps: ["io.vertx", "vertx-core", "vertx-web"], name: "Vert.x", confidence: 1.0 },
243
- { deps: ["io.helidon", "helidon-webserver"], name: "Helidon", confidence: 1.0 },
244
- { deps: ["dropwizard", "io.dropwizard"], name: "Dropwizard", confidence: 1.0 },
245
- { deps: ["spark-core", "com.sparkjava:spark-core"], name: "Spark Java", confidence: 1.0 },
246
- { deps: ["javalin", "io.javalin"], name: "Javalin", confidence: 1.0 },
247
- ];
248
-
249
- for (const fw of webFrameworks) {
250
- const found = fw.deps.find(d => allDeps.has(d));
251
- if (found) {
252
- technologies.push({
253
- name: fw.name,
254
- confidence: fw.confidence,
255
- source: `Dependency: ${found}`,
256
- category: "backend",
257
- });
258
- break;
259
- }
260
- }
261
-
262
- // ORM/Database Detection
263
- const orms = [
264
- { deps: ["hibernate-core", "org.hibernate"], name: "Hibernate", confidence: 1.0 },
265
- { deps: ["spring-data-jpa", "spring-boot-starter-data-jpa"], name: "Spring Data JPA", confidence: 1.0 },
266
- { deps: ["mybatis", "org.mybatis"], name: "MyBatis", confidence: 1.0 },
267
- { deps: ["exposed", "org.jetbrains.exposed"], name: "Exposed", confidence: 1.0 },
268
- { deps: ["jooq", "org.jooq"], name: "jOOQ", confidence: 1.0 },
269
- { deps: ["ebean", "io.ebean"], name: "Ebean", confidence: 1.0 },
270
- { deps: ["jdbi", "org.jdbi"], name: "JDBI", confidence: 1.0 },
271
- { deps: ["slick", "com.typesafe.slick"], name: "Slick", confidence: 1.0 },
272
- ];
273
-
274
- for (const orm of orms) {
275
- const found = orm.deps.find(d => allDeps.has(d));
276
- if (found) {
277
- technologies.push({
278
- name: orm.name,
279
- confidence: orm.confidence,
280
- source: `Dependency: ${found}`,
281
- category: "orm",
282
- });
283
- }
284
- }
285
-
286
- // Database Drivers
287
- const dbDrivers = [
288
- { deps: ["postgresql", "org.postgresql"], name: "PostgreSQL", confidence: 0.9 },
289
- { deps: ["mysql-connector-java", "mysql-connector-j", "com.mysql"], name: "MySQL", confidence: 0.9 },
290
- { deps: ["h2", "com.h2database"], name: "H2", confidence: 0.9 },
291
- { deps: ["mongodb-driver", "org.mongodb"], name: "MongoDB", confidence: 0.9 },
292
- { deps: ["jedis", "lettuce-core", "redis.clients"], name: "Redis", confidence: 0.9 },
293
- { deps: ["elasticsearch-java", "elasticsearch-rest-client"], name: "Elasticsearch", confidence: 0.9 },
294
- { deps: ["cassandra-driver", "datastax"], name: "Cassandra", confidence: 0.9 },
295
- ];
296
-
297
- const detectedDbs = new Set<string>();
298
- for (const db of dbDrivers) {
299
- const found = db.deps.find(d => allDeps.has(d));
300
- if (found && !detectedDbs.has(db.name)) {
301
- technologies.push({
302
- name: db.name,
303
- confidence: db.confidence,
304
- source: `Dependency: ${found}`,
305
- category: "database",
306
- });
307
- detectedDbs.add(db.name);
308
- }
309
- }
310
-
311
- // Testing Frameworks
312
- const testingTools = [
313
- { deps: ["junit-jupiter", "org.junit.jupiter", "junit"], name: "JUnit", confidence: 1.0 },
314
- { deps: ["testng", "org.testng"], name: "TestNG", confidence: 1.0 },
315
- { deps: ["kotest", "io.kotest"], name: "Kotest", confidence: 1.0 },
316
- { deps: ["mockito", "org.mockito"], name: "Mockito", confidence: 0.95 },
317
- { deps: ["mockk", "io.mockk"], name: "MockK", confidence: 0.95 },
318
- { deps: ["assertj", "org.assertj"], name: "AssertJ", confidence: 0.9 },
319
- { deps: ["spock", "org.spockframework"], name: "Spock", confidence: 1.0 },
320
- { deps: ["scalatest", "org.scalatest"], name: "ScalaTest", confidence: 1.0 },
321
- ];
322
-
323
- for (const test of testingTools) {
324
- const found = test.deps.find(d => allDeps.has(d));
325
- if (found) {
326
- technologies.push({
327
- name: test.name,
328
- confidence: test.confidence,
329
- source: `Dependency: ${found}`,
330
- category: "testing",
331
- });
332
- }
333
- }
334
-
335
- // Security/Auth
336
- const authTools = [
337
- { deps: ["spring-security", "spring-boot-starter-security"], name: "Spring Security", confidence: 1.0 },
338
- { deps: ["keycloak", "org.keycloak"], name: "Keycloak", confidence: 1.0 },
339
- { deps: ["nimbus-jose-jwt", "com.nimbusds"], name: "Nimbus JOSE JWT", confidence: 0.9 },
340
- { deps: ["java-jwt", "com.auth0:java-jwt"], name: "Auth0 JWT", confidence: 0.9 },
341
- { deps: ["pac4j", "org.pac4j"], name: "PAC4J", confidence: 1.0 },
342
- ];
343
-
344
- for (const auth of authTools) {
345
- const found = auth.deps.find(d => allDeps.has(d));
346
- if (found) {
347
- technologies.push({
348
- name: auth.name,
349
- confidence: auth.confidence,
350
- source: `Dependency: ${found}`,
351
- category: "auth",
352
- });
353
- }
354
- }
355
-
356
- // DevOps/Observability
357
- const devopsTools = [
358
- { deps: ["lombok", "org.projectlombok"], name: "Lombok", confidence: 0.9 },
359
- { deps: ["micrometer", "io.micrometer"], name: "Micrometer", confidence: 0.9 },
360
- { deps: ["spring-boot-actuator", "spring-boot-starter-actuator"], name: "Spring Actuator", confidence: 0.95 },
361
- { deps: ["logback", "ch.qos.logback"], name: "Logback", confidence: 0.85 },
362
- { deps: ["log4j", "org.apache.logging.log4j"], name: "Log4j", confidence: 0.85 },
363
- { deps: ["opentelemetry", "io.opentelemetry"], name: "OpenTelemetry", confidence: 0.9 },
364
- ];
365
-
366
- for (const tool of devopsTools) {
367
- const found = tool.deps.find(d => allDeps.has(d));
368
- if (found) {
369
- technologies.push({
370
- name: tool.name,
371
- confidence: tool.confidence,
372
- source: `Dependency: ${found}`,
373
- category: "devops",
374
- });
375
- }
376
- }
377
-
378
- // Structure detection (standard Maven/Gradle layout)
379
- const structurePaths = [
380
- { key: "main", paths: ["src/main/java", "src/main/kotlin", "src/main/scala"] },
381
- { key: "test", paths: ["src/test/java", "src/test/kotlin", "src/test/scala"] },
382
- { key: "resources", paths: ["src/main/resources"] },
383
- { key: "testResources", paths: ["src/test/resources"] },
384
- { key: "controllers", paths: ["src/main/java/controller", "src/main/java/controllers", "src/main/kotlin/controller"] },
385
- { key: "services", paths: ["src/main/java/service", "src/main/java/services", "src/main/kotlin/service"] },
386
- { key: "repository", paths: ["src/main/java/repository", "src/main/java/repositories", "src/main/kotlin/repository"] },
387
- { key: "models", paths: ["src/main/java/model", "src/main/java/models", "src/main/java/entity", "src/main/kotlin/model"] },
388
- { key: "config", paths: ["src/main/java/config", "src/main/java/configuration", "src/main/kotlin/config"] },
389
- ];
390
-
391
- for (const { key, paths } of structurePaths) {
392
- for (const p of paths) {
393
- if (dirExists(join(cwd, p))) {
394
- structure[key] = p;
395
- break;
396
- }
397
- }
398
- }
399
-
400
- // Config files
401
- const configPatterns = [
402
- "application.properties",
403
- "application.yml",
404
- "application.yaml",
405
- "application-dev.properties",
406
- "application-dev.yml",
407
- "bootstrap.properties",
408
- "bootstrap.yml",
409
- "lombok.config",
410
- "checkstyle.xml",
411
- "spotbugs.xml",
412
- "Dockerfile",
413
- "docker-compose.yml",
414
- ];
415
-
416
- for (const pattern of configPatterns) {
417
- if (fileExists(join(cwd, pattern)) || fileExists(join(cwd, "src/main/resources", pattern))) {
418
- configFiles.push(pattern);
419
- }
420
- }
421
-
422
- return {
423
- ecosystem: "java",
424
- technologies,
425
- structure,
426
- configFiles: [...new Set(configFiles)],
427
- };
428
- },
429
- };
430
-
431
- // Register the detector
432
- registerDetector(jvmDetector);
433
-
1
+ /**
2
+ * JVM Ecosystem Detector (Java/Kotlin/Scala)
3
+ *
4
+ * Detects JVM projects including:
5
+ * - Build tools: Maven, Gradle, sbt
6
+ * - Frameworks: Spring Boot, Quarkus, Micronaut, Ktor
7
+ * - ORMs: Hibernate, JPA, Exposed, jOOQ
8
+ * - Testing: JUnit, TestNG, Kotest, Mockito
9
+ */
10
+
11
+ import { join } from "path";
12
+ import {
13
+ registerDetector,
14
+ Detector,
15
+ DetectorResult,
16
+ DetectedTechnology,
17
+ fileExists,
18
+ dirExists,
19
+ findFiles,
20
+ readText,
21
+ } from "./index";
22
+
23
+ interface MavenDependency {
24
+ groupId: string;
25
+ artifactId: string;
26
+ version?: string;
27
+ }
28
+
29
+ function parsePomXml(content: string): { dependencies: MavenDependency[]; parent?: MavenDependency } {
30
+ const result: { dependencies: MavenDependency[]; parent?: MavenDependency } = { dependencies: [] };
31
+
32
+ // Parse parent
33
+ const parentMatch = content.match(/<parent>([\s\S]*?)<\/parent>/);
34
+ if (parentMatch) {
35
+ const groupIdMatch = parentMatch[1].match(/<groupId>([^<]+)<\/groupId>/);
36
+ const artifactIdMatch = parentMatch[1].match(/<artifactId>([^<]+)<\/artifactId>/);
37
+ const versionMatch = parentMatch[1].match(/<version>([^<]+)<\/version>/);
38
+ if (groupIdMatch && artifactIdMatch) {
39
+ result.parent = {
40
+ groupId: groupIdMatch[1],
41
+ artifactId: artifactIdMatch[1],
42
+ version: versionMatch?.[1],
43
+ };
44
+ }
45
+ }
46
+
47
+ // Parse dependencies
48
+ const depsMatch = content.match(/<dependencies>([\s\S]*?)<\/dependencies>/);
49
+ if (depsMatch) {
50
+ const depRegex = /<dependency>([\s\S]*?)<\/dependency>/g;
51
+ let match;
52
+ while ((match = depRegex.exec(depsMatch[1])) !== null) {
53
+ const groupIdMatch = match[1].match(/<groupId>([^<]+)<\/groupId>/);
54
+ const artifactIdMatch = match[1].match(/<artifactId>([^<]+)<\/artifactId>/);
55
+ const versionMatch = match[1].match(/<version>([^<]+)<\/version>/);
56
+ if (groupIdMatch && artifactIdMatch) {
57
+ result.dependencies.push({
58
+ groupId: groupIdMatch[1],
59
+ artifactId: artifactIdMatch[1],
60
+ version: versionMatch?.[1],
61
+ });
62
+ }
63
+ }
64
+ }
65
+
66
+ return result;
67
+ }
68
+
69
+ function parseGradleDependencies(content: string): string[] {
70
+ const deps: string[] = [];
71
+
72
+ // Match various dependency patterns
73
+ const patterns = [
74
+ /implementation\s*['"(]([^'")\s]+)['")\s]/g,
75
+ /api\s*['"(]([^'")\s]+)['")\s]/g,
76
+ /compileOnly\s*['"(]([^'")\s]+)['")\s]/g,
77
+ /runtimeOnly\s*['"(]([^'")\s]+)['")\s]/g,
78
+ /testImplementation\s*['"(]([^'")\s]+)['")\s]/g,
79
+ /implementation\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
80
+ ];
81
+
82
+ for (const pattern of patterns) {
83
+ let match;
84
+ while ((match = pattern.exec(content)) !== null) {
85
+ deps.push(match[1]);
86
+ }
87
+ }
88
+
89
+ return deps;
90
+ }
91
+
92
+ const jvmDetector: Detector = {
93
+ name: "jvm",
94
+ ecosystem: "java",
95
+ priority: 70,
96
+ markers: [
97
+ { type: "file", pattern: "pom.xml", weight: 1.0 },
98
+ { type: "file", pattern: "build.gradle", weight: 1.0 },
99
+ { type: "file", pattern: "build.gradle.kts", weight: 1.0 },
100
+ { type: "file", pattern: "settings.gradle", weight: 0.9 },
101
+ { type: "file", pattern: "settings.gradle.kts", weight: 0.9 },
102
+ { type: "file", pattern: "build.sbt", weight: 1.0 },
103
+ { type: "file", pattern: "gradlew", weight: 0.8 },
104
+ { type: "file", pattern: "mvnw", weight: 0.8 },
105
+ { type: "glob", pattern: "**/*.java", weight: 0.7 },
106
+ { type: "glob", pattern: "**/*.kt", weight: 0.7 },
107
+ { type: "glob", pattern: "**/*.scala", weight: 0.7 },
108
+ { type: "directory", pattern: "src/main/java", weight: 0.8 },
109
+ { type: "directory", pattern: "src/main/kotlin", weight: 0.8 },
110
+ ],
111
+
112
+ async detect(cwd: string): Promise<DetectorResult | null> {
113
+ const technologies: DetectedTechnology[] = [];
114
+ const structure: Record<string, string> = {};
115
+ const configFiles: string[] = [];
116
+
117
+ // Detect build tool and collect dependencies
118
+ const allDeps = new Set<string>();
119
+ let buildTool = "";
120
+ let language = "Java";
121
+
122
+ // Check for Maven
123
+ if (fileExists(join(cwd, "pom.xml"))) {
124
+ buildTool = "Maven";
125
+ configFiles.push("pom.xml");
126
+ if (fileExists(join(cwd, "mvnw"))) configFiles.push("mvnw");
127
+
128
+ const pomContent = readText(join(cwd, "pom.xml"));
129
+ if (pomContent) {
130
+ const pom = parsePomXml(pomContent);
131
+
132
+ // Check parent for Spring Boot
133
+ if (pom.parent?.artifactId === "spring-boot-starter-parent") {
134
+ allDeps.add("spring-boot");
135
+ }
136
+
137
+ for (const dep of pom.dependencies) {
138
+ allDeps.add(`${dep.groupId}:${dep.artifactId}`);
139
+ allDeps.add(dep.artifactId);
140
+ }
141
+ }
142
+
143
+ technologies.push({
144
+ name: "Maven",
145
+ confidence: 1.0,
146
+ source: "pom.xml",
147
+ category: "build",
148
+ });
149
+ }
150
+
151
+ // Check for Gradle
152
+ const gradleFile = fileExists(join(cwd, "build.gradle.kts"))
153
+ ? "build.gradle.kts"
154
+ : fileExists(join(cwd, "build.gradle"))
155
+ ? "build.gradle"
156
+ : null;
157
+
158
+ if (gradleFile) {
159
+ buildTool = "Gradle";
160
+ configFiles.push(gradleFile);
161
+ if (fileExists(join(cwd, "gradlew"))) configFiles.push("gradlew");
162
+ if (fileExists(join(cwd, "settings.gradle.kts"))) configFiles.push("settings.gradle.kts");
163
+ else if (fileExists(join(cwd, "settings.gradle"))) configFiles.push("settings.gradle");
164
+
165
+ const gradleContent = readText(join(cwd, gradleFile));
166
+ if (gradleContent) {
167
+ const deps = parseGradleDependencies(gradleContent);
168
+ for (const dep of deps) {
169
+ allDeps.add(dep);
170
+ // Also add artifact name
171
+ const parts = dep.split(":");
172
+ if (parts.length >= 2) {
173
+ allDeps.add(parts[1]);
174
+ }
175
+ }
176
+
177
+ // Check for Kotlin
178
+ if (gradleContent.includes("kotlin(") || gradleContent.includes("org.jetbrains.kotlin")) {
179
+ language = "Kotlin";
180
+ }
181
+ }
182
+
183
+ technologies.push({
184
+ name: "Gradle",
185
+ confidence: 1.0,
186
+ source: gradleFile,
187
+ category: "build",
188
+ });
189
+ }
190
+
191
+ // Check for sbt (Scala)
192
+ if (fileExists(join(cwd, "build.sbt"))) {
193
+ buildTool = "sbt";
194
+ language = "Scala";
195
+ configFiles.push("build.sbt");
196
+
197
+ technologies.push({
198
+ name: "sbt",
199
+ confidence: 1.0,
200
+ source: "build.sbt",
201
+ category: "build",
202
+ });
203
+ }
204
+
205
+ // No build tool found - check for source files
206
+ if (!buildTool) {
207
+ const javaFiles = findFiles(cwd, "**/*.java");
208
+ const ktFiles = findFiles(cwd, "**/*.kt");
209
+ const scalaFiles = findFiles(cwd, "**/*.scala");
210
+
211
+ if (javaFiles.length === 0 && ktFiles.length === 0 && scalaFiles.length === 0) {
212
+ return null;
213
+ }
214
+
215
+ if (ktFiles.length > javaFiles.length) language = "Kotlin";
216
+ else if (scalaFiles.length > javaFiles.length) language = "Scala";
217
+ }
218
+
219
+ // Detect language from files
220
+ if (dirExists(join(cwd, "src/main/kotlin")) || findFiles(cwd, "**/*.kt").length > 0) {
221
+ if (language !== "Scala") language = "Kotlin";
222
+ }
223
+ if (dirExists(join(cwd, "src/main/scala")) || findFiles(cwd, "**/*.scala").length > 0) {
224
+ language = "Scala";
225
+ }
226
+
227
+ // Add language as runtime
228
+ technologies.push({
229
+ name: language,
230
+ confidence: 1.0,
231
+ source: "Source files",
232
+ category: "runtime",
233
+ });
234
+
235
+ // Web Framework Detection
236
+ const webFrameworks = [
237
+ { deps: ["spring-boot", "spring-boot-starter-web", "org.springframework.boot:spring-boot-starter-web"], name: "Spring Boot", confidence: 1.0 },
238
+ { deps: ["spring-webflux", "spring-boot-starter-webflux"], name: "Spring WebFlux", confidence: 1.0 },
239
+ { deps: ["io.quarkus", "quarkus-core", "quarkus-resteasy"], name: "Quarkus", confidence: 1.0 },
240
+ { deps: ["io.micronaut", "micronaut-core", "micronaut-http-server"], name: "Micronaut", confidence: 1.0 },
241
+ { deps: ["io.ktor", "ktor-server-core", "ktor-server"], name: "Ktor", confidence: 1.0 },
242
+ { deps: ["io.vertx", "vertx-core", "vertx-web"], name: "Vert.x", confidence: 1.0 },
243
+ { deps: ["io.helidon", "helidon-webserver"], name: "Helidon", confidence: 1.0 },
244
+ { deps: ["dropwizard", "io.dropwizard"], name: "Dropwizard", confidence: 1.0 },
245
+ { deps: ["spark-core", "com.sparkjava:spark-core"], name: "Spark Java", confidence: 1.0 },
246
+ { deps: ["javalin", "io.javalin"], name: "Javalin", confidence: 1.0 },
247
+ ];
248
+
249
+ for (const fw of webFrameworks) {
250
+ const found = fw.deps.find(d => allDeps.has(d));
251
+ if (found) {
252
+ technologies.push({
253
+ name: fw.name,
254
+ confidence: fw.confidence,
255
+ source: `Dependency: ${found}`,
256
+ category: "backend",
257
+ });
258
+ break;
259
+ }
260
+ }
261
+
262
+ // ORM/Database Detection
263
+ const orms = [
264
+ { deps: ["hibernate-core", "org.hibernate"], name: "Hibernate", confidence: 1.0 },
265
+ { deps: ["spring-data-jpa", "spring-boot-starter-data-jpa"], name: "Spring Data JPA", confidence: 1.0 },
266
+ { deps: ["mybatis", "org.mybatis"], name: "MyBatis", confidence: 1.0 },
267
+ { deps: ["exposed", "org.jetbrains.exposed"], name: "Exposed", confidence: 1.0 },
268
+ { deps: ["jooq", "org.jooq"], name: "jOOQ", confidence: 1.0 },
269
+ { deps: ["ebean", "io.ebean"], name: "Ebean", confidence: 1.0 },
270
+ { deps: ["jdbi", "org.jdbi"], name: "JDBI", confidence: 1.0 },
271
+ { deps: ["slick", "com.typesafe.slick"], name: "Slick", confidence: 1.0 },
272
+ ];
273
+
274
+ for (const orm of orms) {
275
+ const found = orm.deps.find(d => allDeps.has(d));
276
+ if (found) {
277
+ technologies.push({
278
+ name: orm.name,
279
+ confidence: orm.confidence,
280
+ source: `Dependency: ${found}`,
281
+ category: "orm",
282
+ });
283
+ }
284
+ }
285
+
286
+ // Database Drivers
287
+ const dbDrivers = [
288
+ { deps: ["postgresql", "org.postgresql"], name: "PostgreSQL", confidence: 0.9 },
289
+ { deps: ["mysql-connector-java", "mysql-connector-j", "com.mysql"], name: "MySQL", confidence: 0.9 },
290
+ { deps: ["h2", "com.h2database"], name: "H2", confidence: 0.9 },
291
+ { deps: ["mongodb-driver", "org.mongodb"], name: "MongoDB", confidence: 0.9 },
292
+ { deps: ["jedis", "lettuce-core", "redis.clients"], name: "Redis", confidence: 0.9 },
293
+ { deps: ["elasticsearch-java", "elasticsearch-rest-client"], name: "Elasticsearch", confidence: 0.9 },
294
+ { deps: ["cassandra-driver", "datastax"], name: "Cassandra", confidence: 0.9 },
295
+ ];
296
+
297
+ const detectedDbs = new Set<string>();
298
+ for (const db of dbDrivers) {
299
+ const found = db.deps.find(d => allDeps.has(d));
300
+ if (found && !detectedDbs.has(db.name)) {
301
+ technologies.push({
302
+ name: db.name,
303
+ confidence: db.confidence,
304
+ source: `Dependency: ${found}`,
305
+ category: "database",
306
+ });
307
+ detectedDbs.add(db.name);
308
+ }
309
+ }
310
+
311
+ // Testing Frameworks
312
+ const testingTools = [
313
+ { deps: ["junit-jupiter", "org.junit.jupiter", "junit"], name: "JUnit", confidence: 1.0 },
314
+ { deps: ["testng", "org.testng"], name: "TestNG", confidence: 1.0 },
315
+ { deps: ["kotest", "io.kotest"], name: "Kotest", confidence: 1.0 },
316
+ { deps: ["mockito", "org.mockito"], name: "Mockito", confidence: 0.95 },
317
+ { deps: ["mockk", "io.mockk"], name: "MockK", confidence: 0.95 },
318
+ { deps: ["assertj", "org.assertj"], name: "AssertJ", confidence: 0.9 },
319
+ { deps: ["spock", "org.spockframework"], name: "Spock", confidence: 1.0 },
320
+ { deps: ["scalatest", "org.scalatest"], name: "ScalaTest", confidence: 1.0 },
321
+ ];
322
+
323
+ for (const test of testingTools) {
324
+ const found = test.deps.find(d => allDeps.has(d));
325
+ if (found) {
326
+ technologies.push({
327
+ name: test.name,
328
+ confidence: test.confidence,
329
+ source: `Dependency: ${found}`,
330
+ category: "testing",
331
+ });
332
+ }
333
+ }
334
+
335
+ // Security/Auth
336
+ const authTools = [
337
+ { deps: ["spring-security", "spring-boot-starter-security"], name: "Spring Security", confidence: 1.0 },
338
+ { deps: ["keycloak", "org.keycloak"], name: "Keycloak", confidence: 1.0 },
339
+ { deps: ["nimbus-jose-jwt", "com.nimbusds"], name: "Nimbus JOSE JWT", confidence: 0.9 },
340
+ { deps: ["java-jwt", "com.auth0:java-jwt"], name: "Auth0 JWT", confidence: 0.9 },
341
+ { deps: ["pac4j", "org.pac4j"], name: "PAC4J", confidence: 1.0 },
342
+ ];
343
+
344
+ for (const auth of authTools) {
345
+ const found = auth.deps.find(d => allDeps.has(d));
346
+ if (found) {
347
+ technologies.push({
348
+ name: auth.name,
349
+ confidence: auth.confidence,
350
+ source: `Dependency: ${found}`,
351
+ category: "auth",
352
+ });
353
+ }
354
+ }
355
+
356
+ // DevOps/Observability
357
+ const devopsTools = [
358
+ { deps: ["lombok", "org.projectlombok"], name: "Lombok", confidence: 0.9 },
359
+ { deps: ["micrometer", "io.micrometer"], name: "Micrometer", confidence: 0.9 },
360
+ { deps: ["spring-boot-actuator", "spring-boot-starter-actuator"], name: "Spring Actuator", confidence: 0.95 },
361
+ { deps: ["logback", "ch.qos.logback"], name: "Logback", confidence: 0.85 },
362
+ { deps: ["log4j", "org.apache.logging.log4j"], name: "Log4j", confidence: 0.85 },
363
+ { deps: ["opentelemetry", "io.opentelemetry"], name: "OpenTelemetry", confidence: 0.9 },
364
+ ];
365
+
366
+ for (const tool of devopsTools) {
367
+ const found = tool.deps.find(d => allDeps.has(d));
368
+ if (found) {
369
+ technologies.push({
370
+ name: tool.name,
371
+ confidence: tool.confidence,
372
+ source: `Dependency: ${found}`,
373
+ category: "devops",
374
+ });
375
+ }
376
+ }
377
+
378
+ // Structure detection (standard Maven/Gradle layout)
379
+ const structurePaths = [
380
+ { key: "main", paths: ["src/main/java", "src/main/kotlin", "src/main/scala"] },
381
+ { key: "test", paths: ["src/test/java", "src/test/kotlin", "src/test/scala"] },
382
+ { key: "resources", paths: ["src/main/resources"] },
383
+ { key: "testResources", paths: ["src/test/resources"] },
384
+ { key: "controllers", paths: ["src/main/java/controller", "src/main/java/controllers", "src/main/kotlin/controller"] },
385
+ { key: "services", paths: ["src/main/java/service", "src/main/java/services", "src/main/kotlin/service"] },
386
+ { key: "repository", paths: ["src/main/java/repository", "src/main/java/repositories", "src/main/kotlin/repository"] },
387
+ { key: "models", paths: ["src/main/java/model", "src/main/java/models", "src/main/java/entity", "src/main/kotlin/model"] },
388
+ { key: "config", paths: ["src/main/java/config", "src/main/java/configuration", "src/main/kotlin/config"] },
389
+ ];
390
+
391
+ for (const { key, paths } of structurePaths) {
392
+ for (const p of paths) {
393
+ if (dirExists(join(cwd, p))) {
394
+ structure[key] = p;
395
+ break;
396
+ }
397
+ }
398
+ }
399
+
400
+ // Config files
401
+ const configPatterns = [
402
+ "application.properties",
403
+ "application.yml",
404
+ "application.yaml",
405
+ "application-dev.properties",
406
+ "application-dev.yml",
407
+ "bootstrap.properties",
408
+ "bootstrap.yml",
409
+ "lombok.config",
410
+ "checkstyle.xml",
411
+ "spotbugs.xml",
412
+ "Dockerfile",
413
+ "docker-compose.yml",
414
+ ];
415
+
416
+ for (const pattern of configPatterns) {
417
+ if (fileExists(join(cwd, pattern)) || fileExists(join(cwd, "src/main/resources", pattern))) {
418
+ configFiles.push(pattern);
419
+ }
420
+ }
421
+
422
+ return {
423
+ ecosystem: "java",
424
+ technologies,
425
+ structure,
426
+ configFiles: [...new Set(configFiles)],
427
+ };
428
+ },
429
+ };
430
+
431
+ // Register the detector
432
+ registerDetector(jvmDetector);
433
+
434
434
  export default jvmDetector;