diff --git a/.build/Build.CI.cs b/.build/Build.CI.cs index 12fb516b..f879ad1d 100644 --- a/.build/Build.CI.cs +++ b/.build/Build.CI.cs @@ -46,6 +46,10 @@ On = [RocketSurgeonGitHubActionsTrigger.WorkflowCall], InvokedTargets = [nameof(WithOutputs)] )] +[CloseMilestoneJob] +[DraftReleaseJob] +[UpdateMilestoneJob] +[PublishNugetPackagesJob("RSG_NUGET_API_KEY")] [GitHubActionsVariable("THIS_IS_A_VARIABLE", Alias = "ThisIsAOtherVariable")] [GitHubActionsVariable("THIS_IS_ANOTHER_VARIABLE")] [GitHubActionsInput("THIS_IS_A_INPUT" /*, Alias = "ThisIsAInput"*/)] @@ -84,7 +88,7 @@ public static RocketSurgeonGitHubActionsConfiguration CiIgnoreMiddleware(RocketS .ProducesGithubActionsOutput("iSetAThing", "Some output value") .Requires(() => ThisIsAInput) .Executes(() => GitHubActions.Instance?.SetOutput("iSetAThing", "myValue")); - #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. [Parameter] public string ThisIsAInput { get; set; } @@ -122,11 +126,10 @@ public static RocketSurgeonGitHubActionsConfiguration CiMiddleware(RocketSurgeon { _ = configuration .ExcludeRepositoryConfigurationFiles() - .AddNugetPublish() .Jobs.OfType() .First(z => z.Name.Equals("Build", StringComparison.OrdinalIgnoreCase)) .UseDotNetSdks("8.0", "9.0") - // .ConfigureForGitVersion() + // .ConfigureForGitVersion() .ConfigureStep(step => step.FetchDepth = 0) .PublishLogs(); diff --git a/.build/Build.cs b/.build/Build.cs index 220aeae6..cae6a99b 100644 --- a/.build/Build.cs +++ b/.build/Build.cs @@ -19,7 +19,7 @@ [LocalBuildConventions] #pragma warning disable CA1050 internal partial class Pipeline : NukeBuild, - #pragma warning restore CA1050 +#pragma warning restore CA1050 ICanRestoreWithDotNetCore, ICanBuildWithDotNetCore, ICanTestWithDotNetCore, diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 8f6f1c80..c00c0bdb 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -56,6 +56,11 @@ "version": "0.6.0", "commands": ["dotnet-verify"], "rollForward": false + }, + "gitreleasemanager.tool": { + "version": "0.18.0", + "commands": ["dotnet-gitreleasemanager"], + "rollForward": false } } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03027e3a..b52b8051 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,14 +21,10 @@ on: workflow_call: secrets: THIS_IS_A_SECRET: - required: False - RSG_NUGET_API_KEY: - required: False - RSG_AZURE_DEVOPS: - required: False + required: false CODECOV_TOKEN: description: 'The codecov token' - required: False + required: false push: branches: - 'master' @@ -192,10 +188,3 @@ jobs: with: name: 'nuget' path: 'artifacts/nuget/' - Publish: - needs: - - Build - uses: RocketSurgeonsGuild/actions/.github/workflows/publish-nuget.yml@v0.3.15 - secrets: - RSG_NUGET_API_KEY: '${{ secrets.RSG_NUGET_API_KEY }}' - RSG_AZURE_DEVOPS: '${{ secrets.RSG_AZURE_DEVOPS }}' diff --git a/.github/workflows/close-milestone.yml b/.github/workflows/close-milestone.yml index 376e70cb..8df37265 100644 --- a/.github/workflows/close-milestone.yml +++ b/.github/workflows/close-milestone.yml @@ -1,10 +1,94 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [CloseMilestoneJob (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# nuke --generate-configuration GitHubActions_close-milestone --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + name: Close Milestone + on: + workflow_call: + secrets: + RSG_BOT_TOKEN: + required: true release: - types: - - released +permissions: + actions: read + checks: read + contents: read + deployments: read + id-token: none + issues: write + discussions: none + packages: none + pages: none + pull-requests: write + repository-projects: none + security-events: none + statuses: write + jobs: close_milestone: - uses: RocketSurgeonsGuild/actions/.github/workflows/close-milestone6.yml@v0.3.15 - secrets: - RSG_BOT_TOKEN: ${{ secrets.RSG_BOT_TOKEN }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + clean: 'false' + fetch-depth: '0' + - name: Fetch all history for all tags and branches + run: | + git fetch --prune + - name: 📲 Install DotNet + uses: actions/setup-dotnet@v3 + - name: 📲 Install GitVersion + if: ${{ github.event.action == 'opened' }} + uses: gittools/actions/gitversion/setup@v3.1.1 + with: + versionSpec: '6.1.0' + - name: 📲 Install GitReleaseManager + if: ${{ github.event.action == 'opened' }} + uses: gittools/actions/gitreleasemanager/setup@v3.1.1 + with: + versionSpec: '0.18.0' + - name: Create Milestone + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + if: ${{ github.event.action == 'opened' }} + continue-on-error: true + uses: WyriHaximus/github-action-create-milestone@v1 + with: + title: 'v${{ steps.gitversion.outputs.majorMinorPatch }}' + - name: sync milestones + env: + github-token: '${{ secrets.GITHUB_TOKEN }}' + uses: RocketSurgeonsGuild/actions/sync-milestone@v0.3.15 + with: + default-label: ':sparkles: mysterious' + - name: Get Repo and Owner + id: repository + if: ${{ !github.event.release.prerelease && steps.gitversion.outputs.preReleaseTag == '' }} + shell: pwsh + run: | + $parts = $ENV:GITHUB_REPOSITORY.Split('/') + echo "::set-output name=owner::$($parts[0])" + echo "::set-output name=repository::$($parts[1])" + - name: Close Milestone + if: ${{ !github.event.release.prerelease && steps.gitversion.outputs.preReleaseTag == '' }} + shell: pwsh + run: | + dotnet gitreleasemanager close ` + -o "${{ steps.repository.outputs.owner }}" ` + -r "${{ steps.repository.outputs.repository }}" ` + --token "${{ secrets.GITHUB_TOKEN }}" ` + -m "v${{ steps.gitversion.outputs.majorMinorPatch }}" diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml deleted file mode 100644 index 61bafe1c..00000000 --- a/.github/workflows/dependabot-merge.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Dependabot Commenter - -on: - pull_request_target: - types: - - labeled - - opened - - reopened - - closed - - synchronize - -jobs: - comment: - uses: RocketSurgeonsGuild/actions/.github/workflows/dependabot-merge.yml@v0.3.15 - secrets: - RSG_BOT_TOKEN: ${{ secrets.RSG_BOT_TOKEN }} diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index c1c23322..906fa66b 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -1,14 +1,92 @@ -name: Create Milestone and Draft Release +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [DraftReleaseJob (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# nuke --generate-configuration GitHubActions_draft-release --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + +name: Draft Release and Create Milestone + on: + workflow_call: + secrets: + RSG_BOT_TOKEN: + required: true push: branches: - - master + - 'master' paths-ignore: - '**/*.md' schedule: - cron: '0 0 * * 4' +permissions: + actions: read + checks: read + contents: read + deployments: read + id-token: none + issues: write + discussions: none + packages: none + pages: none + pull-requests: write + repository-projects: none + security-events: none + statuses: write + jobs: - create_milestone_and_draft_release: - uses: RocketSurgeonsGuild/actions/.github/workflows/draft-release6.yml@v0.3.15 - secrets: - RSG_BOT_TOKEN: ${{ secrets.RSG_BOT_TOKEN }} + draft_release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + clean: 'false' + fetch-depth: '0' + - name: Fetch all history for all tags and branches + run: | + git fetch --prune + - name: 📲 Install DotNet + uses: actions/setup-dotnet@v3 + - name: 📲 Install GitVersion + if: ${{ github.event.action == 'opened' }} + uses: gittools/actions/gitversion/setup@v3.1.1 + with: + versionSpec: '6.1.0' + - name: 📲 Install GitReleaseManager + if: ${{ github.event.action == 'opened' }} + uses: gittools/actions/gitreleasemanager/setup@v3.1.1 + with: + versionSpec: '0.18.0' + - name: Create Milestone + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + if: ${{ github.event.action == 'opened' }} + uses: WyriHaximus/github-action-create-milestone@v1 + with: + title: 'v${{ steps.gitversion.outputs.majorMinorPatch }}' + - name: sync milestones + uses: RocketSurgeonsGuild/actions/sync-milestone@v0.3.15 + with: + default-label: ':sparkles: mysterious' + github-token: '${{ secrets.GITHUB_TOKEN }' + - name: Create Release + uses: ncipollo/release-action@v1 + with: + allowUpdates: 'true' + generateReleaseNotes: 'true' + draft: 'true' + omitNameDuringUpdate: 'true' + name: 'v${{ steps.gitversion.outputs.majorMinorPatch }}' + tag: 'v${{ steps.gitversion.outputs.majorMinorPatch }}' + token: '${{ secrets.RSG_BOT_TOKEN }}' + commit: '${{ github.base_ref }}' diff --git a/.github/workflows/inputs.yml b/.github/workflows/inputs.yml index 2a618d06..c89918d4 100644 --- a/.github/workflows/inputs.yml +++ b/.github/workflows/inputs.yml @@ -21,10 +21,10 @@ on: inputs: THIS_IS_A_INPUT: type: string - required: False + required: false secrets: THIS_IS_A_SECRET: - required: False + required: false outputs: withOutputsISetAThing: description: 'Some output value' diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml new file mode 100644 index 00000000..bb41a226 --- /dev/null +++ b/.github/workflows/publish-nuget.yml @@ -0,0 +1,53 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [PublishNugetPackagesJob (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# nuke --generate-configuration GitHubActions_publish-nuget --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + +name: Publish Nuget Packages + +on: + workflow_call: + secrets: + RSG_NUGET_API_KEY: + required: true + release: +permissions: + actions: read + checks: read + contents: read + deployments: read + id-token: none + issues: write + discussions: none + packages: none + pages: none + pull-requests: write + repository-projects: none + security-events: none + statuses: write + +jobs: + publish_nuget: + runs-on: ubuntu-latest + steps: + - name: 🚀 nuget + uses: actions/download-artifact@v3 + - name: nuget.org + env: + ApiKey: '${{ secrets.RSG_NUGET_API_KEY }}' + if: startsWith(github.ref, 'refs/tags/') + shell: pwsh + run: | + dotnet nuget push **/*.nupkg --skip-duplicate -s nuget.org --api-key $ENV:ApiKey + dotnet nuget push **/*.snupkg --skip-duplicate -s nuget.org --api-key $ENV:ApiKey diff --git a/.github/workflows/update-milestone.yml b/.github/workflows/update-milestone.yml index 1327fe74..2809c9d3 100644 --- a/.github/workflows/update-milestone.yml +++ b/.github/workflows/update-milestone.yml @@ -1,12 +1,84 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [UpdateMilestoneJob (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# nuke --generate-configuration GitHubActions_update-milestone --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + name: Update Milestone + on: + push: pull_request_target: types: - - closed - - opened - - reopened - - synchronize + - 'closed' + - 'opened' + - 'reopened' + - 'synchronize' +permissions: + actions: read + checks: read + contents: read + deployments: read + id-token: none + issues: write + discussions: none + packages: none + pages: none + pull-requests: write + repository-projects: none + security-events: none + statuses: write jobs: update_milestone: - uses: RocketSurgeonsGuild/actions/.github/workflows/update-milestone6.yml@v0.3.15 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: '${{ github.sha }}' + clean: 'false' + fetch-depth: '0' + - name: Fetch all history for all tags and branches + run: | + git fetch --prune + - name: 📲 Install DotNet + uses: actions/setup-dotnet@v3 + - name: 📲 Install GitVersion + if: ${{ github.event.action == 'opened' }} + uses: gittools/actions/gitversion/setup@v3.1.1 + with: + versionSpec: '6.1.0' + - name: 📲 Install GitReleaseManager + if: ${{ github.event.action == 'opened' }} + uses: gittools/actions/gitreleasemanager/setup@v3.1.1 + with: + versionSpec: '0.18.0' + - name: 🔨 Use GitVersion + id: gitversion + if: ${{ github.event.action == 'opened' }} + uses: gittools/actions/gitversion/execute@v3.1.1 + - name: Create Milestone + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + if: ${{ github.event.action == 'opened' }} + continue-on-error: true + uses: WyriHaximus/github-action-create-milestone@v1 + with: + title: 'v${{ steps.gitversion.outputs.majorMinorPatch }}' + - name: sync milestones + env: + github-token: '${{ secrets.GITHUB_TOKEN }}' + uses: RocketSurgeonsGuild/actions/sync-milestone@v0.3.15 + with: + default-label: ':sparkles: mysterious' diff --git a/Directory.Packages.props b/Directory.Packages.props index c4b54343..d3cd7ad3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,9 +18,9 @@ - + diff --git a/Nuke.sln b/Nuke.sln index 753cd964..04c8abc8 100644 --- a/Nuke.sln +++ b/Nuke.sln @@ -61,12 +61,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ ci-ignore.yml = .github\workflows\ci-ignore.yml ci.yml = .github\workflows\ci.yml close-milestone.yml = .github\workflows\close-milestone.yml - dependabot-merge.yml = .github\workflows\dependabot-merge.yml draft-release.yml = .github\workflows\draft-release.yml inputs.yml = .github\workflows\inputs.yml lint.yml = .github\workflows\lint.yml sync-labels.yml = .github\workflows\sync-labels.yml update-milestone.yml = .github\workflows\update-milestone.yml + publish-nuget.yml = .github\workflows\publish-nuget.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".husky", ".husky", "{39F6D15D-2627-4749-9B1D-EB7C9C656465}" diff --git a/src/Nuke/DotnetTool.cs b/src/Nuke/DotnetTool.cs index 8233e576..73f8569d 100644 --- a/src/Nuke/DotnetTool.cs +++ b/src/Nuke/DotnetTool.cs @@ -32,8 +32,15 @@ public static class DotNetTool /// public static Tool GetProperTool(string nugetPackageName) => ResolveToolsManifest().GetProperTool(nugetPackageName); + /// + /// Gets the tool definition for a given local dotnet tool + /// + /// + /// + public static ToolDefinition GetToolDefinition(string nugetPackageName) => ResolveToolsManifest().GetToolDefinition(nugetPackageName); + private static ResolvedToolsManifest? toolsManifest; - private static Lazy ToolsManifestLocation { get; } = new(() => NukeBuild.RootDirectory / ".config/dotnet-tools.json"); + private static Lazy ToolsManifestLocation { get; } = new(() => NukeBuild.RootDirectory / ".config" / "dotnet-tools.json"); private static ResolvedToolsManifest ResolveToolsManifest() { @@ -45,18 +52,18 @@ private static ResolvedToolsManifest ResolveToolsManifest() if (ToolsManifestLocation.Value.FileExists()) { #pragma warning disable CA1869 - toolsManifest = ResolvedToolsManifest.Create( + var manifest = // ReSharper disable once NullableWarningSuppressionIsUsed JsonSerializer.Deserialize( File.ReadAllText(ToolsManifestLocation.Value), new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } - )! - ); + )!; #pragma warning restore CA1869 + toolsManifest = ResolvedToolsManifest.Create(manifest); } else { - toolsManifest = new(ImmutableDictionary.Empty); + toolsManifest = new(ImmutableDictionary.Empty, ImmutableDictionary.Empty); } return toolsManifest; diff --git a/src/Nuke/FullToolCommandDefinition.cs b/src/Nuke/FullToolCommandDefinition.cs index b16cc152..f2e7adc9 100644 --- a/src/Nuke/FullToolCommandDefinition.cs +++ b/src/Nuke/FullToolCommandDefinition.cs @@ -1,8 +1,26 @@ namespace Rocket.Surgery.Nuke; -internal record FullToolCommandDefinition +[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")] +public record FullToolCommandDefinition ( string PackageId, string Version, string Command -); +) +{ + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + + /* Unmerged change from project 'Rocket.Surgery.Nuke(net9.0)' + Before: + private string DebuggerDisplay + { + get + { + return ToString(); + } + } + After: + private string DebuggerDisplay => ToString(); + */ + private string DebuggerDisplay => ToString(); +} diff --git a/src/Nuke/GithubActions/DownloadArtifactSet.cs b/src/Nuke/GithubActions/DownloadArtifactStep.cs similarity index 85% rename from src/Nuke/GithubActions/DownloadArtifactSet.cs rename to src/Nuke/GithubActions/DownloadArtifactStep.cs index fd901f7b..eee2a69b 100644 --- a/src/Nuke/GithubActions/DownloadArtifactSet.cs +++ b/src/Nuke/GithubActions/DownloadArtifactStep.cs @@ -4,13 +4,14 @@ namespace Rocket.Surgery.Nuke.GithubActions; /// Download a given artifact /// [PublicAPI] -public class DownloadArtifactSet : UsingStep +[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")] +public class DownloadArtifactStep : UsingStep { /// /// The default constructor /// /// - public DownloadArtifactSet(string name) : base(name) => Uses = "actions/download-artifact@v3"; + public DownloadArtifactStep(string name) : base(name) => Uses = "actions/download-artifact@v3"; /// /// Gets or sets the name of the artifact to download. If unspecified, all artifacts for the run are downloaded. @@ -51,12 +52,15 @@ public class DownloadArtifactSet : UsingStep /// public string? RunId { get; set; } + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString(); + /// public override void Write(CustomFileWriter writer) { - #pragma warning disable CA1308 +#pragma warning disable CA1308 WithProperties(x => x.Underscore().Camelize().ToLowerInvariant()); - #pragma warning restore CA1308 +#pragma warning restore CA1308 base.Write(writer); } diff --git a/src/Nuke/GithubActions/GitHubActionsStepsAttribute.cs b/src/Nuke/GithubActions/GitHubActionsStepsAttribute.cs index 274b1437..1ad20feb 100644 --- a/src/Nuke/GithubActions/GitHubActionsStepsAttribute.cs +++ b/src/Nuke/GithubActions/GitHubActionsStepsAttribute.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using System.Diagnostics; using System.Reflection; using Nuke.Common.CI; @@ -20,8 +21,15 @@ namespace Rocket.Surgery.Nuke.GithubActions; [DebuggerDisplay("{DebuggerDisplay,nq}")] public class GitHubActionsStepsAttribute : GithubActionsStepsAttributeBase { - private readonly string[] _images; - private readonly bool _isGithubHosted; + /// + /// The images + /// + public ImmutableArray Images { get; } + + /// + /// If it's github hosted or not + /// + public bool IsGithubHosted { get; } /// /// The default constructor @@ -35,11 +43,9 @@ public GitHubActionsStepsAttribute( params GitHubActionsImage[] images ) : base(name) { - _images = Enumerable - .Concat(new[] { image }, images) - .Select(z => z.GetValue().Replace(".", "_", StringComparison.Ordinal)) - .ToArray(); - _isGithubHosted = true; + ImmutableArray actionsImage = [image, .. images]; + Images = [.. actionsImage.Select(z => z.GetValue().Replace(".", "_", StringComparison.Ordinal))]; + IsGithubHosted = true; } /// @@ -54,7 +60,7 @@ public GitHubActionsStepsAttribute( params string[] images ) : base(name) { - _images = Enumerable.Concat(new[] { image }, images).ToArray(); + Images = [image, .. images]; } /// @@ -71,7 +77,7 @@ params string[] images public override string IdPostfix => Name; /// - public override IEnumerable GeneratedFiles => new[] { ConfigurationFile }; + public override IEnumerable GeneratedFiles => [ConfigurationFile]; /// /// Determine if you always want to build the nuke project during the ci run @@ -167,7 +173,7 @@ .. onePasswordServiceAccountSecrets .Select( z => new KeyValuePair( z.Name, - string.IsNullOrWhiteSpace(z.Variable) + ( string.IsNullOrWhiteSpace(z.Variable) ) ? $"{z.Path}" : $$$"""${{ vars.{{{z.Variable}}} }}/{{{z.Path.TrimStart('/')}}}""" ) @@ -228,7 +234,7 @@ .. onePasswordConnectServerSecrets .Select( z => new KeyValuePair( z.Name, - string.IsNullOrWhiteSpace(z.Variable) + ( string.IsNullOrWhiteSpace(z.Variable) ) ? $"{z.Path}" : $$$"""${{ vars.{{{z.Variable}}} }}/{{{z.Path.TrimStart('/')}}}""" ) @@ -285,13 +291,13 @@ .. onePasswordConnectServerSecrets Enumerable .Concat( GetAllSecrets(secrets) - // ReSharper disable CoVariantArrayConversion + // ReSharper disable CoVariantArrayConversion .Concat(variables) .Concat(onePasswordConnectServerSecrets) .Concat(onePasswordServiceAccountSecrets), environmentAttributes ) - // ReSharper enable CoVariantArrayConversion + // ReSharper enable CoVariantArrayConversion .SelectMany( z => { @@ -320,18 +326,18 @@ .. onePasswordConnectServerSecrets continue; } - var name = string.IsNullOrWhiteSpace(value.Prefix) ? value.Name : $"{value.Prefix}.{value.Name}"; - stepParameters.Add(new(key, $"{name}{( string.IsNullOrWhiteSpace(value.Default) ? "" : $" || {value.Default}" )}")); + var name = ( string.IsNullOrWhiteSpace(value.Prefix) ) ? value.Name : $"{value.Prefix}.{value.Name}"; + stepParameters.Add(new(key, $"{name}{( ( string.IsNullOrWhiteSpace(value.Default) ) ? "" : $" || {value.Default}" )}")); } var jobOutputs = new List(); var requiredInputs = new List(); var lookupTable = new LookupTable(); var initialArguments = localTool ? new Arguments().Add("dotnet").Add("nuke") : new Arguments().Add("nuke"); - foreach (( var execute, var targets ) in relevantTargets + foreach ((var execute, var targets) in relevantTargets .Select( - x => ( ExecutableTarget: x, - Targets: GetInvokedTargets(x, relevantTargets).ToArray() ) + x => (ExecutableTarget: x, + Targets: GetInvokedTargets(x, relevantTargets).ToArray()) ) .ForEachLazy(x => lookupTable.Add(x.ExecutableTarget, [.. x.Targets])) ) @@ -356,7 +362,7 @@ .. onePasswordConnectServerSecrets 0, new( key, - $"{value.Prefix}.{value.Name}{( string.IsNullOrWhiteSpace(value.Default) ? "" : $" || {value.Default}" )}" + $"{value.Prefix}.{value.Name}{( ( string.IsNullOrWhiteSpace(value.Default) ) ? "" : $" || {value.Default}" )}" ) ); } @@ -406,8 +412,8 @@ .. onePasswordConnectServerSecrets { Steps = steps, Outputs = jobOutputs, - RunsOn = !_isGithubHosted ? _images : [], - Matrix = _isGithubHosted ? _images : [], + RunsOn = ( !IsGithubHosted ) ? Images : [], + Matrix = IsGithubHosted ? Images : [], // TODO: Figure out what this looks like here // Environment = inputs // .Concat(GetAllSecrets(secrets)) @@ -456,16 +462,16 @@ .. onePasswordConnectServerSecrets } } - if (_isGithubHosted && _images is { Length: > 1 }) + if (IsGithubHosted && Images is { Length: > 1 }) { - var mainOs = _images.First(); + var mainOs = Images.First(); foreach (var step in steps.OfType().ToList()) { steps.Insert( steps.IndexOf(step), new UploadArtifactStep($$$"""{{{step.StepName}}} (${{ matrix.os }})""") { - If = step.If is { } ? $"{step.If} && matrix.os != '{mainOs}'" : $"matrix.os != '{mainOs}'", + If = ( step.If is { } ) ? $"{step.If} && matrix.os != '{mainOs}'" : $"matrix.os != '{mainOs}'", Name = $$$"""${{ matrix.os }}-{{{step.Name}}}""", Path = step.Path, Environment = step.Environment.ToDictionary(z => z.Key, z => z.Value), @@ -479,7 +485,7 @@ .. onePasswordConnectServerSecrets IfNoFilesFound = step.IfNoFilesFound, } ); - step.If = step.If is { } ? $"{step.If} && matrix.os == '{mainOs}'" : $"matrix.os == '{mainOs}'"; + step.If = ( step.If is { } ) ? $"{step.If} && matrix.os == '{mainOs}'" : $"matrix.os == '{mainOs}'"; } } @@ -515,8 +521,8 @@ protected void NormalizeActionVersions(RocketSurgeonGitHubActionsConfiguration c ) .Select( // ReSharper disable once NullableWarningSuppressionIsUsed - z => ( name: ( (YamlScalarNode)z.Children[key] ).Value!.Split("@")[0], - value: ( (YamlScalarNode)z.Children[key] ).Value ) + z => (name: ( (YamlScalarNode)z.Children[key] ).Value!.Split("@")[0], + value: ( (YamlScalarNode)z.Children[key] ).Value) ) .DistinctBy(z => z.name) .ToDictionary( @@ -532,7 +538,7 @@ protected void NormalizeActionVersions(RocketSurgeonGitHubActionsConfiguration c } var nodeKey = uses.Split('@')[0]; - return nodeList.TryGetValue(nodeKey, out var value) ? value : uses; + return ( nodeList.TryGetValue(nodeKey, out var value) ) ? value : uses; } foreach (var job in config.Jobs) diff --git a/src/Nuke/GithubActions/GithubActionsExtensions.cs b/src/Nuke/GithubActions/GithubActionsExtensions.cs index 34f36c52..580ed792 100644 --- a/src/Nuke/GithubActions/GithubActionsExtensions.cs +++ b/src/Nuke/GithubActions/GithubActionsExtensions.cs @@ -16,33 +16,11 @@ public static class GithubActionsExtensions /// /// Adds the default publish nuget step to the given configuration /// - /// + /// + /// /// - public static RocketSurgeonGitHubActionsConfiguration AddNugetPublish(this RocketSurgeonGitHubActionsConfiguration configuration) - { - configuration - .DetailedTriggers.OfType() - .ForEach( - trigger => - { - trigger.Secrets.Add(new("RSG_NUGET_API_KEY")); - trigger.Secrets.Add(new("RSG_AZURE_DEVOPS")); - } - ); - configuration.Jobs.Add( - new RocketSurgeonsGithubWorkflowJob("Publish") - { - Needs = { "Build" }, - Uses = "RocketSurgeonsGuild/actions/.github/workflows/publish-nuget.yml@v0.3.0", - Secrets = new() - { - ["RSG_NUGET_API_KEY"] = "${{ secrets.RSG_NUGET_API_KEY }}", - ["RSG_AZURE_DEVOPS"] = "${{ secrets.RSG_AZURE_DEVOPS }}", - }, - } - ); - return configuration; - } + [Obsolete("Method no longer does anything, use PublishNugetJob attribute instead")] + public static RocketSurgeonGitHubActionsConfiguration AddNugetPublish(this RocketSurgeonGitHubActionsConfiguration build, string secret) => build; /// /// Adds a new step to the current configuration diff --git a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsConfiguration.cs b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsConfiguration.cs index 6b67f385..5d1cb92c 100644 --- a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsConfiguration.cs +++ b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsConfiguration.cs @@ -2,15 +2,14 @@ using Nuke.Common.CI.GitHubActions.Configuration; using Nuke.Common.Tooling; -#pragma warning disable CA1002 -#pragma warning disable CA1308 -#pragma warning disable CA2227 +#pragma warning disable CA1002, CA1308, CA2227 // ReSharper disable CollectionNeverUpdated.Global namespace Rocket.Surgery.Nuke.GithubActions; /// /// The Github actions configuration entity /// +[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")] public class RocketSurgeonGitHubActionsConfiguration : ConfigurationEntity { // ReSharper disable once NullableWarningSuppressionIsUsed @@ -22,17 +21,17 @@ public class RocketSurgeonGitHubActionsConfiguration : ConfigurationEntity /// /// The short triggers /// - public List ShortTriggers { get; set; } = new(); + public List ShortTriggers { get; set; } = []; /// /// The detailed triggers /// - public List DetailedTriggers { get; set; } = new(); + public List DetailedTriggers { get; set; } = []; /// /// The jobs /// - public List Jobs { get; set; } = new(); + public List Jobs { get; set; } = []; /// /// The dependencies of this workflow @@ -54,6 +53,14 @@ public class RocketSurgeonGitHubActionsConfiguration : ConfigurationEntity /// public GitHubActionsPermissions Permissions { get; set; } = new(); + /// + /// The if condition for the workflow + /// + public GithubActionCondition? If { get; set; } + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString(); + /// public override void Write(CustomFileWriter writer) { @@ -75,6 +82,11 @@ public override void Write(CustomFileWriter writer) Permissions.Write(writer); + if (!string.IsNullOrWhiteSpace(If?.ToString())) + { + writer.WriteLine($"if: {If}"); + } + writer.WriteKeyValues("env", Environment); if (Concurrency is { } concurrency) { diff --git a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsTrigger.cs b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsTrigger.cs index d9fc43f6..dd773c15 100644 --- a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsTrigger.cs +++ b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsTrigger.cs @@ -20,6 +20,12 @@ public enum RocketSurgeonGitHubActionsTrigger [EnumValue("pull_request")] PullRequest, + /// + /// Release + /// + [EnumValue("release")] + Release, + /// /// Workflow dispatch /// diff --git a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsVcsTrigger.cs b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsVcsTrigger.cs index 0c92d7e0..392be194 100644 --- a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsVcsTrigger.cs +++ b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsVcsTrigger.cs @@ -9,6 +9,7 @@ namespace Rocket.Surgery.Nuke.GithubActions; /// /// A detailed trigger for version control /// +[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")] public class RocketSurgeonGitHubActionsVcsTrigger : GitHubActionsDetailedTrigger { /// @@ -36,12 +37,38 @@ public class RocketSurgeonGitHubActionsVcsTrigger : GitHubActionsDetailedTrigger /// public ImmutableArray ExcludePaths { get; set; } = []; + /// + /// The types + /// + public ImmutableArray Types { get; set; } = []; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString(); + /// public override void Write(CustomFileWriter writer) { writer.WriteLine(Kind.GetValue() + ":"); - if (Kind is RocketSurgeonGitHubActionsTrigger.WorkflowDispatch or RocketSurgeonGitHubActionsTrigger.WorkflowCall) return; + if (Kind is RocketSurgeonGitHubActionsTrigger.WorkflowDispatch or RocketSurgeonGitHubActionsTrigger.WorkflowCall) + { + return; + } + + if (Kind is RocketSurgeonGitHubActionsTrigger.PullRequest or RocketSurgeonGitHubActionsTrigger.PullRequestTarget) + { + using (writer.Indent()) + { + if (Types.Length > 0) + { + writer.WriteLine("types:"); + using (writer.Indent()) + { + Types.ForEach(x => writer.WriteLine($"- '{x}'")); + } + } + } + } using (writer.Indent()) { if (Branches.Length > 0) diff --git a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsWorkflowTrigger.cs b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsWorkflowTrigger.cs index a461189e..02789c55 100644 --- a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsWorkflowTrigger.cs +++ b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsWorkflowTrigger.cs @@ -7,6 +7,7 @@ namespace Rocket.Surgery.Nuke.GithubActions; /// /// A detailed trigger for version control /// +[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")] public class RocketSurgeonGitHubActionsWorkflowTrigger : GitHubActionsDetailedTrigger { /// @@ -17,24 +18,31 @@ public class RocketSurgeonGitHubActionsWorkflowTrigger : GitHubActionsDetailedTr /// /// The input variables for the workflow /// - public List Inputs { get; set; } = new(); + public List Inputs { get; set; } = []; /// /// The secret variables for the workflow /// - public List Secrets { get; set; } = new(); + public List Secrets { get; set; } = []; /// /// The output variables for the workflow /// - public List Outputs { get; set; } = new(); + public List Outputs { get; set; } = []; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString(); /// public override void Write(CustomFileWriter writer) { writer.WriteLine(Kind.GetValue() + ":"); - if (Kind is not RocketSurgeonGitHubActionsTrigger.WorkflowDispatch and not RocketSurgeonGitHubActionsTrigger.WorkflowCall) return; + if (Kind is not RocketSurgeonGitHubActionsTrigger.WorkflowDispatch and not RocketSurgeonGitHubActionsTrigger.WorkflowCall) + { + return; + } + using (writer.Indent()) { if (Inputs.Count > 0) @@ -48,11 +56,17 @@ public override void Write(CustomFileWriter writer) using (writer.Indent()) { writer.WriteLine($"type: {input.Type.GetValue()}"); - if (!string.IsNullOrWhiteSpace(input.Description)) writer.WriteLine($"description: '{input.Description}'"); + if (!string.IsNullOrWhiteSpace(input.Description)) + { + writer.WriteLine($"description: '{input.Description}'"); + } - writer.WriteLine($"required: {input.Required ?? false}"); + writer.WriteLine($"required: {( input.Required ?? false ).ToString().ToLowerInvariant()}"); - if (input.Default != null) writer.WriteLine($"default: {input.Default}"); + if (input.Default is not null) + { + writer.WriteLine($"default: {input.Default}"); + } } } } @@ -68,9 +82,12 @@ public override void Write(CustomFileWriter writer) writer.WriteLine($"{input.Name}:"); using (writer.Indent()) { - if (!string.IsNullOrWhiteSpace(input.Description)) writer.WriteLine($"description: '{input.Description}'"); + if (!string.IsNullOrWhiteSpace(input.Description)) + { + writer.WriteLine($"description: '{input.Description}'"); + } - writer.WriteLine($"required: {input.Required ?? false}"); + writer.WriteLine($"required: {( input.Required ?? false ).ToString().ToLowerInvariant()}"); } } } @@ -86,7 +103,10 @@ public override void Write(CustomFileWriter writer) writer.WriteLine($"{input.OutputName}:"); using (writer.Indent()) { - if (!string.IsNullOrWhiteSpace(input.Description)) writer.WriteLine($"description: '{input.Description}'"); + if (!string.IsNullOrWhiteSpace(input.Description)) + { + writer.WriteLine($"description: '{input.Description}'"); + } writer.WriteLine($"value: {input}"); } diff --git a/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJob.cs b/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJob.cs index 7b3efd25..99d42e2d 100644 --- a/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJob.cs +++ b/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJob.cs @@ -1,31 +1,30 @@ using Nuke.Common.CI.GitHubActions.Configuration; -#pragma warning disable CA1002 -#pragma warning disable CA2227 +#pragma warning disable CA1002, CA2227 namespace Rocket.Surgery.Nuke.GithubActions; /// /// Define a job with github actions /// +/// +/// The default constructor +/// +/// +/// [PublicAPI] -public class RocketSurgeonsGithubActionsJob : RocketSurgeonsGithubActionsJobBase +[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")] +public class RocketSurgeonsGithubActionsJob(string name) : RocketSurgeonsGithubActionsJobBase(name) { - /// - /// The default constructor - /// - /// - /// - public RocketSurgeonsGithubActionsJob(string name) : base(name) { } /// /// The images to run on in a matrix /// - public IEnumerable Matrix { get; set; } = Enumerable.Empty(); + public IEnumerable Matrix { get; set; } = []; /// /// The images to run on /// - public IEnumerable RunsOn { get; set; } = Enumerable.Empty(); + public IEnumerable RunsOn { get; set; } = []; /// /// The permissions of this workflow @@ -35,7 +34,7 @@ public RocketSurgeonsGithubActionsJob(string name) : base(name) { } /// /// The steps to run /// - public List Steps { get; set; } = new(); + public List Steps { get; set; } = []; /// /// Should the job matrix fail fast, or wait for all to fail @@ -44,6 +43,9 @@ public RocketSurgeonsGithubActionsJob(string name) : base(name) { } internal IDictionary InternalData { get; } = new Dictionary(); + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString(); + /// public override void Write(CustomFileWriter writer) { @@ -53,11 +55,17 @@ public override void Write(CustomFileWriter writer) { Permissions?.Write(writer); - if (Matrix.Count() > 1 || !FailFast) writer.WriteLine("strategy:"); + if (Matrix.Count() > 1 || !FailFast) + { + writer.WriteLine("strategy:"); + } using (writer.Indent()) { - if (!FailFast) writer.WriteLine("fail-fast: false"); + if (!FailFast) + { + writer.WriteLine("fail-fast: false"); + } if (Matrix.Count() > 1) { @@ -71,10 +79,17 @@ public override void Write(CustomFileWriter writer) } if (!Matrix.Any() && RunsOn.Any()) + { writer.WriteLine($"runs-on: [{string.Join(", ", RunsOn)}]"); + } else if (Matrix.Count() == 1) + { writer.WriteLine($"runs-on: {Matrix.First()}"); - else if (Matrix.Count() > 1) writer.WriteLine("runs-on: ${{ matrix.os }}"); + } + else if (Matrix.Count() > 1) + { + writer.WriteLine("runs-on: ${{ matrix.os }}"); + } writer.WriteLine("steps:"); using (writer.Indent()) diff --git a/src/Nuke/GithubActions/UpdateMilestoneJobAttribute.cs b/src/Nuke/GithubActions/UpdateMilestoneJobAttribute.cs new file mode 100644 index 00000000..7c1642bf --- /dev/null +++ b/src/Nuke/GithubActions/UpdateMilestoneJobAttribute.cs @@ -0,0 +1,433 @@ +using System.Diagnostics; +using Nuke.Common.CI; +using Nuke.Common.CI.GitHubActions; +using Nuke.Common.CI.GitHubActions.Configuration; +using Nuke.Common.Execution; + +namespace Rocket.Surgery.Nuke.GithubActions; + +/// +/// Adds update milestone support to the build +/// +[PublicAPI] +[AttributeUsage(AttributeTargets.Class)] +[DebuggerDisplay("{DebuggerDisplay,nq}")] +public sealed class UpdateMilestoneJobAttribute() : GitHubActionsStepsAttribute("update-milestone", GitHubActionsImage.UbuntuLatest) +{ + private string DebuggerDisplay => ToString(); + + /// + public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) + { + var build = new RocketSurgeonGitHubActionsConfiguration + { + Name = "Update Milestone" + }; + build.DetailedTriggers.Add(new RocketSurgeonGitHubActionsWorkflowTrigger()); + build.DetailedTriggers.Add( + new RocketSurgeonGitHubActionsVcsTrigger + { + Kind = RocketSurgeonGitHubActionsTrigger.PullRequestTarget, + Types = ["closed", "opened", "reopened", "synchronize"], + } + ); + build.Jobs.Add( + new RocketSurgeonsGithubActionsJob("update_milestone") + { + RunsOn = ( !IsGithubHosted ) ? Images : [], + Matrix = IsGithubHosted ? Images : [], + Steps = + [ + new CheckoutStep("Checkout") + { + Ref = "${{ github.sha }}", + FetchDepth = 0, + }, + new RunStep("Fetch all history for all tags and branches") + { + Run = "git fetch --prune", + }, + new SetupDotNetStep("Install DotNet"), + new UsingStep("Install GitVersion") + { + If = "${{ github.event.action == 'opened' }}", + Uses = "gittools/actions/gitversion/setup@v3.1.1", + With = + { + ["versionSpec"] = DotNetTool.GetToolDefinition("GitVersion.Tool").Version + }, + }, + new UsingStep("Install GitReleaseManager") + { + If = "${{ github.event.action == 'opened' }}", + Uses = "gittools/actions/gitreleasemanager/setup@v3.1.1", + With = + { + ["versionSpec"] = DotNetTool.GetToolDefinition("GitReleaseManager.Tool").Version + }, + }, + new UsingStep("Use GitVersion") + { + If = "${{ github.event.action == 'opened' }}", + Id = "gitversion", + Uses = "gittools/actions/gitversion/execute@v3.1.1", + }, + new UsingStep("Create Milestone") + { + If = "${{ github.event.action == 'opened' }}", + Uses = "WyriHaximus/github-action-create-milestone@v1", + With = + { + ["title"] = "v${{ steps.gitversion.outputs.majorMinorPatch }}" + }, + Environment = + { + ["GITHUB_TOKEN"] = "${{ secrets.GITHUB_TOKEN }}" + }, + ContinueOnError = true, + }, + new UsingStep("sync milestones") + { + Uses = "RocketSurgeonsGuild/actions/sync-milestone@v0.3.15", + With = + { + ["default-label"] = ":sparkles: mysterious" + }, + Environment = + { + ["github-token"] = "${{ secrets.GITHUB_TOKEN }}" + }, + }, + ] + } + ); + + return build; + } +} + +/// +/// Adds draft release support to the build +/// +[PublicAPI] +[AttributeUsage(AttributeTargets.Class)] +[DebuggerDisplay("{DebuggerDisplay,nq}")] +public sealed class DraftReleaseJobAttribute() : GitHubActionsStepsAttribute("draft-release", GitHubActionsImage.UbuntuLatest) +{ + private string DebuggerDisplay => ToString(); + + /// + public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) + { + var build = new RocketSurgeonGitHubActionsConfiguration + { + Name = "Draft Release and Create Milestone" + }; + build.DetailedTriggers.Add( + new RocketSurgeonGitHubActionsWorkflowTrigger + { + Kind = RocketSurgeonGitHubActionsTrigger.WorkflowCall, + Secrets = [new GitHubActionsSecret("RSG_BOT_TOKEN", Required: true)], + } + ); + build.DetailedTriggers.Add( + new RocketSurgeonGitHubActionsVcsTrigger + { + Kind = RocketSurgeonGitHubActionsTrigger.Push, + Branches = ["master"], + ExcludePaths = ["**/*.md"], + } + ); + build.DetailedTriggers.Add(new GitHubActionsScheduledTrigger { Cron = "0 0 * * 4" }); + build.Jobs.Add( + new RocketSurgeonsGithubActionsJob("draft_release") + { + RunsOn = ( !IsGithubHosted ) ? Images : [], + Matrix = IsGithubHosted ? Images : [], + Steps = + [ + new CheckoutStep("Checkout") + { + FetchDepth = 0, + }, + new RunStep("Fetch all history for all tags and branches") + { + Run = "git fetch --prune", + }, + new SetupDotNetStep("Install DotNet"), + new UsingStep("Install GitVersion") + { + If = "${{ github.event.action == 'opened' }}", + Uses = "gittools/actions/gitversion/setup@v3.1.1", + With = + { + ["versionSpec"] = DotNetTool.GetToolDefinition("GitVersion.Tool").Version + }, + }, + new UsingStep("Install GitReleaseManager") + { + If = "${{ github.event.action == 'opened' }}", + Uses = "gittools/actions/gitreleasemanager/setup@v3.1.1", + With = + { + ["versionSpec"] = DotNetTool.GetToolDefinition("GitReleaseManager.Tool").Version + }, + }, + new UsingStep("Create Milestone") + { + If = "${{ github.event.action == 'opened' }}", + Uses = "WyriHaximus/github-action-create-milestone@v1", + With = + { + ["title"] = "v${{ steps.gitversion.outputs.majorMinorPatch }}" + }, + Environment = + { + ["GITHUB_TOKEN"] = "${{ secrets.GITHUB_TOKEN }}" + }, + }, + new UsingStep("sync milestones") + { + Uses = "RocketSurgeonsGuild/actions/sync-milestone@v0.3.15", + With = + { + ["default-label"] = ":sparkles: mysterious", + ["github-token"] = "${{ secrets.GITHUB_TOKEN }" + } + }, + + new UsingStep("Create Release") + { + Uses = "ncipollo/release-action@v1", + With = + { + ["allowUpdates"] = "true", ["generateReleaseNotes"] = "true", + ["draft"] = "true", + ["omitNameDuringUpdate"] = "true", + ["name"] = "v${{ steps.gitversion.outputs.majorMinorPatch }}", + ["tag"] = "v${{ steps.gitversion.outputs.majorMinorPatch }}", + ["token"] = "${{ secrets.RSG_BOT_TOKEN }}", + ["commit"] = "${{ github.base_ref }}" + }, + }, + ] + } + ); + + return build; + } +} + +/// +/// Adds close milestone support to the build +/// +[PublicAPI] +[AttributeUsage(AttributeTargets.Class)] +[DebuggerDisplay("{DebuggerDisplay,nq}")] +public sealed class PublishNugetPackagesJobAttribute : GitHubActionsStepsAttribute +{ + private readonly string _secretKey; + + /// + /// Adds draft release support to the build + /// + public PublishNugetPackagesJobAttribute(string secretKey) : base("publish-nuget", GitHubActionsImage.UbuntuLatest) => _secretKey = secretKey; + + /// + /// Adds draft release support to the build + /// + public PublishNugetPackagesJobAttribute(string secretKey, string image, params string[] images) : base("publish-nuget", image, images) => _secretKey = secretKey; + + /// + /// Adds draft release support to the build + /// + public PublishNugetPackagesJobAttribute(string secretKey, GitHubActionsImage image) : base("publish-nuget", image) => _secretKey = secretKey; + + private string DebuggerDisplay => ToString(); + + /// + public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) + { + var build = new RocketSurgeonGitHubActionsConfiguration + { + Name = "Publish Nuget Packages" + }; + build.DetailedTriggers.Add( + new RocketSurgeonGitHubActionsWorkflowTrigger + { + Kind = RocketSurgeonGitHubActionsTrigger.WorkflowCall, + Secrets = [new GitHubActionsSecret(_secretKey, Required: true)], + } + ); + build.DetailedTriggers.Add( + new RocketSurgeonGitHubActionsVcsTrigger + { + Kind = RocketSurgeonGitHubActionsTrigger.Release, + Types = ["released"], + } + ); + build.Jobs.Add( + new RocketSurgeonsGithubActionsJob("publish_nuget") + { + RunsOn = ( !IsGithubHosted ) ? Images : [], + Matrix = IsGithubHosted ? Images : [], + Steps = + [ + new DownloadArtifactStep("nuget"), + new RunStep("nuget.org") + { + If = "startsWith(github.ref, 'refs/tags/')", + Shell = "pwsh", + Environment = + { + ["ApiKey"] = $"${{{{ secrets.{_secretKey} }}}}" + }, + Run = @" + dotnet nuget push **/*.nupkg --skip-duplicate -s nuget.org --api-key $ENV:ApiKey + dotnet nuget push **/*.snupkg --skip-duplicate -s nuget.org --api-key $ENV:ApiKey + " + }, + ] + } + ); + + return build; + } +} + +/// +/// Adds draft release support to the build +/// +[PublicAPI] +[AttributeUsage(AttributeTargets.Class)] +[DebuggerDisplay("{DebuggerDisplay,nq}")] +public sealed class CloseMilestoneJobAttribute : GitHubActionsStepsAttribute +{ + /// + /// Adds draft release support to the build + /// + public CloseMilestoneJobAttribute() : base("close-milestone", GitHubActionsImage.UbuntuLatest) { } + + /// + /// Adds draft release support to the build + /// + public CloseMilestoneJobAttribute(string image, params string[] images) : base("close-milestone", image, images) { } + + /// + /// Adds draft release support to the build + /// + public CloseMilestoneJobAttribute(GitHubActionsImage image) : base("close-milestone", image) { } + + private string DebuggerDisplay => ToString(); + + /// + public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) + { + var build = new RocketSurgeonGitHubActionsConfiguration + { + Name = "Close Milestone" + }; + build.DetailedTriggers.Add( + new RocketSurgeonGitHubActionsWorkflowTrigger + { + Kind = RocketSurgeonGitHubActionsTrigger.WorkflowCall, + Secrets = [new GitHubActionsSecret("RSG_BOT_TOKEN", Required: true)], + } + ); + build.DetailedTriggers.Add( + new RocketSurgeonGitHubActionsVcsTrigger + { + Kind = RocketSurgeonGitHubActionsTrigger.Release, + Types = ["released"], + } + ); + build.Jobs.Add( + new RocketSurgeonsGithubActionsJob("close_milestone") + { + RunsOn = ( !IsGithubHosted ) ? Images : [], + Matrix = IsGithubHosted ? Images : [], + Steps = + [ + new CheckoutStep("Checkout") + { + FetchDepth = 0, + }, + new RunStep("Fetch all history for all tags and branches") + { + Run = "git fetch --prune", + }, + new SetupDotNetStep("Install DotNet"), + new UsingStep("Install GitVersion") + { + If = "${{ github.event.action == 'opened' }}", + Uses = "gittools/actions/gitversion/setup@v3.1.1", + With = + { + ["versionSpec"] = DotNetTool.GetToolDefinition("GitVersion.Tool").Version + }, + }, + new UsingStep("Install GitReleaseManager") + { + If = "${{ github.event.action == 'opened' }}", + Uses = "gittools/actions/gitreleasemanager/setup@v3.1.1", + With = + { + ["versionSpec"] = DotNetTool.GetToolDefinition("GitReleaseManager.Tool").Version + }, + }, + new UsingStep("Create Milestone") + { + If = "${{ github.event.action == 'opened' }}", + Uses = "WyriHaximus/github-action-create-milestone@v1", + With = + { + ["title"] = "v${{ steps.gitversion.outputs.majorMinorPatch }}" + }, + Environment = + { + ["GITHUB_TOKEN"] = "${{ secrets.GITHUB_TOKEN }}" + }, + ContinueOnError = true, + }, + new UsingStep("sync milestones") + { + Uses = "RocketSurgeonsGuild/actions/sync-milestone@v0.3.15", + With = + { + ["default-label"] = ":sparkles: mysterious" + }, + Environment = + { + ["github-token"] = "${{ secrets.GITHUB_TOKEN }}" + }, + }, + new RunStep("Get Repo and Owner") + { + Shell = "pwsh", + Id = "repository", + If = "${{ !github.event.release.prerelease && steps.gitversion.outputs.preReleaseTag == '' }}", + Run = """ + $parts = $ENV:GITHUB_REPOSITORY.Split('/') + echo "::set-output name=owner::$($parts[0])" + echo "::set-output name=repository::$($parts[1])" + """ + }, + new RunStep("Close Milestone") + { + Shell = "pwsh", + If = "${{ !github.event.release.prerelease && steps.gitversion.outputs.preReleaseTag == '' }}", + Run = """ + dotnet gitreleasemanager close ` + -o "${{ steps.repository.outputs.owner }}" ` + -r "${{ steps.repository.outputs.repository }}" ` + --token "${{ secrets.GITHUB_TOKEN }}" ` + -m "v${{ steps.gitversion.outputs.majorMinorPatch }}" + """ + }, + ] + } + ); + + return build; + } +} diff --git a/src/Nuke/ITriggerCodeCoverageReports.cs b/src/Nuke/ITriggerCodeCoverageReports.cs index 093dcd8d..59414a56 100644 --- a/src/Nuke/ITriggerCodeCoverageReports.cs +++ b/src/Nuke/ITriggerCodeCoverageReports.cs @@ -37,28 +37,30 @@ public interface ITriggerCodeCoverageReports : IHaveCodeCoverage, IHaveTestTarge () => { var files = TestResultsDirectory.GlobFiles("**/*.xml", "**/*.json", "**/*.coverage"); - if (files.Any()) + if (!files.Any()) { - _ = ReportGeneratorTasks.ReportGenerator( - settings => settings - .SetReports(files) - .SetSourceDirectories(NukeBuild.RootDirectory) - .SetProcessWorkingDirectory(RootDirectory) - .SetTargetDirectory(CoverageDirectory) - .AddReportTypes( - ReportTypes.Cobertura, - ReportTypes.Xml, - ReportTypes.lcov, - ReportTypes.Latex, - ReportTypes.OpenCover - ) - ); - - _ = ( CoverageDirectory / "Cobertura.xml" ).Move( - CoverageDirectory / "test.cobertura.xml", - ExistsPolicy.FileOverwriteIfNewer - ); + return; } + + _ = ReportGeneratorTasks.ReportGenerator( + settings => settings + .SetReports(files) + .SetSourceDirectories(NukeBuild.RootDirectory) + .SetProcessWorkingDirectory(RootDirectory) + .SetTargetDirectory(CoverageDirectory) + .AddReportTypes( + ReportTypes.Cobertura, + ReportTypes.Xml, + ReportTypes.lcov, + ReportTypes.Latex, + ReportTypes.OpenCover + ) + ); + + _ = ( CoverageDirectory / "Cobertura.xml" ).Move( + CoverageDirectory / "test.cobertura.xml", + ExistsPolicy.FileOverwriteIfNewer + ); } ); @@ -92,20 +94,17 @@ public interface ITriggerCodeCoverageReports : IHaveCodeCoverage, IHaveTestTarge /// protected ReportGeneratorSettings Defaults(ReportGeneratorSettings settings) => ( this switch - { - IHaveGitVersion gitVersion => settings.SetTag( - gitVersion.GitVersion.InformationalVersion - ), - IHaveGitRepository { GitRepository: { } } gitRepository => settings - .SetTag(gitRepository.GitRepository.Head), - _ => settings, - } + { + IHaveGitVersion gitVersion => settings.SetTag(gitVersion.GitVersion.InformationalVersion), + IHaveGitRepository { GitRepository: { } } gitRepository => settings.SetTag(gitRepository.GitRepository.Head), + _ => settings, + } ) .SetReports(InputReports) .SetSourceDirectories(NukeBuild.RootDirectory) .SetFramework(Constants.ReportGeneratorFramework) - // this is more or less a hack / compromise because - // I was unable to coverage to exclude everything in a given assembly by default. + // this is more or less a hack / compromise because + // I was unable to coverage to exclude everything in a given assembly by default. .AddAssemblyFilters( Solution .AnalyzeAllProjects() diff --git a/src/Nuke/ResolvedToolsManifest.cs b/src/Nuke/ResolvedToolsManifest.cs index 6e6b76ed..3c4dbd01 100644 --- a/src/Nuke/ResolvedToolsManifest.cs +++ b/src/Nuke/ResolvedToolsManifest.cs @@ -5,7 +5,11 @@ namespace Rocket.Surgery.Nuke; -internal record ResolvedToolsManifest(ImmutableDictionary CommandDefinitions) +internal class ResolvedToolsManifest +( + ImmutableDictionary toolDefinitions, + ImmutableDictionary commandDefinitions +) { public static ResolvedToolsManifest Create(ToolsManifset source) { @@ -19,7 +23,7 @@ public static ResolvedToolsManifest Create(ToolsManifset source) } } - return new(commandBuilder.ToImmutable()); + return new(source.Tools.ToImmutableDictionary(z => z.Key, z => z.Value, StringComparer.OrdinalIgnoreCase), commandBuilder.ToImmutable()); } private static void DefaultLogger(OutputType kind, string message) @@ -36,113 +40,41 @@ private static void DefaultLogger(OutputType kind, string message) // ReSharper restore TemplateIsNotCompileTimeConstantProblem } - public bool IsInstalled(string commandName) => CommandDefinitions.ContainsKey(commandName) - || CommandDefinitions.Values.Any(z => z.PackageId.Equals(commandName, StringComparison.OrdinalIgnoreCase)); + public bool IsInstalled(string commandName) => commandDefinitions.ContainsKey(commandName) || toolDefinitions.ContainsKey(commandName); - public Tool GetTool(string nugetPackageName) => ( CommandDefinitions.TryGetValue(nugetPackageName, out var tool) ) - ? ( (arguments, directory, variables, timeout, output, invocation, logger, handler) => - { - var newArgs = new ArgumentStringHandler(); - newArgs.AppendLiteral(tool.Command); - newArgs.AppendLiteral(" "); - newArgs.AppendLiteral(arguments.ToStringAndClear().TrimMatchingDoubleQuotes()); - arguments.AppendLiteral(newArgs.ToStringAndClear()); - return DotNetTasks.DotNet( - arguments, - directory, - variables, - timeout, - output, - invocation, - // ReSharper disable once TemplateIsNotCompileTimeConstantProblem - logger ?? DefaultLogger, - process => - { - handler?.Invoke(process); - return null; - } - ); - } ) - : throw new InvalidOperationException($"Tool {nugetPackageName} is not installed"); - - - /* Unmerged change from project 'Rocket.Surgery.Nuke(net9.0)' - Before: - public Tool GetProperTool(string nugetPackageName) - { - return CommandDefinitions.TryGetValue(nugetPackageName, out var tool) - ? (arguments, directory, variables, timeout, output, invocation, logger, handler) => - { - var newArgs = new ArgumentStringHandler(); - newArgs.AppendLiteral(tool.Command); - newArgs.AppendLiteral(" "); - newArgs.AppendLiteral(arguments.ToStringAndClear().TrimMatchingDoubleQuotes()); - arguments.AppendLiteral(newArgs.ToStringAndClear()); + public ToolDefinition GetToolDefinition(string nugetPackageName) => ( toolDefinitions.TryGetValue(nugetPackageName, out var tool) ) + ? tool + : throw new InvalidOperationException($"Tool {nugetPackageName} is not installed"); - var process = ProcessTasks.StartProcess( - DotNetTasks.DotNetPath, - newArgs, - directory, - variables, - timeout, - output, - invocation, - // ReSharper disable once TemplateIsNotCompileTimeConstantProblem - logger ?? DefaultLogger - ); - ( handler ?? ( p => process.AssertZeroExitCode() ) ).Invoke(process.AssertWaitForExit()); - return process.Output; - } - : throw new InvalidOperationException($"Tool {nugetPackageName} is not installed"); - } - After: - public Tool GetProperTool(string nugetPackageName) => ( CommandDefinitions.TryGetValue(nugetPackageName, out var tool) ) - ? (arguments, directory, variables, timeout, output, invocation, logger, handler) => - { - var newArgs = new ArgumentStringHandler(); - newArgs.AppendLiteral(tool.Command); - newArgs.AppendLiteral(" "); - newArgs.AppendLiteral(arguments.ToStringAndClear().TrimMatchingDoubleQuotes()); - arguments.AppendLiteral(newArgs.ToStringAndClear()); + public Tool GetTool(string nugetPackageName) => ( toolDefinitions.TryGetValue(nugetPackageName, out var tool) ) + ? CreateHandler(tool.Commands.First()) + : ( commandDefinitions.TryGetValue(nugetPackageName, out var command) ) + ? CreateHandler(command.Command) + : throw new InvalidOperationException($"Tool {nugetPackageName} is not installed"); - var process = ProcessTasks.StartProcess( - DotNetTasks.DotNetPath, - newArgs, - directory, - variables, - timeout, - output, - invocation, - // ReSharper disable once TemplateIsNotCompileTimeConstantProblem - logger ?? DefaultLogger - ); - ( handler ?? ( p => process.AssertZeroExitCode() ) ).Invoke(process.AssertWaitForExit()); - return process.Output; - } - : throw new InvalidOperationException($"Tool {nugetPackageName} is not installed"); - */ - public Tool GetProperTool(string nugetPackageName) => ( CommandDefinitions.TryGetValue(nugetPackageName, out var tool) ) - ? (arguments, directory, variables, timeout, output, invocation, logger, handler) => - { - var newArgs = new ArgumentStringHandler(); - newArgs.AppendLiteral(tool.Command); - newArgs.AppendLiteral(" "); - newArgs.AppendLiteral(arguments.ToStringAndClear().TrimMatchingDoubleQuotes()); - arguments.AppendLiteral(newArgs.ToStringAndClear()); + public Tool GetProperTool(string nugetPackageName) => GetTool(nugetPackageName); - var process = ProcessTasks.StartProcess( - DotNetTasks.DotNetPath, - newArgs, - directory, - variables, - timeout, - output, - invocation, - // ReSharper disable once TemplateIsNotCompileTimeConstantProblem - logger ?? DefaultLogger - ); - ( handler ?? ( p => process.AssertZeroExitCode() ) ).Invoke(process.AssertWaitForExit()); - return process.Output; - } - : throw new InvalidOperationException($"Tool {nugetPackageName} is not installed"); + private static Tool CreateHandler(string command) => (arguments, directory, variables, timeout, output, invocation, logger, handler) => + { + var newArgs = new ArgumentStringHandler(); + newArgs.AppendLiteral(command); + newArgs.AppendLiteral(" "); + newArgs.AppendLiteral(arguments.ToStringAndClear().TrimMatchingDoubleQuotes()); + arguments.AppendLiteral(newArgs.ToStringAndClear()); + return DotNetTasks.DotNet( + arguments, + directory, + variables, + timeout, + output, + invocation, + // ReSharper disable once TemplateIsNotCompileTimeConstantProblem + logger ?? DefaultLogger, + process => + { + handler?.Invoke(process); + return null; + } + ); + }; } diff --git a/src/Nuke/ToolDefinition.cs b/src/Nuke/ToolDefinition.cs index 5954f223..bd13f981 100644 --- a/src/Nuke/ToolDefinition.cs +++ b/src/Nuke/ToolDefinition.cs @@ -1,8 +1,21 @@ namespace Rocket.Surgery.Nuke; -internal class ToolDefinition +/// +/// A tool definition +/// +[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")] +public class ToolDefinition { + /// + /// The version + /// public string Version { get; set; } = null!; + /// + /// The commands + /// public string[] Commands { get; set; } = []; + + [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString(); // ReSharper disable once NullableWarningSuppressionIsUsed }