@codexa/cli 8.6.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/commands/architect.ts +760 -760
- package/commands/check.ts +131 -131
- package/commands/clear.ts +170 -170
- package/commands/decide.ts +249 -249
- package/commands/discover.ts +1071 -1071
- package/commands/knowledge.ts +361 -361
- package/commands/patterns.ts +621 -621
- package/commands/plan.ts +376 -376
- package/commands/product.ts +626 -626
- package/commands/research.ts +754 -754
- package/commands/review.ts +463 -463
- package/commands/standards.ts +200 -200
- package/commands/task.ts +623 -623
- package/commands/utils.ts +1021 -1021
- package/db/connection.ts +32 -32
- package/db/schema.ts +719 -719
- package/detectors/README.md +109 -109
- package/detectors/dotnet.ts +357 -357
- package/detectors/flutter.ts +350 -350
- package/detectors/go.ts +324 -324
- package/detectors/index.ts +387 -387
- package/detectors/jvm.ts +433 -433
- package/detectors/loader.ts +128 -128
- package/detectors/node.ts +493 -493
- package/detectors/python.ts +423 -423
- package/detectors/rust.ts +348 -348
- package/gates/standards-validator.ts +204 -204
- package/gates/validator.ts +441 -441
- package/package.json +44 -43
- package/protocol/process-return.ts +450 -450
- package/protocol/subagent-protocol.ts +401 -401
- package/workflow.ts +783 -782
package/detectors/dotnet.ts
CHANGED
|
@@ -1,358 +1,358 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* .NET Ecosystem Detector
|
|
3
|
-
*
|
|
4
|
-
* Detects C#, F#, VB.NET projects including:
|
|
5
|
-
* - ASP.NET Core (MVC, Web API, Razor, Blazor)
|
|
6
|
-
* - .NET MAUI
|
|
7
|
-
* - Entity Framework
|
|
8
|
-
* - Dapper
|
|
9
|
-
* - xUnit, NUnit, MSTest
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { join } from "path";
|
|
13
|
-
import {
|
|
14
|
-
registerDetector,
|
|
15
|
-
Detector,
|
|
16
|
-
DetectorResult,
|
|
17
|
-
DetectedTechnology,
|
|
18
|
-
fileExists,
|
|
19
|
-
dirExists,
|
|
20
|
-
findFiles,
|
|
21
|
-
readText,
|
|
22
|
-
} from "./index";
|
|
23
|
-
|
|
24
|
-
interface CsprojInfo {
|
|
25
|
-
path: string;
|
|
26
|
-
sdk?: string;
|
|
27
|
-
targetFramework?: string;
|
|
28
|
-
packages: { name: string; version: string }[];
|
|
29
|
-
projectReferences: string[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function parseCsproj(content: string): Partial<CsprojInfo> {
|
|
33
|
-
const result: Partial<CsprojInfo> = { packages: [], projectReferences: [] };
|
|
34
|
-
|
|
35
|
-
// Extract SDK
|
|
36
|
-
const sdkMatch = content.match(/<Project\s+Sdk="([^"]+)"/i);
|
|
37
|
-
if (sdkMatch) result.sdk = sdkMatch[1];
|
|
38
|
-
|
|
39
|
-
// Extract TargetFramework(s)
|
|
40
|
-
const tfMatch = content.match(/<TargetFramework>([^<]+)<\/TargetFramework>/i);
|
|
41
|
-
if (tfMatch) result.targetFramework = tfMatch[1];
|
|
42
|
-
|
|
43
|
-
const tfsMatch = content.match(/<TargetFrameworks>([^<]+)<\/TargetFrameworks>/i);
|
|
44
|
-
if (tfsMatch) result.targetFramework = tfsMatch[1].split(";")[0];
|
|
45
|
-
|
|
46
|
-
// Extract PackageReferences
|
|
47
|
-
const packageRegex = /<PackageReference\s+Include="([^"]+)"(?:\s+Version="([^"]+)")?/gi;
|
|
48
|
-
let match;
|
|
49
|
-
while ((match = packageRegex.exec(content)) !== null) {
|
|
50
|
-
result.packages!.push({ name: match[1], version: match[2] || "unknown" });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Extract ProjectReferences
|
|
54
|
-
const projRefRegex = /<ProjectReference\s+Include="([^"]+)"/gi;
|
|
55
|
-
while ((match = projRefRegex.exec(content)) !== null) {
|
|
56
|
-
result.projectReferences!.push(match[1]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function parseSln(content: string): string[] {
|
|
63
|
-
const projects: string[] = [];
|
|
64
|
-
const projectRegex = /Project\([^)]+\)\s*=\s*"[^"]+",\s*"([^"]+\.csproj)"/gi;
|
|
65
|
-
let match;
|
|
66
|
-
while ((match = projectRegex.exec(content)) !== null) {
|
|
67
|
-
projects.push(match[1].replace(/\\/g, "/"));
|
|
68
|
-
}
|
|
69
|
-
return projects;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const dotnetDetector: Detector = {
|
|
73
|
-
name: "dotnet",
|
|
74
|
-
ecosystem: "dotnet",
|
|
75
|
-
priority: 100,
|
|
76
|
-
markers: [
|
|
77
|
-
{ type: "glob", pattern: "*.sln", weight: 1.0 },
|
|
78
|
-
{ type: "glob", pattern: "*.csproj", weight: 1.0 },
|
|
79
|
-
{ type: "glob", pattern: "**/*.csproj", weight: 0.9 },
|
|
80
|
-
{ type: "glob", pattern: "*.fsproj", weight: 1.0 },
|
|
81
|
-
{ type: "glob", pattern: "*.vbproj", weight: 1.0 },
|
|
82
|
-
{ type: "file", pattern: "global.json", weight: 0.8 },
|
|
83
|
-
{ type: "file", pattern: "nuget.config", weight: 0.6 },
|
|
84
|
-
{ type: "file", pattern: "Directory.Build.props", weight: 0.7 },
|
|
85
|
-
{ type: "directory", pattern: "obj", weight: 0.3 },
|
|
86
|
-
{ type: "directory", pattern: "bin", weight: 0.3 },
|
|
87
|
-
],
|
|
88
|
-
|
|
89
|
-
async detect(cwd: string): Promise<DetectorResult | null> {
|
|
90
|
-
const technologies: DetectedTechnology[] = [];
|
|
91
|
-
const structure: Record<string, string> = {};
|
|
92
|
-
const configFiles: string[] = [];
|
|
93
|
-
|
|
94
|
-
// Find all project files
|
|
95
|
-
const slnFiles = findFiles(cwd, "*.sln");
|
|
96
|
-
const csprojFiles = findFiles(cwd, "**/*.csproj");
|
|
97
|
-
const fsprojFiles = findFiles(cwd, "**/*.fsproj");
|
|
98
|
-
|
|
99
|
-
if (csprojFiles.length === 0 && fsprojFiles.length === 0 && slnFiles.length === 0) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Parse solution file if exists
|
|
104
|
-
if (slnFiles.length > 0) {
|
|
105
|
-
configFiles.push(slnFiles[0]);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Track detected packages across all projects
|
|
109
|
-
const allPackages = new Map<string, string>();
|
|
110
|
-
const projectInfos: CsprojInfo[] = [];
|
|
111
|
-
|
|
112
|
-
// Parse each csproj
|
|
113
|
-
for (const csproj of csprojFiles) {
|
|
114
|
-
const content = readText(join(cwd, csproj));
|
|
115
|
-
if (!content) continue;
|
|
116
|
-
|
|
117
|
-
configFiles.push(csproj);
|
|
118
|
-
const info = parseCsproj(content);
|
|
119
|
-
info.path = csproj;
|
|
120
|
-
projectInfos.push(info as CsprojInfo);
|
|
121
|
-
|
|
122
|
-
for (const pkg of info.packages || []) {
|
|
123
|
-
allPackages.set(pkg.name.toLowerCase(), pkg.version);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Determine primary SDK/framework type
|
|
128
|
-
const sdks = projectInfos.map(p => p.sdk).filter(Boolean);
|
|
129
|
-
const frameworks = projectInfos.map(p => p.targetFramework).filter(Boolean);
|
|
130
|
-
|
|
131
|
-
// Runtime detection
|
|
132
|
-
if (frameworks.length > 0) {
|
|
133
|
-
const framework = frameworks[0]!;
|
|
134
|
-
let runtimeName = ".NET";
|
|
135
|
-
let version = framework;
|
|
136
|
-
|
|
137
|
-
if (framework.startsWith("net8")) {
|
|
138
|
-
runtimeName = ".NET 8";
|
|
139
|
-
version = "8.0";
|
|
140
|
-
} else if (framework.startsWith("net7")) {
|
|
141
|
-
runtimeName = ".NET 7";
|
|
142
|
-
version = "7.0";
|
|
143
|
-
} else if (framework.startsWith("net6")) {
|
|
144
|
-
runtimeName = ".NET 6";
|
|
145
|
-
version = "6.0";
|
|
146
|
-
} else if (framework.startsWith("net5")) {
|
|
147
|
-
runtimeName = ".NET 5";
|
|
148
|
-
version = "5.0";
|
|
149
|
-
} else if (framework.startsWith("netcoreapp")) {
|
|
150
|
-
runtimeName = ".NET Core";
|
|
151
|
-
version = framework.replace("netcoreapp", "");
|
|
152
|
-
} else if (framework.startsWith("netstandard")) {
|
|
153
|
-
runtimeName = ".NET Standard";
|
|
154
|
-
version = framework.replace("netstandard", "");
|
|
155
|
-
} else if (framework.startsWith("net4")) {
|
|
156
|
-
runtimeName = ".NET Framework";
|
|
157
|
-
version = framework.replace("net", "");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
technologies.push({
|
|
161
|
-
name: runtimeName,
|
|
162
|
-
version,
|
|
163
|
-
confidence: 1.0,
|
|
164
|
-
source: projectInfos[0]?.path || "csproj",
|
|
165
|
-
category: "runtime",
|
|
166
|
-
metadata: { targetFramework: framework },
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Backend framework detection
|
|
171
|
-
const backendFrameworks = [
|
|
172
|
-
{ packages: ["microsoft.aspnetcore.app", "microsoft.aspnetcore"], name: "ASP.NET Core", category: "backend" as const },
|
|
173
|
-
{ packages: ["microsoft.aspnetcore.mvc"], name: "ASP.NET Core MVC", category: "backend" as const },
|
|
174
|
-
{ packages: ["microsoft.aspnetcore.components"], name: "Blazor", category: "frontend" as const },
|
|
175
|
-
{ packages: ["microsoft.maui"], name: ".NET MAUI", category: "frontend" as const },
|
|
176
|
-
{ packages: ["avalonia", "avalonia.desktop"], name: "Avalonia", category: "frontend" as const },
|
|
177
|
-
{ packages: ["uno.ui"], name: "Uno Platform", category: "frontend" as const },
|
|
178
|
-
];
|
|
179
|
-
|
|
180
|
-
// Check SDK for implicit framework
|
|
181
|
-
for (const sdk of sdks) {
|
|
182
|
-
if (sdk?.includes("Microsoft.NET.Sdk.Web")) {
|
|
183
|
-
technologies.push({
|
|
184
|
-
name: "ASP.NET Core",
|
|
185
|
-
confidence: 1.0,
|
|
186
|
-
source: "SDK",
|
|
187
|
-
category: "backend",
|
|
188
|
-
});
|
|
189
|
-
} else if (sdk?.includes("Microsoft.NET.Sdk.BlazorWebAssembly")) {
|
|
190
|
-
technologies.push({
|
|
191
|
-
name: "Blazor WebAssembly",
|
|
192
|
-
confidence: 1.0,
|
|
193
|
-
source: "SDK",
|
|
194
|
-
category: "frontend",
|
|
195
|
-
});
|
|
196
|
-
} else if (sdk?.includes("Microsoft.NET.Sdk.Razor")) {
|
|
197
|
-
technologies.push({
|
|
198
|
-
name: "Razor",
|
|
199
|
-
confidence: 0.9,
|
|
200
|
-
source: "SDK",
|
|
201
|
-
category: "frontend",
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
for (const fw of backendFrameworks) {
|
|
207
|
-
const found = fw.packages.find(p => allPackages.has(p));
|
|
208
|
-
if (found) {
|
|
209
|
-
technologies.push({
|
|
210
|
-
name: fw.name,
|
|
211
|
-
version: allPackages.get(found),
|
|
212
|
-
confidence: 0.95,
|
|
213
|
-
source: `Package: ${found}`,
|
|
214
|
-
category: fw.category,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ORM detection
|
|
220
|
-
const ormMap = [
|
|
221
|
-
{ packages: ["microsoft.entityframeworkcore", "entityframework"], name: "Entity Framework", category: "orm" as const },
|
|
222
|
-
{ packages: ["dapper"], name: "Dapper", category: "orm" as const },
|
|
223
|
-
{ packages: ["npgsql.entityframeworkcore.postgresql"], name: "EF Core + PostgreSQL", category: "orm" as const },
|
|
224
|
-
{ packages: ["pomelo.entityframeworkcore.mysql"], name: "EF Core + MySQL", category: "orm" as const },
|
|
225
|
-
{ packages: ["microsoft.entityframeworkcore.sqlserver"], name: "EF Core + SQL Server", category: "orm" as const },
|
|
226
|
-
{ packages: ["microsoft.entityframeworkcore.sqlite"], name: "EF Core + SQLite", category: "orm" as const },
|
|
227
|
-
{ packages: ["nhibernate"], name: "NHibernate", category: "orm" as const },
|
|
228
|
-
];
|
|
229
|
-
|
|
230
|
-
for (const orm of ormMap) {
|
|
231
|
-
const found = orm.packages.find(p => allPackages.has(p));
|
|
232
|
-
if (found) {
|
|
233
|
-
technologies.push({
|
|
234
|
-
name: orm.name,
|
|
235
|
-
version: allPackages.get(found),
|
|
236
|
-
confidence: 0.95,
|
|
237
|
-
source: `Package: ${found}`,
|
|
238
|
-
category: orm.category,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Database detection
|
|
244
|
-
const dbMap = [
|
|
245
|
-
{ packages: ["npgsql"], name: "PostgreSQL", category: "database" as const },
|
|
246
|
-
{ packages: ["mysql.data", "mysqlconnector"], name: "MySQL", category: "database" as const },
|
|
247
|
-
{ packages: ["microsoft.data.sqlclient", "system.data.sqlclient"], name: "SQL Server", category: "database" as const },
|
|
248
|
-
{ packages: ["microsoft.data.sqlite"], name: "SQLite", category: "database" as const },
|
|
249
|
-
{ packages: ["mongodb.driver"], name: "MongoDB", category: "database" as const },
|
|
250
|
-
{ packages: ["stackexchange.redis"], name: "Redis", category: "database" as const },
|
|
251
|
-
];
|
|
252
|
-
|
|
253
|
-
for (const db of dbMap) {
|
|
254
|
-
const found = db.packages.find(p => allPackages.has(p));
|
|
255
|
-
if (found) {
|
|
256
|
-
technologies.push({
|
|
257
|
-
name: db.name,
|
|
258
|
-
version: allPackages.get(found),
|
|
259
|
-
confidence: 0.9,
|
|
260
|
-
source: `Package: ${found}`,
|
|
261
|
-
category: db.category,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Testing framework detection
|
|
267
|
-
const testMap = [
|
|
268
|
-
{ packages: ["xunit", "xunit.runner.visualstudio"], name: "xUnit", category: "testing" as const },
|
|
269
|
-
{ packages: ["nunit", "nunit3testadapter"], name: "NUnit", category: "testing" as const },
|
|
270
|
-
{ packages: ["mstest.testframework", "microsoft.net.test.sdk"], name: "MSTest", category: "testing" as const },
|
|
271
|
-
{ packages: ["moq"], name: "Moq", category: "testing" as const },
|
|
272
|
-
{ packages: ["nsubstitute"], name: "NSubstitute", category: "testing" as const },
|
|
273
|
-
{ packages: ["fluentassertions"], name: "FluentAssertions", category: "testing" as const },
|
|
274
|
-
{ packages: ["specflow"], name: "SpecFlow", category: "testing" as const },
|
|
275
|
-
];
|
|
276
|
-
|
|
277
|
-
for (const test of testMap) {
|
|
278
|
-
const found = test.packages.find(p => allPackages.has(p));
|
|
279
|
-
if (found) {
|
|
280
|
-
technologies.push({
|
|
281
|
-
name: test.name,
|
|
282
|
-
version: allPackages.get(found),
|
|
283
|
-
confidence: 0.9,
|
|
284
|
-
source: `Package: ${found}`,
|
|
285
|
-
category: test.category,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Auth detection
|
|
291
|
-
const authMap = [
|
|
292
|
-
{ packages: ["microsoft.aspnetcore.authentication.jwtbearer"], name: "JWT Bearer", category: "auth" as const },
|
|
293
|
-
{ packages: ["microsoft.aspnetcore.identity"], name: "ASP.NET Identity", category: "auth" as const },
|
|
294
|
-
{ packages: ["identityserver4"], name: "IdentityServer4", category: "auth" as const },
|
|
295
|
-
{ packages: ["duende.identityserver"], name: "Duende IdentityServer", category: "auth" as const },
|
|
296
|
-
{ packages: ["microsoft.identity.web"], name: "Microsoft Identity Web", category: "auth" as const },
|
|
297
|
-
];
|
|
298
|
-
|
|
299
|
-
for (const auth of authMap) {
|
|
300
|
-
const found = auth.packages.find(p => allPackages.has(p));
|
|
301
|
-
if (found) {
|
|
302
|
-
technologies.push({
|
|
303
|
-
name: auth.name,
|
|
304
|
-
version: allPackages.get(found),
|
|
305
|
-
confidence: 0.9,
|
|
306
|
-
source: `Package: ${found}`,
|
|
307
|
-
category: auth.category,
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Structure detection
|
|
313
|
-
const structurePaths = [
|
|
314
|
-
{ key: "controllers", paths: ["Controllers", "Api/Controllers", "src/Controllers"] },
|
|
315
|
-
{ key: "services", paths: ["Services", "src/Services", "Application/Services"] },
|
|
316
|
-
{ key: "models", paths: ["Models", "Domain/Models", "Entities", "Domain/Entities"] },
|
|
317
|
-
{ key: "data", paths: ["Data", "Infrastructure/Data", "Persistence"] },
|
|
318
|
-
{ key: "api", paths: ["Api", "WebApi", "src/Api"] },
|
|
319
|
-
{ key: "tests", paths: ["Tests", "test", "tests", "*.Tests"] },
|
|
320
|
-
{ key: "migrations", paths: ["Migrations", "Data/Migrations"] },
|
|
321
|
-
];
|
|
322
|
-
|
|
323
|
-
for (const { key, paths } of structurePaths) {
|
|
324
|
-
for (const p of paths) {
|
|
325
|
-
if (p.includes("*")) {
|
|
326
|
-
const matches = findFiles(cwd, p);
|
|
327
|
-
if (matches.length > 0) {
|
|
328
|
-
structure[key] = matches[0];
|
|
329
|
-
break;
|
|
330
|
-
}
|
|
331
|
-
} else if (dirExists(join(cwd, p))) {
|
|
332
|
-
structure[key] = p;
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Check for config files
|
|
339
|
-
const dotnetConfigs = ["appsettings.json", "appsettings.Development.json", "launchSettings.json", "web.config"];
|
|
340
|
-
for (const cfg of dotnetConfigs) {
|
|
341
|
-
if (fileExists(join(cwd, cfg)) || findFiles(cwd, `**/${cfg}`).length > 0) {
|
|
342
|
-
configFiles.push(cfg);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
ecosystem: "dotnet",
|
|
348
|
-
technologies,
|
|
349
|
-
structure,
|
|
350
|
-
configFiles: [...new Set(configFiles)],
|
|
351
|
-
};
|
|
352
|
-
},
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
// Register the detector
|
|
356
|
-
registerDetector(dotnetDetector);
|
|
357
|
-
|
|
1
|
+
/**
|
|
2
|
+
* .NET Ecosystem Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects C#, F#, VB.NET projects including:
|
|
5
|
+
* - ASP.NET Core (MVC, Web API, Razor, Blazor)
|
|
6
|
+
* - .NET MAUI
|
|
7
|
+
* - Entity Framework
|
|
8
|
+
* - Dapper
|
|
9
|
+
* - xUnit, NUnit, MSTest
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import {
|
|
14
|
+
registerDetector,
|
|
15
|
+
Detector,
|
|
16
|
+
DetectorResult,
|
|
17
|
+
DetectedTechnology,
|
|
18
|
+
fileExists,
|
|
19
|
+
dirExists,
|
|
20
|
+
findFiles,
|
|
21
|
+
readText,
|
|
22
|
+
} from "./index";
|
|
23
|
+
|
|
24
|
+
interface CsprojInfo {
|
|
25
|
+
path: string;
|
|
26
|
+
sdk?: string;
|
|
27
|
+
targetFramework?: string;
|
|
28
|
+
packages: { name: string; version: string }[];
|
|
29
|
+
projectReferences: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseCsproj(content: string): Partial<CsprojInfo> {
|
|
33
|
+
const result: Partial<CsprojInfo> = { packages: [], projectReferences: [] };
|
|
34
|
+
|
|
35
|
+
// Extract SDK
|
|
36
|
+
const sdkMatch = content.match(/<Project\s+Sdk="([^"]+)"/i);
|
|
37
|
+
if (sdkMatch) result.sdk = sdkMatch[1];
|
|
38
|
+
|
|
39
|
+
// Extract TargetFramework(s)
|
|
40
|
+
const tfMatch = content.match(/<TargetFramework>([^<]+)<\/TargetFramework>/i);
|
|
41
|
+
if (tfMatch) result.targetFramework = tfMatch[1];
|
|
42
|
+
|
|
43
|
+
const tfsMatch = content.match(/<TargetFrameworks>([^<]+)<\/TargetFrameworks>/i);
|
|
44
|
+
if (tfsMatch) result.targetFramework = tfsMatch[1].split(";")[0];
|
|
45
|
+
|
|
46
|
+
// Extract PackageReferences
|
|
47
|
+
const packageRegex = /<PackageReference\s+Include="([^"]+)"(?:\s+Version="([^"]+)")?/gi;
|
|
48
|
+
let match;
|
|
49
|
+
while ((match = packageRegex.exec(content)) !== null) {
|
|
50
|
+
result.packages!.push({ name: match[1], version: match[2] || "unknown" });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Extract ProjectReferences
|
|
54
|
+
const projRefRegex = /<ProjectReference\s+Include="([^"]+)"/gi;
|
|
55
|
+
while ((match = projRefRegex.exec(content)) !== null) {
|
|
56
|
+
result.projectReferences!.push(match[1]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseSln(content: string): string[] {
|
|
63
|
+
const projects: string[] = [];
|
|
64
|
+
const projectRegex = /Project\([^)]+\)\s*=\s*"[^"]+",\s*"([^"]+\.csproj)"/gi;
|
|
65
|
+
let match;
|
|
66
|
+
while ((match = projectRegex.exec(content)) !== null) {
|
|
67
|
+
projects.push(match[1].replace(/\\/g, "/"));
|
|
68
|
+
}
|
|
69
|
+
return projects;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const dotnetDetector: Detector = {
|
|
73
|
+
name: "dotnet",
|
|
74
|
+
ecosystem: "dotnet",
|
|
75
|
+
priority: 100,
|
|
76
|
+
markers: [
|
|
77
|
+
{ type: "glob", pattern: "*.sln", weight: 1.0 },
|
|
78
|
+
{ type: "glob", pattern: "*.csproj", weight: 1.0 },
|
|
79
|
+
{ type: "glob", pattern: "**/*.csproj", weight: 0.9 },
|
|
80
|
+
{ type: "glob", pattern: "*.fsproj", weight: 1.0 },
|
|
81
|
+
{ type: "glob", pattern: "*.vbproj", weight: 1.0 },
|
|
82
|
+
{ type: "file", pattern: "global.json", weight: 0.8 },
|
|
83
|
+
{ type: "file", pattern: "nuget.config", weight: 0.6 },
|
|
84
|
+
{ type: "file", pattern: "Directory.Build.props", weight: 0.7 },
|
|
85
|
+
{ type: "directory", pattern: "obj", weight: 0.3 },
|
|
86
|
+
{ type: "directory", pattern: "bin", weight: 0.3 },
|
|
87
|
+
],
|
|
88
|
+
|
|
89
|
+
async detect(cwd: string): Promise<DetectorResult | null> {
|
|
90
|
+
const technologies: DetectedTechnology[] = [];
|
|
91
|
+
const structure: Record<string, string> = {};
|
|
92
|
+
const configFiles: string[] = [];
|
|
93
|
+
|
|
94
|
+
// Find all project files
|
|
95
|
+
const slnFiles = findFiles(cwd, "*.sln");
|
|
96
|
+
const csprojFiles = findFiles(cwd, "**/*.csproj");
|
|
97
|
+
const fsprojFiles = findFiles(cwd, "**/*.fsproj");
|
|
98
|
+
|
|
99
|
+
if (csprojFiles.length === 0 && fsprojFiles.length === 0 && slnFiles.length === 0) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Parse solution file if exists
|
|
104
|
+
if (slnFiles.length > 0) {
|
|
105
|
+
configFiles.push(slnFiles[0]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Track detected packages across all projects
|
|
109
|
+
const allPackages = new Map<string, string>();
|
|
110
|
+
const projectInfos: CsprojInfo[] = [];
|
|
111
|
+
|
|
112
|
+
// Parse each csproj
|
|
113
|
+
for (const csproj of csprojFiles) {
|
|
114
|
+
const content = readText(join(cwd, csproj));
|
|
115
|
+
if (!content) continue;
|
|
116
|
+
|
|
117
|
+
configFiles.push(csproj);
|
|
118
|
+
const info = parseCsproj(content);
|
|
119
|
+
info.path = csproj;
|
|
120
|
+
projectInfos.push(info as CsprojInfo);
|
|
121
|
+
|
|
122
|
+
for (const pkg of info.packages || []) {
|
|
123
|
+
allPackages.set(pkg.name.toLowerCase(), pkg.version);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Determine primary SDK/framework type
|
|
128
|
+
const sdks = projectInfos.map(p => p.sdk).filter(Boolean);
|
|
129
|
+
const frameworks = projectInfos.map(p => p.targetFramework).filter(Boolean);
|
|
130
|
+
|
|
131
|
+
// Runtime detection
|
|
132
|
+
if (frameworks.length > 0) {
|
|
133
|
+
const framework = frameworks[0]!;
|
|
134
|
+
let runtimeName = ".NET";
|
|
135
|
+
let version = framework;
|
|
136
|
+
|
|
137
|
+
if (framework.startsWith("net8")) {
|
|
138
|
+
runtimeName = ".NET 8";
|
|
139
|
+
version = "8.0";
|
|
140
|
+
} else if (framework.startsWith("net7")) {
|
|
141
|
+
runtimeName = ".NET 7";
|
|
142
|
+
version = "7.0";
|
|
143
|
+
} else if (framework.startsWith("net6")) {
|
|
144
|
+
runtimeName = ".NET 6";
|
|
145
|
+
version = "6.0";
|
|
146
|
+
} else if (framework.startsWith("net5")) {
|
|
147
|
+
runtimeName = ".NET 5";
|
|
148
|
+
version = "5.0";
|
|
149
|
+
} else if (framework.startsWith("netcoreapp")) {
|
|
150
|
+
runtimeName = ".NET Core";
|
|
151
|
+
version = framework.replace("netcoreapp", "");
|
|
152
|
+
} else if (framework.startsWith("netstandard")) {
|
|
153
|
+
runtimeName = ".NET Standard";
|
|
154
|
+
version = framework.replace("netstandard", "");
|
|
155
|
+
} else if (framework.startsWith("net4")) {
|
|
156
|
+
runtimeName = ".NET Framework";
|
|
157
|
+
version = framework.replace("net", "");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
technologies.push({
|
|
161
|
+
name: runtimeName,
|
|
162
|
+
version,
|
|
163
|
+
confidence: 1.0,
|
|
164
|
+
source: projectInfos[0]?.path || "csproj",
|
|
165
|
+
category: "runtime",
|
|
166
|
+
metadata: { targetFramework: framework },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Backend framework detection
|
|
171
|
+
const backendFrameworks = [
|
|
172
|
+
{ packages: ["microsoft.aspnetcore.app", "microsoft.aspnetcore"], name: "ASP.NET Core", category: "backend" as const },
|
|
173
|
+
{ packages: ["microsoft.aspnetcore.mvc"], name: "ASP.NET Core MVC", category: "backend" as const },
|
|
174
|
+
{ packages: ["microsoft.aspnetcore.components"], name: "Blazor", category: "frontend" as const },
|
|
175
|
+
{ packages: ["microsoft.maui"], name: ".NET MAUI", category: "frontend" as const },
|
|
176
|
+
{ packages: ["avalonia", "avalonia.desktop"], name: "Avalonia", category: "frontend" as const },
|
|
177
|
+
{ packages: ["uno.ui"], name: "Uno Platform", category: "frontend" as const },
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
// Check SDK for implicit framework
|
|
181
|
+
for (const sdk of sdks) {
|
|
182
|
+
if (sdk?.includes("Microsoft.NET.Sdk.Web")) {
|
|
183
|
+
technologies.push({
|
|
184
|
+
name: "ASP.NET Core",
|
|
185
|
+
confidence: 1.0,
|
|
186
|
+
source: "SDK",
|
|
187
|
+
category: "backend",
|
|
188
|
+
});
|
|
189
|
+
} else if (sdk?.includes("Microsoft.NET.Sdk.BlazorWebAssembly")) {
|
|
190
|
+
technologies.push({
|
|
191
|
+
name: "Blazor WebAssembly",
|
|
192
|
+
confidence: 1.0,
|
|
193
|
+
source: "SDK",
|
|
194
|
+
category: "frontend",
|
|
195
|
+
});
|
|
196
|
+
} else if (sdk?.includes("Microsoft.NET.Sdk.Razor")) {
|
|
197
|
+
technologies.push({
|
|
198
|
+
name: "Razor",
|
|
199
|
+
confidence: 0.9,
|
|
200
|
+
source: "SDK",
|
|
201
|
+
category: "frontend",
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const fw of backendFrameworks) {
|
|
207
|
+
const found = fw.packages.find(p => allPackages.has(p));
|
|
208
|
+
if (found) {
|
|
209
|
+
technologies.push({
|
|
210
|
+
name: fw.name,
|
|
211
|
+
version: allPackages.get(found),
|
|
212
|
+
confidence: 0.95,
|
|
213
|
+
source: `Package: ${found}`,
|
|
214
|
+
category: fw.category,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ORM detection
|
|
220
|
+
const ormMap = [
|
|
221
|
+
{ packages: ["microsoft.entityframeworkcore", "entityframework"], name: "Entity Framework", category: "orm" as const },
|
|
222
|
+
{ packages: ["dapper"], name: "Dapper", category: "orm" as const },
|
|
223
|
+
{ packages: ["npgsql.entityframeworkcore.postgresql"], name: "EF Core + PostgreSQL", category: "orm" as const },
|
|
224
|
+
{ packages: ["pomelo.entityframeworkcore.mysql"], name: "EF Core + MySQL", category: "orm" as const },
|
|
225
|
+
{ packages: ["microsoft.entityframeworkcore.sqlserver"], name: "EF Core + SQL Server", category: "orm" as const },
|
|
226
|
+
{ packages: ["microsoft.entityframeworkcore.sqlite"], name: "EF Core + SQLite", category: "orm" as const },
|
|
227
|
+
{ packages: ["nhibernate"], name: "NHibernate", category: "orm" as const },
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
for (const orm of ormMap) {
|
|
231
|
+
const found = orm.packages.find(p => allPackages.has(p));
|
|
232
|
+
if (found) {
|
|
233
|
+
technologies.push({
|
|
234
|
+
name: orm.name,
|
|
235
|
+
version: allPackages.get(found),
|
|
236
|
+
confidence: 0.95,
|
|
237
|
+
source: `Package: ${found}`,
|
|
238
|
+
category: orm.category,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Database detection
|
|
244
|
+
const dbMap = [
|
|
245
|
+
{ packages: ["npgsql"], name: "PostgreSQL", category: "database" as const },
|
|
246
|
+
{ packages: ["mysql.data", "mysqlconnector"], name: "MySQL", category: "database" as const },
|
|
247
|
+
{ packages: ["microsoft.data.sqlclient", "system.data.sqlclient"], name: "SQL Server", category: "database" as const },
|
|
248
|
+
{ packages: ["microsoft.data.sqlite"], name: "SQLite", category: "database" as const },
|
|
249
|
+
{ packages: ["mongodb.driver"], name: "MongoDB", category: "database" as const },
|
|
250
|
+
{ packages: ["stackexchange.redis"], name: "Redis", category: "database" as const },
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
for (const db of dbMap) {
|
|
254
|
+
const found = db.packages.find(p => allPackages.has(p));
|
|
255
|
+
if (found) {
|
|
256
|
+
technologies.push({
|
|
257
|
+
name: db.name,
|
|
258
|
+
version: allPackages.get(found),
|
|
259
|
+
confidence: 0.9,
|
|
260
|
+
source: `Package: ${found}`,
|
|
261
|
+
category: db.category,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Testing framework detection
|
|
267
|
+
const testMap = [
|
|
268
|
+
{ packages: ["xunit", "xunit.runner.visualstudio"], name: "xUnit", category: "testing" as const },
|
|
269
|
+
{ packages: ["nunit", "nunit3testadapter"], name: "NUnit", category: "testing" as const },
|
|
270
|
+
{ packages: ["mstest.testframework", "microsoft.net.test.sdk"], name: "MSTest", category: "testing" as const },
|
|
271
|
+
{ packages: ["moq"], name: "Moq", category: "testing" as const },
|
|
272
|
+
{ packages: ["nsubstitute"], name: "NSubstitute", category: "testing" as const },
|
|
273
|
+
{ packages: ["fluentassertions"], name: "FluentAssertions", category: "testing" as const },
|
|
274
|
+
{ packages: ["specflow"], name: "SpecFlow", category: "testing" as const },
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
for (const test of testMap) {
|
|
278
|
+
const found = test.packages.find(p => allPackages.has(p));
|
|
279
|
+
if (found) {
|
|
280
|
+
technologies.push({
|
|
281
|
+
name: test.name,
|
|
282
|
+
version: allPackages.get(found),
|
|
283
|
+
confidence: 0.9,
|
|
284
|
+
source: `Package: ${found}`,
|
|
285
|
+
category: test.category,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Auth detection
|
|
291
|
+
const authMap = [
|
|
292
|
+
{ packages: ["microsoft.aspnetcore.authentication.jwtbearer"], name: "JWT Bearer", category: "auth" as const },
|
|
293
|
+
{ packages: ["microsoft.aspnetcore.identity"], name: "ASP.NET Identity", category: "auth" as const },
|
|
294
|
+
{ packages: ["identityserver4"], name: "IdentityServer4", category: "auth" as const },
|
|
295
|
+
{ packages: ["duende.identityserver"], name: "Duende IdentityServer", category: "auth" as const },
|
|
296
|
+
{ packages: ["microsoft.identity.web"], name: "Microsoft Identity Web", category: "auth" as const },
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
for (const auth of authMap) {
|
|
300
|
+
const found = auth.packages.find(p => allPackages.has(p));
|
|
301
|
+
if (found) {
|
|
302
|
+
technologies.push({
|
|
303
|
+
name: auth.name,
|
|
304
|
+
version: allPackages.get(found),
|
|
305
|
+
confidence: 0.9,
|
|
306
|
+
source: `Package: ${found}`,
|
|
307
|
+
category: auth.category,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Structure detection
|
|
313
|
+
const structurePaths = [
|
|
314
|
+
{ key: "controllers", paths: ["Controllers", "Api/Controllers", "src/Controllers"] },
|
|
315
|
+
{ key: "services", paths: ["Services", "src/Services", "Application/Services"] },
|
|
316
|
+
{ key: "models", paths: ["Models", "Domain/Models", "Entities", "Domain/Entities"] },
|
|
317
|
+
{ key: "data", paths: ["Data", "Infrastructure/Data", "Persistence"] },
|
|
318
|
+
{ key: "api", paths: ["Api", "WebApi", "src/Api"] },
|
|
319
|
+
{ key: "tests", paths: ["Tests", "test", "tests", "*.Tests"] },
|
|
320
|
+
{ key: "migrations", paths: ["Migrations", "Data/Migrations"] },
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
for (const { key, paths } of structurePaths) {
|
|
324
|
+
for (const p of paths) {
|
|
325
|
+
if (p.includes("*")) {
|
|
326
|
+
const matches = findFiles(cwd, p);
|
|
327
|
+
if (matches.length > 0) {
|
|
328
|
+
structure[key] = matches[0];
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
} else if (dirExists(join(cwd, p))) {
|
|
332
|
+
structure[key] = p;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Check for config files
|
|
339
|
+
const dotnetConfigs = ["appsettings.json", "appsettings.Development.json", "launchSettings.json", "web.config"];
|
|
340
|
+
for (const cfg of dotnetConfigs) {
|
|
341
|
+
if (fileExists(join(cwd, cfg)) || findFiles(cwd, `**/${cfg}`).length > 0) {
|
|
342
|
+
configFiles.push(cfg);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
ecosystem: "dotnet",
|
|
348
|
+
technologies,
|
|
349
|
+
structure,
|
|
350
|
+
configFiles: [...new Set(configFiles)],
|
|
351
|
+
};
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Register the detector
|
|
356
|
+
registerDetector(dotnetDetector);
|
|
357
|
+
|
|
358
358
|
export default dotnetDetector;
|