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()
{