Example #1
0
        /// <summary>
        /// 获取插件加载信息
        /// </summary>
        /// <param name="metadata">数据元素</param>
        /// <returns>ActivatorData</returns>
        private static ActivatorData GetActivatorData(AddinMetadata metadata)
        {
            ActivatorData activator = new ActivatorData();

            activator.Policy = metadata.Immediate ? ActivatorPolicy.Immediate : ActivatorPolicy.Lazy;
            return(activator);
        }
 internal AddinRecord(AddinMetadata metadata)
 {
     _addinMetadata = metadata;
     _addinHeader   = new AddinHeaderRecord();
     _addinFilePack = new AddinFilePack();
     //OperationStatus = AddinOperationStatus.Unaffected;
 }
Example #3
0
        /// <summary>
        /// Get Runtime Data For RuntimeData
        /// </summary>
        /// <returns></returns>
        private static RuntimeData GetRuntimeDataForRuntimeData(AddinMetadata metadata)
        {
            RuntimeData result = new RuntimeData();

            GetDependencyForRuntimeData(result, metadata);
            GetAssemblyDataForRuntimeData(result, metadata);
            return(result);
        }
 internal AddinRecord(AddinHeaderRecord addinHeader, AddinFilePack addinFilePack, AddinActivatorRecord addinActivator)
 {
     _addinFilePack  = addinFilePack;
     _addinHeader    = addinHeader;
     _addinActivator = addinActivator;
     //OperationStatus = AddinOperationStatus.Unaffected;
     _addinMetadata = new AddinMetadata();
 }
Example #5
0
        /// <summary>
        /// 获取组件信息
        /// </summary>
        /// <param name="metadata">数据元素</param>
        /// <returns>BundleInfoData</returns>
        private static BundleInfoData GetBundleInfoData(AddinMetadata metadata)
        {
            BundleInfoData bundle = new BundleInfoData();

            bundle.AssemblyVersion = metadata.AssemblyVersion;
            bundle.Company         = metadata.Company;
            bundle.Copyright       = metadata.Copyright;
            bundle.ContactAddress  = metadata.Path;
            bundle.Description     = metadata.Description;
            bundle.Title           = metadata.Title;
            return(bundle);
        }
Example #6
0
 /// <summary>
 /// Get Assembly Data For RuntimeData
 /// </summary>
 /// <returns></returns>
 private static void GetAssemblyDataForRuntimeData(RuntimeData runtimeData, AddinMetadata metadata)
 {
     if (null != metadata.Runtime.Items && metadata.Runtime.Items.Length > 0)
     {
         foreach (ImportInfo de in metadata.Runtime.Items)
         {
             AssemblyData dd = new AssemblyData();
             dd.AssemblyPatch = de.assembly;
             dd.IsWeb         = de.isweb;
             runtimeData.SetAssembly(dd);
         }
     }
 }
Example #7
0
 /// <summary>
 /// Get Dependency For RuntimeData
 /// </summary>
 /// <returns></returns>
 private static void GetDependencyForRuntimeData(RuntimeData runtimeData, AddinMetadata metadata)
 {
     if (null != metadata.Runtime.Items1 && metadata.Runtime.Items1.Length > 0)
     {
         foreach (Dependency de in metadata.Runtime.Items1)
         {
             DependencyData dd = new DependencyData();
             dd.AssemblyName       = de.AssemblyName;
             dd.BundleSymbolicName = de.BundleSymbolicName;
             runtimeData.AddDependency(dd);
         }
     }
 }
 private IconAnalysisResult AnalyzeIcon(AddinMetadata addin, byte[] recommendedIcon, IEnumerable <KeyValuePair <AddinType, byte[]> > fancyIcons)
 {
     if (addin.EmbeddedIcon != null)
     {
         return(AnalyzeEmbeddedIcon(addin.EmbeddedIcon, addin.Type, recommendedIcon, fancyIcons));
     }
     else if (addin.IconUrl != null)
     {
         return(AnalyzeLinkedIcon(addin.IconUrl));
     }
     else
     {
         return(IconAnalysisResult.Unspecified);
     }
 }
