Exemple #1
0
        public static bool IsFrameworkUpToDate(string[] currentFrameworks, CakeVersion desiredCakeVersion)
        {
            // Make sure the addin currently targets at least one framework
            if (currentFrameworks == null || !currentFrameworks.Any())
            {
                return(false);
            }

            // Check that all required frameworks are targeted
            if (desiredCakeVersion.RequiredFrameworks.Except(currentFrameworks, StringComparer.OrdinalIgnoreCase).Any())
            {
                return(false);
            }

            // Check that no extra framework is targeted
            var unnecessaryFrameworks = currentFrameworks
                                        .Except(desiredCakeVersion.RequiredFrameworks, StringComparer.OrdinalIgnoreCase)
                                        .Except(desiredCakeVersion.OptionalFrameworks, StringComparer.OrdinalIgnoreCase)
                                        .ToArray();

            if (unnecessaryFrameworks.Any())
            {
                return(false);
            }

            return(true);
        }
Exemple #2
0
        public static bool IsFrameworkUpToDate(string[] currentFrameworks, CakeVersion desiredCakeVersion)
        {
            if (currentFrameworks == null || !currentFrameworks.Any())
            {
                return(false);
            }
            else if (!currentFrameworks.Contains(desiredCakeVersion.RequiredFramework, StringComparer.InvariantCultureIgnoreCase))
            {
                return(false);
            }

            var unnecessaryFrameworks = currentFrameworks
                                        .Except(new[] { desiredCakeVersion.RequiredFramework })
                                        .Except(desiredCakeVersion.OptionalFrameworks)
                                        .ToArray();

            if (unnecessaryFrameworks.Any())
            {
                return(false);
            }

            return(true);
        }
        private void GenerateExcelWorksheet(IEnumerable <AddinMetadata> addins, CakeVersion cakeVersion, AddinType type, string caption, ExcelPackage excel)
        {
            var filteredAddins = addins
                                 .Where(addin => addin.Type.IsFlagSet(type))
                                 .ToArray();

            var reportColumns = Constants.REPORT_COLUMNS
                                .Where(column => column.Destination.HasFlag(DataDestination.Excel))
                                .Where(column => column.ApplicableTo.HasFlag(type))
                                .Select((data, index) => new { Index = index, Data = data })
                                .ToArray();

            // Create the worksheet
            var worksheet = excel.Workbook.Worksheets.Add(caption);

            // Header row
            foreach (var column in reportColumns)
            {
                worksheet.Cells[1, column.Index + 1].Value = column.Data.Header;
            }

            // One row per addin
            var row = 1;

            foreach (var addin in filteredAddins.OrderBy(a => a.Name))
            {
                row++;

                foreach (var column in reportColumns)
                {
                    if (column.Data.ApplicableTo.HasFlag(addin.Type))
                    {
                        var cell = worksheet.Cells[row, column.Index + 1];
                        cell.Value = column.Data.GetContent(addin);

                        var color = column.Data.GetCellColor(addin, cakeVersion);
                        if (color != Color.Empty)
                        {
                            cell.Style.Fill.PatternType = ExcelFillStyle.Solid;
                            cell.Style.Fill.BackgroundColor.SetColor(color);
                        }

                        var hyperlink = column.Data.GetHyperLink(addin);
                        if (hyperlink != null)
                        {
                            cell.Hyperlink = hyperlink;
                            cell.StyleName = "HyperLink";
                        }
                    }
                }
            }

            // Freeze the top row and first column
            worksheet.View.FreezePanes(2, 2);

            // Setup auto-filter
            worksheet.Cells[1, 1, 1, reportColumns.Length].AutoFilter = true;

            // Format the worksheet
            worksheet.Row(1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
            if (filteredAddins.Any())
            {
                foreach (var column in reportColumns)
                {
                    worksheet.Cells[2, column.Index + 1, row, column.Index + 1].Style.HorizontalAlignment = column.Data.Align;
                }
            }

            // Resize columns
            worksheet.Cells[1, 1, row, reportColumns.Length].AutoFitColumns();

            // Make columns a little bit wider to account for the filter "drop-down arrow" button
            foreach (var column in reportColumns)
            {
                worksheet.Column(column.Index + 1).Width += 2.14;
            }
        }
        private string GenerateMarkdown(DiscoveryContext context, IEnumerable <AddinMetadata> addins, CakeVersion cakeVersion, AddinType type)
        {
            var filteredAddins = addins
                                 .Where(addin => string.IsNullOrEmpty(addin.AnalysisResult.Notes))
                                 .Where(addin => addin.Type == type)
                                 .ToArray();

            var reportColumns = Constants.REPORT_COLUMNS
                                .Where(column => column.Destination.HasFlag(GetMarkdownDestinationForType(type)))
                                .Where(column => column.ApplicableTo.HasFlag(type))
                                .Select((data, index) => new { Index = index, Data = data })
                                .ToArray();

            var now      = DateTime.UtcNow;
            var markdown = new StringBuilder();

            if (cakeVersion != null)
            {
                markdown.AppendLine($"# Audit Report for Cake {cakeVersion.Version}");
            }
            else
            {
                markdown.AppendLine("# Audit Report");
            }

            markdown.AppendLine();
            markdown.AppendLine($"This report was generated by Cake.AddinDiscoverer {context.Version} on {now.ToLongDateString()} at {now.ToLongTimeString()} GMT");
            markdown.AppendLine();

            if (type == AddinType.Addin)
            {
                markdown.AppendLine("- The `Cake Core Version` and `Cake Common Version` columns  show the version referenced by a given addin");
                markdown.AppendLine($"- The `Cake Core IsPrivate` and `Cake Common IsPrivate` columns indicate whether the references are marked as private. In other words, we are looking for references with the `PrivateAssets=All` attribute like in this example: `<PackageReference Include=\"Cake.Common\" Version=\"{cakeVersion.Version}\" PrivateAssets=\"All\" />`");
                markdown.AppendLine($"- The `Framework` column shows the .NET framework(s) targeted by a given addin. Addins should target {cakeVersion.RequiredFramework} at a minimum, and they can also optionally multi-target {string.Concat(" or ", cakeVersion.OptionalFrameworks)}");
            }

            markdown.AppendLine();

            if (type == AddinType.Addin)
            {
                markdown.AppendLine("## Statistics");
                markdown.AppendLine();

                var addinsReferencingCakeCore = filteredAddins.Where(addin => addin.Type == AddinType.Addin & addin.AnalysisResult.CakeCoreVersion != null);
                markdown.AppendLine($"- Of the {addinsReferencingCakeCore.Count()} audited addins that reference Cake.Core:");
                markdown.AppendLine($"  - {addinsReferencingCakeCore.Count(addin => addin.AnalysisResult.CakeCoreVersion.IsUpToDate(cakeVersion.Version))} are targeting the desired version of Cake.Core");
                markdown.AppendLine($"  - {addinsReferencingCakeCore.Count(addin => addin.AnalysisResult.CakeCoreIsPrivate)} have marked the reference to Cake.Core as private");
                markdown.AppendLine();

                var addinsReferencingCakeCommon = filteredAddins.Where(addin => addin.Type == AddinType.Addin & addin.AnalysisResult.CakeCommonVersion != null);
                markdown.AppendLine($"- Of the {addinsReferencingCakeCommon.Count()} audited addins that reference Cake.Common:");
                markdown.AppendLine($"  - {addinsReferencingCakeCommon.Count(addin => addin.AnalysisResult.CakeCommonVersion.IsUpToDate(cakeVersion.Version))} are targeting the desired version of Cake.Common");
                markdown.AppendLine($"  - {addinsReferencingCakeCommon.Count(addin => addin.AnalysisResult.CakeCommonIsPrivate)} have marked the reference to Cake.Common as private");
                markdown.AppendLine();
            }

            // Title
            markdown.AppendLine("## Addins");
            markdown.AppendLine();

            // Header row 1
            foreach (var column in reportColumns)
            {
                markdown.Append($"| {column.Data.Header} ");
            }

            markdown.AppendLine("|");

            // Header row 2
            foreach (var column in reportColumns)
            {
                markdown.Append("| ");
                if (column.Data.Align == ExcelHorizontalAlignment.Center)
                {
                    markdown.Append(":");
                }
                markdown.Append("---");
                if (column.Data.Align == ExcelHorizontalAlignment.Right || column.Data.Align == ExcelHorizontalAlignment.Center)
                {
                    markdown.Append(":");
                }
                markdown.Append(" ");
            }

            markdown.AppendLine("|");

            // One row per addin
            foreach (var addin in filteredAddins.OrderBy(addin => addin.Name))
            {
                foreach (var column in reportColumns)
                {
                    if (column.Data.ApplicableTo.HasFlag(addin.Type))
                    {
                        var content   = column.Data.GetContent(addin);
                        var hyperlink = column.Data.GetHyperLink(addin);
                        var color     = column.Data.GetCellColor(addin, cakeVersion);

                        var emoji = string.Empty;
                        if (color == Color.LightGreen)
                        {
                            emoji = Constants.GREEN_EMOJI;
                        }
                        else if (color == Color.Red)
                        {
                            emoji = Constants.RED_EMOJI;
                        }
                        else if (color == Color.Gold)
                        {
                            emoji = Constants.YELLOW_EMOJI;
                        }

                        if (hyperlink == null)
                        {
                            markdown.Append($"| {content} {emoji}");
                        }
                        else
                        {
                            markdown.Append($"| [{content}]({hyperlink.AbsoluteUri}) {emoji}");
                        }
                    }
                    else
                    {
                        markdown.Append($"| ");
                    }
                }

                markdown.AppendLine("|");
            }

            return(markdown.ToString());
        }
Exemple #5
0
        private async Task <RecipeFile[]> GetRecipeFilesAsync(DiscoveryContext context, CakeVersion currentCakeVersion, CakeVersion nextCakeVersion, CakeVersion latestCakeVersion)
        {
            var directoryContent = await context.GithubClient.Repository.Content.GetAllContents(Constants.CAKE_CONTRIB_REPO_OWNER, Constants.CAKE_RECIPE_REPO_NAME, "Cake.Recipe/Content").ConfigureAwait(false);

            var cakeFiles = directoryContent.Where(c => c.Type == new StringEnum <ContentType>(ContentType.File) && c.Name.EndsWith(".cake", StringComparison.OrdinalIgnoreCase));

            var recipeFiles = await cakeFiles
                              .ForEachAsync(
                async cakeFile =>
            {
                var contents = await context.GithubClient.Repository.Content.GetAllContents(Constants.CAKE_CONTRIB_REPO_OWNER, Constants.CAKE_RECIPE_REPO_NAME, cakeFile.Path).ConfigureAwait(false);

                var recipeFile = new RecipeFile()
                {
                    Name    = cakeFile.Name,
                    Path    = cakeFile.Path,
                    Content = contents[0].Content
                };

                foreach (var addinReference in recipeFile.AddinReferences)
                {
                    addinReference.LatestVersionForCurrentCake = context.Addins.SingleOrDefault(addin =>
                    {
                        return(addin.Name.Equals(addinReference.Name, StringComparison.OrdinalIgnoreCase) &&
                               !addin.IsPrerelease &&
                               (currentCakeVersion == null || (addin.AnalysisResult.CakeCoreVersion.IsUpToDate(currentCakeVersion.Version) && addin.AnalysisResult.CakeCommonVersion.IsUpToDate(currentCakeVersion.Version))) &&
                               (nextCakeVersion == null || !(addin.AnalysisResult.CakeCoreVersion.IsUpToDate(nextCakeVersion.Version) && addin.AnalysisResult.CakeCommonVersion.IsUpToDate(nextCakeVersion.Version))));
                    })?.NuGetPackageVersion;

                    addinReference.LatestVersionForLatestCake = context.Addins.SingleOrDefault(addin =>
                    {
                        return(addin.Name.Equals(addinReference.Name, StringComparison.OrdinalIgnoreCase) &&
                               !addin.IsPrerelease &&
                               (latestCakeVersion != null && (addin.AnalysisResult.CakeCoreVersion.IsUpToDate(latestCakeVersion.Version) && addin.AnalysisResult.CakeCommonVersion.IsUpToDate(latestCakeVersion.Version))));
                    })?.NuGetPackageVersion;
                }

                var nugetPackageMetadataClient = context.NugetRepository.GetResource <PackageMetadataResource>();
                await recipeFile.ToolReferences.ForEachAsync(
                    async toolReference =>
                {
                    var searchMetadata            = await nugetPackageMetadataClient.GetMetadataAsync(toolReference.Name, false, false, new SourceCacheContext(), NullLogger.Instance, CancellationToken.None).ConfigureAwait(false);
                    var mostRecentPackageMetadata = searchMetadata.OrderByDescending(p => p.Published).FirstOrDefault();
                    if (mostRecentPackageMetadata != null)
                    {
                        toolReference.LatestVersion = mostRecentPackageMetadata.Identity.Version.ToNormalizedString();
                    }
                }, Constants.MAX_NUGET_CONCURENCY)
                .ConfigureAwait(false);

                return(recipeFile);
            }, Constants.MAX_GITHUB_CONCURENCY)
                              .ConfigureAwait(false);

            return(recipeFiles);
        }
Exemple #6
0
        private async Task <Issue> CreateIssueAsync(bool debugging, DiscoveryContext context, AddinMetadata addin, CakeVersion recommendedCakeVersion)
        {
            var issuesDescription = new StringBuilder();

            if (addin.AnalysisResult.CakeCoreVersion == Constants.UNKNOWN_VERSION)
            {
                issuesDescription.AppendLine($"- [ ] We were unable to determine what version of Cake.Core your addin is referencing. Please make sure you are referencing {recommendedCakeVersion.Version}");
            }
            else if (!addin.AnalysisResult.CakeCoreVersion.IsUpToDate(recommendedCakeVersion.Version))
            {
                issuesDescription.AppendLine($"- [ ] You are currently referencing Cake.Core {addin.AnalysisResult.CakeCoreVersion}. Please upgrade to {recommendedCakeVersion.Version}");
            }

            if (addin.AnalysisResult.CakeCommonVersion == Constants.UNKNOWN_VERSION)
            {
                issuesDescription.AppendLine($"- [ ] We were unable to determine what version of Cake.Common your addin is referencing. Please make sure you are referencing {recommendedCakeVersion.Version}");
            }
            else if (!addin.AnalysisResult.CakeCommonVersion.IsUpToDate(recommendedCakeVersion.Version))
            {
                issuesDescription.AppendLine($"- [ ] You are currently referencing Cake.Common {addin.AnalysisResult.CakeCommonVersion}. Please upgrade to {recommendedCakeVersion.Version}");
            }

            if (!addin.AnalysisResult.CakeCoreIsPrivate)
            {
                issuesDescription.AppendLine($"- [ ] The Cake.Core reference should be private. Specifically, your addin's `.csproj` should have a line similar to this: `<PackageReference Include=\"Cake.Core\" Version=\"{recommendedCakeVersion.Version}\" PrivateAssets=\"All\" />`");
            }
            if (!addin.AnalysisResult.CakeCommonIsPrivate)
            {
                issuesDescription.AppendLine($"- [ ] The Cake.Common reference should be private. Specifically, your addin's `.csproj` should have a line similar to this: `<PackageReference Include=\"Cake.Common\" Version=\"{recommendedCakeVersion.Version}\" PrivateAssets=\"All\" />`");
            }
            if (!Misc.IsFrameworkUpToDate(addin.Frameworks, recommendedCakeVersion))
            {
                issuesDescription.AppendLine($"- [ ] Your addin should target {recommendedCakeVersion.RequiredFramework} at a minimum. Optionally, your addin can also multi-target {string.Join(" or ", recommendedCakeVersion.OptionalFrameworks)}.");
            }

            switch (addin.AnalysisResult.Icon)
            {
            case IconAnalysisResult.Unspecified:
            case IconAnalysisResult.RawgitUrl:
            case IconAnalysisResult.CustomUrl:
                issuesDescription.AppendLine("- [ ] The nuget package for your addin should embed the cake-contrib icon. Specifically, your addin's `.csproj` should have a line like this: `<PackageIcon>path/to/icon.png</PackageIcon>`.");
                break;

            case IconAnalysisResult.EmbeddedCustom:
                issuesDescription.AppendLine("- [ ] The icon embedded in your nuget package doesn't appear to be the cake-contrib icon. We strongly recommend that you use the icon [available here](https://github.com/cake-contrib/graphics/blob/master/png/cake-contrib-medium.png).");
                break;
            }

            if (issuesDescription.Length == 0)
            {
                return(null);
            }

            // Create a new issue
            var issueBody = $"We performed an automated audit of your Cake addin and found that it does not follow all the best practices.{Environment.NewLine}{Environment.NewLine}";

            issueBody += $"We encourage you to make the following modifications:{Environment.NewLine}{Environment.NewLine}";
            issueBody += issuesDescription.ToString();
            issueBody += $"{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}";
            issueBody += $"Apologies if this is already being worked on, or if there are existing open issues, this issue was created based on what is currently published for this package on NuGet.{Environment.NewLine}";
            issueBody += $"{Environment.NewLine}This issue was created by a tool: Cake.AddinDiscoverer version {context.Version}{Environment.NewLine}";

            var newIssue = new NewIssue(Constants.ISSUE_TITLE)
            {
                Body = issueBody.ToString()
            };

            Issue issue = null;

            try
            {
                if (debugging)
                {
                    await File.WriteAllTextAsync(Path.Combine(context.TempFolder, $"Issue_{addin.Name}.txt"), issueBody.ToString()).ConfigureAwait(false);
                }
                else
                {
                    issue = await context.GithubClient.Issue.Create(addin.RepositoryOwner, addin.RepositoryName, newIssue).ConfigureAwait(false);
                }
            }
            catch (ApiException e) when(e.ApiError.Message.EqualsIgnoreCase("Issues are disabled for this repo"))
            {
                // There's a NuGet package with a project URL that points to a fork which doesn't allow issues.
                // Therefore it's safe to ignore this error.
            }
            catch (ApiException e) when(e.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                // I know of at least one case where the URL in the NuGet metadata points to a repo that has been deleted.
                // Therefore it's safe to ignore this error.
            }
#pragma warning disable CS0168 // Variable is declared but never used
            catch (Exception e)
#pragma warning restore CS0168 // Variable is declared but never used
            {
                Debugger.Break();
                throw;
            }
            finally
            {
                // This delay is important to avoid triggering GitHub's abuse protection
                await Misc.RandomGithubDelayAsync().ConfigureAwait(false);
            }

            return(issue);
        }
 private async Task FixNuspec(DiscoveryContext context, AddinMetadata addin, CakeVersion cakeVersion, IList <(string CommitMessage, IEnumerable <string> FilesToDelete, IEnumerable <(EncodingType Encoding, string Path, string Content)> FilesToUpsert)> commits)