protected List <string> GetPackagesToIgnore() { // read it from the file var pathToIgnoreFile = Path.Combine( new FileInfo(new System.Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath).Directory.FullName, "packages-to-ignore.txt"); // if there is an env var named TEMPLATE_REPORT_PATH_TO_PREVIOUS, then use that instead var pathToIgnoreFileEnvVar = Environment.GetEnvironmentVariable("TEMPLATE_REPORT_PATH_TO_IGNORE_FILE"); if (!string.IsNullOrEmpty(pathToIgnoreFileEnvVar)) { _reporter.WriteVerboseLine($"Setting path to ignore file using env var override TEMPLATE_REPORT_PATH_TO_IGNORE_FILE='{pathToIgnoreFileEnvVar}'"); if (System.IO.File.Exists(pathToIgnoreFileEnvVar)) { pathToIgnoreFile = pathToIgnoreFileEnvVar; } else { _reporter.WriteVerboseLine($"not changing ignore file path based on env var, because the file is not found at the path provided"); } } if (File.Exists(pathToIgnoreFile)) { _reporter.WriteLine($"pkgs to ignore file found at '{pathToIgnoreFile}'"); var text = File.ReadAllText(pathToIgnoreFile); var lines = text.Split('\n'); if (lines == null || lines.Length <= 0) { return(new List <string>()); } List <string> result = new List <string>(lines.Length); foreach (var line in lines) { if (!string.IsNullOrEmpty(line)) { result.Add(TemplatePack.NormalisePkgId(line)); } } //var ignoreJson = JsonConvert.DeserializeObject<string[]>(File.ReadAllText(pathToIgnoreFile)); //var result = ignoreJson.ToList(); return(result); } else { _reporter.WriteLine($"pkgs to ignore file not found at '{pathToIgnoreFile}'"); } return(new List <string>()); }
// TODO: Move these methods somewhere else public static TemplatePack CreateFromNuSpec(NuGetPackage pkg, string pathToNuspecFile, List <string> pathToTemplateJsonFiles) { Debug.Assert(pkg != null); Debug.Assert(File.Exists(pathToNuspecFile)); Debug.Assert(pathToTemplateJsonFiles != null && pathToTemplateJsonFiles.Count > 0); try { var nuspec = NuspecFile.CreateFromNuspecFile(pathToNuspecFile); var templateList = new List <Template>(); foreach (var filepath in pathToTemplateJsonFiles) { Console.WriteLine($"reading template file from {filepath}"); try { var template = CreateTemplateFromJsonFile(filepath); templateList.Add(template); } catch (Exception ex) { Console.WriteLine(ex.ToString()); throw ex; } } // TODO: get download count var templatePack = new TemplatePack { Authors = nuspec.Metadata.Authors, Copyright = nuspec.Metadata.Copyright, Description = nuspec.Metadata.Description, IconUrl = nuspec.Metadata.IconUrl, LicenseUrl = nuspec.Metadata.LicenseUrl, Owners = nuspec.Metadata.Owners, ProjectUrl = nuspec.Metadata.ProjectUrl, Version = nuspec.Metadata.Version, Templates = templateList.ToArray(), DownloadCount = pkg.TotalDownloads }; return(templatePack); } catch (Exception ex) { Console.WriteLine(ex.ToString()); throw ex; } }
public async Task GenerateTemplateJsonReportAsync(string[] searchTerms, string jsonReportFilepath, List <string> specificPackagesToInclude, string previousReportPath) { Debug.Assert(searchTerms != null && searchTerms.Length > 0); Debug.Assert(!string.IsNullOrEmpty(jsonReportFilepath)); Dictionary <string, TemplatePack> previousPacks = new Dictionary <string, TemplatePack>(); Console.WriteLine($"verbose: '{previousReportPath}', file exists '{File.Exists(previousReportPath)}'"); if (!string.IsNullOrEmpty(previousReportPath) && File.Exists(previousReportPath)) { List <TemplatePack> previousReport = new List <TemplatePack>(); previousReport = JsonConvert.DeserializeObject <List <TemplatePack> >(File.ReadAllText(previousReportPath)); previousPacks = TemplatePack.ConvertToDictionary(previousReport); } Console.WriteLine($"num of previous template packs in previous report: '{previousPacks.Count}'"); // 1: query nuget for search results, we need to query all because we need to get the new download count var packageIdsToIgnore = GetPackagesToIgnore(); var nugetSearchResultPackages = await _nugetHelper.QueryNuGetAsync(_httpClient, searchTerms, specificPackagesToInclude, packageIdsToIgnore); // use below to write full list of search results to a file for debugging // System.IO.File.WriteAllText(@"c:\temp\packages-found.json",JsonConvert.SerializeObject(nugetSearchResultPackages)) var pkgsToDownload = new List <NuGetPackage>(); // go through each found package, if pkg is in previous result with same version number, update download count and move on // if not same version number, remove from dictionary and add to list to download foreach (var pkg in nugetSearchResultPackages) { var id = TemplatePack.NormalisePkgId(pkg.Id); if (previousPacks.ContainsKey(id)) { var previousPackage = previousPacks[id]; // check version number to see if it is the same if (string.Compare(pkg.Version, previousPackage.Version, StringComparison.OrdinalIgnoreCase) == 0) { // same version just update the download count previousPackage.DownloadCount = pkg.TotalDownloads; } else { // removing from previousPacks becuase version mismatch, so latest version should be downloaded previousPacks.Remove(id); pkgsToDownload.Add(pkg); } } else { pkgsToDownload.Add(pkg); } } // 2: download nuget packages locally var downloadedPackages = new List <NuGetPackage>(); // var pkgToRemoveAndExtractPathMap = new Dictionary<NuGetPackage, string>(); var pkgListSkippedExtractExists = new List <NuGetPackage>(); if (pkgsToDownload != null && pkgsToDownload.Count > 0) { // if the nuget pkg extract folder exists, don't download the package var pkgsToRemoveFromDownloadList = new List <NuGetPackage>(); // TODO: the code that skips if the extract folder is there should be refactored // probably into RemoteFile.cs somehow var rf = (RemoteFile)_remoteFile; foreach (var pkg in pkgsToDownload) { var key = TemplatePack.NormalisePkgId(pkg.Id); if (packageIdsToIgnore.Contains(key)) { pkgsToRemoveFromDownloadList.Add(pkg); _reporter.WriteVerboseLine($"verbose: ignoring pkg id '{pkg.Id}' because it's on the ignore list"); continue; } var filepath = rf.GetLocalFilepathFor(pkg.GetPackageFilename()); var filename = new System.IO.FileInfo(filepath).Name; var expectedExtractFolder = System.IO.Path.Combine(rf.CacheFolderpath, "extracted", filename); if (Directory.Exists(expectedExtractFolder)) { _reporter.WriteLine($"adding to exclude list because extract folder exists at '{expectedExtractFolder}'"); pkg.LocalExtractPath = expectedExtractFolder; pkgsToRemoveFromDownloadList.Add(pkg); pkgListSkippedExtractExists.Add(pkg); // pkgToRemoveAndExtractPathMap.Add(pkg, expectedExtractFolder); } } if (pkgsToRemoveFromDownloadList.Count > 0) { foreach (var pkg in pkgsToRemoveFromDownloadList) { pkgsToDownload.Remove(pkg); } } if (pkgsToDownload.Count > 0) { downloadedPackages = await _nugetDownloader.DownloadAllPackagesAsync(pkgsToDownload); } else { _reporter.WriteVerboseLine("no packages found to download"); } } // 3: extract nuget package to local folder var templatePackages = new List <NuGetPackage>(); var listPackagesWithNotemplates = new List <NuGetPackage>(); var pkgNamesWitoutPackages = new List <string>(); foreach (var pkg in downloadedPackages) { var extractPath = string.Empty; try { extractPath = _remoteFile.ExtractZipLocally(pkg.LocalFilepath, false); } catch (Exception ex) { listPackagesWithNotemplates.Add(pkg); pkgNamesWitoutPackages.Add(pkg.Id); _reporter.WriteLine($"ERROR: {ex.ToString()}"); continue; } if (string.IsNullOrEmpty(extractPath)) { continue; } pkg.LocalExtractPath = extractPath; // see if there is a .template var foundDirs = Directory.EnumerateDirectories(extractPath, ".template.config", new EnumerationOptions { RecurseSubdirectories = true }); if (foundDirs.Count() > 0) { templatePackages.Add(pkg); } else { _reporter.WriteLine($"pkg has no templates: {pkg.Id}"); listPackagesWithNotemplates.Add(pkg); pkgNamesWitoutPackages.Add(pkg.Id); } } // look through the extract folders to see if other packages should be added to the exclude list foreach (var pkg in pkgListSkippedExtractExists) { string extractPath = pkg.LocalExtractPath; if (!string.IsNullOrEmpty(extractPath) && Directory.Exists(extractPath)) { var foundDirs = Directory.EnumerateDirectories(extractPath, ".template.config", new EnumerationOptions { RecurseSubdirectories = true }); if (foundDirs.Count() > 0) { templatePackages.Add(pkg); } else { _reporter.WriteLine($"pkg has no templates (from extract path): {pkg.Id}"); listPackagesWithNotemplates.Add(pkg); pkgNamesWitoutPackages.Add(pkg.Id); } } } var reportsPath = Path.Combine(_remoteFile.CacheFolderpath, "reports", DateTime.Now.ToString("MM.dd.yy-H.m.s.ffff")); if (!Directory.Exists(reportsPath)) { Directory.CreateDirectory(reportsPath); } var sb = new StringBuilder(); foreach (var pkg in packageIdsToIgnore) { sb.AppendLine(TemplatePack.NormalisePkgId(pkg)); } foreach (var pkg in pkgNamesWitoutPackages) { sb.AppendLine(TemplatePack.NormalisePkgId(pkg)); } File.WriteAllText( Path.Combine(reportsPath, "newly-found-packages-without-templates.json"), JsonConvert.SerializeObject(listPackagesWithNotemplates, Formatting.Indented)); File.WriteAllText( Path.Combine(reportsPath, "newly-found-package-names-to-ignore.json"), JsonConvert.SerializeObject(pkgNamesWitoutPackages, Formatting.Indented)); File.WriteAllText( Path.Combine(reportsPath, "packages-to-ignore.txt"), sb.ToString()); var ignoreFileOutputPath = Path.Combine(_remoteFile.CacheFolderpath, "packages-to-ignore.txt"); if (File.Exists(ignoreFileOutputPath)) { File.Delete(ignoreFileOutputPath); } File.WriteAllText( Path.Combine(_remoteFile.CacheFolderpath, "packages-to-ignore.txt"), sb.ToString()); var templatePacks = new List <TemplatePack>(); foreach (var pkg in templatePackages) { // get nuspec file path var nuspecFile = Directory.GetFiles(pkg.LocalExtractPath, $"{pkg.Id}.nuspec").FirstOrDefault(); if (nuspecFile == null) { _reporter.WriteLine($"warning: nuspec not found in folder {pkg.LocalFilepath}"); continue; } // get template folders var contentDir = Path.Combine(pkg.LocalExtractPath); if (!Directory.Exists(contentDir)) { continue; } var templateFiles = TemplatePack.GetTemplateFilesUnder(contentDir); try { var tp = TemplatePack.CreateFromNuSpec(pkg, nuspecFile, templateFiles); if (tp != null && tp.Templates != null && tp.Templates.Length > 0) { templatePacks.Add(tp); } else { _reporter.WriteLine($"Not adding package '{pkg.Id}', no templates found"); } } catch (Exception ex) { _reporter.WriteLine($"error creating template pack {nuspecFile} {ex.ToString()}"); } } // add all the downloaded items to existing dictionary then get the full result foreach (var pkg in templatePacks) { var id = TemplatePack.NormalisePkgId(pkg.Package); if (previousPacks.ContainsKey(id)) { // I believe it shouldn't get here, but just in case previousPacks.Remove(id); } previousPacks.Add(id, pkg); } templatePacks = previousPacks.Values.ToList(); templatePacks = templatePacks.OrderBy((tp) => - 1 * tp.DownloadCount).ToList(); // write to cache folder and then copy to dest var cacheFile = Path.Combine(reportsPath, "template-report.json"); File.WriteAllText(cacheFile, JsonConvert.SerializeObject(templatePacks, Formatting.Indented)); if (File.Exists(jsonReportFilepath)) { File.Delete(jsonReportFilepath); } _reporter.WriteLine($"Writing report to '{jsonReportFilepath}'"); File.Copy(cacheFile, jsonReportFilepath); }
public async Task GenerateTemplateJsonReportAsync(string[] searchTerms, string jsonReportFilepath) { Debug.Assert(searchTerms != null && searchTerms.Length > 0); Debug.Assert(!string.IsNullOrEmpty(jsonReportFilepath)); // 1: query nuget for search results var foundPackages = await _nugetHelper.QueryNuGetAsync(_httpClient, searchTerms, GetPackagesToIgnore()); // 2: download nuget packages locally var downloadedPackages = await _nugetDownloader.DownloadAllPackagesAsync(foundPackages); // 3: extract nuget package to local folder var templatePackages = new List <NuGetPackage>(); var listPackagesWithNotemplates = new List <NuGetPackage>(); var pkgNamesWitoutPackages = new List <string>(); foreach (var pkg in downloadedPackages) { var extractPath = _remoteFile.ExtractZipLocally(pkg.LocalFilepath); pkg.LocalExtractPath = extractPath; // see if there is a .template var foundDirs = Directory.EnumerateDirectories(extractPath, ".template.config", new EnumerationOptions { RecurseSubdirectories = true }); if (foundDirs.Count() > 0) { templatePackages.Add(pkg); } else { Console.WriteLine($"pkg has no templates: {pkg.Id}"); listPackagesWithNotemplates.Add(pkg); pkgNamesWitoutPackages.Add(pkg.Id); } } var reportsPath = Path.Combine(_remoteFile.CacheFolderpath, "reports", DateTime.Now.ToString("MM.dd.yy-H.m.s.ffff")); if (!Directory.Exists(reportsPath)) { Directory.CreateDirectory(reportsPath); } Path.Combine(reportsPath, "packages-without-templates.json"); Path.Combine(reportsPath, "package-names-without-templates.txt"); var templatePacks = new List <TemplatePack>(); foreach (var pkg in templatePackages) { // get nuspec file path var nuspecFile = Directory.GetFiles(pkg.LocalExtractPath, $"{pkg.Id}.nuspec").FirstOrDefault(); if (nuspecFile == null) { Console.WriteLine($"warning: nuspec not found in folder {pkg.LocalFilepath}"); continue; } // get template folders var contentDir = Path.Combine(pkg.LocalExtractPath, "content"); if (!Directory.Exists(contentDir)) { continue; } var templateFolders = Directory.GetDirectories(contentDir, ".template.config", SearchOption.AllDirectories); var templateFiles = new List <string>(); foreach (var folder in templateFolders) { var files = Directory.GetFiles(folder, "template.json", new EnumerationOptions { RecurseSubdirectories = true }); if (files != null && files.Length > 0) { templateFiles.AddRange(files); } } try { var tp = TemplatePack.CreateFromNuSpec(pkg, nuspecFile, templateFiles); templatePacks.Add(tp); } catch (Exception ex) { Console.WriteLine($"error creating template pack {nuspecFile} {ex.ToString()}"); } } templatePacks = templatePacks.OrderBy((tp) => - 1 * tp.DownloadCount).ToList(); // write to cache folder and then copy to dest var cacheFile = Path.Combine(reportsPath, "template-report.json"); File.WriteAllText(cacheFile, JsonConvert.SerializeObject(templatePacks)); if (File.Exists(jsonReportFilepath)) { File.Delete(jsonReportFilepath); } File.Move(cacheFile, jsonReportFilepath); }
public Template GetTemplateById(string templateName, TemplatePack templatePack) { return(GetTemplateById(templateName, new List <TemplatePack> { templatePack })); }
internal TemplateSearchResult SearchTemplateByAuthor(string author, Template template, TemplatePack pack) { int score = 0; var authorRes = IsStringMatch(author, template.Author); if (authorRes.IsExactMatch) { score += 500; } else if (authorRes.StartsWith) { score += 100; } else if (authorRes.IsPartialMatch) { score += 50; } // see if there is a match with the TemplatePack itself var packAuthorMatch = IsStringMatch(author, pack.Authors); if (packAuthorMatch.IsExactMatch) { score += 75; } else if (packAuthorMatch.StartsWith) { score += 40; } else if (packAuthorMatch.IsPartialMatch) { score += 20; } var packOwnerMatch = IsStringMatch(author, pack.Owners); if (packOwnerMatch.IsExactMatch) { score += 70; } else if (packOwnerMatch.StartsWith) { score += 35; } else if (packOwnerMatch.IsPartialMatch) { score += 15; } return(new TemplateSearchResult { IsMatch = (score > 0), SearchValue = score }); }
/// <summary> /// This function will search the template for the given term. /// If there is a match the returned object will have IsMatch set to true. /// The SearchValue represents the "quality" of the match. The higher the number /// the better the match. /// </summary> /// <param name="searchTerm"></param> /// <param name="template"></param> /// <returns></returns> internal TemplateSearchResult SearchTemplate(string searchTerm, Template template, TemplatePack pack) { int score = 0; // check all fields in template var nameRes = IsStringMatch(searchTerm, template.Name); if (nameRes.IsExactMatch) { score += 1000; } else if (nameRes.StartsWith) { score += 200; } else if (nameRes.IsPartialMatch) { score += 100; } var authorRes = IsStringMatch(searchTerm, template.Author); if (authorRes.IsExactMatch) { score += 500; } else if (authorRes.StartsWith) { score += 100; } else if (authorRes.IsPartialMatch) { score += 50; } var shortNameRes = IsStringMatch(searchTerm, template.ShortName); if (shortNameRes.IsExactMatch) { score += 500; } else if (shortNameRes.StartsWith) { score += 100; } else if (shortNameRes.IsPartialMatch) { score += 50; } var idRes = IsStringMatch(searchTerm, template.Identity); if (idRes.IsExactMatch) { score += 500; } else if (idRes.StartsWith) { score += 100; } else if (idRes.IsPartialMatch) { score += 50; } var groupIdRes = IsStringMatch(searchTerm, template.GroupIdentity); if (groupIdRes.IsExactMatch) { score += 500; } else if (groupIdRes.StartsWith) { score += 100; } else if (groupIdRes.IsPartialMatch) { score += 50; } // to avoid nullref error if (template.Tags == null) { template.Tags = new Dictionary <string, string>(); } var tagKeysRes = IsStringMatch(searchTerm, template.Tags.Keys); var tagValuesRes = IsStringMatch(searchTerm, template.Tags.Values); var tagRes = Combine( IsStringMatch(searchTerm, template.Tags.Keys), IsStringMatch(searchTerm, template.Tags.Values)); if (tagRes.IsExactMatch) { score += 250; } else if (tagRes.StartsWith) { score += 50; } else if (tagRes.IsPartialMatch) { score += 25; } var classRes = IsStringMatch(searchTerm, template.Classifications); if (classRes.IsExactMatch) { score += 250; } else if (classRes.StartsWith) { score += 50; } else if (classRes.IsPartialMatch) { score += 25; } // see if there is a match with the TemplatePack itself var packAuthorMatch = IsStringMatch(searchTerm, pack.Authors); if (packAuthorMatch.IsExactMatch || packAuthorMatch.StartsWith || packAuthorMatch.IsPartialMatch) { score += 10; } var copyMatch = IsStringMatch(searchTerm, pack.Copyright); if (copyMatch.IsExactMatch || copyMatch.StartsWith || copyMatch.IsPartialMatch) { score += 10; } var descMatch = IsStringMatch(searchTerm, pack.Description); if (descMatch.IsExactMatch || descMatch.StartsWith || descMatch.IsPartialMatch) { score += 10; } var ownerMatch = IsStringMatch(searchTerm, pack.Owners); if (ownerMatch.IsExactMatch || ownerMatch.StartsWith || ownerMatch.IsPartialMatch) { score += 10; } var pkgMatch = IsStringMatch(searchTerm, pack.Package); if (pkgMatch.IsExactMatch || pkgMatch.StartsWith || pkgMatch.IsPartialMatch) { score += 10; } var projUrlMatch = IsStringMatch(searchTerm, pack.ProjectUrl); if (projUrlMatch.IsExactMatch || projUrlMatch.StartsWith || projUrlMatch.IsPartialMatch) { score += 10; } return(new TemplateSearchResult { IsMatch = (score > 0), SearchValue = score }); }
public async Task GenerateTemplateJsonReportAsync(string[] searchTerms, string jsonReportFilepath, List <string> specificPackagesToInclude, string previousReportPath) { Debug.Assert(searchTerms != null && searchTerms.Length > 0); Debug.Assert(!string.IsNullOrEmpty(jsonReportFilepath)); Dictionary <string, TemplatePack> previousPacks = new Dictionary <string, TemplatePack>(); if (!string.IsNullOrEmpty(previousReportPath) && File.Exists(previousReportPath)) { List <TemplatePack> previousReport = new List <TemplatePack>(); previousReport = JsonConvert.DeserializeObject <List <TemplatePack> >(File.ReadAllText(previousReportPath)); previousPacks = TemplatePack.ConvertToDictionary(previousReport); } // 1: query nuget for search results, we need to query all because we need to get the new download count var foundPackages = await _nugetHelper.QueryNuGetAsync(_httpClient, searchTerms, specificPackagesToInclude, GetPackagesToIgnore()); var pkgsToDownload = new List <NuGetPackage>(); // go through each found package, if pkg is in previous result with same version number, update download count and move on // if not same version number, remove from dictionary and add to list to download foreach (var pkg in foundPackages) { var id = TemplatePack.NormalisePkgId(pkg.Id); if (previousPacks.ContainsKey(id)) { var previousPackage = previousPacks[id]; // check version number to see if it is the same if (string.Compare(pkg.Version, previousPackage.Version, StringComparison.OrdinalIgnoreCase) == 0) { // same version just update the download count previousPackage.DownloadCount = pkg.TotalDownloads; } else { previousPacks.Remove(id); pkgsToDownload.Add(pkg); } } else { pkgsToDownload.Add(pkg); } } // 2: download nuget packages locally // var downloadedPackages = await _nugetDownloader.DownloadAllPackagesAsync(foundPackages); var downloadedPackages = await _nugetDownloader.DownloadAllPackagesAsync(pkgsToDownload); // 3: extract nuget package to local folder var templatePackages = new List <NuGetPackage>(); var listPackagesWithNotemplates = new List <NuGetPackage>(); var pkgNamesWitoutPackages = new List <string>(); foreach (var pkg in downloadedPackages) { var extractPath = _remoteFile.ExtractZipLocally(pkg.LocalFilepath); pkg.LocalExtractPath = extractPath; // see if there is a .template var foundDirs = Directory.EnumerateDirectories(extractPath, ".template.config", new EnumerationOptions { RecurseSubdirectories = true }); if (foundDirs.Count() > 0) { templatePackages.Add(pkg); } else { Console.WriteLine($"pkg has no templates: {pkg.Id}"); listPackagesWithNotemplates.Add(pkg); pkgNamesWitoutPackages.Add(pkg.Id); } } var reportsPath = Path.Combine(_remoteFile.CacheFolderpath, "reports", DateTime.Now.ToString("MM.dd.yy-H.m.s.ffff")); if (!Directory.Exists(reportsPath)) { Directory.CreateDirectory(reportsPath); } File.WriteAllText( Path.Combine(reportsPath, "newly-found-packages-without-templates.json"), JsonConvert.SerializeObject(listPackagesWithNotemplates, Formatting.Indented)); File.WriteAllText( Path.Combine(reportsPath, "newly-found-package-names-to-ignore.json"), JsonConvert.SerializeObject(pkgNamesWitoutPackages, Formatting.Indented)); var templatePacks = new List <TemplatePack>(); foreach (var pkg in templatePackages) { // get nuspec file path var nuspecFile = Directory.GetFiles(pkg.LocalExtractPath, $"{pkg.Id}.nuspec").FirstOrDefault(); if (nuspecFile == null) { Console.WriteLine($"warning: nuspec not found in folder {pkg.LocalFilepath}"); continue; } // get template folders var contentDir = Path.Combine(pkg.LocalExtractPath); if (!Directory.Exists(contentDir)) { continue; } var templateFiles = TemplatePack.GetTemplateFilesUnder(contentDir); try { var tp = TemplatePack.CreateFromNuSpec(pkg, nuspecFile, templateFiles); templatePacks.Add(tp); } catch (Exception ex) { Console.WriteLine($"error creating template pack {nuspecFile} {ex.ToString()}"); } } // add all the downloaded items to existing dictionary then get the full result foreach (var pkg in templatePacks) { var id = TemplatePack.NormalisePkgId(pkg.Package); if (previousPacks.ContainsKey(id)) { // I believe it shouldn't get here, but just in case previousPacks.Remove(id); } previousPacks.Add(id, pkg); } templatePacks = previousPacks.Values.ToList(); templatePacks = templatePacks.OrderBy((tp) => - 1 * tp.DownloadCount).ToList(); // write to cache folder and then copy to dest var cacheFile = Path.Combine(reportsPath, "template-report.json"); File.WriteAllText(cacheFile, JsonConvert.SerializeObject(templatePacks, Formatting.Indented)); if (File.Exists(jsonReportFilepath)) { File.Delete(jsonReportFilepath); } Console.WriteLine($"Writing report to '{jsonReportFilepath}'"); File.Copy(cacheFile, jsonReportFilepath); }