From 3f2f96fa4bf37992c619b746fcaf96074be4ff3b Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 15:28:11 -0400 Subject: [PATCH 01/25] feat(source): added a configuration source --- PackageMonster/ActionInputs.cs | 20 ++++++++ PackageMonster/GitHubAction.cs | 2 +- PackageMonster/Models/NugetVersionsModel.cs | 19 -------- PackageMonster/PackageMonster.csproj | 1 + PackageMonster/Services/INugetDataService.cs | 4 +- PackageMonster/Services/NugetDataService.cs | 47 +++++++++++++++---- .../PackageMonsterTests/GitHubActionTests.cs | 10 ++-- .../Models/NugetVersionsModelTests.cs | 13 +++-- 8 files changed, 75 insertions(+), 41 deletions(-) delete mode 100644 PackageMonster/Models/NugetVersionsModel.cs diff --git a/PackageMonster/ActionInputs.cs b/PackageMonster/ActionInputs.cs index c682ea3..f16f4e2 100644 --- a/PackageMonster/ActionInputs.cs +++ b/PackageMonster/ActionInputs.cs @@ -2,6 +2,8 @@ // Copyright (c) KinsonDigital. All rights reserved. // +using PackageMonster.Services; + namespace PackageMonster; /// @@ -30,6 +32,24 @@ public class ActionInputs HelpText = "The version of the NuGet package to check. This is not case-sensitive.")] public string Version { get; set; } = string.Empty; + /// + /// Gets or sets the NuGet repository. + /// + [Option( + "source", + Required = false, + HelpText = $"The source repository to check. Defaults to `{NugetDataService.PublicNugetApiUrl}`.")] + public string Source { get; set; } = string.Empty; + + /// + /// Gets or sets the NuGet repository. + /// + [Option( + "versionsJsonPath", + Required = false, + HelpText = $"The json path to the versions. Defaults to `{NugetDataService.PublicNugetVersionsJsonPath}`.")] + public string VersionsJsonPath { get; set; } = string.Empty; + /// /// Gets or sets a value indicating whether or not the action will fail if the package was not found. /// diff --git a/PackageMonster/GitHubAction.cs b/PackageMonster/GitHubAction.cs index 5001e95..046ed81 100644 --- a/PackageMonster/GitHubAction.cs +++ b/PackageMonster/GitHubAction.cs @@ -39,7 +39,7 @@ public async Task Run(ActionInputs inputs, Action onCompleted, Action try { this.gitHubConsoleService.Write($"Searching for package '{inputs.PackageName} v{inputs.Version}' . . . "); - var versions = await this.nugetDataService.GetNugetVersions(inputs.PackageName); + var versions = await this.nugetDataService.GetNugetVersions(inputs.PackageName, inputs.Source, inputs.VersionsJsonPath); var versionFound = versions .Any(version => diff --git a/PackageMonster/Models/NugetVersionsModel.cs b/PackageMonster/Models/NugetVersionsModel.cs deleted file mode 100644 index 039cb3a..0000000 --- a/PackageMonster/Models/NugetVersionsModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) KinsonDigital. All rights reserved. -// - -namespace PackageMonster.Models; - -/// -/// Holds information about a NuGet package. -/// -/// -/// This information comes from www.nuget.org -/// -public record NugetVersionsModel -{ - /// - /// Gets or sets the list of versions available for a NuGet package. - /// - public string[] Versions { get; set; } = Array.Empty(); -} diff --git a/PackageMonster/PackageMonster.csproj b/PackageMonster/PackageMonster.csproj index dba9e2f..b44515a 100644 --- a/PackageMonster/PackageMonster.csproj +++ b/PackageMonster/PackageMonster.csproj @@ -27,6 +27,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/PackageMonster/Services/INugetDataService.cs b/PackageMonster/Services/INugetDataService.cs index a4bd15d..6aa7095 100644 --- a/PackageMonster/Services/INugetDataService.cs +++ b/PackageMonster/Services/INugetDataService.cs @@ -14,6 +14,8 @@ public interface INugetDataService : IDisposable /// to nuget.org using the given . /// /// The name of the package. + /// The nuget source. + /// The versions json path. /// The list of versions that exist for the package. - Task GetNugetVersions(string packageName); + Task GetNugetVersions(string packageName, string source, string versionsJsonPath); } diff --git a/PackageMonster/Services/NugetDataService.cs b/PackageMonster/Services/NugetDataService.cs index c7e7a78..f49cb99 100644 --- a/PackageMonster/Services/NugetDataService.cs +++ b/PackageMonster/Services/NugetDataService.cs @@ -4,7 +4,8 @@ using System.Diagnostics.CodeAnalysis; using System.Net; -using PackageMonster.Models; +using System.Text.Json.Nodes; +using Newtonsoft.Json.Linq; using RestSharp; namespace PackageMonster.Services; @@ -18,14 +19,15 @@ public sealed class NugetDataService : INugetDataService * 1. Package Content: https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource * 2.Nuget Server API: https://docs.microsoft.com/en-us/nuget/api/overview */ - private const string BaseUrl = "https://api.nuget.org"; + internal const string PublicNugetApiUrl = "https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json"; + internal const string PublicNugetVersionsJsonPath = "$.versions[*]"; private readonly RestClient client; private bool isDisposed; /// /// Initializes a new instance of the class. /// - public NugetDataService() => this.client = new RestClient(BaseUrl); + public NugetDataService() => this.client = new RestClient(PublicNugetApiUrl); /// /// @@ -38,24 +40,49 @@ public sealed class NugetDataService : INugetDataService /// /// Thrown if any HTTP based error occurs. /// - public async Task GetNugetVersions(string packageName) + public async Task GetNugetVersions(string packageName, string source, string versionsJsonPath) { if (string.IsNullOrEmpty(packageName)) { throw new ArgumentNullException(nameof(packageName), $"Must provide a NuGet package name."); } - this.client.AcceptedContentTypes = new[] { "application/vnd.github.v3+json" }; + if (string.IsNullOrWhiteSpace(source)) + { + source = PublicNugetApiUrl; + } + + if (!Uri.IsWellFormedUriString(source, UriKind.Absolute)) + { + throw new ArgumentException(nameof(source), $"Must provide a well-formed NuGet source URI."); + } + + if (!Uri.TryCreate(source, UriKind.Absolute, out _)) + { + throw new ArgumentException(nameof(source), $"Must provide an absolute NuGet source URI."); + } - const string serviceIndexId = "v3-flatcontainer"; - var fullUrl = $"{BaseUrl}/{serviceIndexId}/{packageName.ToLower()}/index.json"; - var request = new RestRequest(fullUrl); + if (string.IsNullOrWhiteSpace(versionsJsonPath)) + { + versionsJsonPath = PublicNugetVersionsJsonPath; + } - var response = await this.client.ExecuteAsync(request, Method.Get); + this.client.AcceptedContentTypes = new[] { "application/vnd.github.v3+json" }; + + var resolvedUrl = source.Replace("PACKAGE-NAME", packageName); + var request = new RestRequest(resolvedUrl); + + var response = await this.client.ExecuteAsync(request, Method.Get); if (response.StatusCode == HttpStatusCode.OK) { - return response.Data is null ? Array.Empty() : response.Data.Versions.ToArray(); + if (string.IsNullOrWhiteSpace(response.Content) || response.ContentLength == 0) + { + return Array.Empty(); + } + + var json = JObject.Parse(response.Content); + return json.SelectTokens(versionsJsonPath).Select(t => t.Value()).ToArray(); } var exception = response.ErrorException ?? new Exception("There was an issue getting data from NuGet."); diff --git a/Testing/PackageMonsterTests/GitHubActionTests.cs b/Testing/PackageMonsterTests/GitHubActionTests.cs index 812ac0b..f3ca22e 100644 --- a/Testing/PackageMonsterTests/GitHubActionTests.cs +++ b/Testing/PackageMonsterTests/GitHubActionTests.cs @@ -55,7 +55,7 @@ public async void Run_WhenNugetPackageWithVersionExists_SetsOutputToCorrectValue var expectedSearchMsgStart = $"Searching for package '{packageName} v{version}' . . . "; var expectedSearchMsgEnd = expectedOutput == "true" ? "package found!!" : "package not found!!"; - this.mockDataService.Setup(m => m.GetNugetVersions(packageName)) + this.mockDataService.Setup(m => m.GetNugetVersions(packageName, It.IsAny(), It.IsAny())) .ReturnsAsync(new[] { "1.2.3", "4.5.6" }); var onCompletedInvoked = false; @@ -84,7 +84,7 @@ public async void Run_WhenPackageIsNotFoundWithFailSetToTrue_ThrowsExceptionWith // Arrange var inputs = CreateInputs(failWhenNotFound: true); - this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny())) + this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(Array.Empty()); var action = CreateAction(); @@ -106,7 +106,7 @@ public async void Run_WhenPackageIsNotFoundWithFailSetToFalseOrNull_DoesNotThrow // Arrange var inputs = CreateInputs(failWhenNotFound: failWhenNotFound); - this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny())) + this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(Array.Empty()); var action = CreateAction(); @@ -123,8 +123,8 @@ await act.Should() public async void Run_WhenAnExceptionIsThrown_InvokesOnErrorActionParam() { // Arrange - this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny())) - .Callback(_ => throw new Exception("test-exception")); + this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((_, _) => throw new Exception("test-exception")); Exception? exception = null; var inputs = CreateInputs(); diff --git a/Testing/PackageMonsterTests/Models/NugetVersionsModelTests.cs b/Testing/PackageMonsterTests/Models/NugetVersionsModelTests.cs index 7331220..7f60c68 100644 --- a/Testing/PackageMonsterTests/Models/NugetVersionsModelTests.cs +++ b/Testing/PackageMonsterTests/Models/NugetVersionsModelTests.cs @@ -3,13 +3,16 @@ // // ReSharper disable UseObjectOrCollectionInitializer + +using Newtonsoft.Json.Linq; +using PackageMonster.Services; + namespace PackageMonsterTests.Models; using FluentAssertions; -using PackageMonster.Models; /// -/// Tests the class. +/// Tests the versions Json Path functionality. /// public class NugetVersionsModelTests { @@ -18,13 +21,13 @@ public class NugetVersionsModelTests public void Version_WhenSettingValue_ReturnsCorrectResult() { // Arrange - var model = new NugetVersionsModel(); + var model = JObject.Parse(@"{ 'versions': [""1.2.3"", ""4.5.6""] }"); // Act - model.Versions = new[] { "1.2.3", "4.5.6" }; + var actual = model.SelectTokens(NugetDataService.PublicNugetVersionsJsonPath).Select(v => v.Value()).ToArray(); // Assert - model.Versions.Should() + actual.Should() .HaveCount(2) .And.Contain("1.2.3") .And.Contain("4.5.6") From d883dc157bdf4bbc79ecca11510ee9a880e304d1 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 15:42:59 -0400 Subject: [PATCH 02/25] fixed a few bugs --- PackageMonster/ActionInputs.cs | 4 ++-- PackageMonster/PackageMonster.csproj | 2 +- Testing/PackageMonsterTests/GitHubActionTests.cs | 2 +- action.yml | 4 ++++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/PackageMonster/ActionInputs.cs b/PackageMonster/ActionInputs.cs index f16f4e2..a0c0b2d 100644 --- a/PackageMonster/ActionInputs.cs +++ b/PackageMonster/ActionInputs.cs @@ -42,10 +42,10 @@ public class ActionInputs public string Source { get; set; } = string.Empty; /// - /// Gets or sets the NuGet repository. + /// Gets or sets the json path to extract the versions. /// [Option( - "versionsJsonPath", + "json-path", Required = false, HelpText = $"The json path to the versions. Defaults to `{NugetDataService.PublicNugetVersionsJsonPath}`.")] public string VersionsJsonPath { get; set; } = string.Empty; diff --git a/PackageMonster/PackageMonster.csproj b/PackageMonster/PackageMonster.csproj index b44515a..50c0ad8 100644 --- a/PackageMonster/PackageMonster.csproj +++ b/PackageMonster/PackageMonster.csproj @@ -8,7 +8,7 @@ enable - 1.0.0-preview.2 + 1.0.0-preview.3 1.0.0-preview.2 diff --git a/Testing/PackageMonsterTests/GitHubActionTests.cs b/Testing/PackageMonsterTests/GitHubActionTests.cs index f3ca22e..b5922ad 100644 --- a/Testing/PackageMonsterTests/GitHubActionTests.cs +++ b/Testing/PackageMonsterTests/GitHubActionTests.cs @@ -124,7 +124,7 @@ public async void Run_WhenAnExceptionIsThrown_InvokesOnErrorActionParam() { // Arrange this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((_, _) => throw new Exception("test-exception")); + .Callback((_, _, _) => throw new Exception("test-exception")); Exception? exception = null; var inputs = CreateInputs(); diff --git a/action.yml b/action.yml index 95a76a5..badaf6f 100644 --- a/action.yml +++ b/action.yml @@ -29,5 +29,9 @@ runs: - ${{ inputs.package-name }} - '--version' - ${{ inputs.version }} + - '--source' + - ${{ inputs.source }} + - '--json-path' + - ${{ inputs.json-path }} - '--fail-when-not-found' - ${{ inputs.fail-when-not-found }} From 0d168debaf22d66a024df7ae2be125c2fa108721 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 15:51:26 -0400 Subject: [PATCH 03/25] fixed missing dependency --- PackageMonster/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/PackageMonster/Program.cs b/PackageMonster/Program.cs index 0388955..9947816 100644 --- a/PackageMonster/Program.cs +++ b/PackageMonster/Program.cs @@ -27,6 +27,7 @@ public static async Task Main(string[] args) { services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton, ArgParsingService>(); services.AddSingleton(); From 309d4a94644f0912cbfb8ba67c5fe84d3bdf0b6a Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 15:59:32 -0400 Subject: [PATCH 04/25] added missing dependency --- PackageMonster/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PackageMonster/Program.cs b/PackageMonster/Program.cs index 9947816..6fdc81f 100644 --- a/PackageMonster/Program.cs +++ b/PackageMonster/Program.cs @@ -3,6 +3,7 @@ // using System.Diagnostics.CodeAnalysis; +using System.IO.Abstractions; using PackageMonster.Services; namespace PackageMonster; @@ -29,6 +30,7 @@ public static async Task Main(string[] args) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton, ArgParsingService>(); services.AddSingleton(); services.AddSingleton(); From ed26ea1738237057155794fd6fcdc240d26fe45c Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 16:01:42 -0400 Subject: [PATCH 05/25] fixed missing dependency --- PackageMonster/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/PackageMonster/Program.cs b/PackageMonster/Program.cs index 6fdc81f..9786662 100644 --- a/PackageMonster/Program.cs +++ b/PackageMonster/Program.cs @@ -31,6 +31,7 @@ public static async Task Main(string[] args) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton, ArgParsingService>(); services.AddSingleton(); services.AddSingleton(); From 8a2d5320a94eced964c450236039f5a5dadf22cd Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 16:22:28 -0400 Subject: [PATCH 06/25] changed ACCEPT header to application/json --- PackageMonster/Services/NugetDataService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PackageMonster/Services/NugetDataService.cs b/PackageMonster/Services/NugetDataService.cs index f49cb99..33e5359 100644 --- a/PackageMonster/Services/NugetDataService.cs +++ b/PackageMonster/Services/NugetDataService.cs @@ -67,7 +67,7 @@ public async Task GetNugetVersions(string packageName, string source, versionsJsonPath = PublicNugetVersionsJsonPath; } - this.client.AcceptedContentTypes = new[] { "application/vnd.github.v3+json" }; + this.client.AcceptedContentTypes = new[] { "application/json" }; var resolvedUrl = source.Replace("PACKAGE-NAME", packageName); var request = new RestRequest(resolvedUrl); From 48fe2f32e2e8cbfac43b7f1f7563a150b9942d78 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 16:28:39 -0400 Subject: [PATCH 07/25] fixed documentation --- README.md | 10 ++++++---- action.yml | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 62b8401..25ed87b 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,12 @@ jobs: ## **➡️ Action Inputs ⬅️** -| Input Name | Description | Required | Default Value | -|---|:---------------------------------------------------------------------------|:---:|:---:| -| `package-name` | The name of the NuGet package. | yes | N/A | -| `version` | The version of the package. | yes | N/A | +| Input Name | Description | Required | Default Value | +|---|:-----------------------------------------------------------------------------------------------|:---:|:---:| +| `package-name` | The name of the NuGet package. | yes | N/A | +| `version` | The version of the package. | yes | N/A | +| `source` | The source repository to check. | no | https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json | +| `json-path` | The json path to extract the versions. | no | $.versions[*] | | `fail-when-not-found` | Will fail the job if the NuGet package of a specific version is not found. | no | false |
diff --git a/action.yml b/action.yml index badaf6f..5d916b0 100644 --- a/action.yml +++ b/action.yml @@ -11,6 +11,14 @@ inputs: version: description: 'The version of the NuGet package to check. This is not case-sensitive.' required: true + source: + description: 'The source repository to check.' + required: false + default: https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json + json-path: + description: 'The json path to extract the versions.' + required: false + default: $.versions[*] fail-when-not-found: description: 'If true, will fail the workflow if the NuGet package of the requested version does not exist.' required: false From ec13d798de171f65fa20fe5cfbdac5683c9d7121 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 17:00:30 -0400 Subject: [PATCH 08/25] added fail when found option --- PackageMonster/ActionInputs.cs | 10 +++++++ .../Exceptions/NugetFoundException.cs | 30 +++++++++++++++++++ PackageMonster/GitHubAction.cs | 5 ++++ README.md | 1 + .../PackageMonsterTests/ActionInputTests.cs | 5 ++++ .../PackageMonsterTests/GitHubActionTests.cs | 4 ++- action.yml | 6 ++++ 7 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 PackageMonster/Exceptions/NugetFoundException.cs diff --git a/PackageMonster/ActionInputs.cs b/PackageMonster/ActionInputs.cs index a0c0b2d..29518ed 100644 --- a/PackageMonster/ActionInputs.cs +++ b/PackageMonster/ActionInputs.cs @@ -59,4 +59,14 @@ public class ActionInputs Default = false, HelpText = "If true, will fail the workflow if the NuGet package of the requested version does not exist.")] public bool? FailWhenNotFound { get; set; } + + /// + /// Gets or sets a value indicating whether or not the action will fail if the package was found. + /// + [Option( + "fail-when-found", + Required = false, + Default = false, + HelpText = "If true, will fail the workflow if the NuGet package of the requested version does exist.")] + public bool? FailWhenFound { get; set; } } diff --git a/PackageMonster/Exceptions/NugetFoundException.cs b/PackageMonster/Exceptions/NugetFoundException.cs new file mode 100644 index 0000000..c31e52e --- /dev/null +++ b/PackageMonster/Exceptions/NugetFoundException.cs @@ -0,0 +1,30 @@ +namespace PackageMonster.Exceptions; + +/// +/// Occurs when a NuGet package is found. +/// +public class NugetFoundException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public NugetFoundException() + : base("The NuGet package was not found.") => HResult = 60; + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public NugetFoundException(string message) + : base(message) => HResult = 60; + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// + /// The instance that caused the current exception. + /// + public NugetFoundException(string message, Exception innerException) + : base(message, innerException) => HResult = 60; +} diff --git a/PackageMonster/GitHubAction.cs b/PackageMonster/GitHubAction.cs index 046ed81..97fb55c 100644 --- a/PackageMonster/GitHubAction.cs +++ b/PackageMonster/GitHubAction.cs @@ -65,6 +65,11 @@ public async Task Run(ActionInputs inputs, Action onCompleted, Action { throw new NugetNotFoundException(foundResultMsg); } + + if (inputs.FailWhenFound is true) + { + throw new NugetFoundException(foundResultMsg); + } } this.gitHubConsoleService.BlankLine(); diff --git a/README.md b/README.md index 25ed87b..58eba60 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ jobs: | `source` | The source repository to check. | no | https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json | | `json-path` | The json path to extract the versions. | no | $.versions[*] | | `fail-when-not-found` | Will fail the job if the NuGet package of a specific version is not found. | no | false | +| `fail-when-found` | Will fail the job if the NuGet package of a specific version is found. | no | false |
diff --git a/Testing/PackageMonsterTests/ActionInputTests.cs b/Testing/PackageMonsterTests/ActionInputTests.cs index 1c9d3a6..670ce60 100644 --- a/Testing/PackageMonsterTests/ActionInputTests.cs +++ b/Testing/PackageMonsterTests/ActionInputTests.cs @@ -38,6 +38,11 @@ public void Ctor_WhenConstructed_PropsHaveCorrectDefaultValuesAndDecoratedWithAt typeof(ActionInputs).GetProperty(nameof(ActionInputs.FailWhenNotFound)).Should().BeDecoratedWith(); inputs.GetAttrFromProp(nameof(ActionInputs.FailWhenNotFound)) .AssertOptionAttrProps("fail-when-not-found", false, false, "If true, will fail the workflow if the NuGet package of the requested version does not exist."); + + inputs.PackageName.Should().BeEmpty(); + typeof(ActionInputs).GetProperty(nameof(ActionInputs.FailWhenFound)).Should().BeDecoratedWith(); + inputs.GetAttrFromProp(nameof(ActionInputs.FailWhenFound)) + .AssertOptionAttrProps("fail-when-found", false, false, "If true, will fail the workflow if the NuGet package of the requested version does exist."); } #endregion } diff --git a/Testing/PackageMonsterTests/GitHubActionTests.cs b/Testing/PackageMonsterTests/GitHubActionTests.cs index b5922ad..cd500a3 100644 --- a/Testing/PackageMonsterTests/GitHubActionTests.cs +++ b/Testing/PackageMonsterTests/GitHubActionTests.cs @@ -160,11 +160,13 @@ public void Dispose_WhenInvoked_DisposesOfAction() private static ActionInputs CreateInputs( string packageName = "test-package", string version = "1.2.3", - bool? failWhenNotFound = true) => new () + bool? failWhenNotFound = true, + bool? failWhenFound = false) => new () { PackageName = packageName, Version = version, FailWhenNotFound = failWhenNotFound, + FailWhenFound = failWhenFound }; /// diff --git a/action.yml b/action.yml index 5d916b0..6f3c0cd 100644 --- a/action.yml +++ b/action.yml @@ -23,6 +23,10 @@ inputs: description: 'If true, will fail the workflow if the NuGet package of the requested version does not exist.' required: false default: false + fail-when-found: + description: 'If true, will fail the workflow if the NuGet package of the requested version does exist.' + required: false + default: false outputs: nuget-exists: @@ -43,3 +47,5 @@ runs: - ${{ inputs.json-path }} - '--fail-when-not-found' - ${{ inputs.fail-when-not-found }} + - '--fail-when-found' + - ${{ inputs.fail-when-found }} From 054df73dd5a645dbfbcc2d96349bb32ddcb3bc45 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 17:04:34 -0400 Subject: [PATCH 09/25] fixed fail logic --- PackageMonster/GitHubAction.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PackageMonster/GitHubAction.cs b/PackageMonster/GitHubAction.cs index 97fb55c..1e138ab 100644 --- a/PackageMonster/GitHubAction.cs +++ b/PackageMonster/GitHubAction.cs @@ -65,7 +65,9 @@ public async Task Run(ActionInputs inputs, Action onCompleted, Action { throw new NugetNotFoundException(foundResultMsg); } - + } + else + { if (inputs.FailWhenFound is true) { throw new NugetFoundException(foundResultMsg); From 3859077f319d3052cb886f8040875a5eb5532c8a Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 17:08:13 -0400 Subject: [PATCH 10/25] changed missing GITHUB_OUTPUT file to a warning --- .../Services/ActionOutputService.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/PackageMonster/Services/ActionOutputService.cs b/PackageMonster/Services/ActionOutputService.cs index 959337e..091665d 100644 --- a/PackageMonster/Services/ActionOutputService.cs +++ b/PackageMonster/Services/ActionOutputService.cs @@ -42,19 +42,21 @@ public void SetOutputValue(string name, string value) if (this.file.Exists(outputPath) is false) { - throw new FileNotFoundException("The GitHub output environment file was not found.", outputPath); + Console.WriteLine($"WARNING: The GitHub output environment file '{outputPath}' was not found.", outputPath); } + else + { + var outputLines = this.file.ReadAllLines(outputPath).ToList(); + outputLines.Add($"{name}={value}"); - var outputLines = this.file.ReadAllLines(outputPath).ToList(); - outputLines.Add($"{name}={value}"); + var fileContent = new StringBuilder(); - var fileContent = new StringBuilder(); + foreach (var line in outputLines) + { + fileContent.AppendLine(line); + } - foreach (var line in outputLines) - { - fileContent.AppendLine(line); + this.file.WriteAllText(outputPath, fileContent.ToString()); } - - this.file.WriteAllText(outputPath, fileContent.ToString()); } } From 1d174eb49dd98c32535011425fe4c8ea4e68d5ac Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 17:12:06 -0400 Subject: [PATCH 11/25] log a warning if the GITHUB_OUTPUT env variable is not set --- .../Services/ActionOutputService.cs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/PackageMonster/Services/ActionOutputService.cs b/PackageMonster/Services/ActionOutputService.cs index 091665d..e1f8233 100644 --- a/PackageMonster/Services/ActionOutputService.cs +++ b/PackageMonster/Services/ActionOutputService.cs @@ -40,23 +40,27 @@ public void SetOutputValue(string name, string value) var outputPath = this.envVarService.GetEnvironmentVariable(GitHubOutput); - if (this.file.Exists(outputPath) is false) + if (string.IsNullOrEmpty(outputPath)) { - Console.WriteLine($"WARNING: The GitHub output environment file '{outputPath}' was not found.", outputPath); + Console.WriteLine("WARNING: The GitHub output environment file was not specified."); + return; } - else + + if (this.file.Exists(outputPath) is false) { - var outputLines = this.file.ReadAllLines(outputPath).ToList(); - outputLines.Add($"{name}={value}"); + throw new FileNotFoundException("The GitHub output environment file was not found.", outputPath); + } - var fileContent = new StringBuilder(); + var outputLines = this.file.ReadAllLines(outputPath).ToList(); + outputLines.Add($"{name}={value}"); - foreach (var line in outputLines) - { - fileContent.AppendLine(line); - } + var fileContent = new StringBuilder(); - this.file.WriteAllText(outputPath, fileContent.ToString()); + foreach (var line in outputLines) + { + fileContent.AppendLine(line); } + + this.file.WriteAllText(outputPath, fileContent.ToString()); } } From a09dce798bbac72a9c4dcdafbcb3ea6f76567608 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 17:13:07 -0400 Subject: [PATCH 12/25] improved log message --- PackageMonster/Services/ActionOutputService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PackageMonster/Services/ActionOutputService.cs b/PackageMonster/Services/ActionOutputService.cs index e1f8233..73bed02 100644 --- a/PackageMonster/Services/ActionOutputService.cs +++ b/PackageMonster/Services/ActionOutputService.cs @@ -42,7 +42,7 @@ public void SetOutputValue(string name, string value) if (string.IsNullOrEmpty(outputPath)) { - Console.WriteLine("WARNING: The GitHub output environment file was not specified."); + Console.WriteLine($"WARNING: The GitHub output environment file variable '{GitHubOutput}' was not specified."); return; } From 2cccae575aec1d4a04f95e88c8a38417a0bbcaf7 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Mon, 22 May 2023 17:55:21 -0400 Subject: [PATCH 13/25] - removed specific mentions of "Nuget" - changed output variable from "nuget-exists" to "result" --- Dockerfile | 2 +- PackageMonster/ActionInputs.cs | 14 ++--- .../Exceptions/NugetNotFoundException.cs | 34 ----------- ...dException.cs => PackageFoundException.cs} | 18 +++--- .../Exceptions/PackageNotFoundException.cs | 34 +++++++++++ PackageMonster/GitHubAction.cs | 20 +++---- PackageMonster/PackageMonster.csproj | 2 +- PackageMonster/Program.cs | 2 +- .../{NugetDataService.cs => DataService.cs} | 25 ++++---- PackageMonster/Services/IDataService.cs | 21 +++++++ PackageMonster/Services/INugetDataService.cs | 21 ------- README.md | 58 ++++++++++++++++--- .../PackageMonsterTests/ActionInputTests.cs | 6 +- ...ts.cs => PackageNotFoundExceptionTests.cs} | 14 ++--- .../PackageMonsterTests/GitHubActionTests.cs | 24 ++++---- ...lTests.cs => PackageVersionsModelTests.cs} | 6 +- action.yml | 12 ++-- 17 files changed, 177 insertions(+), 136 deletions(-) delete mode 100644 PackageMonster/Exceptions/NugetNotFoundException.cs rename PackageMonster/Exceptions/{NugetFoundException.cs => PackageFoundException.cs} (50%) create mode 100644 PackageMonster/Exceptions/PackageNotFoundException.cs rename PackageMonster/Services/{NugetDataService.cs => DataService.cs} (79%) create mode 100644 PackageMonster/Services/IDataService.cs delete mode 100644 PackageMonster/Services/INugetDataService.cs rename Testing/PackageMonsterTests/Exceptions/{NugetNotFoundExceptionTests.cs => PackageNotFoundExceptionTests.cs} (69%) rename Testing/PackageMonsterTests/Models/{NugetVersionsModelTests.cs => PackageVersionsModelTests.cs} (75%) diff --git a/Dockerfile b/Dockerfile index 63104bf..30e1acf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ LABEL repository="https://github.com/KinsonDigital/PackageMonster" LABEL homepage="https://github.com/KinsonDigital/PackageMonster" # Label as GitHub action -LABEL com.github.actions.name="Got Nuget" +LABEL com.github.actions.name="Got Package" # Relayer the .NET SDK, anew with the build output FROM mcr.microsoft.com/dotnet/sdk:7.0 diff --git a/PackageMonster/ActionInputs.cs b/PackageMonster/ActionInputs.cs index 29518ed..2e64556 100644 --- a/PackageMonster/ActionInputs.cs +++ b/PackageMonster/ActionInputs.cs @@ -21,7 +21,7 @@ public class ActionInputs public string PackageName { get; set; } = string.Empty; /// - /// Gets or sets the NuGet package version to check. + /// Gets or sets the package version to check. /// /// /// Version search is not case-sensitive. @@ -29,16 +29,16 @@ public class ActionInputs [Option( "version", Required = true, - HelpText = "The version of the NuGet package to check. This is not case-sensitive.")] + HelpText = "The version of the package to check. This is not case-sensitive.")] public string Version { get; set; } = string.Empty; /// - /// Gets or sets the NuGet repository. + /// Gets or sets the repository. /// [Option( "source", Required = false, - HelpText = $"The source repository to check. Defaults to `{NugetDataService.PublicNugetApiUrl}`.")] + HelpText = $"The source repository to check. Defaults to `{DataService.PublicNugetApiUrl}`.")] public string Source { get; set; } = string.Empty; /// @@ -47,7 +47,7 @@ public class ActionInputs [Option( "json-path", Required = false, - HelpText = $"The json path to the versions. Defaults to `{NugetDataService.PublicNugetVersionsJsonPath}`.")] + HelpText = $"The json path to the versions. Defaults to `{DataService.PublicNugetVersionsJsonPath}`.")] public string VersionsJsonPath { get; set; } = string.Empty; /// @@ -57,7 +57,7 @@ public class ActionInputs "fail-when-not-found", Required = false, Default = false, - HelpText = "If true, will fail the workflow if the NuGet package of the requested version does not exist.")] + HelpText = "If true, will fail the workflow if the package of the requested version does not exist.")] public bool? FailWhenNotFound { get; set; } /// @@ -67,6 +67,6 @@ public class ActionInputs "fail-when-found", Required = false, Default = false, - HelpText = "If true, will fail the workflow if the NuGet package of the requested version does exist.")] + HelpText = "If true, will fail the workflow if the package of the requested version does exist.")] public bool? FailWhenFound { get; set; } } diff --git a/PackageMonster/Exceptions/NugetNotFoundException.cs b/PackageMonster/Exceptions/NugetNotFoundException.cs deleted file mode 100644 index c608558..0000000 --- a/PackageMonster/Exceptions/NugetNotFoundException.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) KinsonDigital. All rights reserved. -// - -namespace PackageMonster.Exceptions; - -/// -/// Occurs when a NuGet package is not found. -/// -public class NugetNotFoundException : Exception -{ - /// - /// Initializes a new instance of the class. - /// - public NugetNotFoundException() - : base("The NuGet package was not found.") => HResult = 60; - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public NugetNotFoundException(string message) - : base(message) => HResult = 60; - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - /// - /// The instance that caused the current exception. - /// - public NugetNotFoundException(string message, Exception innerException) - : base(message, innerException) => HResult = 60; -} diff --git a/PackageMonster/Exceptions/NugetFoundException.cs b/PackageMonster/Exceptions/PackageFoundException.cs similarity index 50% rename from PackageMonster/Exceptions/NugetFoundException.cs rename to PackageMonster/Exceptions/PackageFoundException.cs index c31e52e..8efdd8c 100644 --- a/PackageMonster/Exceptions/NugetFoundException.cs +++ b/PackageMonster/Exceptions/PackageFoundException.cs @@ -1,30 +1,30 @@ namespace PackageMonster.Exceptions; /// -/// Occurs when a NuGet package is found. +/// Occurs when a package is found. /// -public class NugetFoundException : Exception +public class PackageFoundException : Exception { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public NugetFoundException() - : base("The NuGet package was not found.") => HResult = 60; + public PackageFoundException() + : base("The package was not found.") => HResult = 60; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The message that describes the error. - public NugetFoundException(string message) + public PackageFoundException(string message) : base(message) => HResult = 60; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The message that describes the error. /// /// The instance that caused the current exception. /// - public NugetFoundException(string message, Exception innerException) + public PackageFoundException(string message, Exception innerException) : base(message, innerException) => HResult = 60; } diff --git a/PackageMonster/Exceptions/PackageNotFoundException.cs b/PackageMonster/Exceptions/PackageNotFoundException.cs new file mode 100644 index 0000000..4478de5 --- /dev/null +++ b/PackageMonster/Exceptions/PackageNotFoundException.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) KinsonDigital. All rights reserved. +// + +namespace PackageMonster.Exceptions; + +/// +/// Occurs when a package is not found. +/// +public class PackageNotFoundException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public PackageNotFoundException() + : base("The package was not found.") => HResult = 60; + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public PackageNotFoundException(string message) + : base(message) => HResult = 60; + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// + /// The instance that caused the current exception. + /// + public PackageNotFoundException(string message, Exception innerException) + : base(message, innerException) => HResult = 60; +} diff --git a/PackageMonster/GitHubAction.cs b/PackageMonster/GitHubAction.cs index 1e138ab..9bce326 100644 --- a/PackageMonster/GitHubAction.cs +++ b/PackageMonster/GitHubAction.cs @@ -11,7 +11,7 @@ namespace PackageMonster; public sealed class GitHubAction : IGitHubAction { private readonly IGitHubConsoleService gitHubConsoleService; - private readonly INugetDataService nugetDataService; + private readonly IDataService dataService; private readonly IActionOutputService actionOutputService; private bool isDisposed; @@ -19,15 +19,15 @@ public sealed class GitHubAction : IGitHubAction /// Initializes a new instance of the class. /// /// Writes to the console. - /// Provides access to NuGet data. + /// Provides access to data. /// Sets the output data of the action. public GitHubAction( IGitHubConsoleService gitHubConsoleService, - INugetDataService nugetDataService, + IDataService dataService, IActionOutputService actionOutputService) { this.gitHubConsoleService = gitHubConsoleService; - this.nugetDataService = nugetDataService; + this.dataService = dataService; this.actionOutputService = actionOutputService; } @@ -39,7 +39,7 @@ public async Task Run(ActionInputs inputs, Action onCompleted, Action try { this.gitHubConsoleService.Write($"Searching for package '{inputs.PackageName} v{inputs.Version}' . . . "); - var versions = await this.nugetDataService.GetNugetVersions(inputs.PackageName, inputs.Source, inputs.VersionsJsonPath); + var versions = await this.dataService.GetVersions(inputs.PackageName, inputs.Source, inputs.VersionsJsonPath); var versionFound = versions .Any(version => @@ -50,27 +50,27 @@ public async Task Run(ActionInputs inputs, Action onCompleted, Action this.gitHubConsoleService.WriteLine(searchEndMsg); this.gitHubConsoleService.BlankLine(); - this.actionOutputService.SetOutputValue("nuget-exists", versionFound.ToString().ToLower()); + this.actionOutputService.SetOutputValue("result", versionFound.ToString().ToLower()); var emoji = inputs.FailWhenNotFound is false ? "✅" : string.Empty; - var foundResultMsg = $"{emoji}The NuGet package '{inputs.PackageName}'"; + var foundResultMsg = $"{emoji}The package '{inputs.PackageName}'"; foundResultMsg += $" with the version 'v{inputs.Version}' was{(versionFound ? string.Empty : " not")} found."; if (versionFound is false) { if (inputs.FailWhenNotFound is true) { - throw new NugetNotFoundException(foundResultMsg); + throw new PackageNotFoundException(foundResultMsg); } } else { if (inputs.FailWhenFound is true) { - throw new NugetFoundException(foundResultMsg); + throw new PackageFoundException(foundResultMsg); } } @@ -94,7 +94,7 @@ public void Dispose() return; } - this.nugetDataService.Dispose(); + this.dataService.Dispose(); this.isDisposed = true; } diff --git a/PackageMonster/PackageMonster.csproj b/PackageMonster/PackageMonster.csproj index 50c0ad8..1ac670d 100644 --- a/PackageMonster/PackageMonster.csproj +++ b/PackageMonster/PackageMonster.csproj @@ -16,7 +16,7 @@ Calvin Wilkinson Kinson Digital PackageMonster - Custom GitHub action used to check if nuget.org contains a NuGet package of a particular version. + Custom GitHub action used to check if a package repository, like nuget.org, contains a package of a particular version. Copyright ©2023 Kinson Digital diff --git a/PackageMonster/Program.cs b/PackageMonster/Program.cs index 9786662..7f55244 100644 --- a/PackageMonster/Program.cs +++ b/PackageMonster/Program.cs @@ -33,7 +33,7 @@ public static async Task Main(string[] args) services.AddSingleton(); services.AddSingleton(); services.AddSingleton, ArgParsingService>(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); }).Build(); diff --git a/PackageMonster/Services/NugetDataService.cs b/PackageMonster/Services/DataService.cs similarity index 79% rename from PackageMonster/Services/NugetDataService.cs rename to PackageMonster/Services/DataService.cs index 33e5359..33ab9cc 100644 --- a/PackageMonster/Services/NugetDataService.cs +++ b/PackageMonster/Services/DataService.cs @@ -1,10 +1,9 @@ -// +// // Copyright (c) KinsonDigital. All rights reserved. // using System.Diagnostics.CodeAnalysis; using System.Net; -using System.Text.Json.Nodes; using Newtonsoft.Json.Linq; using RestSharp; @@ -12,12 +11,12 @@ namespace PackageMonster.Services; /// [ExcludeFromCodeCoverage] -public sealed class NugetDataService : INugetDataService +public sealed class DataService : IDataService { /* Resources: - * These links refer to the documentation for the NuGet API + * These links refer to the documentation for the Nuget API * 1. Package Content: https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource - * 2.Nuget Server API: https://docs.microsoft.com/en-us/nuget/api/overview + * 2. Server API: https://docs.microsoft.com/en-us/nuget/api/overview */ internal const string PublicNugetApiUrl = "https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json"; internal const string PublicNugetVersionsJsonPath = "$.versions[*]"; @@ -25,13 +24,13 @@ public sealed class NugetDataService : INugetDataService private bool isDisposed; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public NugetDataService() => this.client = new RestClient(PublicNugetApiUrl); + public DataService() => this.client = new RestClient(); /// /// - /// The param is not case sensitive. The NuGet API + /// The param is not case sensitive. The API /// requires that it is in lowercase. This is taken care of for you. /// /// @@ -40,11 +39,11 @@ public sealed class NugetDataService : INugetDataService /// /// Thrown if any HTTP based error occurs. /// - public async Task GetNugetVersions(string packageName, string source, string versionsJsonPath) + public async Task GetVersions(string packageName, string source, string versionsJsonPath) { if (string.IsNullOrEmpty(packageName)) { - throw new ArgumentNullException(nameof(packageName), $"Must provide a NuGet package name."); + throw new ArgumentNullException(nameof(packageName), $"Must provide a package name."); } if (string.IsNullOrWhiteSpace(source)) @@ -54,12 +53,12 @@ public async Task GetNugetVersions(string packageName, string source, if (!Uri.IsWellFormedUriString(source, UriKind.Absolute)) { - throw new ArgumentException(nameof(source), $"Must provide a well-formed NuGet source URI."); + throw new ArgumentException(nameof(source), $"Must provide a well-formed source URI."); } if (!Uri.TryCreate(source, UriKind.Absolute, out _)) { - throw new ArgumentException(nameof(source), $"Must provide an absolute NuGet source URI."); + throw new ArgumentException(nameof(source), $"Must provide an absolute source URI."); } if (string.IsNullOrWhiteSpace(versionsJsonPath)) @@ -85,7 +84,7 @@ public async Task GetNugetVersions(string packageName, string source, return json.SelectTokens(versionsJsonPath).Select(t => t.Value()).ToArray(); } - var exception = response.ErrorException ?? new Exception("There was an issue getting data from NuGet."); + var exception = response.ErrorException ?? new Exception($"There was an issue getting data from '{source}'."); throw new HttpRequestException( exception.Message, diff --git a/PackageMonster/Services/IDataService.cs b/PackageMonster/Services/IDataService.cs new file mode 100644 index 0000000..449d79a --- /dev/null +++ b/PackageMonster/Services/IDataService.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) KinsonDigital. All rights reserved. +// + +namespace PackageMonster.Services; + +/// +/// Provides access to data from a package marketplace. +/// +public interface IDataService : IDisposable +{ + /// + /// Gets all of the versions of a package that have been published + /// using the given . + /// + /// The name of the package. + /// The source. + /// The versions json path. + /// The list of versions that exist for the package. + Task GetVersions(string packageName, string source, string versionsJsonPath); +} diff --git a/PackageMonster/Services/INugetDataService.cs b/PackageMonster/Services/INugetDataService.cs deleted file mode 100644 index 6aa7095..0000000 --- a/PackageMonster/Services/INugetDataService.cs +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) KinsonDigital. All rights reserved. -// - -namespace PackageMonster.Services; - -/// -/// Provides access to data from nuget.org marketplace. -/// -public interface INugetDataService : IDisposable -{ - /// - /// Gets all of the versions of a NuGet package that have been published - /// to nuget.org using the given . - /// - /// The name of the package. - /// The nuget source. - /// The versions json path. - /// The list of versions that exist for the package. - Task GetNugetVersions(string packageName, string source, string versionsJsonPath); -} diff --git a/README.md b/README.md index 58eba60..7e3e6b7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ ## **🤷🏼‍♂️ What is it? 🤷🏼‍♂️**
-### This GitHub action checks whether or not a NuGet package with a particular name and version exists in the public NuGet gallery package repository [nuget.org](https://www.nuget.org). +### This GitHub action checks whether or not a package with a particular name and version exists in a public gallery package repository like [nuget.org](https://www.nuget.org).
@@ -44,7 +44,7 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Check If Nuget Package Exists + - name: Check If Package Exists id: nuget-exists uses: KinsonDigital/PackageMonster@v1.0.0-preview.1 with: @@ -61,9 +61,51 @@ jobs: $nugetExists = "${{ steps.nuget-exists.outputs.nuget-exists }}"; if ($nugetExists -eq "true") { - Write-Host "The NuGet package exists!!"; + Write-Host "The package exists!!"; } else { - Write-Host "The NuGet package does not exist!!"; + Write-Host "The package does not exist!!"; + } +``` + +--- + +

🪧 Example 🪧

+ +```yaml +name: Package Monster Action Sample + +on: + workflow_dispatch: + +jobs: + Test_Action:em + name: Test Package Monster GitHub Action + runs-on: ubuntu-latest 👈🏼 # Required (Refer to the note above) + steps: + - uses: actions/checkout@v3 + + - name: Check If NPM Package Exists + id: npm-exists + uses: KinsonDigital/PackageMonster@v1.0.0-preview.1 + with: + package-name: MyPackage 👈🏻 # Required input + version: 1.2.3 👈🏻 # Required input + source: https://registry.npmjs.org/PACKAGE-NAME + json-path: $.versions[*].version + fail-when-not-found: true 👈🏻 # Optional input + + - name: Print Output Result #PowerShell Core + shell: pwsh 👈🏼 # Must be explicit with the shell to use PowerShell on Ubuntu + run: | + # Output name for the Package Monster GitHub action 👇🏼 + # _____|_____ + # | | + $npmExists = "${{ steps.npm-exists.outputs.nuget-exists }}"; + + if ($nugetExists -eq "true") { + Write-Host "The package exists!!"; + } else { + Write-Host "The package does not exist!!"; } ``` @@ -76,12 +118,12 @@ jobs: | Input Name | Description | Required | Default Value | |---|:-----------------------------------------------------------------------------------------------|:---:|:---:| -| `package-name` | The name of the NuGet package. | yes | N/A | +| `package-name` | The name of the package. | yes | N/A | | `version` | The version of the package. | yes | N/A | | `source` | The source repository to check. | no | https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json | | `json-path` | The json path to extract the versions. | no | $.versions[*] | -| `fail-when-not-found` | Will fail the job if the NuGet package of a specific version is not found. | no | false | -| `fail-when-found` | Will fail the job if the NuGet package of a specific version is found. | no | false | +| `fail-when-not-found` | Will fail the job if the package of a specific version is not found. | no | false | +| `fail-when-found` | Will fail the job if the package of a specific version is found. | no | false |
@@ -90,7 +132,7 @@ jobs: ## **⬅️ Action Output ➡️**
-The name of the output is `nuget-exists` and it returns a `boolean` of `true` or `false`. +The name of the output is `result` and it returns a `boolean` of `true` or `false`. Refer to the **Example** above for how to use the output of the action. --- diff --git a/Testing/PackageMonsterTests/ActionInputTests.cs b/Testing/PackageMonsterTests/ActionInputTests.cs index 670ce60..bcdff88 100644 --- a/Testing/PackageMonsterTests/ActionInputTests.cs +++ b/Testing/PackageMonsterTests/ActionInputTests.cs @@ -32,17 +32,17 @@ public void Ctor_WhenConstructed_PropsHaveCorrectDefaultValuesAndDecoratedWithAt inputs.PackageName.Should().BeEmpty(); typeof(ActionInputs).GetProperty(nameof(ActionInputs.Version)).Should().BeDecoratedWith(); inputs.GetAttrFromProp(nameof(ActionInputs.Version)) - .AssertOptionAttrProps("version", true, "The version of the NuGet package to check. This is not case-sensitive."); + .AssertOptionAttrProps("version", true, "The version of the package to check. This is not case-sensitive."); inputs.PackageName.Should().BeEmpty(); typeof(ActionInputs).GetProperty(nameof(ActionInputs.FailWhenNotFound)).Should().BeDecoratedWith(); inputs.GetAttrFromProp(nameof(ActionInputs.FailWhenNotFound)) - .AssertOptionAttrProps("fail-when-not-found", false, false, "If true, will fail the workflow if the NuGet package of the requested version does not exist."); + .AssertOptionAttrProps("fail-when-not-found", false, false, "If true, will fail the workflow if the package of the requested version does not exist."); inputs.PackageName.Should().BeEmpty(); typeof(ActionInputs).GetProperty(nameof(ActionInputs.FailWhenFound)).Should().BeDecoratedWith(); inputs.GetAttrFromProp(nameof(ActionInputs.FailWhenFound)) - .AssertOptionAttrProps("fail-when-found", false, false, "If true, will fail the workflow if the NuGet package of the requested version does exist."); + .AssertOptionAttrProps("fail-when-found", false, false, "If true, will fail the workflow if the package of the requested version does exist."); } #endregion } diff --git a/Testing/PackageMonsterTests/Exceptions/NugetNotFoundExceptionTests.cs b/Testing/PackageMonsterTests/Exceptions/PackageNotFoundExceptionTests.cs similarity index 69% rename from Testing/PackageMonsterTests/Exceptions/NugetNotFoundExceptionTests.cs rename to Testing/PackageMonsterTests/Exceptions/PackageNotFoundExceptionTests.cs index d1f86c2..d3f99bd 100644 --- a/Testing/PackageMonsterTests/Exceptions/NugetNotFoundExceptionTests.cs +++ b/Testing/PackageMonsterTests/Exceptions/PackageNotFoundExceptionTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) KinsonDigital. All rights reserved. // @@ -8,19 +8,19 @@ namespace PackageMonsterTests.Exceptions; using PackageMonster.Exceptions; /// -/// Tests the class. +/// Tests the class. /// -public class NugetNotFoundExceptionTests +public class PackageNotFoundExceptionTests { #region Constructor Tests [Fact] public void Ctor_WithNoParam_CorrectlySetsExceptionMessage() { // Act - var exception = new NugetNotFoundException(); + var exception = new PackageNotFoundException(); // Assert - exception.Message.Should().Be("The NuGet package was not found."); + exception.Message.Should().Be("The package was not found."); exception.HResult.Should().Be(60); } @@ -28,7 +28,7 @@ public void Ctor_WithNoParam_CorrectlySetsExceptionMessage() public void Ctor_WhenInvokedWithSingleMessageParam_CorrectlySetsMessage() { // Act - var exception = new NugetNotFoundException("test-message"); + var exception = new PackageNotFoundException("test-message"); // Assert exception.Message.Should().Be("test-message"); @@ -42,7 +42,7 @@ public void Ctor_WhenInvokedWithMessageAndInnerException_ThrowsException() var innerException = new Exception("inner-exception"); // Act - var deviceException = new NugetNotFoundException("test-exception", innerException); + var deviceException = new PackageNotFoundException("test-exception", innerException); // Assert deviceException.InnerException.Message.Should().Be("inner-exception"); diff --git a/Testing/PackageMonsterTests/GitHubActionTests.cs b/Testing/PackageMonsterTests/GitHubActionTests.cs index cd500a3..e02540b 100644 --- a/Testing/PackageMonsterTests/GitHubActionTests.cs +++ b/Testing/PackageMonsterTests/GitHubActionTests.cs @@ -14,7 +14,7 @@ namespace PackageMonsterTests; public class GitHubActionTests { private readonly Mock mockConsoleService; - private readonly Mock mockDataService; + private readonly Mock mockDataService; private readonly Mock mockActionOutputService; /// @@ -23,7 +23,7 @@ public class GitHubActionTests public GitHubActionTests() { this.mockConsoleService = new Mock(); - this.mockDataService = new Mock(); + this.mockDataService = new Mock(); this.mockActionOutputService = new Mock(); } @@ -46,7 +46,7 @@ public async void Run_WhenInvoked_ShowsWelcomeMessage() [InlineData("test-package", "4.5.6", "true")] [InlineData("TEST-PACKAGE", "4.5.6", "true")] [InlineData("test-package", "7.8.9", "false")] - public async void Run_WhenNugetPackageWithVersionExists_SetsOutputToCorrectValue( + public async void Run_WhenPackageWithVersionExists_SetsOutputToCorrectValue( string packageName, string version, string expectedOutput) @@ -55,7 +55,7 @@ public async void Run_WhenNugetPackageWithVersionExists_SetsOutputToCorrectValue var expectedSearchMsgStart = $"Searching for package '{packageName} v{version}' . . . "; var expectedSearchMsgEnd = expectedOutput == "true" ? "package found!!" : "package not found!!"; - this.mockDataService.Setup(m => m.GetNugetVersions(packageName, It.IsAny(), It.IsAny())) + this.mockDataService.Setup(m => m.GetVersions(packageName, It.IsAny(), It.IsAny())) .ReturnsAsync(new[] { "1.2.3", "4.5.6" }); var onCompletedInvoked = false; @@ -67,14 +67,14 @@ public async void Run_WhenNugetPackageWithVersionExists_SetsOutputToCorrectValue // Assert await act.Should().NotThrowAsync(); - var expectedResultMsg = $"✅The NuGet package '{packageName}'"; + var expectedResultMsg = $"✅The package '{packageName}'"; expectedResultMsg += $" with the version 'v{version}' was{(expectedOutput == "false" ? " not" : string.Empty)} found."; this.mockConsoleService.VerifyOnce(m => m.Write(expectedSearchMsgStart, false)); this.mockConsoleService.VerifyOnce(m => m.WriteLine(expectedSearchMsgEnd)); this.mockConsoleService.VerifyOnce(m => m.WriteLine(expectedResultMsg)); this.mockConsoleService.Verify(m => m.BlankLine(), Times.Exactly(4)); - this.mockActionOutputService.VerifyOnce(m => m.SetOutputValue("nuget-exists", expectedOutput)); + this.mockActionOutputService.VerifyOnce(m => m.SetOutputValue("result", expectedOutput)); onCompletedInvoked.Should().BeTrue("the 'onCompleted()' was never invoked"); } @@ -84,7 +84,7 @@ public async void Run_WhenPackageIsNotFoundWithFailSetToTrue_ThrowsExceptionWith // Arrange var inputs = CreateInputs(failWhenNotFound: true); - this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny(), It.IsAny(), It.IsAny())) + this.mockDataService.Setup(m => m.GetVersions(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(Array.Empty()); var action = CreateAction(); @@ -94,8 +94,8 @@ public async void Run_WhenPackageIsNotFoundWithFailSetToTrue_ThrowsExceptionWith // Assert await act.Should() - .ThrowAsync() - .WithMessage($"The NuGet package '{inputs.PackageName}' with the version 'v{inputs.Version}' was not found."); + .ThrowAsync() + .WithMessage($"The package '{inputs.PackageName}' with the version 'v{inputs.Version}' was not found."); } [Theory] @@ -106,7 +106,7 @@ public async void Run_WhenPackageIsNotFoundWithFailSetToFalseOrNull_DoesNotThrow // Arrange var inputs = CreateInputs(failWhenNotFound: failWhenNotFound); - this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny(), It.IsAny(), It.IsAny())) + this.mockDataService.Setup(m => m.GetVersions(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(Array.Empty()); var action = CreateAction(); @@ -116,14 +116,14 @@ public async void Run_WhenPackageIsNotFoundWithFailSetToFalseOrNull_DoesNotThrow // Assert await act.Should() - .NotThrowAsync(); + .NotThrowAsync(); } [Fact] public async void Run_WhenAnExceptionIsThrown_InvokesOnErrorActionParam() { // Arrange - this.mockDataService.Setup(m => m.GetNugetVersions(It.IsAny(), It.IsAny(), It.IsAny())) + this.mockDataService.Setup(m => m.GetVersions(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((_, _, _) => throw new Exception("test-exception")); Exception? exception = null; diff --git a/Testing/PackageMonsterTests/Models/NugetVersionsModelTests.cs b/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs similarity index 75% rename from Testing/PackageMonsterTests/Models/NugetVersionsModelTests.cs rename to Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs index 7f60c68..c522e85 100644 --- a/Testing/PackageMonsterTests/Models/NugetVersionsModelTests.cs +++ b/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) KinsonDigital. All rights reserved. // @@ -14,7 +14,7 @@ namespace PackageMonsterTests.Models; /// /// Tests the versions Json Path functionality. /// -public class NugetVersionsModelTests +public class PackageVersionsModelTests { #region Prop Tests [Fact] @@ -24,7 +24,7 @@ public void Version_WhenSettingValue_ReturnsCorrectResult() var model = JObject.Parse(@"{ 'versions': [""1.2.3"", ""4.5.6""] }"); // Act - var actual = model.SelectTokens(NugetDataService.PublicNugetVersionsJsonPath).Select(v => v.Value()).ToArray(); + var actual = model.SelectTokens(DataService.PublicNugetVersionsJsonPath).Select(v => v.Value()).ToArray(); // Assert actual.Should() diff --git a/action.yml b/action.yml index 6f3c0cd..1422636 100644 --- a/action.yml +++ b/action.yml @@ -1,5 +1,5 @@ name: 'PackageMonster' -description: 'Checks if a NuGet package exists in the nuget.org public repository.' +description: 'Checks if a package exists in the a public repository.' author: 'Calvin Wilkinson (KinsonDigital)' branding: icon: settings @@ -9,7 +9,7 @@ inputs: description: 'The name of the package. This is not case-sensitive.' required: true version: - description: 'The version of the NuGet package to check. This is not case-sensitive.' + description: 'The version of the package to check. This is not case-sensitive.' required: true source: description: 'The source repository to check.' @@ -20,17 +20,17 @@ inputs: required: false default: $.versions[*] fail-when-not-found: - description: 'If true, will fail the workflow if the NuGet package of the requested version does not exist.' + description: 'If true, will fail the workflow if the package of the requested version does not exist.' required: false default: false fail-when-found: - description: 'If true, will fail the workflow if the NuGet package of the requested version does exist.' + description: 'If true, will fail the workflow if the package of the requested version does exist.' required: false default: false outputs: - nuget-exists: - description: 'True if the NuGet package exists.' + result: + description: 'True if the package exists.' # These are the arguments that are passed into the console app runs: From efdaa505f237db935c17ecb907a6872e2ff3d93b Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 10:20:19 -0400 Subject: [PATCH 14/25] renamed output variable to "package-exists" --- PackageMonster/GitHubAction.cs | 2 +- action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PackageMonster/GitHubAction.cs b/PackageMonster/GitHubAction.cs index 9bce326..93b0b23 100644 --- a/PackageMonster/GitHubAction.cs +++ b/PackageMonster/GitHubAction.cs @@ -50,7 +50,7 @@ public async Task Run(ActionInputs inputs, Action onCompleted, Action this.gitHubConsoleService.WriteLine(searchEndMsg); this.gitHubConsoleService.BlankLine(); - this.actionOutputService.SetOutputValue("result", versionFound.ToString().ToLower()); + this.actionOutputService.SetOutputValue("package-exists", versionFound.ToString().ToLower()); var emoji = inputs.FailWhenNotFound is false ? "✅" diff --git a/action.yml b/action.yml index 1422636..235cc87 100644 --- a/action.yml +++ b/action.yml @@ -29,7 +29,7 @@ inputs: default: false outputs: - result: + package-exists: description: 'True if the package exists.' # These are the arguments that are passed into the console app From d0e7eed647a6e078f987da23d2d850a28bc712b8 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 11:00:06 -0400 Subject: [PATCH 15/25] improved options for the "source" parameter --- PackageMonster/ActionInputs.cs | 4 +- .../Repositories/CustomPackageRepository.cs | 7 +++ .../Repositories/IPackageRepository.cs | 7 +++ .../Repositories/NpmPackageRepository.cs | 7 +++ .../Repositories/NugetPackageRepository.cs | 12 +++++ PackageMonster/Services/DataService.cs | 51 +++++++++++-------- README.md | 33 ++++++------ .../PackageMonsterTests/GitHubActionTests.cs | 2 +- .../Models/PackageVersionsModelTests.cs | 4 +- action.yml | 11 ++-- 10 files changed, 90 insertions(+), 48 deletions(-) create mode 100644 PackageMonster/Repositories/CustomPackageRepository.cs create mode 100644 PackageMonster/Repositories/IPackageRepository.cs create mode 100644 PackageMonster/Repositories/NpmPackageRepository.cs create mode 100644 PackageMonster/Repositories/NugetPackageRepository.cs diff --git a/PackageMonster/ActionInputs.cs b/PackageMonster/ActionInputs.cs index 2e64556..26a45a7 100644 --- a/PackageMonster/ActionInputs.cs +++ b/PackageMonster/ActionInputs.cs @@ -38,7 +38,7 @@ public class ActionInputs [Option( "source", Required = false, - HelpText = $"The source repository to check. Defaults to `{DataService.PublicNugetApiUrl}`.")] + HelpText = "The source repository to check. Defaults to `nuget`.")] public string Source { get; set; } = string.Empty; /// @@ -47,7 +47,7 @@ public class ActionInputs [Option( "json-path", Required = false, - HelpText = $"The json path to the versions. Defaults to `{DataService.PublicNugetVersionsJsonPath}`.")] + HelpText = "The json path to the versions.")] public string VersionsJsonPath { get; set; } = string.Empty; /// diff --git a/PackageMonster/Repositories/CustomPackageRepository.cs b/PackageMonster/Repositories/CustomPackageRepository.cs new file mode 100644 index 0000000..3e60f0f --- /dev/null +++ b/PackageMonster/Repositories/CustomPackageRepository.cs @@ -0,0 +1,7 @@ +namespace PackageMonster.Repositories; + +internal class CustomPackageRepository : IPackageRepository +{ + public string Url { get; set; } + public string JsonPath { get; set; } +} diff --git a/PackageMonster/Repositories/IPackageRepository.cs b/PackageMonster/Repositories/IPackageRepository.cs new file mode 100644 index 0000000..d38f059 --- /dev/null +++ b/PackageMonster/Repositories/IPackageRepository.cs @@ -0,0 +1,7 @@ +namespace PackageMonster.Repositories; + +internal interface IPackageRepository +{ + string Url { get; } + string JsonPath { get; } +} diff --git a/PackageMonster/Repositories/NpmPackageRepository.cs b/PackageMonster/Repositories/NpmPackageRepository.cs new file mode 100644 index 0000000..b226d84 --- /dev/null +++ b/PackageMonster/Repositories/NpmPackageRepository.cs @@ -0,0 +1,7 @@ +namespace PackageMonster.Repositories; + +internal class NpmPackageRepository : IPackageRepository +{ + public string Url => "https://registry.npmjs.org/PACKAGE-NAME"; + public string JsonPath => "$.versions[*].version"; +} diff --git a/PackageMonster/Repositories/NugetPackageRepository.cs b/PackageMonster/Repositories/NugetPackageRepository.cs new file mode 100644 index 0000000..5fedd58 --- /dev/null +++ b/PackageMonster/Repositories/NugetPackageRepository.cs @@ -0,0 +1,12 @@ +namespace PackageMonster.Repositories; + +/* Resources: + * These links refer to the documentation for the Nuget API + * 1. Package Content: https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource + * 2. Server API: https://docs.microsoft.com/en-us/nuget/api/overview + */ +internal class NugetPackageRepository : IPackageRepository +{ + public string Url => "https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json"; + public string JsonPath => "$.versions[*]"; +} diff --git a/PackageMonster/Services/DataService.cs b/PackageMonster/Services/DataService.cs index 33ab9cc..36ab31d 100644 --- a/PackageMonster/Services/DataService.cs +++ b/PackageMonster/Services/DataService.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Net; using Newtonsoft.Json.Linq; +using PackageMonster.Repositories; using RestSharp; namespace PackageMonster.Services; @@ -13,13 +14,6 @@ namespace PackageMonster.Services; [ExcludeFromCodeCoverage] public sealed class DataService : IDataService { - /* Resources: - * These links refer to the documentation for the Nuget API - * 1. Package Content: https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource - * 2. Server API: https://docs.microsoft.com/en-us/nuget/api/overview - */ - internal const string PublicNugetApiUrl = "https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json"; - internal const string PublicNugetVersionsJsonPath = "$.versions[*]"; private readonly RestClient client; private bool isDisposed; @@ -48,27 +42,42 @@ public async Task GetVersions(string packageName, string source, strin if (string.IsNullOrWhiteSpace(source)) { - source = PublicNugetApiUrl; + source = "nuget"; } - if (!Uri.IsWellFormedUriString(source, UriKind.Absolute)) - { - throw new ArgumentException(nameof(source), $"Must provide a well-formed source URI."); - } - - if (!Uri.TryCreate(source, UriKind.Absolute, out _)) - { - throw new ArgumentException(nameof(source), $"Must provide an absolute source URI."); - } + IPackageRepository packageRepository; - if (string.IsNullOrWhiteSpace(versionsJsonPath)) + switch (source.ToLowerInvariant()) { - versionsJsonPath = PublicNugetVersionsJsonPath; + case "nuget": + packageRepository = new NugetPackageRepository(); + break; + case "npm": + packageRepository = new NpmPackageRepository(); + break; + default: + if (!Uri.IsWellFormedUriString(source, UriKind.Absolute)) + { + throw new ArgumentException(nameof(source), $"Must provide a well-formed source URI."); + } + + if (!Uri.TryCreate(source, UriKind.Absolute, out _)) + { + throw new ArgumentException(nameof(source), $"Must provide an absolute source URI."); + } + + if (string.IsNullOrWhiteSpace(versionsJsonPath)) + { + throw new ArgumentException(nameof(versionsJsonPath), $"Must provide a json path for a custom source. Make sure the variable `PACKAGE-NAME` is in the url."); + } + + packageRepository = new CustomPackageRepository { Url = source, JsonPath = versionsJsonPath }; + break; } this.client.AcceptedContentTypes = new[] { "application/json" }; - var resolvedUrl = source.Replace("PACKAGE-NAME", packageName); + var resolvedUrl = packageRepository.Url.Replace("PACKAGE-NAME", packageName); var request = new RestRequest(resolvedUrl); var response = await this.client.ExecuteAsync(request, Method.Get); @@ -81,7 +90,7 @@ public async Task GetVersions(string packageName, string source, strin } var json = JObject.Parse(response.Content); - return json.SelectTokens(versionsJsonPath).Select(t => t.Value()).ToArray(); + return json.SelectTokens(packageRepository.JsonPath).Select(t => t.Value()).Cast().ToArray(); } var exception = response.ErrorException ?? new Exception($"There was an issue getting data from '{source}'."); diff --git a/README.md b/README.md index 7e3e6b7..49a723b 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ jobs: - uses: actions/checkout@v3 - name: Check If Package Exists - id: nuget-exists + id: package-exists uses: KinsonDigital/PackageMonster@v1.0.0-preview.1 with: package-name: MyPackage 👈🏻 # Required input @@ -58,9 +58,9 @@ jobs: # Output name for the Package Monster GitHub action 👇🏼 # _____|_____ # | | - $nugetExists = "${{ steps.nuget-exists.outputs.nuget-exists }}"; + $packageExists = "${{ steps.package-exists.outputs.package-exists }}"; - if ($nugetExists -eq "true") { + if ($packageExists -eq "true") { Write-Host "The package exists!!"; } else { Write-Host "The package does not exist!!"; @@ -85,13 +85,12 @@ jobs: - uses: actions/checkout@v3 - name: Check If NPM Package Exists - id: npm-exists + id: package-exists uses: KinsonDigital/PackageMonster@v1.0.0-preview.1 with: package-name: MyPackage 👈🏻 # Required input version: 1.2.3 👈🏻 # Required input - source: https://registry.npmjs.org/PACKAGE-NAME - json-path: $.versions[*].version + source: npm fail-when-not-found: true 👈🏻 # Optional input - name: Print Output Result #PowerShell Core @@ -100,9 +99,9 @@ jobs: # Output name for the Package Monster GitHub action 👇🏼 # _____|_____ # | | - $npmExists = "${{ steps.npm-exists.outputs.nuget-exists }}"; + $packageExists = "${{ steps.package-exists.outputs.package-exists }}"; - if ($nugetExists -eq "true") { + if ($packageExists -eq "true") { Write-Host "The package exists!!"; } else { Write-Host "The package does not exist!!"; @@ -116,14 +115,14 @@ jobs: ## **➡️ Action Inputs ⬅️**
-| Input Name | Description | Required | Default Value | -|---|:-----------------------------------------------------------------------------------------------|:---:|:---:| -| `package-name` | The name of the package. | yes | N/A | -| `version` | The version of the package. | yes | N/A | -| `source` | The source repository to check. | no | https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json | -| `json-path` | The json path to extract the versions. | no | $.versions[*] | -| `fail-when-not-found` | Will fail the job if the package of a specific version is not found. | no | false | -| `fail-when-found` | Will fail the job if the package of a specific version is found. | no | false | +| Input Name | Description | Required | Default Value | Notes | +|-----------------------|:---------------------------------------------------------------------|:--------:|:-------------:|:-------:| +| `package-name` | The name of the package. | yes | N/A | | +| `version` | The version of the package. | yes | N/A | | +| `source` | The source repository to check. | no | nuget | Valid options are: `nuget`, `npm`, or a custom url. If 'PACKAGE-NAME' is in the url, it will be replaced with the value from the `package-name` input parameter. | +| `json-path` | The json path to extract the versions. | no | N/A | Required if `source` is set to a custom url. https://jsonpath.com | +| `fail-when-found` | Will fail the job if the package of a specific version is found. | no | false | | +| `fail-when-not-found` | Will fail the job if the package of a specific version is not found. | no | false | |
@@ -132,7 +131,7 @@ jobs: ## **⬅️ Action Output ➡️**
-The name of the output is `result` and it returns a `boolean` of `true` or `false`. +The name of the output is `package-exists` and it returns a `boolean` of `true` or `false`. Refer to the **Example** above for how to use the output of the action. --- diff --git a/Testing/PackageMonsterTests/GitHubActionTests.cs b/Testing/PackageMonsterTests/GitHubActionTests.cs index e02540b..a0e2a28 100644 --- a/Testing/PackageMonsterTests/GitHubActionTests.cs +++ b/Testing/PackageMonsterTests/GitHubActionTests.cs @@ -74,7 +74,7 @@ public async void Run_WhenPackageWithVersionExists_SetsOutputToCorrectValue( this.mockConsoleService.VerifyOnce(m => m.WriteLine(expectedSearchMsgEnd)); this.mockConsoleService.VerifyOnce(m => m.WriteLine(expectedResultMsg)); this.mockConsoleService.Verify(m => m.BlankLine(), Times.Exactly(4)); - this.mockActionOutputService.VerifyOnce(m => m.SetOutputValue("result", expectedOutput)); + this.mockActionOutputService.VerifyOnce(m => m.SetOutputValue("package-exists", expectedOutput)); onCompletedInvoked.Should().BeTrue("the 'onCompleted()' was never invoked"); } diff --git a/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs b/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs index c522e85..9f100ce 100644 --- a/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs +++ b/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs @@ -5,6 +5,7 @@ // ReSharper disable UseObjectOrCollectionInitializer using Newtonsoft.Json.Linq; +using PackageMonster.Repositories; using PackageMonster.Services; namespace PackageMonsterTests.Models; @@ -21,10 +22,11 @@ public class PackageVersionsModelTests public void Version_WhenSettingValue_ReturnsCorrectResult() { // Arrange + var packageRepository = new NugetPackageRepository(); var model = JObject.Parse(@"{ 'versions': [""1.2.3"", ""4.5.6""] }"); // Act - var actual = model.SelectTokens(DataService.PublicNugetVersionsJsonPath).Select(v => v.Value()).ToArray(); + var actual = model.SelectTokens(packageRepository.JsonPath).Select(v => v.Value()).ToArray(); // Assert actual.Should() diff --git a/action.yml b/action.yml index 235cc87..c1bd19d 100644 --- a/action.yml +++ b/action.yml @@ -12,21 +12,20 @@ inputs: description: 'The version of the package to check. This is not case-sensitive.' required: true source: - description: 'The source repository to check.' + description: 'The source repository to check. The string `PACKAGE-NAME` will be replaced with the value from the `package-name` input parameter. Options: [ `nuget`, `npm`, custom url ]' required: false - default: https://api.nuget.org/v3-flatcontainer/PACKAGE-NAME/index.json + default: nuget json-path: - description: 'The json path to extract the versions.' + description: 'The json path to extract the versions. Required only if a custom source url is specified.' required: false - default: $.versions[*] fail-when-not-found: description: 'If true, will fail the workflow if the package of the requested version does not exist.' required: false - default: false + default: 'false' fail-when-found: description: 'If true, will fail the workflow if the package of the requested version does exist.' required: false - default: false + default: 'false' outputs: package-exists: From 0d5ce7048e3d6853598361fd6f5d47471bab86c9 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 11:23:32 -0400 Subject: [PATCH 16/25] added unit test --- .../Services/ActionOutputService.cs | 10 +++-- .../Services/ActionOutputServiceTests.cs | 42 +++++++++++++++++-- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/PackageMonster/Services/ActionOutputService.cs b/PackageMonster/Services/ActionOutputService.cs index 73bed02..537bff8 100644 --- a/PackageMonster/Services/ActionOutputService.cs +++ b/PackageMonster/Services/ActionOutputService.cs @@ -15,19 +15,23 @@ public class ActionOutputService : IActionOutputService private const string GitHubOutput = "GITHUB_OUTPUT"; private readonly IEnvVarService envVarService; private readonly IFile file; + private readonly IGitHubConsoleService consoleService; /// /// Initializes a new instance of the class. /// /// Manages environment variables. /// Manages files. - public ActionOutputService(IEnvVarService envVarService, IFile file) + public ActionOutputService(IEnvVarService envVarService, IFile file, IGitHubConsoleService consoleService) { EnsureThat.CtorParamIsNotNull(envVarService); EnsureThat.CtorParamIsNotNull(file); + EnsureThat.CtorParamIsNotNull(consoleService); this.envVarService = envVarService; this.file = file; + this.consoleService = consoleService; + } /// @@ -42,13 +46,13 @@ public void SetOutputValue(string name, string value) if (string.IsNullOrEmpty(outputPath)) { - Console.WriteLine($"WARNING: The GitHub output environment file variable '{GitHubOutput}' was not specified."); + this.consoleService.WriteLine($"WARNING: The environment variable '{GitHubOutput}' was not specified."); return; } if (this.file.Exists(outputPath) is false) { - throw new FileNotFoundException("The GitHub output environment file was not found.", outputPath); + throw new FileNotFoundException("The GitHub output file was not found.", outputPath); } var outputLines = this.file.ReadAllLines(outputPath).ToList(); diff --git a/Testing/PackageMonsterTests/Services/ActionOutputServiceTests.cs b/Testing/PackageMonsterTests/Services/ActionOutputServiceTests.cs index 863d3b5..b3bea01 100644 --- a/Testing/PackageMonsterTests/Services/ActionOutputServiceTests.cs +++ b/Testing/PackageMonsterTests/Services/ActionOutputServiceTests.cs @@ -15,6 +15,7 @@ public class ActionOutputServiceTests { private readonly Mock mockEnvVarService; private readonly Mock mockFile; + private readonly Mock mockConsoleService; /// /// Initializes a new instance of the class. @@ -23,6 +24,7 @@ public ActionOutputServiceTests() { this.mockEnvVarService = new Mock(); this.mockFile = new Mock(); + this.mockConsoleService = new Mock(); } #region Constructor Tests @@ -32,7 +34,7 @@ public void Ctor_WithNullEnvVarServiceParam_ThrowsException() // Arrange & Act var act = () => { - _ = new ActionOutputService(null, Mock.Of()); + _ = new ActionOutputService(null, Mock.Of(), Mock.Of()); }; // Assert @@ -47,7 +49,7 @@ public void Ctor_WithFileParam_ThrowsException() // Arrange & Act var act = () => { - _ = new ActionOutputService(Mock.Of(), null); + _ = new ActionOutputService(Mock.Of(), null, null); }; // Assert @@ -55,6 +57,21 @@ public void Ctor_WithFileParam_ThrowsException() .Throw() .WithMessage("The parameter must not be null. (Parameter 'file')"); } + + [Fact] + public void Ctor_WithConsoleServiceParam_ThrowsException() + { + // Arrange & Act + var act = () => + { + _ = new ActionOutputService(Mock.Of(), Mock.Of(), null); + }; + + // Assert + act.Should() + .Throw() + .WithMessage("The parameter must not be null. (Parameter 'consoleService')"); + } #endregion #region Method Tests @@ -75,6 +92,23 @@ public void SetOutputValue_WithNullOrEmptyOutputName_ThrowsException(string name .WithMessage("The parameter 'name' must not be null or empty."); } + [Fact] + public void SetOutputValue_WhenOutputPathNotSpecified_LogWarning() + { + // Arrange + const string outputPath = ""; + this.mockEnvVarService + .Setup(m => m.GetEnvironmentVariable(It.IsAny(), It.IsAny())) + .Returns(outputPath); + var sut = CreateSystemUnderTest(); + + // Act + sut.SetOutputValue("test-output", "test-value"); + + // Assert + this.mockConsoleService.VerifyOnce(m => m.WriteLine("WARNING: The environment variable 'GITHUB_OUTPUT' was not specified.")); + } + [Fact] public void SetOutputValue_WhenOutputPathDoesNotExist_ThrowsException() { @@ -91,7 +125,7 @@ public void SetOutputValue_WhenOutputPathDoesNotExist_ThrowsException() // Assert act.Should().Throw() - .WithMessage("The GitHub output environment file was not found."); + .WithMessage("The GitHub output file was not found."); } [Fact] @@ -134,5 +168,5 @@ public void SetOutputValue_WhenInvoked_SetsOutputValue() /// /// The instance to test. private ActionOutputService CreateSystemUnderTest() => - new (this.mockEnvVarService.Object, this.mockFile.Object); + new (this.mockEnvVarService.Object, this.mockFile.Object, this.mockConsoleService.Object); } From ad185968f8bff112ad310a74da41865083f09d9a Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 13:33:50 -0400 Subject: [PATCH 17/25] fixed unit test --- .../Services/ActionOutputServiceTests.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Testing/PackageMonsterTests/Services/ActionOutputServiceTests.cs b/Testing/PackageMonsterTests/Services/ActionOutputServiceTests.cs index b3bea01..cecc01d 100644 --- a/Testing/PackageMonsterTests/Services/ActionOutputServiceTests.cs +++ b/Testing/PackageMonsterTests/Services/ActionOutputServiceTests.cs @@ -132,11 +132,9 @@ public void SetOutputValue_WhenOutputPathDoesNotExist_ThrowsException() public void SetOutputValue_WhenInvoked_SetsOutputValue() { // Arrange - var expected = - $""" - other-output=other-value - test-output=test-value{Environment.NewLine} - """; + var expected = @"other-output=other-value +test-output=test-value +".ReplaceLineEndings(Environment.NewLine); const string outputPath = "test-path"; var lines = new[] From b8587867f33864b4c6e87bc051a204da9f10c356 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 13:37:55 -0400 Subject: [PATCH 18/25] changed example titles --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 49a723b..221c763 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ > - [Defining outputs for jobs](https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs) > - [Setting a step action output parameter](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter) -

