feat: implements first version

This commit is contained in:
Matthias Langhard
2021-10-31 07:44:49 +01:00
parent 915e23cf24
commit 69f872082d
25 changed files with 979 additions and 0 deletions

106
src/Cli/AppRunner.cs Normal file
View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Application.Commands;
using Application.Queries;
using Cli.Models;
using MediatR;
using Spectre.Console;
using Version = Application.Models.Version;
namespace Cli
{
public class AppRunner
{
private readonly IMediator _mediator;
public AppRunner(IMediator mediator)
{
_mediator = mediator;
}
public async Task Run(string repoPath)
{
// Check if git dir
var services = new List<string>();
try
{
services = await _mediator.Send(new GetServicesFromGitRepo.Query(repoPath));
}
catch (LibGit2Sharp.RepositoryNotFoundException)
{
AnsiConsole.Markup("[red]Error:[/] Unable to extract Versions. Are we running inside a git repository?\n\n");
Environment.Exit(1);
}
var chosenService = "";
if (services.Count > 1)
{
chosenService = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("Which [green]service[/] is the new version for?")
.PageSize(10)
.MoreChoicesText("[grey](Move up and down to reveal more services)[/]")
.AddChoices(services));
}
var versionInfo = await _mediator.Send(new GetVersionInformationFromRepo.Query(repoPath, chosenService));
Selection selection;
if (versionInfo != null)
{
selection = AnsiConsole.Prompt(
new SelectionPrompt<Selection>()
.Title($"Select new version. (Current version is [green]{versionInfo.CurrentVersion}[/])")
.PageSize(10)
.AddChoices(
new Selection("rc ", versionInfo.NextMinorRcVersion),
new Selection("patch", versionInfo.NextPatchVersion),
new Selection("minor", versionInfo.NextMinorVersion),
new Selection("major", versionInfo.NextMajorVersion)
)
);
}
else
{
selection = AnsiConsole.Prompt(
new SelectionPrompt<Selection>()
.Title("[red]Error evaluating version from newest tag.[/]\nAdd new version tag **AND** push to origin?)")
.PageSize(10)
.AddChoices(
new Selection("yes", new Version(0, 1, 0)),
new Selection("yes", new Version(0, 1, 0, 1)),
new Selection("no", null)
)
);
}
if (selection.Version == null)
{
Environment.Exit(0);
}
try
{
await _mediator.Send(new AddTagToGitRepo.Command(repoPath, selection.Version.ToString()));
}
catch (Exception ex)
{
AnsiConsole.Markup("[red]Error:[/] Unable to write Tag to repository\n\n");
AnsiConsole.WriteException(ex);
Environment.Exit(1);
}
try
{
await _mediator.Send(new PushCommitsToRemote.Command(repoPath));
}
catch (Exception ex)
{
AnsiConsole.Markup("[red]Error:[/] Tag was written but unable to push to remote\n\n");
AnsiConsole.WriteException(ex);
Environment.Exit(1);
}
}
}
}

24
src/Cli/Cli.csproj Normal file
View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>Cli</RootNamespace>
<PackAsTool>true</PackAsTool>
<ToolCommandName>update-tag</ToolCommandName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Spectre.Console" Version="0.42.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
namespace Cli
{
public static class DependencyInjection
{
public static IServiceCollection AddCli(this IServiceCollection services)
{
return services
.AddTransient<AppRunner>();
}
}
}

View File

@@ -0,0 +1,21 @@
using Semver;
namespace Cli.Extensions
{
public static class SemverParser
{
public static bool TryParse(string version, out SemVersion semverVersion)
{
try
{
semverVersion = SemVersion.Parse(version.TrimStart('v').TrimStart('V'));
return true;
}
catch
{
semverVersion = null;
return false;
}
}
}
}

View File

@@ -0,0 +1,21 @@
using Application.Models;
namespace Cli.Models
{
public class Selection
{
public Selection(string title, Version version)
{
Title = title;
Version = version;
}
private string Title { get; }
public Version Version { get; }
public override string ToString()
{
return Version == null ? Title : $"{Title}\t [green]{Version}[/]";
}
}
}

39
src/Cli/Program.cs Normal file
View File

@@ -0,0 +1,39 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Application;
using Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Cli
{
class Program
{
static async Task Main(string[] args)
{
using var host = CreateHostBuilder()
.Build();
var appRunner = host
.Services
.CreateScope()
.ServiceProvider
.GetRequiredService<AppRunner>();
await appRunner.Run(args.FirstOrDefault() ?? Environment.CurrentDirectory);
}
private static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices(
(_, services) =>
services
.AddCli()
.AddInfrastructure()
.AddCore()
);
}
}
}

