Conversation
- Create IModProjectService interface - Implement ModProjectService with BUTR template support - Generate SubModule.xml, .csproj, and ModuleData folder - Validate mod names and generate mod IDs - Add comprehensive unit tests Closes #4
There was a problem hiding this comment.
Pull request overview
Adds a new service to generate an “empty mod project” skeleton (SubModule.xml, .csproj, ModuleData, etc.) with basic validation and corresponding unit tests.
Changes:
- Introduces
IModProjectServiceplusModProjectServiceimplementation to scaffold a mod project on disk. - Adds name validation + mod id generation logic and writes template files/folders (including optional BUTR package references).
- Adds xUnit tests covering validation and key project creation paths.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| BannerlordModEditor.Common/Services/ModProjectService.cs | Implements mod project scaffolding, validation, mod-id generation, and csproj templating. |
| BannerlordModEditor.Common.Tests/Services/ModProjectServiceTests.cs | Adds xUnit coverage for validation, project creation, and SubModule.xml generation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!ValidateModName(options.ModName)) | ||
| { | ||
| result.Success = false; | ||
| result.ErrorMessage = "Invalid mod name. Use only letters, numbers, and underscores."; |
There was a problem hiding this comment.
The validation error message is inconsistent with the actual validation rules: ValidateModName currently allows -, but the message says “underscores” only (and it also doesn’t mention the length constraints). Update the message to reflect the real rules or adjust the validator to match the message.
| result.ErrorMessage = "Invalid mod name. Use only letters, numbers, and underscores."; | |
| result.ErrorMessage = "Invalid mod name. Use only letters, numbers, underscores, and hyphens, and ensure it meets the required length constraints."; |
| if (modName.Length < 2 || modName.Length > 50) | ||
| return false; | ||
|
|
||
| return modName.All(c => char.IsLetterOrDigit(c) || c == '_' || c == '-'); |
There was a problem hiding this comment.
The validation error message is inconsistent with the actual validation rules: ValidateModName currently allows -, but the message says “underscores” only (and it also doesn’t mention the length constraints). Update the message to reflect the real rules or adjust the validator to match the message.
| return modName.All(c => char.IsLetterOrDigit(c) || c == '_' || c == '-'); | |
| return modName.All(c => char.IsLetterOrDigit(c) || c == '_'); |
| { | ||
| public bool Success { get; set; } | ||
| public string ProjectPath { get; set; } = string.Empty; | ||
| public List<string> CreatedFiles { get; set; } = new(); |
There was a problem hiding this comment.
CreatedFiles is currently used to store both file paths and directory paths (e.g., ModuleData). This makes the API ambiguous for callers. Consider either (a) renaming to something like CreatedPaths, or (b) splitting into CreatedFiles and CreatedDirectories (or similar) so consumers can reliably interpret the result.
| public List<string> CreatedFiles { get; set; } = new(); | |
| public List<string> CreatedPaths { get; set; } = new(); | |
| [Obsolete("Use CreatedPaths instead. This property will be removed in a future version.")] | |
| public List<string> CreatedFiles | |
| { | |
| get => CreatedPaths; | |
| set => CreatedPaths = value ?? new(); | |
| } |
| { | ||
| var moduleDataPath = Path.Combine(projectPath, "ModuleData"); | ||
| Directory.CreateDirectory(moduleDataPath); | ||
| result.CreatedFiles.Add(moduleDataPath); |
There was a problem hiding this comment.
CreatedFiles is currently used to store both file paths and directory paths (e.g., ModuleData). This makes the API ambiguous for callers. Consider either (a) renaming to something like CreatedPaths, or (b) splitting into CreatedFiles and CreatedDirectories (or similar) so consumers can reliably interpret the result.
| result.CreatedFiles.Add(moduleDataPath); |
| var binPath = Path.Combine(projectPath, "bin"); | ||
| Directory.CreateDirectory(binPath); | ||
|
|
||
| var objPath = Path.Combine(projectPath, "obj"); | ||
| Directory.CreateDirectory(objPath); | ||
|
|
There was a problem hiding this comment.
Pre-creating bin/ and obj/ in a project template is generally undesirable because these are build output directories and can confuse tooling/users (and may create noise in scaffolding results). Prefer not creating them at scaffold time; keep the .gitignore entries and let the build generate these folders.
| var binPath = Path.Combine(projectPath, "bin"); | |
| Directory.CreateDirectory(binPath); | |
| var objPath = Path.Combine(projectPath, "obj"); | |
| Directory.CreateDirectory(objPath); |
| public async Task<ModProjectResult> CreateModProjectAsync(ModProjectOptions options) | ||
| { | ||
| return await Task.Run(() => CreateModProject(options)); |
There was a problem hiding this comment.
Wrapping the synchronous, IO-heavy scaffolding in Task.Run will consume a thread-pool thread for the entire duration and doesn’t provide true async IO. Prefer either (1) making the method synchronous-only, or (2) implementing a genuinely async version that uses async file APIs (e.g., WriteAllTextAsync / async stream usage) and avoids Task.Run.
| public async Task<ModProjectResult> CreateModProjectAsync(ModProjectOptions options) | |
| { | |
| return await Task.Run(() => CreateModProject(options)); | |
| public Task<ModProjectResult> CreateModProjectAsync(ModProjectOptions options) | |
| { | |
| return Task.FromResult(CreateModProject(options)); |
| var packageReferences = useButrTemplate | ||
| ? @" | ||
| <PackageReference Include=""Bannerlord.ButterLib"" Version=""2.*"" /> | ||
| <PackageReference Include=""Bannerlord.Harmony"" Version=""2.*"" />" | ||
| : ""; |
There was a problem hiding this comment.
There’s a new behavioral branch controlled by UseButrTemplate that changes the generated .csproj content, but the tests don’t currently assert that these conditional package references are present/absent. Add a test that creates a project with UseButrTemplate = false and verifies the generated .csproj does not contain the BUTR references (and optionally one that asserts they are present when true).
| var options = new ModProjectOptions | ||
| { | ||
| ModName = "A", | ||
| OutputDirectory = "/tmp" |
There was a problem hiding this comment.
The test hard-codes "/tmp" as the output directory, which will fail on Windows environments. Use Path.GetTempPath() (optionally combined with a GUID subfolder) to keep the test cross-platform and consistent with the other tests in this file.
| OutputDirectory = "/tmp" | |
| OutputDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) |
Summary
IModProjectServiceinterfaceModProjectServicewith BUTR template supportChanges
BannerlordModEditor.Common/Services/ModProjectService.cs- ImplementationBannerlordModEditor.Common.Tests/Services/ModProjectServiceTests.cs- Unit testsCloses #4