Example #9
0
        /// <summary>
        /// 获取XML扩张数据信息
        /// </summary>
        /// <param name="metadata">The metadata.</param>
        /// <returns>ExtensionData</returns>
        private static ExtensionData GetXmlForExtensionData(AddinMetadata metadata)
        {
            ExtensionData dd = new ExtensionData();

            //获取配置信息
            //将配置信息加入组件信息中
            if (null != metadata.ApplicationMenu)
            {
                var menuItem   = metadata.ApplicationMenu.Items.Where(o => o.Language == BundleRuntime.CultureInfoLanguage);
                var enumerable = menuItem as ExtendsMenu[] ?? menuItem.ToArray();
                if (enumerable.Any())
                {
                    dd.ApplicationExtends.ApplicationIco = metadata.ApplicationMenu.ApplicationIco;
                    foreach (var extendsMenu in enumerable)
                    {
                        dd.ApplicationExtends.ApplicationName = extendsMenu.ApplicationName;
                        dd.ApplicationExtends.Language        = extendsMenu.Language;
                        if (extendsMenu.Items.Any())
                        {
                            foreach (var de in extendsMenu.Items)
                            {
                                List <LoadOtherContainerPanles> loadOther = null;
                                if (de.Items != null)
                                {
                                    loadOther = de.Items.ToList();
                                }
                                dd.ApplicationExtends.MenuList.Add(new ExtendionMenu()
                                {
                                    Caption            = de.Caption,
                                    ClassForm          = de.ClassForm,
                                    Group              = de.Group,
                                    MenuIco            = de.MenuIco,
                                    ToolTip            = de.ToolTip,
                                    PluginDockStyle    = de.DockType,
                                    Width              = de.Width,
                                    High               = de.High,
                                    Sort               = de.Sort,
                                    LoadOtherContainer = loadOther
                                });
                            }
                        }
                    }
                }
            }

            return(dd);
        }
Example #10
0
        /// <summary>
        /// Get Xml For BundleData
        /// </summary>
        /// <param name="dirName">Name of the dir.</param>
        /// <returns>BundleData.</returns>
        internal static BundleData GetXmlForBundleData(string dirName)
        {
            BundleData result = new BundleData();
            //获取配置信息
            AddinMetadata metadata = SearchXml(dirName);

            //将配置信息加入组件信息中
            if (null != metadata)
            {
                try
                {
                    result.Name         = metadata.Name;
                    result.SymbolicName = metadata.Name;
                    result.Path         = metadata.Path;
                    result.Runtime      = GetRuntimeDataForRuntimeData(metadata);
                    result.Extensions   = GetXmlForExtensionData(metadata);
                    result.PageSerivce  = GetXmlForPageServiceData(metadata);
                    result.Enable       = metadata.Enabled;
                    result.Immediate    = metadata.Immediate;
                    result.Description  = metadata.Description;
                    result.Activator    = GetActivatorData(metadata);
                    result.BundleInfo   = GetBundleInfoData(metadata);
                    result.StartLevel   = metadata.StartLevel;
                    result.Company      = metadata.Company;
                    result.Copyright    = metadata.Copyright;
                    result.Product      = metadata.Product;
                    result.AppSettings  = metadata.AppSettings;
                    if (metadata.AssemblyVersion == null)
                    {
                        metadata.AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
                    }
                    result.Version = metadata.AssemblyVersion;
                    if (metadata.ApplicationMenu != null && !string.IsNullOrEmpty(metadata.ApplicationMenu.ApplicationIco))
                    {
                        result.ApplicationIco = metadata.ApplicationMenu.ApplicationIco;
                    }
                }
                catch (ArgumentNullException ex)
                {
                    FileLogUtility.Error(dirName + ex.Message);
                }
            }
            return(result);
        }
Example #11
0
        /// <summary>
        /// Gets the XML for page service data.
        /// </summary>
        /// <param name="metadata">The metadata.</param>
        /// <returns>PageServiceData.</returns>
        private static PageServiceData GetXmlForPageServiceData(AddinMetadata metadata)
        {
            PageServiceData page = new PageServiceData();

            if (null != metadata.PageService)
            {
                page.PageServicePoint = metadata.PageService.ServicePoint;
                foreach (var pageServiceData in metadata.PageService.Items)
                {
                    page.PageNodeList.Add(new PageNode()
                    {
                        PageName             = pageServiceData.PageName,
                        PagePlguinClassValue = pageServiceData.PagePlguinClassValue,
                        PageType             = pageServiceData.PageType
                    });
                }
            }
            return(page);
        }