View File

@@ -0,0 +1,34 @@
using Application.Interfaces;
using MediatR;
namespace Application.Commands
{
public class AddTagToGitRepo : RequestHandler<AddTagToGitRepo.Command>
{
private readonly IGitRepoWriteService _gitRepoWriteService;
public AddTagToGitRepo(IGitRepoWriteService gitRepoWriteService)
{
_gitRepoWriteService = gitRepoWriteService;
}
public class Command : IRequest
{
public string RepoPath { get; }
public string Tag { get; }
public Command(string repoPath, string tag)
{
RepoPath = repoPath;
Tag = tag;
}
}
protected override void Handle(Command request)
{
_gitRepoWriteService.AddTag(request.RepoPath, request.Tag);
}
}
}

View File

@@ -0,0 +1,31 @@
using Application.Interfaces;
using MediatR;
namespace Application.Commands
{
public class PushCommitsToRemote : RequestHandler<PushCommitsToRemote.Command>
{
private readonly IGitRepoWriteService _gitRepoWriteService;
public PushCommitsToRemote(IGitRepoWriteService gitRepoWriteService)
{
_gitRepoWriteService = gitRepoWriteService;
}
public class Command : IRequest
{
public string RepoPath { get; }
public Command(string repoPath)
{
RepoPath = repoPath;
}
}
protected override void Handle(Command request)
{
_gitRepoWriteService.Push(request.RepoPath);
}
}
}