🪧 Example 🪧

+

🪧 NuGet Example 🪧

```yaml name: Package Monster Action Sample @@ -69,7 +69,7 @@ jobs: --- -

🪧 Example 🪧

+

🪧 NPM Example 🪧

```yaml name: Package Monster Action Sample From b3fd866a5084de4297448ab0d7cb800499ed8404 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 13:40:01 -0400 Subject: [PATCH 19/25] readability --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 221c763..183be1c 100644 --- a/README.md +++ b/README.md @@ -115,14 +115,14 @@ jobs: ## **➡️ Action Inputs ⬅️** -| Input Name | Description | Required | Default Value | Notes | -|-----------------------|:---------------------------------------------------------------------|:--------:|:-------------:|:-------:| -| `package-name` | The name of the package. | yes | N/A | | -| `version` | The version of the package. | yes | N/A | | -| `source` | The source repository to check. | no | nuget | Valid options are: `nuget`, `npm`, or a custom url. If 'PACKAGE-NAME' is in the url, it will be replaced with the value from the `package-name` input parameter. | -| `json-path` | The json path to extract the versions. | no | N/A | Required if `source` is set to a custom url. https://jsonpath.com | -| `fail-when-found` | Will fail the job if the package of a specific version is found. | no | false | | -| `fail-when-not-found` | Will fail the job if the package of a specific version is not found. | no | false | | +| Input Name | Description | Required | Default Value | Notes | +|------------------------------------|:---------------------------------------------------------------------|:--------:|:-------------:|:-------:| +| `package-name` | The name of the package. | yes | N/A | | +| `version` | The version of the package. | yes | N/A | | +| `source` | The source repository to check. | no | nuget | Valid options are: `nuget`, `npm`, or a custom url. If 'PACKAGE-NAME' is in the url, it will be replaced with the value from the `package-name` input parameter. | +| `json-path` | The json path to extract the versions. | no | N/A | Required if `source` is set to a custom url. https://jsonpath.com | +| `fail-when-found` | Will fail the job if the package of a specific version is found. | no | false | | +| `fail-when-not-found` | Will fail the job if the package of a specific version is not found. | no | false | |
From 12c112f25a936f0a16ca0f7ebcace817628ad82f Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 13:42:16 -0400 Subject: [PATCH 20/25] readability --- README.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 183be1c..c2a87ec 100644 --- a/README.md +++ b/README.md @@ -115,14 +115,20 @@ jobs: ## **➡️ Action Inputs ⬅️**
-| Input Name | Description | Required | Default Value | Notes | -|------------------------------------|:---------------------------------------------------------------------|:--------:|:-------------:|:-------:| -| `package-name` | The name of the package. | yes | N/A | | -| `version` | The version of the package. | yes | N/A | | -| `source` | The source repository to check. | no | nuget | Valid options are: `nuget`, `npm`, or a custom url. If 'PACKAGE-NAME' is in the url, it will be replaced with the value from the `package-name` input parameter. | -| `json-path` | The json path to extract the versions. | no | N/A | Required if `source` is set to a custom url. https://jsonpath.com | -| `fail-when-found` | Will fail the job if the package of a specific version is found. | no | false | | -| `fail-when-not-found` | Will fail the job if the package of a specific version is not found. | no | false | | + + +| Input Name | Description | Required | Default Value | Notes | +|-----------------------|:---------------------------------------------------------------------|:--------:|:-------------:|:-------:| +| `package-name` | The name of the package. | yes | N/A | | +| `version` | The version of the package. | yes | N/A | | +| `source` | The source repository to check. | no | nuget | Valid options are: `nuget`, `npm`, or a custom url. If 'PACKAGE-NAME' is in the url, it will be replaced with the value from the `package-name` input parameter. | +| `json-path` | The json path to extract the versions. | no | N/A | Required if `source` is set to a custom url. https://jsonpath.com | +| `fail-when-found` | Will fail the job if the package of a specific version is found. | no | false | | +| `fail-when-not-found` | Will fail the job if the package of a specific version is not found. | no | false | |
From f6769324c88f0f20319e492fb17e6492ed271090 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 13:43:07 -0400 Subject: [PATCH 21/25] readability --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c2a87ec..00b2693 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ + +

