ai-first-cli 1.1.1 → 1.1.2
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/CHANGELOG.md +78 -0
- package/README.es.md +137 -1
- package/README.md +136 -4
- package/ai/ai_context.md +2 -2
- package/ai/architecture.md +3 -3
- package/ai/cache.json +85 -57
- package/ai/ccp/jira-123/context.json +7 -0
- package/ai/context/repo.json +56 -0
- package/ai/context/utils.json +7 -0
- package/ai/dependencies.json +51 -1026
- package/ai/files.json +195 -3
- package/ai/git/commit-activity.json +8646 -0
- package/ai/git/recent-features.json +1 -0
- package/ai/git/recent-files.json +52 -0
- package/ai/git/recent-flows.json +1 -0
- package/ai/graph/knowledge-graph.json +43643 -0
- package/ai/graph/module-graph.json +4 -0
- package/ai/graph/symbol-graph.json +3307 -879
- package/ai/graph/symbol-references.json +119 -32
- package/ai/index-state.json +843 -188
- package/ai/index.db +0 -0
- package/ai/modules.json +4 -0
- package/ai/repo-map.json +81 -17
- package/ai/repo_map.json +81 -17
- package/ai/repo_map.md +21 -7
- package/ai/summary.md +5 -5
- package/ai/symbols.json +1 -20287
- package/dist/analyzers/androidResources.d.ts +23 -0
- package/dist/analyzers/androidResources.d.ts.map +1 -0
- package/dist/analyzers/androidResources.js +93 -0
- package/dist/analyzers/androidResources.js.map +1 -0
- package/dist/analyzers/dependencies.d.ts.map +1 -1
- package/dist/analyzers/dependencies.js +37 -0
- package/dist/analyzers/dependencies.js.map +1 -1
- package/dist/analyzers/entrypoints.d.ts.map +1 -1
- package/dist/analyzers/entrypoints.js +71 -1
- package/dist/analyzers/entrypoints.js.map +1 -1
- package/dist/analyzers/gradleModules.d.ts +22 -0
- package/dist/analyzers/gradleModules.d.ts.map +1 -0
- package/dist/analyzers/gradleModules.js +75 -0
- package/dist/analyzers/gradleModules.js.map +1 -0
- package/dist/analyzers/techStack.d.ts +7 -0
- package/dist/analyzers/techStack.d.ts.map +1 -1
- package/dist/analyzers/techStack.js +44 -1
- package/dist/analyzers/techStack.js.map +1 -1
- package/dist/commands/ai-first.d.ts.map +1 -1
- package/dist/commands/ai-first.js +311 -1
- package/dist/commands/ai-first.js.map +1 -1
- package/dist/core/adapters/adapterRegistry.d.ts +39 -0
- package/dist/core/adapters/adapterRegistry.d.ts.map +1 -0
- package/dist/core/adapters/adapterRegistry.js +155 -0
- package/dist/core/adapters/adapterRegistry.js.map +1 -0
- package/dist/core/adapters/baseAdapter.d.ts +49 -0
- package/dist/core/adapters/baseAdapter.d.ts.map +1 -0
- package/dist/core/adapters/baseAdapter.js +28 -0
- package/dist/core/adapters/baseAdapter.js.map +1 -0
- package/dist/core/adapters/community/fastapiAdapter.d.ts +7 -0
- package/dist/core/adapters/community/fastapiAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/fastapiAdapter.js +40 -0
- package/dist/core/adapters/community/fastapiAdapter.js.map +1 -0
- package/dist/core/adapters/community/index.d.ts +11 -0
- package/dist/core/adapters/community/index.d.ts.map +1 -0
- package/dist/core/adapters/community/index.js +11 -0
- package/dist/core/adapters/community/index.js.map +1 -0
- package/dist/core/adapters/community/laravelAdapter.d.ts +7 -0
- package/dist/core/adapters/community/laravelAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/laravelAdapter.js +47 -0
- package/dist/core/adapters/community/laravelAdapter.js.map +1 -0
- package/dist/core/adapters/community/nestjsAdapter.d.ts +7 -0
- package/dist/core/adapters/community/nestjsAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/nestjsAdapter.js +48 -0
- package/dist/core/adapters/community/nestjsAdapter.js.map +1 -0
- package/dist/core/adapters/community/phoenixAdapter.d.ts +7 -0
- package/dist/core/adapters/community/phoenixAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/phoenixAdapter.js +45 -0
- package/dist/core/adapters/community/phoenixAdapter.js.map +1 -0
- package/dist/core/adapters/community/springBootAdapter.d.ts +7 -0
- package/dist/core/adapters/community/springBootAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/springBootAdapter.js +44 -0
- package/dist/core/adapters/community/springBootAdapter.js.map +1 -0
- package/dist/core/adapters/dotnetAdapter.d.ts +20 -0
- package/dist/core/adapters/dotnetAdapter.d.ts.map +1 -0
- package/dist/core/adapters/dotnetAdapter.js +86 -0
- package/dist/core/adapters/dotnetAdapter.js.map +1 -0
- package/dist/core/adapters/index.d.ts +18 -0
- package/dist/core/adapters/index.d.ts.map +1 -0
- package/dist/core/adapters/index.js +19 -0
- package/dist/core/adapters/index.js.map +1 -0
- package/dist/core/adapters/javascriptAdapter.d.ts +11 -0
- package/dist/core/adapters/javascriptAdapter.d.ts.map +1 -0
- package/dist/core/adapters/javascriptAdapter.js +47 -0
- package/dist/core/adapters/javascriptAdapter.js.map +1 -0
- package/dist/core/adapters/pythonAdapter.d.ts +20 -0
- package/dist/core/adapters/pythonAdapter.d.ts.map +1 -0
- package/dist/core/adapters/pythonAdapter.js +99 -0
- package/dist/core/adapters/pythonAdapter.js.map +1 -0
- package/dist/core/adapters/railsAdapter.d.ts +10 -0
- package/dist/core/adapters/railsAdapter.d.ts.map +1 -0
- package/dist/core/adapters/railsAdapter.js +52 -0
- package/dist/core/adapters/railsAdapter.js.map +1 -0
- package/dist/core/adapters/salesforceAdapter.d.ts +16 -0
- package/dist/core/adapters/salesforceAdapter.d.ts.map +1 -0
- package/dist/core/adapters/salesforceAdapter.js +64 -0
- package/dist/core/adapters/salesforceAdapter.js.map +1 -0
- package/dist/core/adapters/sdk.d.ts +83 -0
- package/dist/core/adapters/sdk.d.ts.map +1 -0
- package/dist/core/adapters/sdk.js +114 -0
- package/dist/core/adapters/sdk.js.map +1 -0
- package/dist/core/ccp.d.ts +37 -0
- package/dist/core/ccp.d.ts.map +1 -0
- package/dist/core/ccp.js +184 -0
- package/dist/core/ccp.js.map +1 -0
- package/dist/core/gitAnalyzer.d.ts +74 -0
- package/dist/core/gitAnalyzer.d.ts.map +1 -0
- package/dist/core/gitAnalyzer.js +298 -0
- package/dist/core/gitAnalyzer.js.map +1 -0
- package/dist/core/incrementalAnalyzer.d.ts +28 -0
- package/dist/core/incrementalAnalyzer.d.ts.map +1 -0
- package/dist/core/incrementalAnalyzer.js +343 -0
- package/dist/core/incrementalAnalyzer.js.map +1 -0
- package/dist/core/knowledgeGraphBuilder.d.ts +31 -0
- package/dist/core/knowledgeGraphBuilder.d.ts.map +1 -0
- package/dist/core/knowledgeGraphBuilder.js +197 -0
- package/dist/core/knowledgeGraphBuilder.js.map +1 -0
- package/dist/core/lazyAnalyzer.d.ts +57 -0
- package/dist/core/lazyAnalyzer.d.ts.map +1 -0
- package/dist/core/lazyAnalyzer.js +204 -0
- package/dist/core/lazyAnalyzer.js.map +1 -0
- package/dist/core/schema.d.ts +57 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +131 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/semanticContexts.d.ts +40 -0
- package/dist/core/semanticContexts.d.ts.map +1 -0
- package/dist/core/semanticContexts.js +454 -0
- package/dist/core/semanticContexts.js.map +1 -0
- package/docs/es/guide/adapters.md +143 -0
- package/docs/es/guide/ai-repository-schema.md +119 -0
- package/docs/es/guide/features.md +67 -0
- package/docs/es/guide/flows.md +134 -0
- package/docs/es/guide/git-intelligence.md +170 -0
- package/docs/es/guide/incremental-analysis.md +131 -0
- package/docs/es/guide/knowledge-graph.md +135 -0
- package/docs/es/guide/lazy-indexing.md +144 -0
- package/docs/es/guide/performance.md +125 -0
- package/docs/guide/adapters.md +225 -0
- package/docs/guide/ai-repository-schema.md +119 -0
- package/docs/guide/architecture.md +69 -1
- package/docs/guide/flows.md +134 -0
- package/docs/guide/git-intelligence.md +170 -0
- package/docs/guide/incremental-analysis.md +131 -0
- package/docs/guide/knowledge-graph.md +135 -0
- package/docs/guide/lazy-indexing.md +144 -0
- package/docs/guide/performance.md +125 -0
- package/package.json +5 -2
- package/src/analyzers/androidResources.ts +113 -0
- package/src/analyzers/dependencies.ts +41 -0
- package/src/analyzers/entrypoints.ts +80 -1
- package/src/analyzers/gradleModules.ts +100 -0
- package/src/analyzers/techStack.ts +56 -0
- package/src/commands/ai-first.ts +342 -1
- package/src/core/adapters/adapterRegistry.ts +187 -0
- package/src/core/adapters/baseAdapter.ts +82 -0
- package/src/core/adapters/community/fastapiAdapter.ts +50 -0
- package/src/core/adapters/community/index.ts +11 -0
- package/src/core/adapters/community/laravelAdapter.ts +56 -0
- package/src/core/adapters/community/nestjsAdapter.ts +57 -0
- package/src/core/adapters/community/phoenixAdapter.ts +54 -0
- package/src/core/adapters/community/springBootAdapter.ts +53 -0
- package/src/core/adapters/dotnetAdapter.ts +104 -0
- package/src/core/adapters/index.ts +24 -0
- package/src/core/adapters/javascriptAdapter.ts +56 -0
- package/src/core/adapters/pythonAdapter.ts +118 -0
- package/src/core/adapters/railsAdapter.ts +65 -0
- package/src/core/adapters/salesforceAdapter.ts +76 -0
- package/src/core/adapters/sdk.ts +172 -0
- package/src/core/ccp.ts +240 -0
- package/src/core/gitAnalyzer.ts +391 -0
- package/src/core/incrementalAnalyzer.ts +382 -0
- package/src/core/knowledgeGraphBuilder.ts +181 -0
- package/src/core/lazyAnalyzer.ts +261 -0
- package/src/core/schema.ts +157 -0
- package/src/core/semanticContexts.ts +575 -0
- package/tests/adapters.test.ts +159 -0
- package/tests/gitAnalyzer.test.ts +133 -0
- package/tests/incrementalAnalyzer.test.ts +83 -0
- package/tests/knowledgeGraph.test.ts +146 -0
- package/tests/lazyAnalyzer.test.ts +230 -0
- package/tests/schema.test.ts +203 -0
- package/tests/semanticContexts.test.ts +435 -0
- package/ai/context/analyzers.Symbol.json +0 -19
- package/ai/context/analyzers.extractSymbols.json +0 -19
|
@@ -66,6 +66,8 @@ function parseFileForDependencies(file: FileInfo): Dependency[] {
|
|
|
66
66
|
deps.push(...parseRustImports(content, file.relativePath));
|
|
67
67
|
} else if (file.extension === "java" || file.extension === "cs") {
|
|
68
68
|
deps.push(...parseJavaImports(content, file.relativePath));
|
|
69
|
+
} else if (file.extension === "gradle" || file.extension === "gradle.kts") {
|
|
70
|
+
deps.push(...parseGradleDependencies(content, file.relativePath));
|
|
69
71
|
}
|
|
70
72
|
} catch {}
|
|
71
73
|
|
|
@@ -305,3 +307,42 @@ export function generateDependenciesJson(analysis: DependencyAnalysis): string {
|
|
|
305
307
|
};
|
|
306
308
|
return JSON.stringify(output, null, 2);
|
|
307
309
|
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Parse Gradle dependencies from build.gradle files
|
|
313
|
+
*/
|
|
314
|
+
function parseGradleDependencies(content: string, sourceFile: string): Dependency[] {
|
|
315
|
+
const deps: Dependency[] = [];
|
|
316
|
+
|
|
317
|
+
// implementation "com.example:library:1.0"
|
|
318
|
+
const implMatches = content.matchAll(/(?:implementation|api|compile|testImplementation|androidTestImplementation)\s+["\']([@\w.\-]+):([@\w.\-]+):([@\w.\-]+)["\']/g);
|
|
319
|
+
for (const match of implMatches) {
|
|
320
|
+
deps.push({
|
|
321
|
+
source: sourceFile,
|
|
322
|
+
target: `${match[1]}:${match[2]}:${match[3]}`,
|
|
323
|
+
type: "import",
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// implementation project(":module")
|
|
328
|
+
const projMatches = content.matchAll(/implementation\s+project\s*\(\s*["\']([@\w.\-]+)["\']\s*\)/g);
|
|
329
|
+
for (const match of projMatches) {
|
|
330
|
+
deps.push({
|
|
331
|
+
source: sourceFile,
|
|
332
|
+
target: `project:${match[1]}`,
|
|
333
|
+
type: "import",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// include ":module"
|
|
338
|
+
const includeMatches = content.matchAll(/include\s*\(\s*["\']([@\w.\-]+)["\']\s*\)/g);
|
|
339
|
+
for (const match of includeMatches) {
|
|
340
|
+
deps.push({
|
|
341
|
+
source: sourceFile,
|
|
342
|
+
target: `module:${match[1]}`,
|
|
343
|
+
type: "include",
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return deps;
|
|
348
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FileInfo } from "../core/repoScanner.js";
|
|
2
|
-
import { readJsonFile } from "../utils/fileUtils.js";
|
|
2
|
+
import { readJsonFile, readFile } from "../utils/fileUtils.js";
|
|
3
3
|
import path from "path";
|
|
4
4
|
|
|
5
5
|
export interface Entrypoint {
|
|
@@ -69,6 +69,85 @@ export function discoverEntrypoints(files: FileInfo[], rootDir: string): Entrypo
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// Detect Android entrypoints from AndroidManifest.xml
|
|
73
|
+
const androidManifests = files.filter(f => f.name === "AndroidManifest.xml");
|
|
74
|
+
for (const manifest of androidManifests) {
|
|
75
|
+
try {
|
|
76
|
+
const manifestContent = readFile(path.join(rootDir, manifest.relativePath));
|
|
77
|
+
const androidEntrypoints = parseAndroidManifest(manifestContent, manifest.relativePath);
|
|
78
|
+
entrypoints.push(...androidEntrypoints);
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return entrypoints;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parse AndroidManifest.xml to extract entrypoints
|
|
87
|
+
*/
|
|
88
|
+
function parseAndroidManifest(content: string, manifestPath: string): Entrypoint[] {
|
|
89
|
+
const entrypoints: Entrypoint[] = [];
|
|
90
|
+
|
|
91
|
+
// Extract package name
|
|
92
|
+
const packageMatch = content.match(/package="([^"]+)"/);
|
|
93
|
+
const packageName = packageMatch ? packageMatch[1] : "unknown";
|
|
94
|
+
|
|
95
|
+
// Extract activities
|
|
96
|
+
const activityRegex = /<activity[^>]+android:name="([^"]+)"[^>]*>/g;
|
|
97
|
+
let match;
|
|
98
|
+
while ((match = activityRegex.exec(content)) !== null) {
|
|
99
|
+
const activityName = match[1];
|
|
100
|
+
const isMain = content.includes(`android:name="${activityName}"`) &&
|
|
101
|
+
content.includes("android.intent.action.MAIN") &&
|
|
102
|
+
content.includes("android.intent.category.LAUNCHER");
|
|
103
|
+
|
|
104
|
+
entrypoints.push({
|
|
105
|
+
name: activityName.split('.').pop() || activityName,
|
|
106
|
+
path: `${manifestPath}#${activityName}`,
|
|
107
|
+
type: isMain ? "client" : "other",
|
|
108
|
+
description: isMain ? `Main Activity (${packageName})` : `Activity: ${activityName}`,
|
|
109
|
+
command: isMain ? `adb shell am start -n ${packageName}/${activityName}` : undefined,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Extract services
|
|
114
|
+
const serviceRegex = /<service[^>]+android:name="([^"]+)"[^>]*>/g;
|
|
115
|
+
while ((match = serviceRegex.exec(content)) !== null) {
|
|
116
|
+
entrypoints.push({
|
|
117
|
+
name: match[1].split('.').pop() || match[1],
|
|
118
|
+
path: `${manifestPath}#service.${match[1]}`,
|
|
119
|
+
type: "other",
|
|
120
|
+
description: `Service: ${match[1]}`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Extract receivers
|
|
125
|
+
const receiverRegex = /<receiver[^>]+android:name="([^"]+)"[^>]*>/g;
|
|
126
|
+
while ((match = receiverRegex.exec(content)) !== null) {
|
|
127
|
+
entrypoints.push({
|
|
128
|
+
name: match[1].split('.').pop() || match[1],
|
|
129
|
+
path: `${manifestPath}#receiver.${match[1]}`,
|
|
130
|
+
type: "other",
|
|
131
|
+
description: `BroadcastReceiver: ${match[1]}`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Extract permissions
|
|
136
|
+
const permissionRegex = /<uses-permission[^>]+android:name="([^"]+)"[^>]*>/g;
|
|
137
|
+
const permissions: string[] = [];
|
|
138
|
+
while ((match = permissionRegex.exec(content)) !== null) {
|
|
139
|
+
permissions.push(match[1]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (permissions.length > 0) {
|
|
143
|
+
entrypoints.push({
|
|
144
|
+
name: "Permissions",
|
|
145
|
+
path: `${manifestPath}#permissions`,
|
|
146
|
+
type: "config",
|
|
147
|
+
description: `${permissions.length} permissions declared`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
72
151
|
return entrypoints;
|
|
73
152
|
}
|
|
74
153
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { FileInfo } from "../core/repoScanner.js";
|
|
2
|
+
import { readFile } from "../utils/fileUtils.js";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
export interface GradleModule {
|
|
6
|
+
name: string;
|
|
7
|
+
path: string;
|
|
8
|
+
isIncluded: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GradleModulesAnalysis {
|
|
12
|
+
isGradle: boolean;
|
|
13
|
+
isMultiModule: boolean;
|
|
14
|
+
modules: GradleModule[];
|
|
15
|
+
rootProjectName?: string;
|
|
16
|
+
settingsFile?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Analyze Gradle settings to detect modules
|
|
21
|
+
*/
|
|
22
|
+
export function analyzeGradleModules(files: FileInfo[], rootDir: string): GradleModulesAnalysis {
|
|
23
|
+
const settingsFiles = files.filter(f =>
|
|
24
|
+
f.name === "settings.gradle" || f.name === "settings.gradle.kts"
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (settingsFiles.length === 0) {
|
|
28
|
+
return {
|
|
29
|
+
isGradle: false,
|
|
30
|
+
isMultiModule: false,
|
|
31
|
+
modules: [],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const modules: GradleModule[] = [];
|
|
36
|
+
let rootProjectName = "app";
|
|
37
|
+
let settingsContent = "";
|
|
38
|
+
|
|
39
|
+
// Use root settings.gradle or settings.gradle.kts
|
|
40
|
+
const settingsFile = settingsFiles.find(f => f.relativePath === "settings.gradle" || f.relativePath === "settings.gradle.kts");
|
|
41
|
+
|
|
42
|
+
if (settingsFile) {
|
|
43
|
+
try {
|
|
44
|
+
settingsContent = readFile(path.join(rootDir, settingsFile.relativePath));
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Parse include statements: include(":module")
|
|
49
|
+
const includeMatches = settingsContent.matchAll(/include\s*\(\s*["']([^"']+)["']\s*\)/g);
|
|
50
|
+
for (const match of includeMatches) {
|
|
51
|
+
const modulePath = match[1];
|
|
52
|
+
modules.push({
|
|
53
|
+
name: modulePath.replace(/^:/, ""),
|
|
54
|
+
path: modulePath,
|
|
55
|
+
isIncluded: true,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Parse project statements: project(":module")
|
|
60
|
+
const projectMatches = settingsContent.matchAll(/project\s*\(\s*["']([^"']+)["']\s*\)\s*\.+/g);
|
|
61
|
+
for (const match of projectMatches) {
|
|
62
|
+
const modulePath = match[1];
|
|
63
|
+
const moduleName = modulePath.replace(/^:/, "");
|
|
64
|
+
if (!modules.find(m => m.name === moduleName)) {
|
|
65
|
+
modules.push({
|
|
66
|
+
name: moduleName,
|
|
67
|
+
path: modulePath,
|
|
68
|
+
isIncluded: true,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Extract root project name
|
|
74
|
+
const rootNameMatch = settingsContent.match(/rootProject\s*\(\s*["']name["']\s*=\s*["']([^"']+)["']/);
|
|
75
|
+
if (rootNameMatch) {
|
|
76
|
+
rootProjectName = rootNameMatch[1];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
isGradle: true,
|
|
81
|
+
isMultiModule: modules.length > 0,
|
|
82
|
+
modules,
|
|
83
|
+
rootProjectName,
|
|
84
|
+
settingsFile: settingsFile?.relativePath,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Generate gradle-modules.json content
|
|
90
|
+
*/
|
|
91
|
+
export function generateGradleModulesJson(analysis: GradleModulesAnalysis): string {
|
|
92
|
+
const output = {
|
|
93
|
+
isGradle: analysis.isGradle,
|
|
94
|
+
isMultiModule: analysis.isMultiModule,
|
|
95
|
+
rootProjectName: analysis.rootProjectName,
|
|
96
|
+
settingsFile: analysis.settingsFile,
|
|
97
|
+
modules: analysis.modules,
|
|
98
|
+
};
|
|
99
|
+
return JSON.stringify(output, null, 2);
|
|
100
|
+
}
|
|
@@ -12,6 +12,13 @@ export interface TechStack {
|
|
|
12
12
|
linters: string[];
|
|
13
13
|
formatters: string[];
|
|
14
14
|
description: string;
|
|
15
|
+
android?: {
|
|
16
|
+
minSdk?: string;
|
|
17
|
+
targetSdk?: string;
|
|
18
|
+
compileSdk?: string;
|
|
19
|
+
gradleVersion?: string;
|
|
20
|
+
kotlinVersion?: string;
|
|
21
|
+
};
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
/**
|
|
@@ -34,6 +41,8 @@ export function detectTechStack(files: FileInfo[], rootDir: string): TechStack {
|
|
|
34
41
|
languages, frameworks, libraries, tools, packageManagers, testing, linters, formatters
|
|
35
42
|
);
|
|
36
43
|
|
|
44
|
+
const android = detectAndroidSDK(files, rootDir);
|
|
45
|
+
|
|
37
46
|
return {
|
|
38
47
|
languages,
|
|
39
48
|
frameworks,
|
|
@@ -44,6 +53,7 @@ export function detectTechStack(files: FileInfo[], rootDir: string): TechStack {
|
|
|
44
53
|
linters,
|
|
45
54
|
formatters,
|
|
46
55
|
description,
|
|
56
|
+
android,
|
|
47
57
|
};
|
|
48
58
|
}
|
|
49
59
|
|
|
@@ -127,6 +137,10 @@ function detectFrameworks(files: FileInfo[], fileNames: Set<string>, rootDir: st
|
|
|
127
137
|
"postcss.config": "PostCSS",
|
|
128
138
|
"babel.config": "Babel",
|
|
129
139
|
"tsconfig": "TypeScript",
|
|
140
|
+
"build.gradle": "Android",
|
|
141
|
+
"build.gradle.kts": "Android",
|
|
142
|
+
"settings.gradle": "Android",
|
|
143
|
+
"AndroidManifest.xml": "Android",
|
|
130
144
|
};
|
|
131
145
|
|
|
132
146
|
for (const [indicator, framework] of Object.entries(frameworkIndicators)) {
|
|
@@ -303,6 +317,48 @@ function detectFormatters(files: FileInfo[], fileNames: Set<string>): string[] {
|
|
|
303
317
|
return formatters;
|
|
304
318
|
}
|
|
305
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Detect Android SDK versions from build.gradle files
|
|
322
|
+
*/
|
|
323
|
+
function detectAndroidSDK(files: FileInfo[], rootDir: string): TechStack["android"] {
|
|
324
|
+
const fileNames = files.map(f => f.name);
|
|
325
|
+
const hasAndroid = fileNames.some(n =>
|
|
326
|
+
n === "build.gradle" || n === "build.gradle.kts" ||
|
|
327
|
+
n === "app/build.gradle" || n === "app/build.gradle.kts"
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
if (!hasAndroid) return undefined;
|
|
331
|
+
|
|
332
|
+
const android: TechStack["android"] = {};
|
|
333
|
+
|
|
334
|
+
const gradleFiles = files.filter(f =>
|
|
335
|
+
f.name === "build.gradle" || f.name === "build.gradle.kts" ||
|
|
336
|
+
f.relativePath.includes("app/build.gradle")
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
for (const gf of gradleFiles) {
|
|
340
|
+
try {
|
|
341
|
+
const content = readFile(path.join(rootDir, gf.relativePath));
|
|
342
|
+
const minSdkMatch = content.match(/minSdk(?:Version)?\s*[=:]\s*(\d+)/);
|
|
343
|
+
const targetSdkMatch = content.match(/targetSdk(?:Version)?\s*[=:]\s*(\d+)/);
|
|
344
|
+
const compileSdkMatch = content.match(/compileSdk(?:Version)?\s*[=:]\s*(\d+)/);
|
|
345
|
+
|
|
346
|
+
if (minSdkMatch && !android.minSdk) android.minSdk = minSdkMatch[1];
|
|
347
|
+
if (targetSdkMatch && !android.targetSdk) android.targetSdk = targetSdkMatch[1];
|
|
348
|
+
if (compileSdkMatch && !android.compileSdk) android.compileSdk = compileSdkMatch[1];
|
|
349
|
+
} catch {}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const propsPath = path.join(rootDir, "gradle/wrapper/gradle-wrapper.properties");
|
|
354
|
+
const props = readFile(propsPath);
|
|
355
|
+
const v = props.match(/gradle-(\d+\.\d+)/);
|
|
356
|
+
if (v) android.gradleVersion = v[1];
|
|
357
|
+
} catch {}
|
|
358
|
+
|
|
359
|
+
return (android.minSdk || android.targetSdk || android.compileSdk || android.gradleVersion) ? android : undefined;
|
|
360
|
+
}
|
|
361
|
+
|
|
306
362
|
/**
|
|
307
363
|
* Generate tech stack description
|
|
308
364
|
*/
|