Skip to content

feat(#11): Auto-detect project type#47

Open
ModerRAS wants to merge 1 commit intomasterfrom
feature/issue-11-auto-detect-project-type
Open

feat(#11): Auto-detect project type#47
ModerRAS wants to merge 1 commit intomasterfrom
feature/issue-11-auto-detect-project-type

Conversation

@ModerRAS
Copy link
Copy Markdown
Contributor

Summary

  • Create IProjectTypeDetector interface
  • Implement ProjectTypeDetector with BUTR template detection
  • Support detection of SubModule.xml, .csproj files, and BUTR markers
  • Add comprehensive unit tests
  • Register service in DI container

Changes

  • BannerlordModEditor.Common/Services/ProjectTypeDetector.cs - Implementation
  • BannerlordModEditor.Common.Tests/Services/ProjectTypeDetectorTests.cs - Unit tests
  • BannerlordModEditor.UI/App.axaml.cs - DI registration

Closes #11

- Create IProjectTypeDetector interface
- Implement ProjectTypeDetector with BUTR template detection
- Support detection of SubModule.xml, .csproj files, and BUTR markers
- Add comprehensive unit tests
- Register service in DI container

Closes #11
Copilot AI review requested due to automatic review settings March 20, 2026 01:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an auto-detection service for identifying Bannerlord mod project types (including BUTR template markers) and wires it into the app’s DI container, with unit tests validating common detection scenarios.

Changes:

  • Introduce IProjectTypeDetector and ProjectTypeDetector with heuristics for SubModule.xml, .csproj, and BUTR markers
  • Add unit tests covering the main detection paths
  • Register the detector (and scanner) in the UI DI container

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
BannerlordModEditor.UI/App.axaml.cs Registers the new detector (and scanner) in DI
BannerlordModEditor.Common/Services/ProjectTypeDetector.cs Implements project-type detection and marker scanning
BannerlordModEditor.Common.Tests/Services/ProjectTypeDetectorTests.cs Adds unit tests for detection behavior

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +111 to +135
private bool CheckForButrMarkers(string projectPath)
{
var allFiles = GetAllFiles(projectPath);

foreach (var file in allFiles)
{
var fileName = Path.GetFileName(file);
if (ButrMarkers.Any(marker => fileName.Contains(marker, StringComparison.OrdinalIgnoreCase)))
return true;

var content = SafeReadFile(file);
if (content != null && content.Contains("BUTR", StringComparison.OrdinalIgnoreCase))
return true;
}

var directories = Directory.GetDirectories(projectPath, "*", SearchOption.AllDirectories);
foreach (var dir in directories)
{
var dirName = new DirectoryInfo(dir).Name;
if (ButrDirectories.Any(marker => dirName.Contains(marker, StringComparison.OrdinalIgnoreCase)))
return true;
}

return false;
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckForButrMarkers traverses the directory tree twice (once via GetAllFiles, then again via Directory.GetDirectories(..., AllDirectories)) and attempts to read the full contents of every file. This can become very slow for large repos and can cause significant IO/memory pressure. Consider a single-pass traversal (e.g., using Directory.EnumerateFileSystemEntries/EnumerateFiles + EnumerateDirectories with early-exit) that checks directory names as they’re discovered, and only reads a small, filtered subset of files (by extension and/or max size) when content scanning is needed.

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +156
catch
{
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty catch { } in GetAllFiles will silently hide IO/permission/path errors and may lead to non-obvious mis-detections (e.g., returning Unknown/missing markers without any diagnosability). Prefer catching specific exceptions (e.g., UnauthorizedAccessException, IOException, PathTooLongException) and either (a) surface a meaningful result (like “inconclusive”) or (b) log at debug/trace level so failures can be investigated without crashing.

Suggested change
catch
{
}
catch (UnauthorizedAccessException ex)
{
System.Diagnostics.Debug.WriteLine($"[ProjectTypeDetector] Access denied while enumerating directory '{directory}': {ex}");
}
catch (IOException ex)
{
System.Diagnostics.Debug.WriteLine($"[ProjectTypeDetector] I/O error while enumerating directory '{directory}': {ex}");
}
catch (PathTooLongException ex)
{
System.Diagnostics.Debug.WriteLine($"[ProjectTypeDetector] Path too long while enumerating directory '{directory}': {ex}");
}

Copilot uses AI. Check for mistakes.
var csprojFiles = Directory.GetFiles(projectPath, "*.csproj");
if (csprojFiles.Any())
{
var csprojPath = csprojFiles.First();
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directory.GetFiles(...).First() is not deterministic when multiple .csproj files exist, so GetModName can return different results across machines/filesystems or as files are added. To make this stable and predictable, select deterministically (e.g., sort by name/path), and/or prefer a .csproj matching the directory name, or extract the module name from SubModule.xml when present.

Suggested change
var csprojPath = csprojFiles.First();
var directoryName = new DirectoryInfo(projectPath).Name;
// Prefer a .csproj whose name matches the directory name, otherwise choose deterministically.
var matchingCsproj = csprojFiles.FirstOrDefault(f =>
string.Equals(
Path.GetFileNameWithoutExtension(f),
directoryName,
StringComparison.OrdinalIgnoreCase));
var csprojPath = matchingCsproj ??
csprojFiles
.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)
.First();

Copilot uses AI. Check for mistakes.
[Fact]
public void DetectProjectType_ReturnsUnknownForNonExistentPath()
{
var result = _detector.DetectProjectType("/nonexistent/path");
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hard-coded "/nonexistent/path" is OS/path-format dependent and can make intent less clear on Windows. Prefer generating a guaranteed-nonexistent path under Path.GetTempPath() (e.g., combining a GUID) to keep the test portable and explicit.

Suggested change
var result = _detector.DetectProjectType("/nonexistent/path");
var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
var result = _detector.DetectProjectType(nonExistentPath);

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +138
var tempDir = Path.Combine(Path.GetTempPath(), "TestModProject");
Directory.CreateDirectory(tempDir);

try
{
var result = _detector.GetModName(tempDir);
Assert.Equal("TestModProject", result);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a fixed temp directory name (TestModProject) risks cross-test/process collisions and can make the test flaky if the directory already exists (or contains leftover files like a .csproj). Use a unique directory name (GUID) like the other tests to ensure isolation.

Suggested change
var tempDir = Path.Combine(Path.GetTempPath(), "TestModProject");
Directory.CreateDirectory(tempDir);
try
{
var result = _detector.GetModName(tempDir);
Assert.Equal("TestModProject", result);
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDir);
try
{
var result = _detector.GetModName(tempDir);
Assert.Equal(Path.GetFileName(tempDir), result);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 自动识别项目类型

2 participants