From 8950b2138bc531ce9eafd51938ec606c0c694f4b Mon Sep 17 00:00:00 2001 From: Remco Fischer <6353023+remco1271@users.noreply.github.com> Date: Thu, 27 Jun 2024 22:21:13 +0200 Subject: [PATCH] Add support for Gitea (#134) * Add NuGet package * Add Gitea support * Add OS option * Add Gitea test Add test for release check --------- Co-authored-by: Caelan Sayler --- src/Velopack.Deployment/GiteaRepository.cs | 188 +++++++++++++++ .../Velopack.Deployment.csproj | 1 + src/Velopack.Deployment/packages.lock.json | 119 +++++++++- .../Commands/Deployment/GiteaBaseCommand.cs | 19 ++ .../Deployment/GiteaDownloadCommand.cs | 12 + .../Commands/Deployment/GiteaUploadCommand.cs | 49 ++++ src/Velopack.Vpk/OptionMapper.cs | 2 + src/Velopack.Vpk/Program.cs | 2 + src/Velopack.Vpk/packages.lock.json | 217 +++++++++++++++++- src/Velopack/Internal/CompiledJson.cs | 11 + src/Velopack/Sources/GiteaSource.cs | 135 +++++++++++ src/Velopack/packages.lock.json | 20 +- test/Velopack.Tests/UpdateManagerTests.cs | 16 +- 13 files changed, 766 insertions(+), 25 deletions(-) create mode 100644 src/Velopack.Deployment/GiteaRepository.cs create mode 100644 src/Velopack.Vpk/Commands/Deployment/GiteaBaseCommand.cs create mode 100644 src/Velopack.Vpk/Commands/Deployment/GiteaDownloadCommand.cs create mode 100644 src/Velopack.Vpk/Commands/Deployment/GiteaUploadCommand.cs create mode 100644 src/Velopack/Sources/GiteaSource.cs diff --git a/src/Velopack.Deployment/GiteaRepository.cs b/src/Velopack.Deployment/GiteaRepository.cs new file mode 100644 index 00000000..cde3a812 --- /dev/null +++ b/src/Velopack.Deployment/GiteaRepository.cs @@ -0,0 +1,188 @@ +using System.Net; +using System.Text; +using Gitea.Net.Api; +using Gitea.Net.Client; +using Gitea.Net.Model; +using Microsoft.Extensions.Logging; +using Velopack.NuGet; +using Velopack.Packaging; +using Velopack.Packaging.Exceptions; +using Velopack.Sources; + +namespace Velopack.Deployment; + +public class GiteaDownloadOptions : RepositoryOptions +{ + public bool Prerelease { get; set; } + + public string RepoUrl { get; set; } + + public string Token { get; set; } + + ///// + ///// Example https://gitea.com + ///// + //public string ServerUrl { get; set; } + + //public int ServerPort { get; set; } +} + +public class GiteaUploadOptions : GiteaDownloadOptions +{ + public bool Publish { get; set; } + + public string ReleaseName { get; set; } + + public string TagName { get; set; } + + public string TargetCommitish { get; set; } + + public bool Merge { get; set; } +} +public class GiteaRepository : SourceRepository, IRepositoryCanUpload +{ + public GiteaRepository(ILogger logger) : base(logger) + { + } + public override GiteaSource CreateSource(GiteaDownloadOptions options) + { + return new GiteaSource(options.RepoUrl, options.Token, options.Prerelease); + } + + public static (string owner, string repo) GetOwnerAndRepo(string repoUrl) + { + var repoUri = new Uri(repoUrl); + var repoParts = repoUri.AbsolutePath.Trim('/').Split('/'); + if (repoParts.Length != 2) + throw new Exception($"Invalid Gitea URL, '{repoUri.AbsolutePath}' should be in the format 'owner/repo'"); + + var repoOwner = repoParts[0]; + var repoName = repoParts[1]; + return (repoOwner, repoName); + } + + public async Task UploadMissingAssetsAsync(GiteaUploadOptions options) + { + var (repoOwner, repoName) = GetOwnerAndRepo(options.RepoUrl); + var helper = new ReleaseEntryHelper(options.ReleaseDir.FullName, options.Channel, Log, options.TargetOs); + var build = BuildAssets.Read(options.ReleaseDir.FullName, options.Channel); + var latest = helper.GetLatestFullRelease(); + var latestPath = Path.Combine(options.ReleaseDir.FullName, latest.FileName); + var releaseNotes = new ZipPackage(latestPath).ReleaseNotes; + var semVer = options.TagName ?? latest.Version.ToString(); + var releaseName = string.IsNullOrWhiteSpace(options.ReleaseName) ? semVer.ToString() : options.ReleaseName; + + // Setup Gitea config + Configuration config = new Configuration(); + // Example: http://www.Gitea.com/api/v1 + var uri = new Uri(options.RepoUrl); + var baseUri = uri.GetLeftPart(System.UriPartial.Authority); + config.BasePath = baseUri + "/api/v1"; + + Log.Info($"Preparing to upload {build.Files.Count} asset(s) to Gitea"); + + // Set token if provided + if (!string.IsNullOrWhiteSpace(options.Token)) { + config.ApiKey.Add("token", options.Token); + } + var apiInstance = new RepositoryApi(config); + // Get all releases + // Get repository info for total releases + List existingReleases = null; + ApiResponse repositoryInfo = await apiInstance.RepoGetWithHttpInfoAsync(repoOwner, repoName); + if (repositoryInfo != null && repositoryInfo.StatusCode == HttpStatusCode.OK) { + // Get all releases + var allReleases = await apiInstance.RepoListReleasesWithHttpInfoAsync(repoOwner, repoName, page: 1, limit: (int) repositoryInfo.Data.ReleaseCounter); + existingReleases = allReleases.Data; + if (allReleases != null && allReleases.StatusCode == HttpStatusCode.OK && allReleases.Data.Any(r => r.Name == releaseName)) { + throw new UserInfoException($"There is already an existing release named '{releaseName}'. Please delete this release or provide a new release name."); + } + } else { + throw new UserInfoException("Could not get all releases from server"); + } + + if (!options.Merge) { + if (existingReleases.Any(r => r.TagName == semVer.ToString())) { + throw new UserInfoException($"There is already an existing release tagged '{semVer}'. Please delete this release or provide a new version number."); + } + if (existingReleases.Any(r => r.Name == releaseName)) { + throw new UserInfoException($"There is already an existing release named '{releaseName}'. Please delete this release or provide a new release name."); + } + } + + // create or retrieve github release + var release = existingReleases.FirstOrDefault(r => r.TagName == semVer.ToString()) + ?? existingReleases.FirstOrDefault(r => r.Name == releaseName); ; + + if (release != null) { + if (release.TagName != semVer.ToString()) + throw new UserInfoException($"Found existing release matched by name ({release.Name} [{release.TagName}]), but tag name does not match ({semVer})."); + Log.Info($"Found existing release ({release.Name} [{release.TagName}]). Merge flag is enabled."); + } else { + var newReleaseReq = new CreateReleaseOption( + body: releaseNotes, + draft: true, + prerelease: options.Prerelease, + name: string.IsNullOrWhiteSpace(options.ReleaseName) ? semVer.ToString() : options.ReleaseName, + targetCommitish: options.TargetCommitish, + tagName: semVer.ToString() + ); + Log.Info($"Creating draft release titled '{newReleaseReq.Name}'"); + release = await apiInstance.RepoCreateReleaseAsync(repoOwner, repoName, newReleaseReq); + } + + // check if there is an existing releasesFile to merge + var releasesFileName = Utility.GetVeloReleaseIndexName(options.Channel); + var releaseAsset = release.Assets.FirstOrDefault(a => a.Name == releasesFileName); + if (releaseAsset != null) { + throw new UserInfoException($"There is already a remote asset named '{releasesFileName}', and merging release files on Gitea is not supported."); + } + + // upload all assets (incl packages) + foreach (var a in build.Files) { + await RetryAsync(() => UploadFileAsAsset(apiInstance, release, repoOwner, repoName, a), $"Uploading asset '{Path.GetFileName(a)}'.."); + } + + var feed = new VelopackAssetFeed { + Assets = build.GetReleaseEntries().ToArray(), + }; + var json = ReleaseEntryHelper.GetAssetFeedJson(feed); + + await RetryAsync(async () => { + await apiInstance.RepoCreateReleaseAttachmentAsync(repoOwner, repoName, release.Id, releasesFileName, new MemoryStream(Encoding.UTF8.GetBytes(json))); + }, "Uploading " + releasesFileName); + + if (options.Channel == ReleaseEntryHelper.GetDefaultChannel(RuntimeOs.Windows)) { + var legacyReleasesContent = ReleaseEntryHelper.GetLegacyMigrationReleaseFeedString(feed); + var legacyReleasesBytes = Encoding.UTF8.GetBytes(legacyReleasesContent); + await RetryAsync(async () => { + await apiInstance.RepoCreateReleaseAttachmentAsync(repoOwner, repoName, release.Id, "RELEASES", new MemoryStream(legacyReleasesBytes)); + }, "Uploading legacy RELEASES (compatibility)"); + } + + // convert draft to full release + if (options.Publish) { + if (release.Draft) { + Log.Info("Converting draft to full published release."); + var body = new EditReleaseOption( + release.Body, + false, // Draft + release.Name, + release.Prerelease, + release.TagName, + release.TargetCommitish + ); + Release result = await apiInstance.RepoEditReleaseAsync(repoOwner, repoName, release.Id, body); + } else { + Log.Info("Skipping publish, release is already not a draft."); + } + } + } + + private async Task UploadFileAsAsset(RepositoryApi client, Release release, string repoOwner, string repoName, string filePath) + { + using var stream = File.OpenRead(filePath); + // Create a release attachment + await client.RepoCreateReleaseAttachmentAsync(repoOwner, repoName, release.Id, Path.GetFileName(filePath), stream); + } +} \ No newline at end of file diff --git a/src/Velopack.Deployment/Velopack.Deployment.csproj b/src/Velopack.Deployment/Velopack.Deployment.csproj index 426291fe..07592e2e 100644 --- a/src/Velopack.Deployment/Velopack.Deployment.csproj +++ b/src/Velopack.Deployment/Velopack.Deployment.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Velopack.Deployment/packages.lock.json b/src/Velopack.Deployment/packages.lock.json index 9f0fdbbe..dc9028ff 100644 --- a/src/Velopack.Deployment/packages.lock.json +++ b/src/Velopack.Deployment/packages.lock.json @@ -21,6 +21,19 @@ "System.Text.Json": "4.7.2" } }, + "Gitea.Net.API": { + "type": "Direct", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "5ZhKnsZfIjJ/aFeZFibi1VY8oOam0rQFf30A5gqxM5F3yoxpr1qHxeks10hvdgerGFMHSXgV8FDvtTHM4OA3Qw==", + "dependencies": { + "JsonSubTypes": "2.0.1", + "Microsoft.AspNetCore.SystemWebAdapters": "1.3.0", + "Newtonsoft.Json": "13.0.3", + "Polly": "8.4.0", + "RestSharp": "111.2.0" + } + }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", @@ -72,16 +85,40 @@ "System.IO.Hashing": "6.0.0" } }, + "JsonSubTypes": { + "type": "Transitive", + "resolved": "2.0.1", + "contentHash": "1Po+Ypf0SjCeEKx5+C89Nb5OgTcqNvfS3uTI46MUM+KEp6Rq/M0h+vVsTUt/6DFRwZMTpsAJM4yJrZmEObVANA==", + "dependencies": { + "Newtonsoft.Json": "13.0.1" + } + }, "Markdig": { "type": "Transitive", "resolved": "0.37.0", "contentHash": "biiu4MTPFjW55qw6v5Aphtj0MjDLJ14x8ndZwkJUHIeqvaSGKeqhLY7S7Vu/S3k7/c9KwhhnaCDP9hdFNUhcNA==" }, + "Microsoft.AspNetCore.SystemWebAdapters": { + "type": "Transitive", + "resolved": "1.3.0", + "contentHash": "1gomLUkk2dfZbznBAPb/HCZzlA+7QNKb08v5GzPh/tqhkUaKZHR8PHh0vmJlZGAj8vnvNLnzpfIwwCdq/9BHWQ==", + "dependencies": { + "System.Runtime.Caching": "6.0.0" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" }, + "Microsoft.Bcl.TimeProvider": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "f5Kr5JepAbiGo7uDmhgvMqhntwxqXNn6/IpTBSSI4cuHhgnJGrLxFRhMjVpRkLPp6zJXO0/G0l3j9p9zSJxa+w==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", @@ -144,11 +181,42 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, "NuGet.Versioning": { "type": "Transitive", "resolved": "6.10.0", "contentHash": "ytTA08tgZWo/Pbk333hnzQfiMyyynkkwMt4GyC5T0bzExYYSRNrkwv4jT8jmWr5LUOYsumEZvXdoven2SA7YZw==" }, + "Polly": { + "type": "Transitive", + "resolved": "8.4.0", + "contentHash": "z2EeUutuy49jBQyZ5s2FUuTCGx3GCzJ0cJ2HbjWwks94TsC6bKTtAHKBkMZOa/DyYRl5yIX7MshvMTWl1J6RNg==", + "dependencies": { + "Polly.Core": "8.4.0" + } + }, + "Polly.Core": { + "type": "Transitive", + "resolved": "8.4.0", + "contentHash": "3AZxuP//pxOeBo9tQs7/tz4Z5TTbu4BYfjpaXZD0JYKJo98ngN9TMUz1nybh4k0ykWkMBpGZALV/AmVIE3ew7A==", + "dependencies": { + "Microsoft.Bcl.TimeProvider": "8.0.0" + } + }, + "RestSharp": { + "type": "Transitive", + "resolved": "111.2.0", + "contentHash": "OkWr6AGM0zirdkRpxVOxXMWx9OHEIWrDzrpx1XIx5KyvTcmtjJVsKBscVkYTcFerhj5w5Bny7boRIV4a0ENGaQ==" + }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.2", @@ -283,6 +351,15 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Security.Permissions": "6.0.0" + } + }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", @@ -311,6 +388,14 @@ "System.Runtime": "4.3.0" } }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "6.0.0" + } + }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", @@ -506,6 +591,14 @@ "Microsoft.NETCore.Targets": "1.1.0" } }, + "System.Runtime.Caching": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "E0e03kUp5X2k+UAoVl6efmI7uU7JRBWi5EIdlQ7cr0NpBGjHG4fWII35PgsBY9T4fJQ8E4QPsL0rKksU9gcL5A==", + "dependencies": { + "System.Configuration.ConfigurationManager": "6.0.0" + } + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", @@ -555,6 +648,11 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" + }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", @@ -669,8 +767,8 @@ }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "wLBKzFnDCxP12VL9ANydSYhk59fC4cvOr9ypYQLPnAj48NQIhqnjdD2yhP8yEKyBJEjERWS9DisKL7rX5eU25Q==" + "resolved": "6.0.0", + "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==" }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", @@ -704,6 +802,15 @@ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", + "dependencies": { + "System.Security.AccessControl": "6.0.0", + "System.Windows.Extensions": "6.0.0" + } + }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", @@ -748,6 +855,14 @@ "resolved": "4.5.4", "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", + "dependencies": { + "System.Drawing.Common": "6.0.0" + } + }, "velopack": { "type": "Project", "dependencies": { diff --git a/src/Velopack.Vpk/Commands/Deployment/GiteaBaseCommand.cs b/src/Velopack.Vpk/Commands/Deployment/GiteaBaseCommand.cs new file mode 100644 index 00000000..7354115f --- /dev/null +++ b/src/Velopack.Vpk/Commands/Deployment/GiteaBaseCommand.cs @@ -0,0 +1,19 @@ +namespace Velopack.Vpk.Commands.Deployment; +public abstract class GiteaBaseCommand : OutputCommand +{ + public string RepoUrl { get; private set; } + + public string Token { get; private set; } + + protected GiteaBaseCommand(string name, string description) + : base(name, description) + { + var repoUrl = AddOption((v) => RepoUrl = v.ToAbsoluteOrNull(), "--repoUrl") + .SetDescription("Full url to the gitea repository (eg. 'https://gitea.com/myname/myrepo').") + .SetRequired() + .MustBeValidHttpUri(); + + AddOption((v) => Token = v, "--token") + .SetDescription("OAuth token to use as login credentials."); + } +} \ No newline at end of file diff --git a/src/Velopack.Vpk/Commands/Deployment/GiteaDownloadCommand.cs b/src/Velopack.Vpk/Commands/Deployment/GiteaDownloadCommand.cs new file mode 100644 index 00000000..c09a50f4 --- /dev/null +++ b/src/Velopack.Vpk/Commands/Deployment/GiteaDownloadCommand.cs @@ -0,0 +1,12 @@ +namespace Velopack.Vpk.Commands.Deployment; +public class GiteaDownloadCommand : GiteaBaseCommand +{ + public bool Prerelease { get; private set; } + + public GiteaDownloadCommand() + : base("gitea", "Download latest release from Gitea repository.") + { + AddOption((v) => Prerelease = v, "--pre") + .SetDescription("Get latest pre-release instead of stable."); + } +} \ No newline at end of file diff --git a/src/Velopack.Vpk/Commands/Deployment/GiteaUploadCommand.cs b/src/Velopack.Vpk/Commands/Deployment/GiteaUploadCommand.cs new file mode 100644 index 00000000..6771b922 --- /dev/null +++ b/src/Velopack.Vpk/Commands/Deployment/GiteaUploadCommand.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Velopack.Vpk.Commands.Deployment; +public class GiteaUploadCommand : GiteaBaseCommand +{ + public bool Publish { get; private set; } + + public string ReleaseName { get; private set; } + + public string TagName { get; private set; } + + public string TargetCommitish { get; private set; } + + public bool Prerelease { get; private set; } + + public bool Merge { get; private set; } + + public GiteaUploadCommand() + : base("gitea", "Upload releases to a Gitea repository.") + { + AddOption((v) => Publish = v, "--publish") + .SetDescription("Create and publish instead of leaving as draft."); + + AddOption((v) => Prerelease = v, "--pre") + .SetDescription("Create as pre-release instead of stable."); + + AddOption((v) => Merge = v, "--merge") + .SetDescription("Allow merging this upload with an existing release."); + + AddOption((v) => ReleaseName = v, "--releaseName") + .SetDescription("A custom name for the release.") + .SetArgumentHelpName("NAME"); + + AddOption((v) => TagName = v, "--tag") + .SetDescription("A custom tag for the release.") + .SetArgumentHelpName("NAME"); + + AddOption((v) => TargetCommitish = v, "--targetCommitish") + .SetDescription("A commitish value for tag (branch or commit SHA).") + .SetArgumentHelpName("NAME"); + + ReleaseDirectoryOption.SetRequired(); + ReleaseDirectoryOption.MustNotBeEmpty(); + } +} \ No newline at end of file diff --git a/src/Velopack.Vpk/OptionMapper.cs b/src/Velopack.Vpk/OptionMapper.cs index 7285eb4c..39b330f8 100644 --- a/src/Velopack.Vpk/OptionMapper.cs +++ b/src/Velopack.Vpk/OptionMapper.cs @@ -22,6 +22,8 @@ public static partial class OptionMapper public static partial OsxBundleOptions ToOptions(this OsxBundleCommand cmd); public static partial GitHubDownloadOptions ToOptions(this GitHubDownloadCommand cmd); public static partial GitHubUploadOptions ToOptions(this GitHubUploadCommand cmd); + public static partial GiteaDownloadOptions ToOptions(this GiteaDownloadCommand cmd); + public static partial GiteaUploadOptions ToOptions(this GiteaUploadCommand cmd); public static partial HttpDownloadOptions ToOptions(this HttpDownloadCommand cmd); public static partial LocalDownloadOptions ToOptions(this LocalDownloadCommand cmd); public static partial LocalUploadOptions ToOptions(this LocalUploadCommand cmd); diff --git a/src/Velopack.Vpk/Program.cs b/src/Velopack.Vpk/Program.cs index 0a39a312..80673293 100644 --- a/src/Velopack.Vpk/Program.cs +++ b/src/Velopack.Vpk/Program.cs @@ -132,6 +132,7 @@ public class Program var downloadCommand = new CliCommand("download", "Download's the latest release from a remote update source."); downloadCommand.AddRepositoryDownload(provider); + downloadCommand.AddRepositoryDownload(provider); downloadCommand.AddRepositoryDownload(provider); downloadCommand.AddRepositoryDownload(provider); downloadCommand.AddRepositoryDownload(provider); @@ -140,6 +141,7 @@ public class Program var uploadCommand = new CliCommand("upload", "Upload local package(s) to a remote update source."); uploadCommand.AddRepositoryUpload(provider); + uploadCommand.AddRepositoryUpload(provider); uploadCommand.AddRepositoryUpload(provider); uploadCommand.AddRepositoryUpload(provider); uploadCommand.AddRepositoryUpload(provider); diff --git a/src/Velopack.Vpk/packages.lock.json b/src/Velopack.Vpk/packages.lock.json index 4743ddac..4812a74d 100644 --- a/src/Velopack.Vpk/packages.lock.json +++ b/src/Velopack.Vpk/packages.lock.json @@ -218,16 +218,52 @@ "resolved": "2.17.3", "contentHash": "BFKpnrs3yKH/jo1yewQdvLKMkEaxE6Wj3Lu5LHRNJfcAdOQ9YiY4dfQ5+5kz6ji1E/Xcxy5ta+qqKGA6KiVQMw==" }, + "Gitea.Net.API": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "5ZhKnsZfIjJ/aFeZFibi1VY8oOam0rQFf30A5gqxM5F3yoxpr1qHxeks10hvdgerGFMHSXgV8FDvtTHM4OA3Qw==", + "dependencies": { + "JsonSubTypes": "2.0.1", + "Microsoft.AspNetCore.SystemWebAdapters": "1.3.0", + "Newtonsoft.Json": "13.0.3", + "Polly": "8.4.0", + "RestSharp": "111.2.0" + } + }, + "JsonSubTypes": { + "type": "Transitive", + "resolved": "2.0.1", + "contentHash": "1Po+Ypf0SjCeEKx5+C89Nb5OgTcqNvfS3uTI46MUM+KEp6Rq/M0h+vVsTUt/6DFRwZMTpsAJM4yJrZmEObVANA==", + "dependencies": { + "Newtonsoft.Json": "13.0.1" + } + }, "Markdig": { "type": "Transitive", "resolved": "0.37.0", "contentHash": "biiu4MTPFjW55qw6v5Aphtj0MjDLJ14x8ndZwkJUHIeqvaSGKeqhLY7S7Vu/S3k7/c9KwhhnaCDP9hdFNUhcNA==" }, + "Microsoft.AspNetCore.SystemWebAdapters": { + "type": "Transitive", + "resolved": "1.3.0", + "contentHash": "1gomLUkk2dfZbznBAPb/HCZzlA+7QNKb08v5GzPh/tqhkUaKZHR8PHh0vmJlZGAj8vnvNLnzpfIwwCdq/9BHWQ==", + "dependencies": { + "System.Runtime.Caching": "6.0.0" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" }, + "Microsoft.Bcl.TimeProvider": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "f5Kr5JepAbiGo7uDmhgvMqhntwxqXNn6/IpTBSSI4cuHhgnJGrLxFRhMjVpRkLPp6zJXO0/G0l3j9p9zSJxa+w==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", @@ -543,6 +579,11 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" + }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", @@ -591,6 +632,27 @@ "resolved": "11.0.1", "contentHash": "fpwF/DxMJyGS+pIXDKQozx0wCIpOfG8VDr10ls+m9Gn9Lz8GslhhApxkKD9JCNO60HRUeMJsedsAS24gCjTD7Q==" }, + "Polly": { + "type": "Transitive", + "resolved": "8.4.0", + "contentHash": "z2EeUutuy49jBQyZ5s2FUuTCGx3GCzJ0cJ2HbjWwks94TsC6bKTtAHKBkMZOa/DyYRl5yIX7MshvMTWl1J6RNg==", + "dependencies": { + "Polly.Core": "8.4.0" + } + }, + "Polly.Core": { + "type": "Transitive", + "resolved": "8.4.0", + "contentHash": "3AZxuP//pxOeBo9tQs7/tz4Z5TTbu4BYfjpaXZD0JYKJo98ngN9TMUz1nybh4k0ykWkMBpGZALV/AmVIE3ew7A==", + "dependencies": { + "Microsoft.Bcl.TimeProvider": "8.0.0" + } + }, + "RestSharp": { + "type": "Transitive", + "resolved": "111.2.0", + "contentHash": "OkWr6AGM0zirdkRpxVOxXMWx9OHEIWrDzrpx1XIx5KyvTcmtjJVsKBscVkYTcFerhj5w5Bny7boRIV4a0ENGaQ==" + }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.2", @@ -753,6 +815,15 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Security.Permissions": "6.0.0" + } + }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", @@ -786,6 +857,14 @@ "System.Runtime": "4.3.0" } }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "6.0.0" + } + }, "System.Formats.Asn1": { "type": "Transitive", "resolved": "6.0.0", @@ -991,6 +1070,14 @@ "Microsoft.NETCore.Targets": "1.1.0" } }, + "System.Runtime.Caching": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "E0e03kUp5X2k+UAoVl6efmI7uU7JRBWi5EIdlQ7cr0NpBGjHG4fWII35PgsBY9T4fJQ8E4QPsL0rKksU9gcL5A==", + "dependencies": { + "System.Configuration.ConfigurationManager": "6.0.0" + } + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", @@ -1040,6 +1127,11 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" + }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", @@ -1162,8 +1254,8 @@ }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "wLBKzFnDCxP12VL9ANydSYhk59fC4cvOr9ypYQLPnAj48NQIhqnjdD2yhP8yEKyBJEjERWS9DisKL7rX5eU25Q==" + "resolved": "6.0.0", + "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==" }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", @@ -1197,6 +1289,15 @@ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", + "dependencies": { + "System.Security.AccessControl": "6.0.0", + "System.Windows.Extensions": "6.0.0" + } + }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", @@ -1256,6 +1357,14 @@ "resolved": "4.5.4", "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", + "dependencies": { + "System.Drawing.Common": "6.0.0" + } + }, "velopack": { "type": "Project", "dependencies": { @@ -1268,6 +1377,7 @@ "dependencies": { "AWSSDK.S3": "[3.7.308.8, )", "Azure.Storage.Blobs": "[12.20.0, )", + "Gitea.Net.API": "[1.0.0, )", "Octokit": "[11.0.1, )", "Velopack.Packaging": "[1.0.0, )" } @@ -1523,11 +1633,39 @@ "resolved": "2.17.3", "contentHash": "BFKpnrs3yKH/jo1yewQdvLKMkEaxE6Wj3Lu5LHRNJfcAdOQ9YiY4dfQ5+5kz6ji1E/Xcxy5ta+qqKGA6KiVQMw==" }, + "Gitea.Net.API": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "5ZhKnsZfIjJ/aFeZFibi1VY8oOam0rQFf30A5gqxM5F3yoxpr1qHxeks10hvdgerGFMHSXgV8FDvtTHM4OA3Qw==", + "dependencies": { + "JsonSubTypes": "2.0.1", + "Microsoft.AspNetCore.SystemWebAdapters": "1.3.0", + "Newtonsoft.Json": "13.0.3", + "Polly": "8.4.0", + "RestSharp": "111.2.0" + } + }, + "JsonSubTypes": { + "type": "Transitive", + "resolved": "2.0.1", + "contentHash": "1Po+Ypf0SjCeEKx5+C89Nb5OgTcqNvfS3uTI46MUM+KEp6Rq/M0h+vVsTUt/6DFRwZMTpsAJM4yJrZmEObVANA==", + "dependencies": { + "Newtonsoft.Json": "13.0.1" + } + }, "Markdig": { "type": "Transitive", "resolved": "0.37.0", "contentHash": "biiu4MTPFjW55qw6v5Aphtj0MjDLJ14x8ndZwkJUHIeqvaSGKeqhLY7S7Vu/S3k7/c9KwhhnaCDP9hdFNUhcNA==" }, + "Microsoft.AspNetCore.SystemWebAdapters": { + "type": "Transitive", + "resolved": "1.3.0", + "contentHash": "1gomLUkk2dfZbznBAPb/HCZzlA+7QNKb08v5GzPh/tqhkUaKZHR8PHh0vmJlZGAj8vnvNLnzpfIwwCdq/9BHWQ==", + "dependencies": { + "System.Runtime.Caching": "6.0.0" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "6.0.0", @@ -1843,6 +1981,11 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" + }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", @@ -1891,6 +2034,24 @@ "resolved": "11.0.1", "contentHash": "fpwF/DxMJyGS+pIXDKQozx0wCIpOfG8VDr10ls+m9Gn9Lz8GslhhApxkKD9JCNO60HRUeMJsedsAS24gCjTD7Q==" }, + "Polly": { + "type": "Transitive", + "resolved": "8.4.0", + "contentHash": "z2EeUutuy49jBQyZ5s2FUuTCGx3GCzJ0cJ2HbjWwks94TsC6bKTtAHKBkMZOa/DyYRl5yIX7MshvMTWl1J6RNg==", + "dependencies": { + "Polly.Core": "8.4.0" + } + }, + "Polly.Core": { + "type": "Transitive", + "resolved": "8.4.0", + "contentHash": "3AZxuP//pxOeBo9tQs7/tz4Z5TTbu4BYfjpaXZD0JYKJo98ngN9TMUz1nybh4k0ykWkMBpGZALV/AmVIE3ew7A==" + }, + "RestSharp": { + "type": "Transitive", + "resolved": "111.2.0", + "contentHash": "OkWr6AGM0zirdkRpxVOxXMWx9OHEIWrDzrpx1XIx5KyvTcmtjJVsKBscVkYTcFerhj5w5Bny7boRIV4a0ENGaQ==" + }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.2", @@ -2053,6 +2214,15 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Security.Permissions": "6.0.0" + } + }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", @@ -2083,6 +2253,14 @@ "System.Runtime": "4.3.0" } }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "6.0.0" + } + }, "System.Formats.Asn1": { "type": "Transitive", "resolved": "6.0.0", @@ -2283,6 +2461,14 @@ "Microsoft.NETCore.Targets": "1.1.0" } }, + "System.Runtime.Caching": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "E0e03kUp5X2k+UAoVl6efmI7uU7JRBWi5EIdlQ7cr0NpBGjHG4fWII35PgsBY9T4fJQ8E4QPsL0rKksU9gcL5A==", + "dependencies": { + "System.Configuration.ConfigurationManager": "6.0.0" + } + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "5.0.0", @@ -2332,6 +2518,11 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" + }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", @@ -2454,8 +2645,8 @@ }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "wLBKzFnDCxP12VL9ANydSYhk59fC4cvOr9ypYQLPnAj48NQIhqnjdD2yhP8yEKyBJEjERWS9DisKL7rX5eU25Q==" + "resolved": "6.0.0", + "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==" }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", @@ -2489,6 +2680,15 @@ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", + "dependencies": { + "System.Security.AccessControl": "6.0.0", + "System.Windows.Extensions": "6.0.0" + } + }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", @@ -2544,6 +2744,14 @@ "resolved": "4.5.4", "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", + "dependencies": { + "System.Drawing.Common": "6.0.0" + } + }, "velopack": { "type": "Project", "dependencies": { @@ -2556,6 +2764,7 @@ "dependencies": { "AWSSDK.S3": "[3.7.308.8, )", "Azure.Storage.Blobs": "[12.20.0, )", + "Gitea.Net.API": "[1.0.0, )", "Octokit": "[11.0.1, )", "Velopack.Packaging": "[1.0.0, )" } diff --git a/src/Velopack/Internal/CompiledJson.cs b/src/Velopack/Internal/CompiledJson.cs index 15bc14b4..ed9d82ad 100644 --- a/src/Velopack/Internal/CompiledJson.cs +++ b/src/Velopack/Internal/CompiledJson.cs @@ -28,6 +28,7 @@ namespace Velopack.Json [JsonSerializable(typeof(List))] [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(List))] [JsonSerializable(typeof(VelopackAssetFeed))] [JsonSerializable(typeof(VelopackFlowReleaseAsset[]))] #if NET8_0_OR_GREATER @@ -60,6 +61,11 @@ namespace Velopack.Json return JsonSerializer.Deserialize(json, Context.ListGithubRelease); } + public static List? DeserializeGiteaReleaseList(string json) + { + return JsonSerializer.Deserialize(json, Context.ListGiteaRelease); + } + public static List? DeserializeGitlabReleaseList(string json) { return JsonSerializer.Deserialize(json, Context.ListGitlabRelease); @@ -119,6 +125,11 @@ namespace Velopack.Json return JsonConvert.DeserializeObject>(json, Options); } + public static List? DeserializeGiteaReleaseList(string json) + { + return JsonConvert.DeserializeObject>(json, Options); + } + public static List? DeserializeGitlabReleaseList(string json) { return JsonConvert.DeserializeObject>(json, Options); diff --git a/src/Velopack/Sources/GiteaSource.cs b/src/Velopack/Sources/GiteaSource.cs new file mode 100644 index 00000000..7bdd629e --- /dev/null +++ b/src/Velopack/Sources/GiteaSource.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Velopack.Json; +namespace Velopack.Sources +{ + /// Describes a Gitea release, including attached assets. + public class GiteaRelease + { + /// The name of this release. + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// True if this release is a prerelease. + [JsonPropertyName("prerelease")] + public bool Prerelease { get; set; } + + /// The date which this release was published publicly. + [JsonPropertyName("published_at")] + public DateTime? PublishedAt { get; set; } + + /// A list of assets (files) uploaded to this release. + [JsonPropertyName("assets")] + public GiteaReleaseAsset[] Assets { get; set; } = new GiteaReleaseAsset[0]; + } + /// Describes a asset (file) uploaded to a Gitea release. + public class GiteaReleaseAsset + { + /// + /// The asset URL for this release asset. Requests to this URL will use API + /// quota and return JSON unless the 'Accept' header is "application/octet-stream". + /// + [JsonPropertyName("url")] + public string? Url { get; set; } + + /// + /// The browser URL for this release asset. This does not use API quota, + /// however this URL only works for public repositories. If downloading + /// assets from a private repository, the property must + /// be used with an appropriate access token. + /// + [JsonPropertyName("browser_download_url")] + public string? BrowserDownloadUrl { get; set; } + + /// The name of this release asset. + [JsonPropertyName("name")] + public string? Name { get; set; } + } + + /// + /// Retrieves available releases from a Gitea repository. + /// + public class GiteaSource : GitBase + { + /// + /// + /// The URL of the Gitea repository to download releases from + /// (e.g. https://Gitea.com/myuser/myrepo) + /// + /// + /// The Gitea access token to use with the request to download releases. + /// If left empty, the Gitea rate limit for unauthenticated requests allows + /// for up to 60 requests per hour, limited by IP address. + /// + /// + /// If true, pre-releases will be also be searched / downloaded. If false, only + /// stable releases will be considered. + /// + /// + /// The file downloader used to perform HTTP requests. + /// + public GiteaSource(string repoUrl, string? accessToken, bool prerelease, IFileDownloader? downloader = null) + : base(repoUrl, accessToken, prerelease, downloader) + { + } + /// + /// The authorization token used in the request. + /// Overwrite it to token + /// + protected override string? Authorization => string.IsNullOrWhiteSpace(AccessToken) ? null : "token " + AccessToken; + /// + protected override async Task GetReleases(bool includePrereleases) + { + // https://gitea.com/api/swagger#/repository/repoListReleases + // https://docs.gitea.com/api/1.22/#tag/repository/operation/repoListReleases + + const int perPage = 10; + const int page = 1; + var releasesPath = $"repos{RepoUri.AbsolutePath}/releases?limit={perPage}&page={page}&draft=false"; + var baseUri = GetApiBaseUrl(RepoUri); + var getReleasesUri = new Uri(baseUri, releasesPath); + var response = await Downloader.DownloadString(getReleasesUri.ToString(), Authorization, "application/json").ConfigureAwait(false); + var releases = CompiledJson.DeserializeGiteaReleaseList(response); + if (releases == null) return new GiteaRelease[0]; + return releases.OrderByDescending(d => d.PublishedAt).Where(x => includePrereleases || !x.Prerelease).ToArray(); + } + + /// + protected override string GetAssetUrlFromName(GiteaRelease release, string assetName) + { + if (release.Assets == null || release.Assets.Count() == 0) { + throw new ArgumentException($"No assets found in Gitea Release '{release.Name}'."); + } + + IEnumerable allReleasesFiles = release.Assets.Where(a => a.Name?.Equals(assetName, StringComparison.InvariantCultureIgnoreCase) == true); + if (allReleasesFiles == null || allReleasesFiles.Count() == 0) { + throw new ArgumentException($"Could not find asset called '{assetName}' in Gitea Release '{release.Name}'."); + } + + var asset = allReleasesFiles.First(); + + if (asset.BrowserDownloadUrl != null) { + return asset.BrowserDownloadUrl; + } else { + throw new ArgumentException("Could not find a valid asset url for the specified asset."); + } + } + + /// + /// Given a repository URL (e.g. https://Gitea.com/myuser/myrepo) this function + /// returns the API base for performing requests. (eg. "https://api.Gitea.com/" + /// or http://internal.Gitea.server.local/api/v1) + /// + /// + /// + protected virtual Uri GetApiBaseUrl(Uri repoUrl) + { + Uri baseAddress = new Uri(string.Format("{0}{1}{2}/api/v1/", repoUrl.Scheme, Uri.SchemeDelimiter, repoUrl.Host)); + // above ^^ notice the end slashes for the baseAddress, explained here: http://stackoverflow.com/a/23438417/162694 + return baseAddress; + } + } +} \ No newline at end of file diff --git a/src/Velopack/packages.lock.json b/src/Velopack/packages.lock.json index f0e61172..71dddc57 100644 --- a/src/Velopack/packages.lock.json +++ b/src/Velopack/packages.lock.json @@ -8,15 +8,6 @@ "resolved": "2.2.0", "contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A==" }, - "Microsoft.NETFramework.ReferenceAssemblies": { - "type": "Direct", - "requested": "[1.0.3, )", - "resolved": "1.0.3", - "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", - "dependencies": { - "Microsoft.NETFramework.ReferenceAssemblies.net462": "1.0.3" - } - }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", @@ -50,11 +41,6 @@ "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, - "Microsoft.NETFramework.ReferenceAssemblies.net462": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "IzAV30z22ESCeQfxP29oVf4qEo8fBGXLXSU6oacv/9Iqe6PzgHDKCaWfwMBak7bSJQM0F5boXWoZS+kChztRIQ==" - }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -159,9 +145,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.1, )", - "resolved": "8.0.1", - "contentHash": "ADdJXuKNjwZDfBmybMnpvwd5CK3gp92WkWqqeQhW4W+q4MO3Qaa9QyW2DcFLAvCDMcCWxT5hRXqGdv13oon7nA==" + "requested": "[8.0.6, )", + "resolved": "8.0.6", + "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/test/Velopack.Tests/UpdateManagerTests.cs b/test/Velopack.Tests/UpdateManagerTests.cs index 576f926e..0396d3d4 100644 --- a/test/Velopack.Tests/UpdateManagerTests.cs +++ b/test/Velopack.Tests/UpdateManagerTests.cs @@ -389,7 +389,6 @@ public class UpdateManagerTests Assert.True(new SemanticVersion(1, 0, 1) == info.TargetFullRelease.Version); Assert.Equal(0, info.DeltasToTarget.Count()); } - [Fact(Skip = "Consumes API Quota")] public void CheckGithubWithNonExistingChannel() { @@ -402,7 +401,20 @@ public class UpdateManagerTests var um = new UpdateManager(source, opt, logger, locator); Assert.Throws(() => um.CheckForUpdates()); } - + [Fact(Skip = "Consumes API Quota")] + public void CheckGitea() + { + // https://github.com/caesay/SquirrelCustomLauncherTestApp + using var logger = _output.BuildLoggerFor(); + using var _1 = Utility.GetTempDirectory(out var tempPath); + var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger); + var source = new GiteaSource("https://gitea.com/remco1271/VeloPackTest", null, false); + var um = new UpdateManager(source, null, logger, locator); + var info = um.CheckForUpdates(); + Assert.NotNull(info); + Assert.True(new SemanticVersion(1, 0, 1) == info.TargetFullRelease.Version); + Assert.Equal(0, info.DeltasToTarget.Count()); + } [Fact] public void CheckFromEmptyFileSource() {