@codexa/cli 8.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +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
+
434
+ export default jvmDetector;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Detector Loader
3
+ *
4
+ * Loads all ecosystem detectors and provides unified detection API.
5
+ * This module should be imported once to register all detectors.
6
+ */
7
+
8
+ // Import all detectors to register them
9
+ import "./dotnet";
10
+ import "./node";
11
+ import "./python";
12
+ import "./go";
13
+ import "./rust";
14
+ import "./jvm";
15
+ import "./flutter";
16
+
17
+ // Re-export main functions from index
18
+ export {
19
+ detectUniversal,
20
+ detectStackLegacy,
21
+ getDetectors,
22
+ type UnifiedDetectionResult,
23
+ type LegacyStackDetection,
24
+ type LegacyStructureDetection,
25
+ type DetectedTechnology,
26
+ type DetectorResult,
27
+ } from "./index";
28
+
29
+ // Re-export utilities for custom detectors
30
+ export {
31
+ registerDetector,
32
+ fileExists,
33
+ dirExists,
34
+ findFiles,
35
+ readJson,
36
+ readText,
37
+ parseToml,
38
+ parseYaml,
39
+ } from "./index";
40
+
41
+ /**
42
+ * Convenience function for CLI display
43
+ */
44
+ export function formatDetectionResult(result: import("./index").UnifiedDetectionResult): string {
45
+ const lines: string[] = [];
46
+
47
+ lines.push("═".repeat(60));
48
+ lines.push("STACK DETECTADO");
49
+ lines.push("═".repeat(60));
50
+
51
+ // Primary info
52
+ lines.push("");
53
+ lines.push(` Linguagem: ${result.primary.language}`);
54
+ if (result.primary.runtime) lines.push(` Runtime: ${result.primary.runtime}`);
55
+ if (result.primary.framework) lines.push(` Framework: ${result.primary.framework}`);
56
+
57
+ // Ecosystems
58
+ if (result.ecosystems.length > 0) {
59
+ lines.push(` Ecossistemas: ${result.ecosystems.join(", ")}`);
60
+ }
61
+
62
+ // Stack by category
63
+ const categories: [string, string[] | undefined][] = [
64
+ ["Frontend", result.stack.frontend],
65
+ ["Backend", result.stack.backend],
66
+ ["Database", result.stack.database],
67
+ ["ORM", result.stack.orm],
68
+ ["Styling", result.stack.styling],
69
+ ["Auth", result.stack.auth],
70
+ ["Testing", result.stack.testing],
71
+ ["DevOps", result.stack.devops],
72
+ ];
73
+
74
+ const hasStack = categories.some(([_, items]) => items && items.length > 0);
75
+ if (hasStack) {
76
+ lines.push("");
77
+ lines.push("─".repeat(60));
78
+ lines.push("TECNOLOGIAS");
79
+ lines.push("─".repeat(60));
80
+ lines.push("");
81
+
82
+ for (const [name, items] of categories) {
83
+ if (items && items.length > 0) {
84
+ lines.push(` ${name}: ${items.join(", ")}`);
85
+ }
86
+ }
87
+ }
88
+
89
+ // Structure
90
+ if (Object.keys(result.structure).length > 0) {
91
+ lines.push("");
92
+ lines.push("─".repeat(60));
93
+ lines.push("ESTRUTURA");
94
+ lines.push("─".repeat(60));
95
+ lines.push("");
96
+
97
+ for (const [key, path] of Object.entries(result.structure)) {
98
+ lines.push(` ${key}: ${path}/`);
99
+ }
100
+ }
101
+
102
+ // Config files
103
+ if (result.configFiles.length > 0) {
104
+ lines.push("");
105
+ lines.push("─".repeat(60));
106
+ lines.push("ARQUIVOS DE CONFIGURACAO");
107
+ lines.push("─".repeat(60));
108
+ lines.push("");
109
+
110
+ for (const file of result.configFiles.slice(0, 10)) {
111
+ lines.push(` • ${file}`);
112
+ }
113
+ if (result.configFiles.length > 10) {
114
+ lines.push(` ... e mais ${result.configFiles.length - 10} arquivos`);
115
+ }
116
+ }
117
+
118
+ lines.push("");
119
+ lines.push("─".repeat(60));
120
+
121
+ return lines.join("\n");
122
+ }
123
+
124
+ /**
125
+ * Get detailed technology list for JSON output
126
+ */
127
+ export function getDetailedTechnologies(result: import("./index").UnifiedDetectionResult): {
128
+ category: string;
129
+ name: string;
130
+ version?: string;
131
+ confidence: number;
132
+ source: string;
133
+ }[] {
134
+ return result.allTechnologies.map(t => ({
135
+ category: t.category,
136
+ name: t.name,
137
+ version: t.version,
138
+ confidence: t.confidence,
139
+ source: t.source,
140
+ }));
141
+ }