14
src/Core/Core.csproj Normal file
View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>Application</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="9.0.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,14 @@
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace Application
{
public static class DependencyInjection
{
public static IServiceCollection AddCore(this IServiceCollection services)
{
return services
.AddMediatR(typeof(DependencyInjection));
}
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using Application.Models;
namespace Application.Interfaces
{
public interface IGitRepoReadService
{
public IEnumerable<Version> GetAllVersions(string repoPath);
}
}

View File

@@ -0,0 +1,8 @@
namespace Application.Interfaces
{
public interface IGitRepoWriteService
{
void AddTag(string repoPath, string tag);
void Push(string repoPath);
}
}

136
src/Core/Models/Version.cs Normal file
View File

@@ -0,0 +1,136 @@
using System.Text;
using System.Text.RegularExpressions;
namespace Application.Models
{
/// <summary>
/// Construct:
/// "Major.Minor.Patch-Rc+Service"
///
/// Example:
/// "0.1.4-RC.4+ErpNext"
/// </summary>
public class Version
{
public Version(int major, int minor, int patch)
{
Major = major;
Minor = minor;
Patch = patch;
}
public Version(int major, int minor, int patch, int rc)
{
Major = major;
Minor = minor;
Patch = patch;
Rc = rc;
}
public Version(int major, int minor, int patch, string rc, string service)
{
Major = major;
Minor = minor;
Patch = patch;
Rc = ExtractNumberFromRcString(rc);
Service = service ?? "";
}
private static int? ExtractNumberFromRcString(string rc)
{
var resultString = Regex.Match(rc, @"\d+").Value;
return int.TryParse(resultString, out var rcNumber) ? rcNumber : null;
}
public int Major { get; private set; }
public int Minor { get; private set; }
public int Patch { get; private set; }
public int? Rc { get; private set; }
public string Service { get; }
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(Major);
sb.Append('.');
sb.Append(Minor);
sb.Append('.');
sb.Append(Patch);
if (Rc != null)
{
sb.Append('-');
sb.Append("RC.");
sb.Append(Rc);
}
if (Service != null && Service.Length > 0)
{
sb.Append('+');
sb.Append(Service);
}
return sb.ToString();
}
public Version NextMajor()
{
var nextVersion = new Version(Major, Minor, Patch, Rc.ToString(), Service);
nextVersion.BumpMajor();
return nextVersion;
}
public Version NextMinor()
{
var nextVersion = new Version(Major, Minor, Patch, Rc.ToString(), Service);
nextVersion.BumpMinor();
return nextVersion;
}
public Version NextPatch()
{
var nextVersion = new Version(Major, Minor, Patch, Rc.ToString(), Service);
nextVersion.BumpPatch();
return nextVersion;
}
public Version NextRc()
{
var nextVersion = new Version(Major, Minor, Patch, Rc.ToString(), Service);
nextVersion.BumpRc();
return nextVersion;
}
public void BumpMajor()
{
Major++;
Minor = 0;
Patch = 0;
Rc = null;
}
public void BumpMinor()
{
Minor++;
Patch = 0;
Rc = null;
}
public void BumpPatch()
{
Patch++;
Rc = null;
}
public void BumpRc()
{
if (Rc == null)
{
BumpMinor();
}
Rc = Rc == null ? 0 : Rc + 1;
}
}
}

View File

@@ -0,0 +1,20 @@
namespace Application.Models
{
public class VersionInformation
{
public VersionInformation(Version currentVersion)
{
CurrentVersion = currentVersion;
NextMajorVersion = currentVersion.NextMajor();
NextMinorVersion = currentVersion.NextMinor();
NextPatchVersion = currentVersion.NextPatch();
NextMinorRcVersion = currentVersion.NextRc();
}
public Version CurrentVersion { get; }
public Version NextMajorVersion { get; }
public Version NextMinorVersion { get; }
public Version NextPatchVersion { get; }
public Version NextMinorRcVersion { get; }
}
}

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using Application.Interfaces;
using MediatR;
namespace Application.Queries
{
public class GetServicesFromGitRepo : RequestHandler<GetServicesFromGitRepo.Query, List<string>>
{
public class Query : IRequest<List<string>>
{
public Query(string repositoryPath)
{
RepositoryPath = repositoryPath;
}
public string RepositoryPath { get; }
}
private readonly IGitRepoReadService _gitRepoReadService;
public GetServicesFromGitRepo(IGitRepoReadService gitRepoReadService)
{
_gitRepoReadService = gitRepoReadService;
}
protected override List<string> Handle(Query request)
{
return _gitRepoReadService
.GetAllVersions(request.RepositoryPath)
.Select(v => v.Service)
.Distinct()
.OrderBy(v => v)
.ToList();
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Linq;
using Application.Interfaces;
using Application.Models;
using MediatR;
namespace Application.Queries
{
public class GetVersionInformationFromRepo : RequestHandler<GetVersionInformationFromRepo.Query, VersionInformation>
{
public class Query : IRequest<VersionInformation>
{
public Query(string repositoryPath, string onlyForService = "")
{
RepositoryPath = repositoryPath;
OnlyForService = onlyForService;
}
public string RepositoryPath { get; }
public string OnlyForService { get; }
}
private readonly IGitRepoReadService _gitRepoReadService;
public GetVersionInformationFromRepo(IGitRepoReadService gitRepoReadService)
{
_gitRepoReadService = gitRepoReadService;
}
protected override VersionInformation Handle(Query request)
{
var versions = _gitRepoReadService
.GetAllVersions(request.RepositoryPath);
if (!string.IsNullOrWhiteSpace(request.OnlyForService))
{
versions = versions
.Where(v => v.Service.Equals(request.OnlyForService, StringComparison.InvariantCultureIgnoreCase));
}
var currentVersion = versions
.OrderByDescending(v => v.Major)
.ThenByDescending(v => v.Minor)
.ThenByDescending(v => v.Patch)
.ThenByDescending(v => v.Rc)
.FirstOrDefault();
return currentVersion == null ? null : new VersionInformation(currentVersion);
}
}
}

7
src/Domain/Domain.csproj Normal file
View File

@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,16 @@
using Application.Interfaces;
using Infrastructure.Services;
using Microsoft.Extensions.DependencyInjection;
namespace Infrastructure
{
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
{
return services
.AddSingleton<IGitRepoReadService, GitRepoReadService>()
.AddSingleton<IGitRepoWriteService, GitRepoWriteService>();
}
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="LibGit2Sharp" Version="0.26.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Include="semver" Version="2.0.6" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using Application.Interfaces;
using LibGit2Sharp;
using Semver;
using Version = Application.Models.Version;
namespace Infrastructure.Services
{
public class GitRepoReadService : IGitRepoReadService
{
public IEnumerable<Version> GetAllVersions(string repoPath)
{
using var repo = new Repository(repoPath);
foreach (var tag in repo.Tags)
{
if (TryParse(tag.FriendlyName, out var semver))
{
yield return new Version(semver.Major, semver.Minor, semver.Patch, semver.Prerelease, semver.Build);
}
}
}
private static bool TryParse(string version, out SemVersion semverVersion)
{
try
{
semverVersion = SemVersion.Parse(version.TrimStart('v').TrimStart('V'));
return true;
}
catch
{
semverVersion = null;
return false;
}
}
}
}

View File

@@ -0,0 +1,20 @@
using Application.Interfaces;
using LibGit2Sharp;
namespace Infrastructure.Services
{
public class GitRepoWriteService : IGitRepoWriteService
{
public void AddTag(string repoPath, string tag)
{
using var repo = new Repository(repoPath);
repo.ApplyTag(tag);
}
public void Push(string repoPath)
{
using var repo = new Repository(repoPath);
repo.Network.Push(repo.Head);
}
}
}