From 227984ba3f46b4ce18141105484790127b02ad32 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Mon, 20 Nov 2023 18:17:22 -0500 Subject: [PATCH 1/2] Added target to regenerate all configured build configurations for use in the pre-commit task --- .build/Build.CI.cs | 22 +- .build/Build.cs | 2 +- .github/workflows/ci-ignore.yml | 14 ++ .github/workflows/ci.yml | 15 +- .github/workflows/inputs.yml | 14 ++ .github/workflows/lint.yml | 72 ++++++ .husky/pre-commit | 7 +- .lintstagedrc.js | 18 +- .nuke/build.schema.json | 2 + Directory.Packages.props | 8 +- Nuke.sln | 1 + src/Nuke/Extensions.cs | 36 +++ .../GitHubActionsLintAttribute.cs | 79 ++++++ .../GithubActions/GitHubActionsPermission.cs | 24 ++ .../GithubActions/GitHubActionsPermissions.cs | 224 ++++++++++++++++++ .../GitHubActionsStepsAttribute.cs | 10 +- ...RocketSurgeonGitHubActionsConfiguration.cs | 7 + .../RocketSurgeonGitHubActionsTrigger.cs | 7 +- .../RocketSurgeonsGithubActionsJob.cs | 7 + .../RocketSurgeonsGithubActionsJobBase.cs | 3 +- src/Nuke/ICanLint.cs | 62 +++++ src/Nuke/ICanRegenerateBuildConfiguration.cs | 6 +- src/Nuke/PublicAPI.Shipped.txt | 2 + 23 files changed, 603 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 src/Nuke/GithubActions/GitHubActionsLintAttribute.cs create mode 100644 src/Nuke/GithubActions/GitHubActionsPermission.cs create mode 100644 src/Nuke/GithubActions/GitHubActionsPermissions.cs diff --git a/.build/Build.CI.cs b/.build/Build.CI.cs index 06dcdc67..5d17ccb0 100644 --- a/.build/Build.CI.cs +++ b/.build/Build.CI.cs @@ -46,6 +46,13 @@ ExcludedTargets = new[] { nameof(ICanClean.Clean), nameof(ICanRestoreWithDotNetCore.DotnetToolRestore) }, Enhancements = new[] { nameof(CiMiddleware) } )] +[GitHubActionsLint( + "lint", + GitHubActionsImage.UbuntuLatest, + AutoGenerate = false, + OnPullRequestBranches = new[] { "master", "main", "next" }, + Enhancements = new[] { nameof(LintStagedMiddleware) } +)] [GitHubActionsSteps( "inputs", GitHubActionsImage.UbuntuLatest, @@ -103,10 +110,19 @@ public static RocketSurgeonGitHubActionsConfiguration CiMiddleware(RocketSurgeon .First(z => z.Name.Equals("Build", StringComparison.OrdinalIgnoreCase)) .UseDotNetSdks("6.0", "8.0") .AddNuGetCache() - // .ConfigureForGitVersion() + // .ConfigureForGitVersion() .ConfigureStep(step => step.FetchDepth = 0) - .PublishLogs() - .FailFast = false; + .PublishLogs(); + + return configuration; + } + + public static RocketSurgeonGitHubActionsConfiguration LintStagedMiddleware(RocketSurgeonGitHubActionsConfiguration configuration) + { + configuration + .Jobs.OfType() + .First(z => z.Name.Equals("Build", StringComparison.OrdinalIgnoreCase)) + .UseDotNetSdks("6.0", "8.0"); return configuration; } diff --git a/.build/Build.cs b/.build/Build.cs index 74d5855f..f70dde5a 100644 --- a/.build/Build.cs +++ b/.build/Build.cs @@ -27,6 +27,7 @@ public partial class Pipeline : NukeBuild, ICanPackWithDotNetCore, IHaveDataCollector, ICanClean, + ICanLintStagedFiles, ICanDotNetFormat, ICanPrettier, IHavePublicApis, @@ -34,7 +35,6 @@ public partial class Pipeline : NukeBuild, IGenerateCodeCoverageReport, IGenerateCodeCoverageSummary, IGenerateCodeCoverageBadges, - ICanRegenerateBuildConfiguration, IHaveConfiguration { /// diff --git a/.github/workflows/ci-ignore.yml b/.github/workflows/ci-ignore.yml index 9f2bff03..82ac1a9e 100644 --- a/.github/workflows/ci-ignore.yml +++ b/.github/workflows/ci-ignore.yml @@ -67,6 +67,20 @@ on: - '.github/labels.yml' - '.github/release.yml' - '.github/renovate.json' +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: build: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0218ffe..65bf1461 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,13 +76,26 @@ on: - '.github/labels.yml' - '.github/release.yml' - '.github/renovate.json' +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: build: env: NUGET_PACKAGES: '${{ github.workspace }}/.nuget/packages' strategy: - fail-fast: false matrix: os: [macos-latest, windows-latest, ubuntu-latest] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/inputs.yml b/.github/workflows/inputs.yml index 502c6b7f..910b5d66 100644 --- a/.github/workflows/inputs.yml +++ b/.github/workflows/inputs.yml @@ -29,6 +29,20 @@ on: withOutputsISetAThing: description: 'Some output value' value: ${{ jobs.build.outputs.withOutputsISetAThing }} +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: build: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..5a685d73 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,72 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [GitHubActionsLint (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# nuke --generate-configuration GitHubActions_lint --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + +name: lint + +on: + pull_request: + branches: + - 'master' + - 'main' + - 'next' +permissions: + actions: read + checks: read + contents: write + 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: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + repository: '${{ github.event.pull_request.head.repo.full_name }}' + ref: '${{ github.event.pull_request.head.ref }}' + clean: 'false' + - name: 🔨 Use .NET Core 6.0 SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0.x' + - name: 🔨 Use .NET Core 8.0 SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '8.0.x' + - name: 🎁 dotnet tool restore + run: | + dotnet tool restore + - name: Regenerate Build Configurations + id: regenerateBuildConfigurations + run: | + dotnet nuke RegenerateBuildConfigurations --skip --thisisaothervariable '${{ vars.THIS_IS_A_VARIABLE }}' --thisisanothervariable '${{ vars.THIS_IS_ANOTHER_VARIABLE }}' --this_is_a_variable '${{ vars.THIS_IS_A_VARIABLE }}' --thisisaenv '${{ env.THIS_IS_A_ENV || 'test' }}' --thisisasecret '${{ secrets.THIS_IS_A_SECRET }}' --githubtoken '${{ secrets.GITHUB_TOKEN }}' + - name: Lint Staged + id: lintStaged + run: | + dotnet nuke LintStaged --skip --thisisaothervariable '${{ vars.THIS_IS_A_VARIABLE }}' --thisisanothervariable '${{ vars.THIS_IS_ANOTHER_VARIABLE }}' --this_is_a_variable '${{ vars.THIS_IS_A_VARIABLE }}' --thisisaenv '${{ env.THIS_IS_A_ENV || 'test' }}' --thisisasecret '${{ secrets.THIS_IS_A_SECRET }}' --githubtoken '${{ secrets.GITHUB_TOKEN }}' + - name: Add & Commit + uses: EndBug/add-and-commit@v9 + with: + message: 'Automatically linting code' diff --git a/.husky/pre-commit b/.husky/pre-commit index 92e1c321..7bc7b1c1 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,9 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -dotnet nuke --help > /dev/null 2>&1 || true -export NUKE_INTERNAL_INTERCEPTOR=1 -npx lint-staged -d -r -dotnet .build/bin/Debug/net6.0/.build.dll regenerate-build-configurations -git add .github/workflows/*.yml -git add .nuke/build.schema.json +dotnet nuke lint-staged diff --git a/.lintstagedrc.js b/.lintstagedrc.js index 0495f3ef..beb02eb5 100644 --- a/.lintstagedrc.js +++ b/.lintstagedrc.js @@ -1,16 +1,10 @@ -function forEachChunk(chunks, callback, chunkSize = 50) { - var mappedFiles = []; - var files = chunks.concat(); - while (files.length > 0) { - var chunk = files.splice(0, chunkSize); - mappedFiles = mappedFiles.concat(callback(chunk)); - } - return mappedFiles; +if (!process.env.NUKE_BUILD_ASSEMBLY) { + throw new Error("Environment variable 'NUKE_BUILD_ASSEMBLY' is not set."); } module.exports = { - '!(*verified|*received).cs': filenames => [`dotnet .build/bin/Debug/net6.0/.build.dll lint --lint-files ${filenames.join(' ')}`], - '*.{Shipped.txt,Unshipped.txt}': filenames => [`dotnet .build/bin/Debug/net6.0/.build.dll move-unshipped-to-shipped --lint-files ${filenames.join(' ')}`], - '*.{csproj,targets,props,xml}': filenames => forEachChunk(filenames, chunk => [`prettier --write '${chunk.join(`' '`)}'`]), - '*.{js,ts,jsx,tsx,json,yml,yaml}': filenames => forEachChunk(filenames, chunk => [`prettier --write '${chunk.join(`' '`)}'`]), + '!(*verified|*received).cs': filenames => [`dotnet ${process.env.NUKE_BUILD_ASSEMBLY} lint --lint-files ${filenames.join(' ')}`], + '*.{Shipped.txt,Unshipped.txt}': filenames => [`dotnet ${process.env.NUKE_BUILD_ASSEMBLY} move-unshipped-to-shipped --lint-files ${filenames.join(' ')}`], + '*.{csproj,targets,props,xml}': filenames => [`prettier --write '${filenames.join(`' '`)}`], + '*.{js,ts,jsx,tsx,json,yml,yaml}': filenames => [`prettier --write '${filenames.join(`' '`)}`], }; diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 17f8181f..7534273c 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -107,6 +107,7 @@ "JetBrainsCleanupCode", "Lint", "LintPublicApiAnalyzers", + "LintStaged", "MoveUnshippedToShipped", "Pack", "PostLint", @@ -147,6 +148,7 @@ "JetBrainsCleanupCode", "Lint", "LintPublicApiAnalyzers", + "LintStaged", "MoveUnshippedToShipped", "Pack", "PostLint", diff --git a/Directory.Packages.props b/Directory.Packages.props index 0c72d950..896e6121 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,7 +14,7 @@ - + @@ -38,9 +38,9 @@ - - - + + + (this T toolSettings, IEnumerable rep { return toolSettings.SetReports(reports.Select(z => z.ToString()).ToArray()); } + + /// + /// Add a value to the dictionary if it's missing + /// + /// + /// + /// + /// + /// + /// + public static IDictionary AddIfMissing(this IDictionary dictionary, TKey key, TValue value) where TKey : notnull + { + if (dictionary.TryGetValue(key, out _)) return dictionary; + dictionary[key] = value; + return dictionary; + } + + /// + /// Add a value to the dictionary if it's missing + /// + /// + /// + /// + /// + /// + /// + public static IReadOnlyDictionary AddIfMissing(this IReadOnlyDictionary dictionary, TKey key, TValue value) where TKey : notnull + { + if (dictionary.TryGetValue(key, out _)) return dictionary; + + var newDictionary = dictionary.ToDictionary(z => z.Key, z => z.Value); + newDictionary[key] = value; + return new ReadOnlyDictionary(newDictionary); + } } diff --git a/src/Nuke/GithubActions/GitHubActionsLintAttribute.cs b/src/Nuke/GithubActions/GitHubActionsLintAttribute.cs new file mode 100644 index 00000000..fedeeebc --- /dev/null +++ b/src/Nuke/GithubActions/GitHubActionsLintAttribute.cs @@ -0,0 +1,79 @@ +using Nuke.Common.CI; +using Nuke.Common.CI.GitHubActions; +using Nuke.Common.Execution; + +namespace Rocket.Surgery.Nuke.GithubActions; + +/// +/// An attribute to help adding the lint workflow +/// +[PublicAPI] +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +public class GitHubActionsLintAttribute : GitHubActionsStepsAttribute +{ + /// + /// The default constructor + /// + /// + /// + /// + public GitHubActionsLintAttribute( + string name, + GitHubActionsImage image, + params GitHubActionsImage[] images + ) : base(name, image, images) + { + InvokedTargets = new[] { nameof(ICanLintStagedFiles.LintStaged) }; + } + + /// + /// The default constructor + /// + /// + /// + /// + public GitHubActionsLintAttribute( + string name, + string image, + params string[] images + ) : base(name, image, images) + { + } + + /// + public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) + { + var config = base.GetConfiguration(relevantTargets); + if (config is not RocketSurgeonGitHubActionsConfiguration configuration) + { + return config; + } + + var buildJob = + configuration + .Jobs.OfType() + .First(z => z.Name.Equals("Build", StringComparison.OrdinalIgnoreCase)); + + configuration.Permissions.Contents = GitHubActionsPermission.Write; + + buildJob + .ConfigureStep( + step => + { + step.Repository = "${{ github.event.pull_request.head.repo.full_name }}"; + step.Ref = "${{ github.event.pull_request.head.ref }}"; + } + ) + .AddStep( + new UsingStep("Add & Commit") + { + Uses = "EndBug/add-and-commit@v9", + With = { ["message"] = "Automatically linting code", } + } + ); + + buildJob.Name = "lint"; + + return configuration; + } +} diff --git a/src/Nuke/GithubActions/GitHubActionsPermission.cs b/src/Nuke/GithubActions/GitHubActionsPermission.cs new file mode 100644 index 00000000..1562d453 --- /dev/null +++ b/src/Nuke/GithubActions/GitHubActionsPermission.cs @@ -0,0 +1,24 @@ +using Nuke.Common.Tooling; + +namespace Rocket.Surgery.Nuke.GithubActions; + +/// +/// Github actions permissions +/// +public enum GitHubActionsPermission +{ + /// + /// None + /// + [EnumValue("none")] None = 0, + + /// + /// Read + /// + [EnumValue("read")] Read = 1, + + /// + /// Write + /// + [EnumValue("write")] Write = 2, +} diff --git a/src/Nuke/GithubActions/GitHubActionsPermissions.cs b/src/Nuke/GithubActions/GitHubActionsPermissions.cs new file mode 100644 index 00000000..22ea1c92 --- /dev/null +++ b/src/Nuke/GithubActions/GitHubActionsPermissions.cs @@ -0,0 +1,224 @@ +using Nuke.Common.Tooling; + +namespace Rocket.Surgery.Nuke.GithubActions; + +/// +/// The permissions collections object +/// +public record GitHubActionsPermissions +{ + /// + /// No permissions to anything + /// + public static GitHubActionsPermissions None { get; } = new() + { + Actions = GitHubActionsPermission.None, + Checks = GitHubActionsPermission.None, + Contents = GitHubActionsPermission.None, + Deployments = GitHubActionsPermission.None, + IdToken = GitHubActionsPermission.None, + Issues = GitHubActionsPermission.None, + Discussions = GitHubActionsPermission.None, + Packages = GitHubActionsPermission.None, + Pages = GitHubActionsPermission.None, + PullRequests = GitHubActionsPermission.None, + RepositoryProjects = GitHubActionsPermission.None, + SecurityEvents = GitHubActionsPermission.None, + Statuses = GitHubActionsPermission.None, + }; + + /// + /// Write all permissions + /// + public static GitHubActionsPermissions WriteAll { get; } = new() + { + Actions = GitHubActionsPermission.Write, + Checks = GitHubActionsPermission.Write, + Contents = GitHubActionsPermission.Write, + Deployments = GitHubActionsPermission.Write, + IdToken = GitHubActionsPermission.Write, + Issues = GitHubActionsPermission.Write, + Discussions = GitHubActionsPermission.Write, + Packages = GitHubActionsPermission.Write, + Pages = GitHubActionsPermission.Write, + PullRequests = GitHubActionsPermission.Write, + RepositoryProjects = GitHubActionsPermission.Write, + SecurityEvents = GitHubActionsPermission.Write, + Statuses = GitHubActionsPermission.Write, + }; + + /// + /// Read all permissions + /// + public static GitHubActionsPermissions ReadAll { get; } = new() + { + Actions = GitHubActionsPermission.Read, + Checks = GitHubActionsPermission.Read, + Contents = GitHubActionsPermission.Read, + Deployments = GitHubActionsPermission.Read, + IdToken = GitHubActionsPermission.Read, + Issues = GitHubActionsPermission.Read, + Discussions = GitHubActionsPermission.Read, + Packages = GitHubActionsPermission.Read, + Pages = GitHubActionsPermission.Read, + PullRequests = GitHubActionsPermission.Read, + RepositoryProjects = GitHubActionsPermission.Read, + SecurityEvents = GitHubActionsPermission.Read, + Statuses = GitHubActionsPermission.Read, + }; + + /// + /// The actions + /// + public GitHubActionsPermission Actions { get; set; } = GitHubActionsPermission.Read; + + /// + /// The checks + /// + public GitHubActionsPermission Checks { get; set; } = GitHubActionsPermission.Read; + + /// + /// The contents + /// + public GitHubActionsPermission Contents { get; set; } = GitHubActionsPermission.Read; + + /// + /// The deployments + /// + public GitHubActionsPermission Deployments { get; set; } = GitHubActionsPermission.Read; + + /// + /// The id-token + /// + public GitHubActionsPermission IdToken { get; set; } + + /// + /// The issues + /// + public GitHubActionsPermission Issues { get; set; } = GitHubActionsPermission.Write; + + /// + /// The discussions + /// + public GitHubActionsPermission Discussions { get; set; } + + /// + /// The packages + /// + public GitHubActionsPermission Packages { get; set; } + + /// + /// The pages + /// + public GitHubActionsPermission Pages { get; set; } + + /// + /// The pull-requests + /// + public GitHubActionsPermission PullRequests { get; set; } = GitHubActionsPermission.Write; + + /// + /// The repository-projects + /// + public GitHubActionsPermission RepositoryProjects { get; set; } + + /// + /// The security-events + /// + public GitHubActionsPermission SecurityEvents { get; set; } + + /// + /// The statuses + /// + public GitHubActionsPermission Statuses { get; set; } = GitHubActionsPermission.Write; + + public void Write(CustomFileWriter writer) + { + if (this == None) + { + writer.WriteLine("permissions: {}"); + return; + } + + if (this == WriteAll) + { + writer.WriteLine("permissions: write-all"); + return; + } + + if (this == ReadAll) + { + writer.WriteLine("permissions: read-all"); + return; + } + + writer.WriteLine("permissions:"); + using (writer.Indent()) + { + if (Actions is { } actions) + { + writer.WriteLine($"actions: {actions.GetValue()}"); + } + + if (Checks is { } checks) + { + writer.WriteLine($"checks: {checks.GetValue()}"); + } + + if (Contents is { } contents) + { + writer.WriteLine($"contents: {contents.GetValue()}"); + } + + if (Deployments is { } deployments) + { + writer.WriteLine($"deployments: {deployments.GetValue()}"); + } + + if (IdToken is { } idToken) + { + writer.WriteLine($"id-token: {idToken.GetValue()}"); + } + + if (Issues is { } issues) + { + writer.WriteLine($"issues: {issues.GetValue()}"); + } + + if (Discussions is { } discussions) + { + writer.WriteLine($"discussions: {discussions.GetValue()}"); + } + + if (Packages is { } packages) + { + writer.WriteLine($"packages: {packages.GetValue()}"); + } + + if (Pages is { } pages) + { + writer.WriteLine($"pages: {pages.GetValue()}"); + } + + if (PullRequests is { } pullRequests) + { + writer.WriteLine($"pull-requests: {pullRequests.GetValue()}"); + } + + if (RepositoryProjects is { } repositoryProjects) + { + writer.WriteLine($"repository-projects: {repositoryProjects.GetValue()}"); + } + + if (SecurityEvents is { } securityEvents) + { + writer.WriteLine($"security-events: {securityEvents.GetValue()}"); + } + + if (Statuses is { } statuses) + { + writer.WriteLine($"statuses: {statuses.GetValue()}"); + } + } + } +} diff --git a/src/Nuke/GithubActions/GitHubActionsStepsAttribute.cs b/src/Nuke/GithubActions/GitHubActionsStepsAttribute.cs index c093ec6a..b75dd197 100644 --- a/src/Nuke/GithubActions/GitHubActionsStepsAttribute.cs +++ b/src/Nuke/GithubActions/GitHubActionsStepsAttribute.cs @@ -100,7 +100,7 @@ public override ConfigurationEntity GetConfiguration(IReadOnlyCollection(variables) - // ReSharper disable once CoVariantArrayConversion + // ReSharper disable once CoVariantArrayConversion .Concat(environmentAttributes) .SelectMany( z => @@ -183,8 +183,8 @@ public override ConfigurationEntity GetConfiguration(IReadOnlyCollection(); foreach (var (execute, 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.ToArray())) ) diff --git a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsConfiguration.cs b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsConfiguration.cs index 31f5b6d8..655869ae 100644 --- a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsConfiguration.cs +++ b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsConfiguration.cs @@ -39,6 +39,11 @@ public class RocketSurgeonGitHubActionsConfiguration : ConfigurationEntity /// public Dictionary Environment { get; set; } = new(StringComparer.OrdinalIgnoreCase); + /// + /// The permissions of this workflow + /// + public GitHubActionsPermissions Permissions { get; set; } = new(); + /// public override void Write(CustomFileWriter writer) { @@ -58,6 +63,8 @@ public override void Write(CustomFileWriter writer) } } + Permissions.Write(writer); + writer.WriteKeyValues("env", Environment); writer.WriteLine(); diff --git a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsTrigger.cs b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsTrigger.cs index b6cec013..14f39c3f 100644 --- a/src/Nuke/GithubActions/RocketSurgeonGitHubActionsTrigger.cs +++ b/src/Nuke/GithubActions/RocketSurgeonGitHubActionsTrigger.cs @@ -26,5 +26,10 @@ public enum RocketSurgeonGitHubActionsTrigger /// /// Workflow call /// - [EnumValue("workflow_call")] WorkflowCall + [EnumValue("workflow_call")] WorkflowCall, + + /// + /// Pull request target + /// + [EnumValue("pull_request_target")] PullRequestTarget, } diff --git a/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJob.cs b/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJob.cs index c6267184..2aee03f1 100644 --- a/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJob.cs +++ b/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJob.cs @@ -29,6 +29,11 @@ public RocketSurgeonsGithubActionsJob(string name) : base(name) /// public IEnumerable RunsOn { get; set; } = Enumerable.Empty(); + /// + /// The permissions of this workflow + /// + public GitHubActionsPermissions? Permissions { get; set; } + /// /// The steps to run /// @@ -48,6 +53,8 @@ public override void Write(CustomFileWriter writer) using (writer.Indent()) { + Permissions?.Write(writer); + if (Matrix.Count() > 1 || !FailFast) { writer.WriteLine("strategy:"); diff --git a/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJobBase.cs b/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJobBase.cs index 82489d77..80f96977 100644 --- a/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJobBase.cs +++ b/src/Nuke/GithubActions/RocketSurgeonsGithubActionsJobBase.cs @@ -23,7 +23,7 @@ protected RocketSurgeonsGithubActionsJobBase(string name) /// /// The name of the job /// - public string Name { get; } + public string Name { get; set; } /// /// The dependencies of this job @@ -40,7 +40,6 @@ protected RocketSurgeonsGithubActionsJobBase(string name) /// public GithubActionCondition? If { get; set; } - /// /// The outputs of this job /// diff --git a/src/Nuke/ICanLint.cs b/src/Nuke/ICanLint.cs index 591c447d..458ba848 100644 --- a/src/Nuke/ICanLint.cs +++ b/src/Nuke/ICanLint.cs @@ -1,4 +1,10 @@ +using System.Reflection; +using Nuke.Common.CI.GitHubActions; using Nuke.Common.IO; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.Git; +using Nuke.Common.Utilities.Collections; +using Serilog; namespace Rocket.Surgery.Nuke; @@ -30,3 +36,59 @@ public interface ICanLint : INukeBuild /// protected internal IEnumerable LintPaths => PrivateLintFiles.Select(z => Path.IsPathRooted(z) ? (AbsolutePath)z : RootDirectory / z); } + +/// +/// Allows the build to lint staged files, as well as lint files from a given pull request. +/// +/// +/// uses the lint-staged npm package under the covers, with dotnet nuke lint-staged as the entry point. +/// +public interface ICanLintStagedFiles : ICanRegenerateBuildConfiguration, INukeBuild +{ + /// + /// Run staged lint tasks + /// + public Target LintStaged => t => + t + .DependsOn(RegenerateBuildConfigurations) + .OnlyWhenDynamic(() => LintStagedIsPullRequest || IsLocalBuild) + .Executes( + () => ProcessTasks.StartProcess( + ToolPathResolver.GetPathExecutable("npx"), + $"lint-staged -r {( LintStagedIsPullRequest ? $"""--diff="origin/{GitHubActions.Instance.BaseRef}...origin/{GitHubActions.Instance.HeadRef}" """ : "" )}", + environmentVariables: EnvironmentInfo.Variables + .AddIfMissing("NUKE_INTERNAL_INTERCEPTOR", "1") + .AddIfMissing("NUKE_BUILD_ASSEMBLY", RootDirectory.GetRelativePathTo(Assembly.GetEntryAssembly().Location)), + logOutput: true, + logger: (type, s) => + { + if (type == OutputType.Std) + { + // ReSharper disable once TemplateIsNotCompileTimeConstantProblem + Log.Information(s); + } + else + { + // ReSharper disable once TemplateIsNotCompileTimeConstantProblem + Log.Error(s); + } + } + ).AssertWaitForExit().AssertZeroExitCode() + ) + .Executes( + () => + { + GitTasks.Git("add .nuke/build.schema.json"); + GitTasks.Git("add .github/workflows/*.yml"); + if (this is IHavePublicApis) + { + GitTasks.Git("add *PublicAPI.Shipped.txt *PublicAPI.Unshipped.txt"); + } + } + ); + + /// + /// Defines if the current environment is a pull request + /// + public bool LintStagedIsPullRequest => GitHubActions.Instance?.IsPullRequest == true; +} diff --git a/src/Nuke/ICanRegenerateBuildConfiguration.cs b/src/Nuke/ICanRegenerateBuildConfiguration.cs index ffd971a5..6bc2f74c 100644 --- a/src/Nuke/ICanRegenerateBuildConfiguration.cs +++ b/src/Nuke/ICanRegenerateBuildConfiguration.cs @@ -10,7 +10,7 @@ namespace Rocket.Surgery.Nuke; /// public interface ICanRegenerateBuildConfiguration { - private Target RegenerateBuildConfigurations => t => + internal Target RegenerateBuildConfigurations => t => t .Unlisted() .Executes( @@ -25,9 +25,7 @@ public interface ICanRegenerateBuildConfiguration .ForEach( command => DotNetTasks.DotNet( command, - environmentVariables: EnvironmentInfo.Variables.ContainsKey("NUKE_INTERNAL_INTERCEPTOR") - ? EnvironmentInfo.Variables - : new Dictionary { ["NUKE_INTERNAL_INTERCEPTOR"] = "1" }.AddDictionary(EnvironmentInfo.Variables) + environmentVariables: EnvironmentInfo.Variables.AddIfMissing("NUKE_INTERNAL_INTERCEPTOR", "1") ) ); } diff --git a/src/Nuke/PublicAPI.Shipped.txt b/src/Nuke/PublicAPI.Shipped.txt index 7af4109a..868ebe28 100644 --- a/src/Nuke/PublicAPI.Shipped.txt +++ b/src/Nuke/PublicAPI.Shipped.txt @@ -495,6 +495,8 @@ Rocket.Surgery.Nuke.ICanClean.Clean.get -> Nuke.Common.Target! Rocket.Surgery.Nuke.ICanLint Rocket.Surgery.Nuke.ICanLint.Lint.get -> Nuke.Common.Target! Rocket.Surgery.Nuke.ICanLint.PostLint.get -> Nuke.Common.Target! +Rocket.Surgery.Nuke.ICanLintStagedFiles.LintStaged.get -> Nuke.Common.Target! +Rocket.Surgery.Nuke.ICanLintStagedFiles.LintStaged.get -> Nuke.Common.Target! Rocket.Surgery.Nuke.ICanPrettier Rocket.Surgery.Nuke.ICanPrettier.Prettier.get -> Nuke.Common.Target! Rocket.Surgery.Nuke.ICanRegenerateBuildConfiguration From 15d69b950e82599ea8c86a17150b0b6b388a3e40 Mon Sep 17 00:00:00 2001 From: David Driscoll Date: Mon, 20 Nov 2023 18:21:14 -0500 Subject: [PATCH 2/2] Updated attribute to support pull_request_target --- .build/Build.CI.cs | 2 +- .github/workflows/lint.yml | 2 +- .../GitHubActionsLintAttribute.cs | 3 +- .../GithubActions/GitHubActionsPermission.cs | 2 ++ .../GithubActions/GitHubActionsPermissions.cs | 4 +++ .../GithubActionsStepsAttributeBase.cs | 35 +++++++++++++++++++ src/Nuke/ICanLint.cs | 3 +- src/Nuke/ICanRegenerateBuildConfiguration.cs | 3 +- src/Nuke/PublicAPI.Shipped.txt | 1 - 9 files changed, 49 insertions(+), 6 deletions(-) diff --git a/.build/Build.CI.cs b/.build/Build.CI.cs index 5d17ccb0..c0e1e73a 100644 --- a/.build/Build.CI.cs +++ b/.build/Build.CI.cs @@ -50,7 +50,7 @@ "lint", GitHubActionsImage.UbuntuLatest, AutoGenerate = false, - OnPullRequestBranches = new[] { "master", "main", "next" }, + OnPullRequestTargetBranches = new[] { "master", "main", "next" }, Enhancements = new[] { nameof(LintStagedMiddleware) } )] [GitHubActionsSteps( diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5a685d73..5f0569b4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ name: lint on: - pull_request: + pull_request_target: branches: - 'master' - 'main' diff --git a/src/Nuke/GithubActions/GitHubActionsLintAttribute.cs b/src/Nuke/GithubActions/GitHubActionsLintAttribute.cs index fedeeebc..e34a11fd 100644 --- a/src/Nuke/GithubActions/GitHubActionsLintAttribute.cs +++ b/src/Nuke/GithubActions/GitHubActionsLintAttribute.cs @@ -1,6 +1,7 @@ using Nuke.Common.CI; using Nuke.Common.CI.GitHubActions; using Nuke.Common.Execution; +#pragma warning disable CA1019 namespace Rocket.Surgery.Nuke.GithubActions; @@ -9,7 +10,7 @@ namespace Rocket.Surgery.Nuke.GithubActions; /// [PublicAPI] [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] -public class GitHubActionsLintAttribute : GitHubActionsStepsAttribute +public sealed class GitHubActionsLintAttribute : GitHubActionsStepsAttribute { /// /// The default constructor diff --git a/src/Nuke/GithubActions/GitHubActionsPermission.cs b/src/Nuke/GithubActions/GitHubActionsPermission.cs index 1562d453..ed7a0e60 100644 --- a/src/Nuke/GithubActions/GitHubActionsPermission.cs +++ b/src/Nuke/GithubActions/GitHubActionsPermission.cs @@ -5,7 +5,9 @@ namespace Rocket.Surgery.Nuke.GithubActions; /// /// Github actions permissions /// +#pragma warning disable CA1711 public enum GitHubActionsPermission +#pragma warning restore CA1711 { /// /// None diff --git a/src/Nuke/GithubActions/GitHubActionsPermissions.cs b/src/Nuke/GithubActions/GitHubActionsPermissions.cs index 22ea1c92..0e9d0a6e 100644 --- a/src/Nuke/GithubActions/GitHubActionsPermissions.cs +++ b/src/Nuke/GithubActions/GitHubActionsPermissions.cs @@ -132,6 +132,10 @@ public record GitHubActionsPermissions /// public GitHubActionsPermission Statuses { get; set; } = GitHubActionsPermission.Write; + /// + /// Write the permissions to the given yaml file + /// + /// public void Write(CustomFileWriter writer) { if (this == None) diff --git a/src/Nuke/GithubActions/GithubActionsStepsAttributeBase.cs b/src/Nuke/GithubActions/GithubActionsStepsAttributeBase.cs index 6dac1668..7b750080 100644 --- a/src/Nuke/GithubActions/GithubActionsStepsAttributeBase.cs +++ b/src/Nuke/GithubActions/GithubActionsStepsAttributeBase.cs @@ -79,6 +79,26 @@ protected GithubActionsStepsAttributeBase(string name) /// public string[] OnPullRequestExcludePaths { get; set; } = Array.Empty(); + /// + /// The branches for pull requests + /// + public string[] OnPullRequestTargetBranches { get; set; } = Array.Empty(); + + /// + /// The tags for pull requests + /// + public string[] OnPullRequestTargetTags { get; set; } = Array.Empty(); + + /// + /// The paths to include for pull requests + /// + public string[] OnPullRequestTargetIncludePaths { get; set; } = Array.Empty(); + + /// + /// The paths to exclude for pull requests + /// + public string[] OnPullRequestTargetExcludePaths { get; set; } = Array.Empty(); + /// /// The schedule to run on /// @@ -225,6 +245,21 @@ IEnumerable secrets }; } + if (OnPullRequestTargetBranches.Length > 0 || + OnPullRequestTargetTags.Length > 0 || + OnPullRequestTargetIncludePaths.Length > 0 || + OnPullRequestTargetExcludePaths.Length > 0) + { + yield return new RocketSurgeonGitHubActionsVcsTrigger + { + Kind = RocketSurgeonGitHubActionsTrigger.PullRequestTarget, + Branches = OnPullRequestTargetBranches, + Tags = OnPullRequestTargetTags, + IncludePaths = OnPullRequestTargetIncludePaths, + ExcludePaths = OnPullRequestTargetExcludePaths + }; + } + if (OnCronSchedule != null) yield return new GitHubActionsScheduledTrigger { Cron = OnCronSchedule }; } diff --git a/src/Nuke/ICanLint.cs b/src/Nuke/ICanLint.cs index 458ba848..5afc91a4 100644 --- a/src/Nuke/ICanLint.cs +++ b/src/Nuke/ICanLint.cs @@ -58,7 +58,8 @@ public interface ICanLintStagedFiles : ICanRegenerateBuildConfiguration, INukeBu $"lint-staged -r {( LintStagedIsPullRequest ? $"""--diff="origin/{GitHubActions.Instance.BaseRef}...origin/{GitHubActions.Instance.HeadRef}" """ : "" )}", environmentVariables: EnvironmentInfo.Variables .AddIfMissing("NUKE_INTERNAL_INTERCEPTOR", "1") - .AddIfMissing("NUKE_BUILD_ASSEMBLY", RootDirectory.GetRelativePathTo(Assembly.GetEntryAssembly().Location)), + // ReSharper disable once NullableWarningSuppressionIsUsed + .AddIfMissing("NUKE_BUILD_ASSEMBLY", RootDirectory.GetRelativePathTo(Assembly.GetEntryAssembly()!.Location)), logOutput: true, logger: (type, s) => { diff --git a/src/Nuke/ICanRegenerateBuildConfiguration.cs b/src/Nuke/ICanRegenerateBuildConfiguration.cs index 6bc2f74c..08c1e734 100644 --- a/src/Nuke/ICanRegenerateBuildConfiguration.cs +++ b/src/Nuke/ICanRegenerateBuildConfiguration.cs @@ -21,7 +21,8 @@ public interface ICanRegenerateBuildConfiguration .OfType(); allHosts - .Select(z => $"""{Assembly.GetEntryAssembly().Location} --{BuildServerConfigurationGeneration.ConfigurationParameterName} {z.Id} --host {z.HostName}""") + // ReSharper disable once NullableWarningSuppressionIsUsed + .Select(z => $"""{Assembly.GetEntryAssembly()!.Location} --{BuildServerConfigurationGeneration.ConfigurationParameterName} {z.Id} --host {z.HostName}""") .ForEach( command => DotNetTasks.DotNet( command, diff --git a/src/Nuke/PublicAPI.Shipped.txt b/src/Nuke/PublicAPI.Shipped.txt index 868ebe28..03e21d7f 100644 --- a/src/Nuke/PublicAPI.Shipped.txt +++ b/src/Nuke/PublicAPI.Shipped.txt @@ -496,7 +496,6 @@ Rocket.Surgery.Nuke.ICanLint Rocket.Surgery.Nuke.ICanLint.Lint.get -> Nuke.Common.Target! Rocket.Surgery.Nuke.ICanLint.PostLint.get -> Nuke.Common.Target! Rocket.Surgery.Nuke.ICanLintStagedFiles.LintStaged.get -> Nuke.Common.Target! -Rocket.Surgery.Nuke.ICanLintStagedFiles.LintStaged.get -> Nuke.Common.Target! Rocket.Surgery.Nuke.ICanPrettier Rocket.Surgery.Nuke.ICanPrettier.Prettier.get -> Nuke.Common.Target! Rocket.Surgery.Nuke.ICanRegenerateBuildConfiguration