@@ -115,12 +121,6 @@ jobs: ## **➡️ Action Inputs ⬅️**
- - | Input Name | Description | Required | Default Value | Notes | |-----------------------|:---------------------------------------------------------------------|:--------:|:-------------:|:-------:| | `package-name` | The name of the package. | yes | N/A | | From 7f6459e7fcf15ffc9e2fdddcec8d7348a3b4e797 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 13:45:07 -0400 Subject: [PATCH 22/25] removed nowrap css --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 00b2693..221c763 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ - -

From 6dcd16f29d53390ee2982a47431819f4ea96d718 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Wed, 24 May 2023 13:54:09 -0400 Subject: [PATCH 23/25] documentation change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 221c763..aa12f07 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ jobs: | `package-name` | The name of the package. | yes | N/A | | | `version` | The version of the package. | yes | N/A | | | `source` | The source repository to check. | no | nuget | Valid options are: `nuget`, `npm`, or a custom url. If 'PACKAGE-NAME' is in the url, it will be replaced with the value from the `package-name` input parameter. | -| `json-path` | The json path to extract the versions. | no | N/A | Required if `source` is set to a custom url. https://jsonpath.com | +| `json-path` | The json path to extract the versions. | no | N/A | Required if `source` is set to a custom url. Refer to https://jsonpath.com for syntax. | | `fail-when-found` | Will fail the job if the package of a specific version is found. | no | false | | | `fail-when-not-found` | Will fail the job if the package of a specific version is not found. | no | false | | From b7059bad60c3bd5a9eac88ec397b98a29420030f Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Thu, 25 May 2023 12:10:40 -0400 Subject: [PATCH 24/25] fixed npm json path --- .../Repositories/NpmPackageRepository.cs | 2 +- .../Models/PackageVersionsModelTests.cs | 43 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/PackageMonster/Repositories/NpmPackageRepository.cs b/PackageMonster/Repositories/NpmPackageRepository.cs index b226d84..0d5fbb6 100644 --- a/PackageMonster/Repositories/NpmPackageRepository.cs +++ b/PackageMonster/Repositories/NpmPackageRepository.cs @@ -3,5 +3,5 @@ internal class NpmPackageRepository : IPackageRepository { public string Url => "https://registry.npmjs.org/PACKAGE-NAME"; - public string JsonPath => "$.versions[*].version"; + public string JsonPath => "$.versions.*.version"; } diff --git a/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs b/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs index 9f100ce..e40947c 100644 --- a/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs +++ b/Testing/PackageMonsterTests/Models/PackageVersionsModelTests.cs @@ -19,11 +19,50 @@ public class PackageVersionsModelTests { #region Prop Tests [Fact] - public void Version_WhenSettingValue_ReturnsCorrectResult() + public void Version_WhenUsingNugetJsonPath_ReturnsCorrectResult() { // Arrange var packageRepository = new NugetPackageRepository(); - var model = JObject.Parse(@"{ 'versions': [""1.2.3"", ""4.5.6""] }"); + var model = JObject.Parse(@" +{ + ""versions"": [ + ""1.2.3"", + ""4.5.6"" + ] +} +"); + + // Act + var actual = model.SelectTokens(packageRepository.JsonPath).Select(v => v.Value()).ToArray(); + + // Assert + actual.Should() + .HaveCount(2) + .And.Contain("1.2.3") + .And.Contain("4.5.6") + .And.HaveElementPreceding("4.5.6", "1.2.3"); + } + #endregion + + + #region Prop Tests + [Fact] + public void Version_WhenUsingNpmJsonPath_ReturnsCorrectResult() + { + // Arrange + var packageRepository = new NpmPackageRepository(); + var model = JObject.Parse(@" +{ + ""versions"": { + ""1.2.3"": { + ""version"": ""1.2.3"" + }, + ""4.5.6"": { + ""version"": ""4.5.6"" + } + } +} +"); // Act var actual = model.SelectTokens(packageRepository.JsonPath).Select(v => v.Value()).ToArray(); From 708de2a084daf72b4128ac3c05dd2b2bca692709 Mon Sep 17 00:00:00 2001 From: Charles Mathis Date: Thu, 25 May 2023 12:51:33 -0400 Subject: [PATCH 25/25] improved input validation --- PackageMonster/GitHubAction.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/PackageMonster/GitHubAction.cs b/PackageMonster/GitHubAction.cs index 93b0b23..5328c76 100644 --- a/PackageMonster/GitHubAction.cs +++ b/PackageMonster/GitHubAction.cs @@ -38,6 +38,16 @@ public async Task Run(ActionInputs inputs, Action onCompleted, Action try { + if (string.IsNullOrWhiteSpace(inputs.PackageName)) + { + throw new ArgumentException("Package name is empty!"); + } + + if (string.IsNullOrWhiteSpace(inputs.Version)) + { + throw new ArgumentException("Version is empty!"); + } + this.gitHubConsoleService.Write($"Searching for package '{inputs.PackageName} v{inputs.Version}' . . . "); var versions = await this.dataService.GetVersions(inputs.PackageName, inputs.Source, inputs.VersionsJsonPath); @@ -45,7 +55,7 @@ public async Task Run(ActionInputs inputs, Action onCompleted, Action .Any(version => string.Equals(version, inputs.Version, StringComparison.CurrentCultureIgnoreCase)); - var searchEndMsg = versionFound ? "package found!!" : "package not found!!"; + var searchEndMsg = versionFound ? "package found!" : "package not found!"; this.gitHubConsoleService.WriteLine(searchEndMsg); this.gitHubConsoleService.BlankLine();