diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs index fd5734d1..876b023a 100644 --- a/src/WingetCreateCLI/Commands/NewCommand.cs +++ b/src/WingetCreateCLI/Commands/NewCommand.cs @@ -235,6 +235,20 @@ public override async Task Execute() } ShiftRootFieldsToInstallerLevel(manifests.InstallerManifest); + try + { + Logger.InfoLocalized(nameof(Resources.PopulatingGitHubMetadata_Message)); + if (this.GitHubClient != null) + { + await this.GitHubClient.PopulateGitHubMetadata(manifests, this.Format.ToString()); + } + } + catch (Octokit.ApiException) + { + // Print a warning, but continue with the command flow. + Logger.ErrorLocalized(nameof(Resources.CouldNotPopulateGitHubMetadata_Warning)); + } + PromptManifestProperties(manifests); MergeNestedInstallerFilesIfApplicable(manifests.InstallerManifest); ShiftInstallerFieldsToRootLevel(manifests.InstallerManifest); diff --git a/src/WingetCreateCLI/Commands/UpdateCommand.cs b/src/WingetCreateCLI/Commands/UpdateCommand.cs index 46168e6c..d81bfbe7 100644 --- a/src/WingetCreateCLI/Commands/UpdateCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateCommand.cs @@ -73,6 +73,18 @@ public static IEnumerable Examples [Option('d', "display-version", Required = false, HelpText = "DisplayVersion_HelpText", ResourceType = typeof(Resources))] public string DisplayVersion { get; set; } + /// + /// Gets or sets the release notes URL for the manifest. + /// + [Option("release-notes-url", Required = false, HelpText = "ReleaseNotesUrl_HelpText", ResourceType = typeof(Resources))] + public string ReleaseNotesUrl { get; set; } + + /// + /// Gets or sets the release date for the manifest. + /// + [Option("release-date", Required = false, HelpText = "ReleaseDate_HelpText", ResourceType = typeof(Resources))] + public DateTimeOffset? ReleaseDate { get; set; } + /// /// Gets or sets the outputPath where the generated manifest file should be saved to. /// @@ -153,6 +165,19 @@ public override async Task Execute() bool submitFlagMissing = !this.SubmitToGitHub && (!string.IsNullOrEmpty(this.PRTitle) || this.Replace); + if (!string.IsNullOrEmpty(this.ReleaseNotesUrl)) + { + Uri uriResult; + bool isValid = Uri.TryCreate(this.ReleaseNotesUrl, UriKind.Absolute, out uriResult) && + (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); + + if (!isValid) + { + Logger.ErrorLocalized(nameof(Resources.SentenceBadFormatConversionErrorOption), nameof(this.ReleaseNotesUrl)); + return false; + } + } + if (submitFlagMissing) { Logger.WarnLocalized(nameof(Resources.SubmitFlagMissing_Warning)); @@ -440,6 +465,22 @@ public async Task UpdateManifestsAutonomously(Manifests manifests) PackageParser.UpdateInstallerNodesAsync(installerMetadataList, installerManifest); DisplayArchitectureWarnings(installerMetadataList); ResetVersionSpecificFields(manifests); + try + { + Logger.InfoLocalized(nameof(Resources.PopulatingGitHubMetadata_Message)); + + if (this.GitHubClient != null) + { + await this.GitHubClient.PopulateGitHubMetadata(manifests, this.Format.ToString()); + } + } + catch (Octokit.ApiException) + { + // Print a warning, but continue with the update. + Logger.ErrorLocalized(nameof(Resources.CouldNotPopulateGitHubMetadata_Warning)); + } + + this.AddVersionSpecificMetadata(manifests); ShiftInstallerFieldsToRootLevel(manifests.InstallerManifest); } catch (InvalidOperationException) @@ -711,6 +752,27 @@ private static bool AreInstallerUrlsVanityUrls(Manifests baseManifest, Manifests return true; } + private void AddVersionSpecificMetadata(Manifests updatedManifests) + { + if (this.ReleaseDate != null) + { + switch (this.Format) + { + case ManifestFormat.Yaml: + updatedManifests.InstallerManifest.ReleaseDateTime = this.ReleaseDate.Value.ToString("yyyy-MM-dd"); + break; + case ManifestFormat.Json: + updatedManifests.InstallerManifest.ReleaseDate = this.ReleaseDate; + break; + } + } + + if (!string.IsNullOrEmpty(this.ReleaseNotesUrl)) + { + updatedManifests.DefaultLocaleManifest.ReleaseNotesUrl = this.ReleaseNotesUrl; + } + } + private string ObtainMatchingRelativeFilePath(string oldRelativeFilePath, string directory, string archiveName) { string fileName = Path.GetFileName(oldRelativeFilePath); @@ -870,6 +932,21 @@ private async Task UpdateManifestsInteractively(Manifests manifests) await this.UpdateInstallersInteractively(manifests.InstallerManifest.Installers); ShiftInstallerFieldsToRootLevel(manifests.InstallerManifest); ResetVersionSpecificFields(manifests); + try + { + Logger.InfoLocalized(nameof(Resources.PopulatingGitHubMetadata_Message)); + if (this.GitHubClient != null) + { + await this.GitHubClient.PopulateGitHubMetadata(manifests, this.Format.ToString()); + } + } + catch (Octokit.ApiException) + { + // Print a warning, but continue with the update. + Logger.ErrorLocalized(nameof(Resources.CouldNotPopulateGitHubMetadata_Warning)); + } + + this.AddVersionSpecificMetadata(manifests); DisplayManifestPreview(manifests); ValidateManifestsInTempDir(manifests); } diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index 4a4e0a5a..05763bf8 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -402,6 +402,15 @@ public static string CopyrightUrl_KeywordDescription { } } + /// + /// Looks up a localized string similar to Could not populate manifest metadata through GitHub's API.. + /// + public static string CouldNotPopulateGitHubMetadata_Warning { + get { + return ResourceManager.GetString("CouldNotPopulateGitHubMetadata_Warning", resourceCulture); + } + } + /// /// Looks up a localized string similar to Would you like to create another locale?. /// @@ -2301,6 +2310,15 @@ public static string Platform_KeywordDescription { } } + /// + /// Looks up a localized string similar to GitHub URL detected. The CLI will automatically fill some manifests fields.. + /// + public static string PopulatingGitHubMetadata_Message { + get { + return ResourceManager.GetString("PopulatingGitHubMetadata_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to The command alias to be used for calling the nested portable package. /// @@ -2472,6 +2490,15 @@ public static string RelativeFilePath_KeywordDescription { } } + /// + /// Looks up a localized string similar to Date to be used when updating the release date field. Expected format is "YYYY-MM-DD".. + /// + public static string ReleaseDate_HelpText { + get { + return ResourceManager.GetString("ReleaseDate_HelpText", resourceCulture); + } + } + /// /// Looks up a localized string similar to The installer release date. /// @@ -2499,6 +2526,15 @@ public static string ReleaseNotes_KeywordDescription { } } + /// + /// Looks up a localized string similar to URL to be used when updating the release notes url field.. + /// + public static string ReleaseNotesUrl_HelpText { + get { + return ResourceManager.GetString("ReleaseNotesUrl_HelpText", resourceCulture); + } + } + /// /// Looks up a localized string similar to The package release notes url. /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index dcb09166..c5ce75c1 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -1371,4 +1371,16 @@ Single installer with multiple display versions detected. Winget-Create will only update the first DisplayVersion for a given installer. + + Could not populate manifest metadata through GitHub's API. + + + GitHub URL detected. The CLI will automatically fill some manifests fields. + + + Date to be used when updating the release date field. Expected format is "YYYY-MM-DD". + + + URL to be used when updating the release notes url field. + \ No newline at end of file diff --git a/src/WingetCreateCore/Common/GitHub.cs b/src/WingetCreateCore/Common/GitHub.cs index ddb47267..6aaad00e 100644 --- a/src/WingetCreateCore/Common/GitHub.cs +++ b/src/WingetCreateCore/Common/GitHub.cs @@ -12,6 +12,8 @@ namespace Microsoft.WingetCreateCore.Common using Jose; using Microsoft.WingetCreateCore.Common.Exceptions; using Microsoft.WingetCreateCore.Models; + using Microsoft.WingetCreateCore.Models.DefaultLocale; + using Microsoft.WingetCreateCore.Models.Installer; using Octokit; using Polly; @@ -229,6 +231,21 @@ public async Task FindPackageId(string packageId) return await this.FindPackageIdRecursive(packageId.Split('.'), path, string.Empty, 0); } + /// + /// Uses the GitHub API to retrieve and populate metadata for manifests in the provided object. + /// + /// Wrapper object for manifest object models to be populated with GitHub metadata. + /// The output format of the manifest serializer. + /// A representing the asynchronous operation. + public async Task PopulateGitHubMetadata(Manifests manifests, string serializerFormat) + { + // Only populate metadata if we have a valid GitHub token. + if (this.github.Credentials.AuthenticationType != AuthenticationType.Anonymous) + { + await GitHubManifestMetadata.PopulateManifestMetadata(manifests, serializerFormat, this.github); + } + } + /// /// Generate a signed-JWT token for specified GitHub app, per instructions here: https://docs.github.com/en/developers/apps/authenticating-with-github-apps#authenticating-as-an-installation. /// @@ -479,5 +496,135 @@ private async Task DeletePullRequestBranch(int pullRequestId) await this.github.Git.Reference.Delete(this.wingetRepoOwner, this.wingetRepo, newBranchNameHeads); } } + + private static class GitHubManifestMetadata + { + public static async Task PopulateManifestMetadata(Manifests manifests, string serializerFormat, GitHubClient client) + { + // Get owner and repo from the installer manifest + GitHubUrlMetadata? metadata = GetMetadataFromGitHubUrl(manifests.InstallerManifest); + + if (metadata == null) + { + // Could not populate GitHub metadata. + return; + } + + string owner = metadata.Value.Owner; + string repo = metadata.Value.Repo; + string tag = metadata.Value.ReleaseTag; + + var githubRepo = await client.Repository.Get(owner, repo); + var githubRelease = await client.Repository.Release.Get(owner, repo, tag); + + // License + if (string.IsNullOrEmpty(manifests.DefaultLocaleManifest.License)) + { + // License will only ever be empty in new command flow + manifests.DefaultLocaleManifest.License = githubRepo.License?.SpdxId ?? githubRepo.License?.Name; + } + + // ShortDescription + if (string.IsNullOrEmpty(manifests.DefaultLocaleManifest.ShortDescription)) + { + // ShortDescription will only ever be empty in new command flow + manifests.DefaultLocaleManifest.ShortDescription = githubRepo.Description; + } + + // PackageUrl + if (string.IsNullOrEmpty(manifests.DefaultLocaleManifest.PackageUrl)) + { + manifests.DefaultLocaleManifest.PackageUrl = githubRepo.HtmlUrl; + } + + // PublisherUrl + if (string.IsNullOrEmpty(manifests.DefaultLocaleManifest.PublisherUrl)) + { + manifests.DefaultLocaleManifest.PublisherUrl = githubRepo.Owner.HtmlUrl; + } + + // PublisherSupportUrl + if (string.IsNullOrEmpty(manifests.DefaultLocaleManifest.PublisherSupportUrl) && githubRepo.HasIssues) + { + manifests.DefaultLocaleManifest.PublisherSupportUrl = $"{githubRepo.HtmlUrl}/issues"; + } + + // Tags + // 16 is the maximum number of tags allowed in the manifest + manifests.DefaultLocaleManifest.Tags ??= githubRepo.Topics?.Take(count: 16).ToList(); + + // ReleaseNotesUrl + if (string.IsNullOrEmpty(manifests.DefaultLocaleManifest.ReleaseNotesUrl)) + { + manifests.DefaultLocaleManifest.ReleaseNotesUrl = githubRelease.HtmlUrl; + } + + // ReleaseDate + SetReleaseDate(manifests, serializerFormat, githubRelease); + + // Documentations + if (manifests.DefaultLocaleManifest.Documentations == null && githubRepo.HasWiki) + { + manifests.DefaultLocaleManifest.Documentations = new List + { + new() + { + DocumentLabel = "Wiki", + DocumentUrl = $"{githubRepo.HtmlUrl}/wiki", + }, + }; + } + } + + private static void SetReleaseDate(Manifests manifests, string serializerFormat, Release githubRelease) + { + DateTimeOffset? releaseDate = githubRelease.PublishedAt; + if (releaseDate == null) + { + return; + } + + switch (serializerFormat.ToLower()) + { + case "yaml": + manifests.InstallerManifest.ReleaseDateTime = releaseDate.Value.ToString("yyyy-MM-dd"); + break; + case "json": + manifests.InstallerManifest.ReleaseDate = releaseDate; + break; + } + } + + private static GitHubUrlMetadata? GetMetadataFromGitHubUrl(InstallerManifest installerManifest) + { + // Get all GitHub URLs from the installer manifest + List gitHubUrls = installerManifest.Installers + .Where(x => x.InstallerUrl.StartsWith("https://github.com/", StringComparison.OrdinalIgnoreCase)) + .Select(x => x.InstallerUrl) + .ToList(); + + if (gitHubUrls.Count != installerManifest.Installers.Count) + { + // No GitHub URLs found OR not all manifest InstallerUrls are GitHub URLs. + return null; + } + + string domainTrimmed = gitHubUrls.First().Replace("https://github.com/", string.Empty); + string[] parts = domainTrimmed.Split("/"); + string owner = parts[0]; + string repo = parts[1]; + string tag = domainTrimmed.Replace($"{owner}/{repo}/releases/download/", string.Empty).Split("/")[0]; + + // Check if all GitHub URLs have the same owner, repo and tag + if (gitHubUrls.Any(x => !x.StartsWith($"https://github.com/{owner}/{repo}/releases/download/{tag}", StringComparison.OrdinalIgnoreCase))) + { + return null; + } + + return new GitHubUrlMetadata(owner, repo, tag); + } + + private record struct GitHubUrlMetadata(string Owner, string Repo, string ReleaseTag); + } } } diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.GitHubAutoFillTest/Multifile.Json.GitHubAutoFillTest.installer.json b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.GitHubAutoFillTest/Multifile.Json.GitHubAutoFillTest.installer.json new file mode 100644 index 00000000..d98a1346 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.GitHubAutoFillTest/Multifile.Json.GitHubAutoFillTest.installer.json @@ -0,0 +1,14 @@ +{ + "PackageIdentifier": "Multifile.Json.GitHubAutoFillTest", + "PackageVersion": "1.2.3.4", + "Installers": [ + { + "Architecture": "x64", + "InstallerUrl": "https://fakedomain.com/WingetCreateTestExeInstaller.exe", + "InstallerType": "exe", + "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC" + } + ], + "ManifestType": "installer", + "ManifestVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.GitHubAutoFillTest/Multifile.Json.GitHubAutoFillTest.json b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.GitHubAutoFillTest/Multifile.Json.GitHubAutoFillTest.json new file mode 100644 index 00000000..5d1c45c0 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.GitHubAutoFillTest/Multifile.Json.GitHubAutoFillTest.json @@ -0,0 +1,7 @@ +{ + "PackageIdentifier": "Multifile.Json.GitHubAutoFillTest", + "PackageVersion": "1.2.3.4", + "DefaultLocale": "en-US", + "ManifestType": "version", + "ManifestVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.GitHubAutoFillTest/Multifile.Json.GitHubAutoFillTest.locale.en-US.json b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.GitHubAutoFillTest/Multifile.Json.GitHubAutoFillTest.locale.en-US.json new file mode 100644 index 00000000..ce256593 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Json.GitHubAutoFillTest/Multifile.Json.GitHubAutoFillTest.locale.en-US.json @@ -0,0 +1,9 @@ +{ + "PackageIdentifier": "Multifile.Json.GitHubAutoFillTest", + "PackageVersion": "1.2.3.4", + "PackageLocale": "en-US", + "Publisher": "Multifile", + "PackageName": "MsixTest", + "ManifestType": "defaultLocale", + "ManifestVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.GitHubAutoFillTest/Multifile.Yaml.GitHubAutoFillTest.installer.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.GitHubAutoFillTest/Multifile.Yaml.GitHubAutoFillTest.installer.yaml new file mode 100644 index 00000000..daeb3469 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.GitHubAutoFillTest/Multifile.Yaml.GitHubAutoFillTest.installer.yaml @@ -0,0 +1,10 @@ +PackageIdentifier: Multifile.Yaml.GitHubAutoFillTest +PackageVersion: 1.2.3.4 +Installers: + - Architecture: x64 + InstallerUrl: https://fakedomain.com/WingetCreateTestExeInstaller.exe + InstallerType: exe + InstallerSha256: A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC + Scope: user +ManifestType: installer +ManifestVersion: 1.0.0 \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.GitHubAutoFillTest/Multifile.Yaml.GitHubAutoFillTest.locale.en-US.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.GitHubAutoFillTest/Multifile.Yaml.GitHubAutoFillTest.locale.en-US.yaml new file mode 100644 index 00000000..fafbc835 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.GitHubAutoFillTest/Multifile.Yaml.GitHubAutoFillTest.locale.en-US.yaml @@ -0,0 +1,10 @@ +PackageIdentifier: Multifile.Yaml.GitHubAutoFillTest +PackageVersion: 1.2.3.4 +PackageLocale: en-US +Publisher: Multifile +PackageName: MsixTest +# Will be auto-filled through GitHub's API +# License: MIT +# ShortDescription: Testing metadata auto-fill through GitHub's API +ManifestType: defaultLocale +ManifestVersion: 1.0.0 \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.GitHubAutoFillTest/Multifile.Yaml.GitHubAutoFillTest.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.GitHubAutoFillTest/Multifile.Yaml.GitHubAutoFillTest.yaml new file mode 100644 index 00000000..4780a0a4 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/Multifile.Yaml.GitHubAutoFillTest/Multifile.Yaml.GitHubAutoFillTest.yaml @@ -0,0 +1,5 @@ +PackageIdentifier: Multifile.Yaml.GitHubAutoFillTest +PackageVersion: 1.2.3.4 +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.0.0 \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/GitHubTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/GitHubTests.cs index 2fd99bed..c31a72eb 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/GitHubTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/GitHubTests.cs @@ -5,8 +5,13 @@ namespace Microsoft.WingetCreateUnitTests { using System; using System.Collections.Generic; + using System.IO; using System.Linq; using System.Threading.Tasks; + using Microsoft.WingetCreateCLI.Commands; + using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Models.Settings; + using Microsoft.WingetCreateCLI.Telemetry.Events; using Microsoft.WingetCreateCore; using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateCore.Models; @@ -31,16 +36,38 @@ public class GitHubTests : GitHubTestsBase private const string TitleMismatch = "Pull request title does not match test title."; private GitHub gitHub; + private StringWriter sw; /// /// Setup for the GitHub.cs unit tests. /// [OneTimeSetUp] - public void Setup() + public void OneTimeSetup() { this.gitHub = new GitHub(this.GitHubApiKey, this.WingetPkgsTestRepoOwner, this.WingetPkgsTestRepo); Serialization.ProducedBy = "WingetCreateUnitTests"; Serialization.ManifestSerializer = new YamlSerializer(); + Logger.Initialize(); + } + + /// + /// Setup method for each individual test. + /// + [SetUp] + public void Setup() + { + this.sw = new StringWriter(); + Console.SetOut(this.sw); + } + + /// + /// Teardown method for each individual test. + /// + [TearDown] + public void TearDown() + { + this.sw.Dispose(); + PackageParser.SetHttpMessageHandler(null); } /// @@ -107,5 +134,95 @@ public async Task RemoveWhitespaceFromBranchName() await this.gitHub.ClosePullRequest(pullRequest.Number); StringAssert.StartsWith(string.Format(GitHubPullRequestBaseUrl, this.WingetPkgsTestRepoOwner, this.WingetPkgsTestRepo), pullRequest.HtmlUrl, PullRequestFailedToGenerate); } + + /// + /// Verifies that manifest metadata is automatically filled through GitHub's API if we have a GitHub Installer URL. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ValidateAutoPopulatedManifestMetadata_Yaml() + { + var initialManifestContent = TestUtils.GetInitialMultifileManifestContent("Multifile.Yaml.GitHubAutoFillTest"); + UpdateCommand command = new UpdateCommand + { + Id = "Multifile.Yaml.GitHubAutoFillTest", + Version = "1.2.3.4", + InstallerUrls = new[] + { + "https://github.com/microsoft/winget-pkgs-submission-test/releases/download/v1.0.1/WingetCreateTestExeInstaller.exe", + }, + Format = ManifestFormat.Yaml, + GitHubToken = this.GitHubApiKey, + }; + ClassicAssert.IsTrue(await command.LoadGitHubClient(), "Failed to create GitHub client"); + Manifests initialManifests = command.DeserializeManifestContentAndApplyInitialUpdate(initialManifestContent); + Manifests updatedManifests = await command.UpdateManifestsAutonomously(initialManifests); + ClassicAssert.IsNotNull(updatedManifests, "Command should have succeeded"); + + Assert.That(updatedManifests.DefaultLocaleManifest.License, Is.EqualTo("MIT"), "Could not populate License field"); + Assert.That(updatedManifests.DefaultLocaleManifest.ShortDescription, Is.EqualTo("Mirror of winget-pkgs for testing submission"), "Could not populate ShortDescription field"); + Assert.That(updatedManifests.DefaultLocaleManifest.PackageUrl, Is.EqualTo("https://github.com/microsoft/winget-pkgs-submission-test"), "Could not populate PackageUrl field"); + Assert.That(updatedManifests.DefaultLocaleManifest.PublisherUrl, Is.EqualTo("https://github.com/microsoft"), "Could not populate PublisherUrl field"); + Assert.That(updatedManifests.DefaultLocaleManifest.PublisherSupportUrl, Is.EqualTo("https://github.com/microsoft/winget-pkgs-submission-test/issues"), "Could not populate PublisherSupportUrl field"); + Assert.That(updatedManifests.DefaultLocaleManifest.ReleaseNotesUrl, Is.EqualTo("https://github.com/microsoft/winget-pkgs-submission-test/releases/tag/v1.0.1"), "Could not populate ReleaseNotesUrl field"); + + // ReleaseDateTime needs to be set a workaround as our YAML serializer has trouble with ReleaseDate field + Assert.That(updatedManifests.InstallerManifest.ReleaseDateTime, Is.EqualTo("2024-08-06"), "Could not populate ReleaseDateTime field"); + + List expectedTags = new() + { + "winget-pkgs", + "winget-create", + "winget-pkgs-submission-test", + }; + Assert.That(updatedManifests.DefaultLocaleManifest.Tags, Is.EquivalentTo(expectedTags), "Could not populate Tags field"); + Assert.That(updatedManifests.DefaultLocaleManifest.Documentations[0].DocumentLabel, Is.EqualTo("Wiki"), "Could not populate DocumentLabel field"); + Assert.That(updatedManifests.DefaultLocaleManifest.Documentations[0].DocumentUrl, Is.EqualTo("https://github.com/microsoft/winget-pkgs-submission-test/wiki"), "Could not populate DocumentUrl field"); + } + + /// + /// Verifies that manifest metadata is automatically filled through GitHub's API if we have a GitHub Installer URL. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task ValidateAutoPopulatedManifestMetadata_Json() + { + var initialManifestContent = TestUtils.GetInitialMultifileManifestContent("Multifile.Json.GitHubAutoFillTest"); + UpdateCommand command = new UpdateCommand + { + Id = "Multifile.Yaml.GitHubAutoFillTest", + Version = "1.2.3.4", + InstallerUrls = new[] + { + "https://github.com/microsoft/winget-pkgs-submission-test/releases/download/v1.0.1/WingetCreateTestExeInstaller.exe", + }, + Format = ManifestFormat.Json, + GitHubToken = this.GitHubApiKey, + }; + ClassicAssert.IsTrue(await command.LoadGitHubClient(), "Failed to create GitHub client"); + Manifests initialManifests = command.DeserializeManifestContentAndApplyInitialUpdate(initialManifestContent); + Manifests updatedManifests = await command.UpdateManifestsAutonomously(initialManifests); + ClassicAssert.IsNotNull(updatedManifests, "Command should have succeeded"); + + Assert.That(updatedManifests.DefaultLocaleManifest.License, Is.EqualTo("MIT"), "Could not populate License field"); + Assert.That(updatedManifests.DefaultLocaleManifest.ShortDescription, Is.EqualTo("Mirror of winget-pkgs for testing submission"), "Could not populate ShortDescription field"); + Assert.That(updatedManifests.DefaultLocaleManifest.PackageUrl, Is.EqualTo("https://github.com/microsoft/winget-pkgs-submission-test"), "Could not populate PackageUrl field"); + Assert.That(updatedManifests.DefaultLocaleManifest.PublisherUrl, Is.EqualTo("https://github.com/microsoft"), "Could not populate PublisherUrl field"); + Assert.That(updatedManifests.DefaultLocaleManifest.PublisherSupportUrl, Is.EqualTo("https://github.com/microsoft/winget-pkgs-submission-test/issues"), "Could not populate PublisherSupportUrl field"); + Assert.That(updatedManifests.DefaultLocaleManifest.ReleaseNotesUrl, Is.EqualTo("https://github.com/microsoft/winget-pkgs-submission-test/releases/tag/v1.0.1"), "Could not populate ReleaseNotesUrl field"); + + DateTimeOffset expectedReleaseDate = DateTimeOffset.Parse("2024-08-06 01:43:15+00:00"); + Assert.That(updatedManifests.InstallerManifest.ReleaseDate, Is.EqualTo(expectedReleaseDate), "Could not populate ReleaseDateTime field"); + + List expectedTags = new() + { + "winget-pkgs", + "winget-create", + "winget-pkgs-submission-test", + }; + Assert.That(updatedManifests.DefaultLocaleManifest.Tags, Is.EquivalentTo(expectedTags), "Could not populate Tags field"); + Assert.That(updatedManifests.DefaultLocaleManifest.Documentations[0].DocumentLabel, Is.EqualTo("Wiki"), "Could not populate DocumentLabel field"); + Assert.That(updatedManifests.DefaultLocaleManifest.Documentations[0].DocumentUrl, Is.EqualTo("https://github.com/microsoft/winget-pkgs-submission-test/wiki"), "Could not populate DocumentUrl field"); + } } } diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocalizationTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocalizationTests.cs index e25ef9d7..e645df4f 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocalizationTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocalizationTests.cs @@ -7,9 +7,11 @@ namespace Microsoft.WingetCreateUnitTests using System.Collections; using System.Collections.Generic; using System.Globalization; + using System.IO; using System.Linq; using System.Reflection; using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCore; using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Installer; using Microsoft.WingetCreateCore.Models.Locale; @@ -31,6 +33,28 @@ public class LocalizationTests nameof(WingetCreateCore.Models.Installer.Installer.ReleaseDate), }; + private StringWriter sw; + + /// + /// Setup method for each individual test. + /// + [SetUp] + public void Setup() + { + this.sw = new StringWriter(); + Console.SetOut(this.sw); + } + + /// + /// Teardown method for each individual test. + /// + [TearDown] + public void TearDown() + { + this.sw.Dispose(); + PackageParser.SetHttpMessageHandler(null); + } + /// /// Verifies that all localized strings exist for every property that exists /// in the Manifest object model. diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs index 6f70e23a..216b8e60 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.WingetCreateUnitTests using System.Threading.Tasks; using Microsoft.WingetCreateCLI.Commands; using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Models.Settings; using Microsoft.WingetCreateCLI.Properties; using Microsoft.WingetCreateCLI.Telemetry.Events; using Microsoft.WingetCreateCore; @@ -92,18 +93,33 @@ public async Task UpdateAndVerifyManifestsCreated() [Test] public async Task UpdateAndVerifyUpdatedProperties() { - TestUtils.InitializeMockDownloads(TestConstants.TestMsiInstaller); + string packageId = TestConstants.YamlConstants.TestMultifileMsixPackageIdentifier; string version = "1.2.3.4"; - (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData(TestConstants.YamlConstants.TestMsiPackageIdentifier, version, this.tempPath, null); + string installerUrl = $"https://fakedomain.com/{TestConstants.TestMsixInstaller}"; + string releaseDateString = "2024-01-01"; + TestUtils.InitializeMockDownloads(TestConstants.TestMsixInstaller); + var initialManifestContent = TestUtils.GetInitialMultifileManifestContent(packageId); + UpdateCommand command = new UpdateCommand + { + Id = packageId, + Version = version, + InstallerUrls = new[] { installerUrl }, + SubmitToGitHub = false, + OutputDir = this.tempPath, + ReleaseDate = DateTimeOffset.Parse(releaseDateString), + ReleaseNotesUrl = "https://fakedomain.com/", + Format = ManifestFormat.Yaml, + }; var initialManifests = Serialization.DeserializeManifestContents(initialManifestContent); - var initialInstaller = initialManifests.SingletonManifest.Installers.First(); + var initialInstaller = initialManifests.InstallerManifest.Installers.First(); var updatedManifests = await RunUpdateCommand(command, initialManifestContent); ClassicAssert.IsNotNull(updatedManifests, "Command should have succeeded"); var updatedInstaller = updatedManifests.InstallerManifest.Installers.First(); ClassicAssert.AreEqual(version, updatedManifests.VersionManifest.PackageVersion, "Version should be updated"); - ClassicAssert.AreNotEqual(initialInstaller.ProductCode, updatedManifests.InstallerManifest.ProductCode, "ProductCode should be updated"); ClassicAssert.AreNotEqual(initialInstaller.InstallerSha256, updatedInstaller.InstallerSha256, "InstallerSha256 should be updated"); + ClassicAssert.AreEqual(releaseDateString, updatedManifests.InstallerManifest.ReleaseDateTime, "ReleaseDate should be updated"); + ClassicAssert.AreEqual(command.ReleaseNotesUrl, updatedManifests.DefaultLocaleManifest.ReleaseNotesUrl, "ReleaseNotesUrl should be updated"); } /// diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj index 304a360e..966a9442 100644 --- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj +++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj @@ -27,6 +27,24 @@ + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest