diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index ae2c6696..82a68959 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -10,6 +10,8 @@
+
+
diff --git a/src/GitReleaseManager.Cli/Program.cs b/src/GitReleaseManager.Cli/Program.cs
index dc44e89c..96280e1c 100644
--- a/src/GitReleaseManager.Cli/Program.cs
+++ b/src/GitReleaseManager.Cli/Program.cs
@@ -13,6 +13,8 @@
using GitReleaseManager.Core.Provider;
using GitReleaseManager.Core.ReleaseNotes;
using GitReleaseManager.Core.Templates;
+using GraphQL.Client.Http;
+using GraphQL.Client.Serializer.SystemTextJson;
using Microsoft.Extensions.DependencyInjection;
using NGitLab;
using Octokit;
@@ -211,6 +213,12 @@ private static void RegisterVcsProvider(BaseVcsOptions vcsOptions, IServiceColle
// default to Github
serviceCollection
.AddSingleton((_) => new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = new Credentials(vcsOptions.Token) })
+ .AddSingleton(_ =>
+ {
+ var client = new GraphQLHttpClient(new GraphQLHttpClientOptions { EndPoint = new Uri("https://api.github.com/graphql") }, new SystemTextJsonSerializer());
+ client.HttpClient.DefaultRequestHeaders.Add("Authorization", $"bearer {vcsOptions.Token}");
+ return client;
+ })
.AddSingleton();
}
}
diff --git a/src/GitReleaseManager.Core/Configuration/Config.cs b/src/GitReleaseManager.Core/Configuration/Config.cs
index 149e7fe6..262c12bc 100644
--- a/src/GitReleaseManager.Core/Configuration/Config.cs
+++ b/src/GitReleaseManager.Core/Configuration/Config.cs
@@ -27,6 +27,7 @@ public Config()
ShaSectionHeading = "SHA256 Hashes of the release artifacts",
ShaSectionLineFormat = "- `{1}\t{0}`",
AllowUpdateToPublishedRelease = false,
+ IncludeContributors = false,
};
Export = new ExportConfig
diff --git a/src/GitReleaseManager.Core/Configuration/CreateConfig.cs b/src/GitReleaseManager.Core/Configuration/CreateConfig.cs
index 512e4a28..00baed7d 100644
--- a/src/GitReleaseManager.Core/Configuration/CreateConfig.cs
+++ b/src/GitReleaseManager.Core/Configuration/CreateConfig.cs
@@ -34,5 +34,8 @@ public class CreateConfig
[YamlMember(Alias = "allow-update-to-published")]
public bool AllowUpdateToPublishedRelease { get; set; }
+
+ [YamlMember(Alias = "include-contributors")]
+ public bool IncludeContributors { get; set; }
}
}
\ No newline at end of file
diff --git a/src/GitReleaseManager.Core/Extensions/JsonExtensions.cs b/src/GitReleaseManager.Core/Extensions/JsonExtensions.cs
new file mode 100644
index 00000000..d8920c65
--- /dev/null
+++ b/src/GitReleaseManager.Core/Extensions/JsonExtensions.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+
+namespace GitReleaseManager.Core.Extensions
+{
+ internal static class JsonExtensions
+ {
+ ///
+ /// Get a JsonElement from a path. Each level in the path is seperated by a dot.
+ ///
+ /// The parent Json element.
+ /// The path of the desired child element.
+ /// The child element.
+ public static JsonElement GetJsonElement(this JsonElement jsonElement, string path)
+ {
+ if (jsonElement.ValueKind is JsonValueKind.Null || jsonElement.ValueKind is JsonValueKind.Undefined)
+ {
+ return default(JsonElement);
+ }
+
+ string[] segments = path.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var segment in segments)
+ {
+ if (int.TryParse(segment, out var index) && jsonElement.ValueKind == JsonValueKind.Array)
+ {
+ jsonElement = jsonElement.EnumerateArray().ElementAtOrDefault(index);
+ if (jsonElement.ValueKind is JsonValueKind.Null || jsonElement.ValueKind is JsonValueKind.Undefined)
+ {
+ return default(JsonElement);
+ }
+
+ continue;
+ }
+
+ jsonElement = jsonElement.TryGetProperty(segment, out var value) ? value : default;
+
+ if (jsonElement.ValueKind is JsonValueKind.Null || jsonElement.ValueKind is JsonValueKind.Undefined)
+ {
+ return default(JsonElement);
+ }
+ }
+
+ return jsonElement;
+ }
+
+ ///
+ /// Get the first JsonElement matching a path from the provided list of paths.
+ ///
+ /// The parent Json element.
+ /// The path of the desired child element.
+ /// The child element.
+ public static JsonElement GetFirstJsonElement(this JsonElement jsonElement, IEnumerable paths)
+ {
+ if (jsonElement.ValueKind is JsonValueKind.Null || jsonElement.ValueKind is JsonValueKind.Undefined)
+ {
+ return default(JsonElement);
+ }
+
+ var element = default(JsonElement);
+
+ foreach (var path in paths)
+ {
+ element = jsonElement.GetJsonElement(path);
+
+ if (element.ValueKind is JsonValueKind.Null || element.ValueKind is JsonValueKind.Undefined)
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ return element;
+ }
+
+ public static string GetJsonElementValue(this JsonElement jsonElement) => jsonElement.ValueKind != JsonValueKind.Null &&
+ jsonElement.ValueKind != JsonValueKind.Undefined
+ ? jsonElement.ToString()
+ : default;
+ }
+}
diff --git a/src/GitReleaseManager.Core/GitReleaseManager.Core.csproj b/src/GitReleaseManager.Core/GitReleaseManager.Core.csproj
index 350a0959..7e614e0c 100644
--- a/src/GitReleaseManager.Core/GitReleaseManager.Core.csproj
+++ b/src/GitReleaseManager.Core/GitReleaseManager.Core.csproj
@@ -19,6 +19,8 @@
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/src/GitReleaseManager.Core/MappingProfiles/GitHubProfile.cs b/src/GitReleaseManager.Core/MappingProfiles/GitHubProfile.cs
index dff999dc..11e90526 100644
--- a/src/GitReleaseManager.Core/MappingProfiles/GitHubProfile.cs
+++ b/src/GitReleaseManager.Core/MappingProfiles/GitHubProfile.cs
@@ -1,4 +1,5 @@
using System;
+using System.Text.Json;
using AutoMapper;
using GitReleaseManager.Core.Extensions;
@@ -8,10 +9,11 @@ public class GitHubProfile : Profile
{
public GitHubProfile()
{
+ // These mappings convert the result of Octokit queries to model classes
CreateMap()
.ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.Number))
.ForMember(dest => dest.InternalNumber, act => act.MapFrom(src => src.Id))
- .ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => src.HtmlUrl.IndexOf("/pull/", StringComparison.OrdinalIgnoreCase) >= 0))
+ .ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => src.HtmlUrl.Contains("/pull/", StringComparison.OrdinalIgnoreCase)))
.ReverseMap();
CreateMap().ReverseMap();
CreateMap().ReverseMap();
@@ -23,11 +25,35 @@ public GitHubProfile()
CreateMap().ReverseMap();
CreateMap().ReverseMap();
CreateMap().ReverseMap();
+ CreateMap().ReverseMap();
CreateMap();
CreateMap()
.ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.Number))
.ForMember(dest => dest.InternalNumber, act => act.MapFrom(src => src.Number))
.AfterMap((src, dest) => dest.Version = src.Version());
+
+ // These mappings convert the result of GraphQL queries to model classes
+ CreateMap()
+ .ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.GetProperty("number").GetInt32()))
+ .ForMember(dest => dest.InternalNumber, act => act.MapFrom(src => -1)) // Not available in graphQL (there's a "id" property but it contains a string which represents the Node ID of the object).
+ .ForMember(dest => dest.Title, act => act.MapFrom(src => src.GetProperty("title").GetString()))
+ .ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => src.GetProperty("url").GetString()))
+ .ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => src.GetProperty("url").GetString().Contains("/pull/", StringComparison.OrdinalIgnoreCase)))
+ .ForMember(dest => dest.User, act => act.MapFrom(src => src.GetProperty("author")))
+ .ForMember(dest => dest.Labels, act => act.MapFrom(src => src.GetJsonElement("labels.nodes").EnumerateArray()))
+ .ReverseMap();
+
+ CreateMap()
+ .ForMember(dest => dest.Name, act => act.MapFrom(src => src.GetProperty("name").GetString()))
+ .ForMember(dest => dest.Color, act => act.MapFrom(src => src.GetProperty("color").GetString()))
+ .ForMember(dest => dest.Description, act => act.MapFrom(src => src.GetProperty("description").GetString()))
+ .ReverseMap();
+
+ CreateMap()
+ .ForMember(dest => dest.Login, act => act.MapFrom(src => src.GetProperty("login").GetString()))
+ .ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => $"https://github.com{src.GetProperty("resourcePath").GetString()}")) // The resourcePath contains a value similar to "/jericho". That's why we must manually prepend "https://github.com
+ .ForMember(dest => dest.AvatarUrl, act => act.MapFrom(src => src.GetProperty("avatarUrl").GetString()))
+ .ReverseMap();
}
}
}
\ No newline at end of file
diff --git a/src/GitReleaseManager.Core/MappingProfiles/GitLabProfile.cs b/src/GitReleaseManager.Core/MappingProfiles/GitLabProfile.cs
index e8d3aad4..0fad81b0 100644
--- a/src/GitReleaseManager.Core/MappingProfiles/GitLabProfile.cs
+++ b/src/GitReleaseManager.Core/MappingProfiles/GitLabProfile.cs
@@ -23,12 +23,14 @@ public GitLabProfile()
.ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.IssueId))
.ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => src.WebUrl))
.ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => false))
+ .ForMember(dest => dest.User, act => act.MapFrom(src => src.Author))
.ReverseMap();
CreateMap()
.ForMember(dest => dest.InternalNumber, act => act.MapFrom(src => src.Id))
.ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.Iid))
.ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => src.WebUrl))
.ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => true))
+ .ForMember(dest => dest.User, act => act.MapFrom(src => src.Author))
.ReverseMap();
CreateMap().ForMember(dest => dest.Name, act => act.MapFrom(src => src));
CreateMap()
@@ -43,6 +45,16 @@ public GitLabProfile()
.ReverseMap();
CreateMap()
.ReverseMap();
+ CreateMap()
+ .ForMember(dest => dest.Username, act => act.MapFrom(src => src.Login))
+ .ForMember(dest => dest.WebURL, act => act.MapFrom(src => src.HtmlUrl))
+ .ForMember(dest => dest.AvatarURL, act => act.MapFrom(src => src.AvatarUrl))
+ .ReverseMap();
+ CreateMap()
+ .ForMember(dest => dest.Username, act => act.MapFrom(src => src.Login))
+ .ForMember(dest => dest.WebUrl, act => act.MapFrom(src => src.HtmlUrl))
+ .ForMember(dest => dest.AvatarUrl, act => act.MapFrom(src => src.AvatarUrl))
+ .ReverseMap();
}
}
}
\ No newline at end of file
diff --git a/src/GitReleaseManager.Core/Model/Issue.cs b/src/GitReleaseManager.Core/Model/Issue.cs
index 222d8eb9..5ad73c7c 100644
--- a/src/GitReleaseManager.Core/Model/Issue.cs
+++ b/src/GitReleaseManager.Core/Model/Issue.cs
@@ -15,5 +15,9 @@ public sealed class Issue
public IReadOnlyList