Skip to content

Commit

Permalink
feat: add support for project containers (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
philasmar authored Oct 24, 2024
1 parent dec7dc1 commit b6b0d82
Show file tree
Hide file tree
Showing 26 changed files with 1,503 additions and 84 deletions.
12 changes: 12 additions & 0 deletions .autover/changes/107cbaff-fc23-40ee-a9cb-24fc12d7d744.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Projects": [
{
"Name": "AutoVer",
"Type": "Patch",
"ChangelogMessages": [
"Add support for project containers, which allow multiple projects to be versioned as one",
"Add a unit test project that tests a combination of project configurations"
]
}
]
}
4 changes: 4 additions & 0 deletions .github/workflows/BuildandTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Setup Git User
run: |
git config --global user.email "[email protected]"
git config --global user.name "GitHub User"
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
22 changes: 19 additions & 3 deletions src/AutoVer/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using AutoVer.Extensions;
using AutoVer.Models;
using AutoVer.Services;
using AutoVer.Services.IO;

namespace AutoVer.Commands;

Expand All @@ -19,7 +20,10 @@ public class CommandFactory(
IChangelogHandler changelogHandler,
IChangeFileHandler changeFileHandler,
IVersionHandler versionHandler,
IVersionIncrementer versionIncrementer
IVersionIncrementer versionIncrementer,
IFileManager fileManager,
IDirectoryManager directoryManager,
IPathManager pathManager
) : ICommandFactory
{
private static readonly Option<string> OptionProjectPath = new("--project-path", Directory.GetCurrentDirectory, "Path to the project");
Expand Down Expand Up @@ -78,6 +82,10 @@ private Command BuildVersionCommand()
var optionNoTag = context.ParseResult.GetValueForOption(noTagOption);
var optionUseVersion = context.ParseResult.GetValueForOption(useVersionOption);

fileManager.SetCurrentDirectory(optionProjectPath);
directoryManager.SetCurrentDirectory(optionProjectPath);
pathManager.SetCurrentDirectory(optionProjectPath);

var command = new VersionCommand(
projectHandler,
gitHandler,
Expand Down Expand Up @@ -138,7 +146,11 @@ private Command BuildChangelogCommand()
var optionOutputToConsole = context.ParseResult.GetValueForOption(outputToConsoleOption);
var optionReleaseName = context.ParseResult.GetValueForOption(releaseNameOption);
var optionTagName = context.ParseResult.GetValueForOption(tagNameOption);


fileManager.SetCurrentDirectory(optionProjectPath);
directoryManager.SetCurrentDirectory(optionProjectPath);
pathManager.SetCurrentDirectory(optionProjectPath);

var command = new ChangelogCommand(configurationManager, gitHandler, changelogHandler, toolInteractiveService, versionHandler);
await command.ExecuteAsync(optionProjectPath, optionIncrementType, optionOutputToConsole, optionReleaseName, optionTagName);

Expand Down Expand Up @@ -190,7 +202,11 @@ private Command BuildChangeCommand()
var optionIncrementType = context.ParseResult.GetValueForOption(OptionIncrementType);
var optionProjectName = context.ParseResult.GetValueForOption(projectNameOption);
var optionMessage = context.ParseResult.GetValueForOption(messageOption);


fileManager.SetCurrentDirectory(optionProjectPath);
directoryManager.SetCurrentDirectory(optionProjectPath);
pathManager.SetCurrentDirectory(optionProjectPath);

var command = new ChangeCommand(configurationManager, toolInteractiveService, changeFileHandler);
await command.ExecuteAsync(optionProjectPath, optionIncrementType, optionProjectName, optionMessage);

Expand Down
69 changes: 47 additions & 22 deletions src/AutoVer/Commands/VersionCommand.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using AutoVer.Constants;
using AutoVer.Exceptions;
using AutoVer.Extensions;
using AutoVer.Models;
using AutoVer.Services;

Expand Down Expand Up @@ -32,11 +31,7 @@ public async Task ExecuteAsync(
{
foreach (var availableProject in userConfiguration.Projects)
{
if (availableProject.ProjectDefinition is null)
throw new InvalidUserConfigurationException($"The configured project '{availableProject.Path}' is invalid.");

if (!projectHandler.ProjectHasVersionTag(availableProject.ProjectDefinition))
throw new NoVersionTagException($"The project '{availableProject.Path}' does not have a {ProjectConstants.VersionTag} tag. Add a {ProjectConstants.VersionTag} tag and run the tool again.");
availableProject.EnsureProjectHasVersionTag();
}
}

Expand All @@ -59,8 +54,6 @@ public async Task ExecuteAsync(
var projectsIncremented = false;
foreach (var availableProject in userConfiguration.Projects)
{
if (availableProject.ProjectDefinition is null)
throw new InvalidUserConfigurationException($"The configured project '{availableProject.Path}' is invalid.");
if (!availableProject.IncrementType.Equals(IncrementType.None))
projectsIncremented = true;
if (userConfiguration.UseSameVersionForAllProjects)
Expand All @@ -69,11 +62,18 @@ public async Task ExecuteAsync(
if (userConfiguration.ChangeFilesDetermineIncrementType &&
projectIncrements.ContainsKey(availableProject.Name))
projectIncrementType = projectIncrements[availableProject.Name];
projectHandler.UpdateVersion(
availableProject.ProjectDefinition,
projectIncrementType,
availableProject.PrereleaseLabel,
optionUseVersion ?? maxNextVersion?.ToString());
var localMaxVersion = versionIncrementer.GetNextMaxVersion(
availableProject,
userConfiguration.ChangeFilesDetermineIncrementType ? projectIncrements : null,
incrementType);
foreach (var project in availableProject.Projects)
{
projectHandler.UpdateVersion(
project.ProjectDefinition,
projectIncrementType,
availableProject.PrereleaseLabel,
optionUseVersion ?? maxNextVersion?.ToString() ?? localMaxVersion?.ToString());
}
}
else
{
Expand All @@ -82,20 +82,45 @@ public async Task ExecuteAsync(
var projectIncrementType = IncrementType.None;
if (projectIncrements.ContainsKey(availableProject.Name))
projectIncrementType = projectIncrements[availableProject.Name];
projectHandler.UpdateVersion(
availableProject.ProjectDefinition,
projectIncrementType,
availableProject.PrereleaseLabel,
optionUseVersion);
if (projectIncrementType.Equals(IncrementType.None))
continue;
var localMaxVersion = versionIncrementer.GetNextMaxVersion(
availableProject,
userConfiguration.ChangeFilesDetermineIncrementType ? projectIncrements : null,
incrementType);
foreach (var project in availableProject.Projects)
{
projectHandler.UpdateVersion(
project.ProjectDefinition,
projectIncrementType,
availableProject.PrereleaseLabel,
optionUseVersion ?? localMaxVersion?.ToString());
}
}
else
{
var projectIncrementType = availableProject.IncrementType ?? IncrementType.Patch;
projectHandler.UpdateVersion(availableProject.ProjectDefinition, projectIncrementType, availableProject.PrereleaseLabel, optionUseVersion);
var localMaxVersion = versionIncrementer.GetNextMaxVersion(
availableProject,
userConfiguration.ChangeFilesDetermineIncrementType ? projectIncrements : null,
incrementType);
if (projectIncrementType.Equals(IncrementType.None))
continue;
foreach (var project in availableProject.Projects)
{
projectHandler.UpdateVersion(
project.ProjectDefinition,
projectIncrementType,
availableProject.PrereleaseLabel,
optionUseVersion ?? localMaxVersion?.ToString());
}
}
}

gitHandler.StageChanges(userConfiguration, availableProject.Path);

foreach (var project in availableProject.Projects)
{
gitHandler.StageChanges(userConfiguration, project.Path);
}
}

// When done, reset the config file if the user had one
Expand Down
23 changes: 23 additions & 0 deletions src/AutoVer/Extensions/ProjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Xml;
using AutoVer.Constants;
using AutoVer.Exceptions;
using AutoVer.Models;

namespace AutoVer.Extensions;

public static class ProjectExtensions
{
public static void EnsureProjectHasVersionTag(this ProjectContainer projectContainer)
{
foreach (var project in projectContainer.Projects)
{
var extension = Path.GetExtension(project.ProjectDefinition.ProjectPath);
var versionTag = ProjectConstants.VersionTag;
if (string.Equals(extension, ".nuspec"))
versionTag = ProjectConstants.NuspecVersionTag;
var versionTagList = project.ProjectDefinition.Contents.GetElementsByTagName(versionTag).Cast<XmlNode>().ToList();
if (!versionTagList.Any())
throw new NoVersionTagException($"The project '{projectContainer.Name}' does not have a '{versionTag}' tag. Add a '{versionTag}' tag and run the tool again.");
}
}
}
152 changes: 143 additions & 9 deletions src/AutoVer/Models/UserConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,164 @@
using System.Text.Json.Serialization;
using System.Xml;
using AutoVer.Constants;
using AutoVer.Services.IO;

namespace AutoVer.Models;

public class UserConfiguration
{
[JsonIgnore] public string GitRoot { get; set; } = string.Empty;
internal bool PersistConfiguration { get; set; }
public List<Project> Projects { get; set; } = [];
public List<ProjectContainer> Projects { get; set; } = [];
public bool UseCommitsForChangelog { get; set; } = true;
public bool UseSameVersionForAllProjects { get; set; } = false;

[JsonConverter(typeof(JsonStringEnumConverter))]
public IncrementType DefaultIncrementType { get; set; } = IncrementType.Patch;
public Dictionary<string, string>? ChangelogCategories { get; set; }
public bool ChangeFilesDetermineIncrementType { get; set; } = false;
}

public class ProjectContainer : IJsonOnDeserialized
{
private IFileManager? _fileManager;
private IPathManager? _pathManager;

public required string Name { get; set; }
public string? Path { get; set; }

public List<string>? Paths { get; set; }

public class Project
{
public required string Name { get; set; }
public required string Path { get; set; }
internal List<Project> Projects { get; set; } = [];

[JsonConverter(typeof(JsonStringEnumConverter))]
public IncrementType? IncrementType { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public IncrementType? IncrementType { get; set; }

public string? PrereleaseLabel { get; set; }
public string? PrereleaseLabel { get; set; }

public List<string> GetPaths()
{
if (!string.IsNullOrEmpty(Path))
{
return new List<string> { Path };
}
else
{
return Paths ?? [];
}
}

public void OnDeserialized()
{
if (_fileManager == null || _pathManager == null)
return;

bool isPathProvided = !string.IsNullOrEmpty(Path);
bool isPathsProvided = Paths is { Count: > 0 };

if (!isPathProvided && !isPathsProvided)
{
var errorMessage = $"{Name} - Either 'Path' or 'Paths' must be provided.";
Console.WriteLine(errorMessage);
throw new Exception(errorMessage);
}

if (isPathProvided && isPathsProvided)
{
var errorMessage = $"{Name} - 'Path' and 'Paths' cannot both be provided. Please provide only one.";
Console.WriteLine(errorMessage);
throw new Exception(errorMessage);
}

foreach (var path in GetPaths())
{
var normalizedPath = path.Replace('\\', _pathManager.DirectorySeparatorChar).Replace('/', _pathManager.DirectorySeparatorChar);
if (!_fileManager.Exists(normalizedPath))
throw new Exception($"Failed to find a valid .csproj or .nuspec file at path {normalizedPath}");

var extension = _pathManager.GetExtension(normalizedPath);
if (!string.Equals(extension, ".csproj") && !string.Equals(extension, ".nuspec"))
{
var errorMessage = $"Invalid project path {normalizedPath}. The project path must point to a .csproj or .nuspec file";
throw new Exception(errorMessage);
}

var xmlProjectFile = new XmlDocument{ PreserveWhitespace = true };
xmlProjectFile.LoadXml(_fileManager.ReadAllText(normalizedPath));

var projectDefinition = new ProjectDefinition(
xmlProjectFile,
_pathManager.GetFullPath(normalizedPath)
);

var versionTag = ProjectConstants.VersionTag;
if (string.Equals(extension, ".nuspec"))
versionTag = ProjectConstants.NuspecVersionTag;
var version = xmlProjectFile.GetElementsByTagName(versionTag);
if (version.Count > 0)
{
projectDefinition.Version = version[0]?.InnerText;
}

Projects.Add(new Project(normalizedPath, projectDefinition));
}
}

public void InjectDependency(IFileManager fileManager, IPathManager pathManager)
{
_fileManager = fileManager;
_pathManager = pathManager;
}
}

public class Project(string path, ProjectDefinition definition)
{
private IFileManager? _fileManager;
private IPathManager? _pathManager;

public string Path { get; set; } = path;

internal ProjectDefinition? ProjectDefinition { get; set; }
internal ProjectDefinition ProjectDefinition { get; set; } = definition;

public void OnDeserialized()
{
if (_fileManager == null || _pathManager == null)
return;

var normalizedPath = Path.Replace('\\', _pathManager.DirectorySeparatorChar).Replace('/', _pathManager.DirectorySeparatorChar);
if (!_fileManager.Exists(normalizedPath))
throw new Exception($"Failed to find a valid .csproj or .nuspec file at path {normalizedPath}");

var extension = _pathManager.GetExtension(normalizedPath);
if (!string.Equals(extension, ".csproj") && !string.Equals(extension, ".nuspec"))
{
var errorMessage = $"Invalid project path {normalizedPath}. The project path must point to a .csproj or .nuspec file";
throw new Exception(errorMessage);
}

var xmlProjectFile = new XmlDocument{ PreserveWhitespace = true };
xmlProjectFile.LoadXml(_fileManager.ReadAllText(normalizedPath));

var projectDefinition = new ProjectDefinition(
xmlProjectFile,
_pathManager.GetFullPath(normalizedPath)
);

var versionTag = ProjectConstants.VersionTag;
if (string.Equals(extension, ".nuspec"))
versionTag = ProjectConstants.NuspecVersionTag;
var version = xmlProjectFile.GetElementsByTagName(versionTag);
if (version.Count > 0)
{
projectDefinition.Version = version[0]?.InnerText;
}

ProjectDefinition = projectDefinition;
}

public void InjectDependency(IFileManager fileManager, IPathManager pathManager)
{
_fileManager = fileManager;
_pathManager = pathManager;
}
}
Loading

0 comments on commit b6b0d82

Please sign in to comment.