Example #12
0
        private async Task DownloadSymbolsPackage(DiscoveryContext context, AddinMetadata package)
        {
            var packageFileName = Path.Combine(context.PackagesFolder, $"{package.Name}.{package.NuGetPackageVersion}.snupkg");

            if (!File.Exists(packageFileName))
            {
                // Delete prior versions of this package
                foreach (var f in Directory.EnumerateFiles(context.PackagesFolder, $"{package.Name}.*.snupkg"))
                {
                    var expectedSplitLength = package.Name.Split('.').Length + package.NuGetPackageVersion.Split('.').Length;
                    var fileName            = Path.GetFileNameWithoutExtension(f);
                    if (fileName.Split('.').Length == expectedSplitLength)
                    {
                        File.Delete(f);
                    }
                }

                // Download the latest version of the symbols package
                try
                {
                    var response = await context.HttpClient
                                   .GetAsync($"https://www.nuget.org/api/v2/symbolpackage/{package.Name}/{package.NuGetPackageVersion}")
                                   .ConfigureAwait(false);

                    if (response.IsSuccessStatusCode)
                    {
                        await using var getStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);

                        await using var fileStream = File.OpenWrite(packageFileName);
                        await getStream.CopyToAsync(fileStream).ConfigureAwait(false);
                    }
                }
                catch (Exception e)
                {
                    throw new Exception($"An error occured while attempting to download symbol package for '{package.Name} {package.NuGetPackageVersion}'", e);
                }
            }
        }
Example #13
0
        static void ParseAddinConfig(Addin addin)
        {
            Log <PluginManager> .Debug("Processing config file for \"{0}\".", addin.Name);

            Assembly addinAssembly = Assembly.LoadFile(addin.AddinFile);

            string addinManifestName = addinAssembly.GetManifestResourceNames().FirstOrDefault(res => res.Contains("addin.xml"));

            if (string.IsNullOrEmpty(addinManifestName))
            {
                Log <PluginManager> .Warn("Could not find addin manifest for '{0}'.", addin.AddinFile);

                return;
            }

            using (Stream s = addinAssembly.GetManifestResourceStream(addinManifestName)) {
                XmlDocument addinManifest = new XmlDocument();
                addinManifest.Load(s);

                if (!AddinMetadata.ContainsKey(addin))
                {
                    AddinMetadata[addin] = new Dictionary <string, string> ();
                }

                foreach (XmlAttribute a in addinManifest.SelectSingleNode("/Addin").Attributes)
                {
                    AddinMetadata [addin] [a.Name] = a.Value;
                }
            }

            AddinMetadata [addin] ["AssemblyFullName"] = addinAssembly.FullName;

            if (AddinMetadata [addin].ContainsKey("icon") && AddinMetadata [addin] ["icon"].EndsWith("@"))
            {
                AddinMetadata [addin] ["icon"] = string.Format("{0}{1}", AddinMetadata [addin] ["icon"], addinAssembly.FullName);
            }
        }
Example #14
0
        private async Task DownloadNugetPackage(DownloadResource nugetClient, DiscoveryContext context, AddinMetadata package)
        {
            var packageFileName = Path.Combine(context.PackagesFolder, $"{package.Name}.{package.NuGetPackageVersion}.nupkg");

            if (!File.Exists(packageFileName))
            {
                // Delete prior versions of this package
                foreach (var f in Directory.EnumerateFiles(context.PackagesFolder, $"{package.Name}.*.nupkg"))
                {
                    var expectedSplitLength = package.Name.Split('.').Length + package.NuGetPackageVersion.Split('.').Length;
                    var fileName            = Path.GetFileNameWithoutExtension(f);
                    if (fileName.Split('.').Length == expectedSplitLength)
                    {
                        File.Delete(f);
                    }
                }

                // Download the latest version of the package
                using var sourceCacheContext = new SourceCacheContext()
                      {
                          NoCache = true
                      };
                var downloadContext = new PackageDownloadContext(sourceCacheContext, Path.GetTempPath(), true);
                var packageIdentity = new PackageIdentity(package.Name, new NuGet.Versioning.NuGetVersion(package.NuGetPackageVersion));

                using var result = await nugetClient.GetDownloadResourceResultAsync(packageIdentity, downloadContext, string.Empty, NullLogger.Instance, CancellationToken.None).ConfigureAwait(false);

                switch (result.Status)
                {
                case DownloadResourceResultStatus.Cancelled:
                    throw new OperationCanceledException();

                case DownloadResourceResultStatus.NotFound:
                    throw new Exception($"Package '{package.Name} {package.NuGetPackageVersion}' not found");

                default:
                {
                    await using var fileStream = File.OpenWrite(packageFileName);
                    await result.PackageStream.CopyToAsync(fileStream).ConfigureAwait(false);

                    break;
                }
                }
            }
        }
        public async Task ExecuteAsync(DiscoveryContext context, TextWriter log)
        {
            var nugetPackageMetadataClient = await context.NugetRepository.GetResourceAsync <PackageMetadataResource>().ConfigureAwait(false);

            var addinPackages = new List <IPackageSearchMetadata>();

            //--------------------------------------------------
            // Get the metadata from NuGet.org
            if (!string.IsNullOrEmpty(context.Options.AddinName))
            {
                // Get metadata for one specific package
                var packageMetadata = await FetchPackageMetadata(nugetPackageMetadataClient, context.Options.AddinName).ConfigureAwait(false);

                if (packageMetadata != null)
                {
                    addinPackages.AddRange(packageMetadata);
                }
            }
            else
            {
                // Get the most recent "stable" version of packages matching the naming convention.
                await foreach (var packageMetadata in SearchForPackages(context.NugetRepository, "Cake", false, CancellationToken.None))
                {
                    addinPackages.Add(packageMetadata);
                }

                // Get the most recent version of packages matching the naming convention (regardless of the stable/prerelease status)
                await foreach (var packageMetadata in SearchForPackages(context.NugetRepository, "Cake", true, CancellationToken.None))
                {
                    addinPackages.Add(packageMetadata);
                }

                // Get metadata for the packages we specifically want to include
                foreach (var additionalPackageName in context.IncludedAddins)
                {
                    var packageMetadata = await FetchPackageMetadata(nugetPackageMetadataClient, additionalPackageName).ConfigureAwait(false);

                    if (packageMetadata != null)
                    {
                        addinPackages.AddRange(packageMetadata);
                    }
                }
            }

            //--------------------------------------------------
            // Select the most recent "stable" release of each addin.
            // If an addin does not have any stable release, select the most recent, regardless of its stable/prerelease status
            var uniqueAddinPackages = addinPackages
                                      .GroupBy(p => p.Identity.Id)
                                      .Select(g => g
                                              .OrderBy(p => p.Identity.Version.IsPrerelease ? 1 : 0) // Stable versions are sorted first, prerelease versions sorted second
                                              .ThenByDescending(p => p.Published)
                                              .First())
                                      .ToArray();

            //--------------------------------------------------
            // Convert metadata from nuget into our own metadata
            context.Addins = await uniqueAddinPackages
                             .ForEachAsync(
                async package =>
            {
                // As of June 2019, the 'Owners' metadata value returned from NuGet is always null.
                // This code is just in case they add this information to the metadata and don't let us know.
                // See feature request: https://github.com/NuGet/NuGetGallery/issues/5647
                var packageOwners = package.Owners?
                                    .Split(',', StringSplitOptions.RemoveEmptyEntries)
                                    .Select(owner => owner.Trim())
                                    .ToArray() ?? Array.Empty <string>();

                var tags = package.Tags?
                           .Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries)
                           .Select(tag => tag.Trim())
                           .ToArray() ?? Array.Empty <string>();

                var addinMetadata = new AddinMetadata()
                {
                    AnalysisResult = new AddinAnalysisResult()
                    {
                        CakeRecipeIsUsed       = false,
                        CakeRecipeVersion      = null,
                        CakeRecipeIsPrerelease = false,
                        CakeRecipeIsLatest     = false
                    },
                    Maintainer                = package.Authors,
                    Description               = package.Description,
                    ProjectUrl                = package.ProjectUrl,
                    IconUrl                   = package.IconUrl,
                    Name                      = package.Identity.Id,
                    NuGetPackageUrl           = new Uri($"https://www.nuget.org/packages/{package.Identity.Id}/"),
                    NuGetPackageOwners        = packageOwners,
                    NuGetPackageVersion       = package.Identity.Version.ToNormalizedString(),
                    IsDeprecated              = false,
                    IsPrerelease              = package.Identity.Version.IsPrerelease,
                    HasPrereleaseDependencies = false,
                    Tags                      = tags,
                    Type                      = AddinType.Unknown,
                    PublishedOn               = Constants.UtcMinDateTime,
                    RepoContent               = ImmutableDictionary <string, Stream> .Empty
                };

                if (package.Title.Contains("[DEPRECATED]", StringComparison.OrdinalIgnoreCase))
                {
                    addinMetadata.IsDeprecated         = true;
                    addinMetadata.AnalysisResult.Notes = package.Description;
                }
                else
                {
                    // The metadata returned by nugetSearchClient.SearchAsync is minimal.
                    // That's why we need to invoke nugetPackageMetadataClient.GetMetadataAsync to get more detailed metadata.
                    var packageMetadata         = await nugetPackageMetadataClient.GetMetadataAsync(package.Identity.Id, true, false, new SourceCacheContext(), NullLogger.Instance, CancellationToken.None).ConfigureAwait(false);
                    var detailedPackageMetadata = packageMetadata.SingleOrDefault(m => m.Identity.Equals(package.Identity));

                    if (detailedPackageMetadata != null)
                    {
                        addinMetadata.PublishedOn = detailedPackageMetadata.Published.Value;
                    }

                    // We need to look at the most recent version (even if that's not the version that would otherwise be analyzed)
                    // to determine if the package has been deprecated
                    var mostRecentPackageMetadata = packageMetadata.OrderByDescending(p => p.Published).FirstOrDefault();
                    var deprecationMetadata       = mostRecentPackageMetadata != null ? await mostRecentPackageMetadata.GetDeprecationMetadataAsync().ConfigureAwait(false) : null;

                    if (deprecationMetadata != null)
                    {
                        // Derive a message based on the 'Reasons' enumeration in case an actual message has not been provided
                        var deprecationReasonMessage = default(string);
                        if (deprecationMetadata.Reasons == null || !deprecationMetadata.Reasons.Any())
                        {
                            deprecationReasonMessage = "This package has been deprecated but the author has not provided a reason.";
                        }
                        else if (deprecationMetadata.Reasons.Count() == 1)
                        {
                            deprecationReasonMessage = "This package has been deprecated for the following reason: " + deprecationMetadata.Reasons.First();
                        }
                        else
                        {
                            deprecationReasonMessage = "This package has been deprecated for the following reasons: " + string.Join(", ", deprecationMetadata.Reasons);
                        }

                        addinMetadata.IsDeprecated         = true;
                        addinMetadata.AnalysisResult.Notes = deprecationMetadata.Message ?? deprecationReasonMessage;
                    }
                }

                return(addinMetadata);
            }, Constants.MAX_NUGET_CONCURENCY)
                             .ConfigureAwait(false);
        }
Example #16
0
        private async Task <IssueComment> AddCommentAsync(bool debugging, DiscoveryContext context, AddinMetadata addin)
        {
            var comment = new StringBuilder();

            comment.AppendLine($"We performed a follow up automated audit of your Cake addin and found that some (or all) issues previously identified have not been resolved.{Environment.NewLine}");
            comment.AppendLine($"We strongly encourage you to make the modifications previously highlighted.{Environment.NewLine}");

            if (addin.AnalysisResult.Icon == IconAnalysisResult.RawgitUrl)
            {
                comment.AppendLine($"In particular would would like to highlight the fact that you use the rawgit CDN to serve your addin's icon. On October 8 2018 the maintainer of rawgit made the [announcement](https://rawgit.com/) that rawgit would shutdown in October 2019. Therefore it's **urgent** that you change your addin's icon URL to the new recommended URL: `{Constants.NEW_CAKE_CONTRIB_ICON_URL}`.{Environment.NewLine}");
            }

            if (addin.AnalysisResult.Icon != IconAnalysisResult.EmbeddedCakeContrib && addin.AnalysisResult.Icon != IconAnalysisResult.EmbeddedFancyCakeContrib)
            {
                comment.AppendLine($"Please also note that the recommendation changed following .netcore3.0's release: you should now embedded the icon in your Nuget package. Read more about embedded icons in the [.nuspec reference](https://docs.microsoft.com/en-us/nuget/reference/nuspec#icon).{Environment.NewLine}");
            }

            comment.AppendLine($"{Environment.NewLine}This comment was created by a tool: Cake.AddinDiscoverer version {context.Version}{Environment.NewLine}");

            IssueComment issueComment = null;

            try
            {
                if (debugging)
                {
                    await File.WriteAllTextAsync(Path.Combine(context.TempFolder, $"Comment_{addin.Name}.txt"), comment.ToString()).ConfigureAwait(false);
                }
                else
                {
                    issueComment = await context.GithubClient.Issue.Comment.Create(addin.RepositoryOwner, addin.RepositoryName, addin.AuditIssue.Number, comment.ToString()).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 issue.
                // 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(issueComment);
        }
Example #17
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)