mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
changed.修改了PLONDS上传逻辑
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
namespace Plonds.Core.Publishing;
|
||||
|
||||
public sealed record PlondsPublishOptions(
|
||||
string ReleaseTag,
|
||||
string Repository,
|
||||
string ManifestPath,
|
||||
string ChangedZipPath,
|
||||
string WorkDir,
|
||||
string S3KeyPrefix,
|
||||
PlondsS3ClientOptions S3);
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Plonds.Core.Publishing;
|
||||
|
||||
public sealed record PlondsPublishResult(
|
||||
string ReleaseTag,
|
||||
string Version,
|
||||
string VersionPrefix,
|
||||
string ManifestKey,
|
||||
string ManifestUrl,
|
||||
string ChangedZipKey,
|
||||
string ChangedZipUrl,
|
||||
string ChangedFolderKey,
|
||||
string ChangedFolderUrl,
|
||||
int ChangedFileCount);
|
||||
@@ -0,0 +1,141 @@
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Plonds.Shared.Models;
|
||||
|
||||
namespace Plonds.Core.Publishing;
|
||||
|
||||
public sealed class PlondsPublisher
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public async Task<PlondsPublishResult> PublishAsync(PlondsPublishOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var releaseTag = Require(options.ReleaseTag, nameof(options.ReleaseTag));
|
||||
var repository = Require(options.Repository, nameof(options.Repository));
|
||||
var manifestPath = Path.GetFullPath(Require(options.ManifestPath, nameof(options.ManifestPath)));
|
||||
var changedZipPath = Path.GetFullPath(Require(options.ChangedZipPath, nameof(options.ChangedZipPath)));
|
||||
var workDir = Path.GetFullPath(Require(options.WorkDir, nameof(options.WorkDir)));
|
||||
var version = releaseTag.TrimStart('v', 'V');
|
||||
var prefix = NormalizePrefix(options.S3KeyPrefix);
|
||||
var versionPrefix = $"{prefix}/{version}";
|
||||
var changedFolderName = $"{version}-changed";
|
||||
var changedExtractRoot = Path.Combine(workDir, changedFolderName);
|
||||
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
throw new FileNotFoundException("PLONDS manifest not found.", manifestPath);
|
||||
}
|
||||
|
||||
if (!File.Exists(changedZipPath))
|
||||
{
|
||||
throw new FileNotFoundException("PLONDS changed.zip not found.", changedZipPath);
|
||||
}
|
||||
|
||||
var manifest = LoadManifest(manifestPath);
|
||||
PayloadUtilities.EnsureCleanDirectory(changedExtractRoot);
|
||||
ZipFile.ExtractToDirectory(changedZipPath, changedExtractRoot, overwriteFiles: true);
|
||||
|
||||
var manifestKey = $"{versionPrefix}/PLONDS.json";
|
||||
var changedZipKey = $"{versionPrefix}/changed.zip";
|
||||
var changedFolderKey = $"{versionPrefix}/{changedFolderName}";
|
||||
|
||||
using var s3 = new PlondsS3Client(options.S3);
|
||||
|
||||
var changedFileCount = 0;
|
||||
foreach (var filePath in Directory.EnumerateFiles(changedExtractRoot, "*", SearchOption.AllDirectories).OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var relativePath = PayloadUtilities.NormalizeRelativePath(Path.GetRelativePath(changedExtractRoot, filePath));
|
||||
var objectKey = $"{changedFolderKey}/{relativePath}";
|
||||
await s3.UploadFileAsync(new PlondsS3ObjectUpload(filePath, objectKey, ResolveContentType(filePath)), cancellationToken).ConfigureAwait(false);
|
||||
changedFileCount++;
|
||||
}
|
||||
|
||||
await s3.UploadFileAsync(new PlondsS3ObjectUpload(changedZipPath, changedZipKey, "application/zip"), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var updatedManifest = manifest with
|
||||
{
|
||||
Downloads = new PlondsDownloadInfo(
|
||||
ReleaseTag: releaseTag,
|
||||
GitHub: new PlondsGitHubDownloadInfo(
|
||||
ReleaseUrl: $"https://github.com/{repository}/releases/tag/{releaseTag}",
|
||||
ManifestUrl: $"https://github.com/{repository}/releases/download/{releaseTag}/PLONDS.json",
|
||||
ChangedZipUrl: $"https://github.com/{repository}/releases/download/{releaseTag}/changed.zip"),
|
||||
S3: new PlondsS3DownloadInfo(
|
||||
Bucket: options.S3.Bucket,
|
||||
Prefix: versionPrefix,
|
||||
ManifestKey: manifestKey,
|
||||
ManifestUrl: s3.BuildPublicUrl(manifestKey),
|
||||
ChangedZipKey: changedZipKey,
|
||||
ChangedZipUrl: s3.BuildPublicUrl(changedZipKey),
|
||||
ChangedFolderKey: changedFolderKey,
|
||||
ChangedFolderUrl: s3.BuildPublicUrl(changedFolderKey)))
|
||||
};
|
||||
|
||||
File.WriteAllText(manifestPath, JsonSerializer.Serialize(updatedManifest, JsonOptions), new UTF8Encoding(false));
|
||||
await s3.UploadFileAsync(new PlondsS3ObjectUpload(manifestPath, manifestKey, "application/json"), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await s3.EnsureObjectExistsAsync(manifestKey, cancellationToken).ConfigureAwait(false);
|
||||
await s3.EnsureObjectExistsAsync(changedZipKey, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new PlondsPublishResult(
|
||||
ReleaseTag: releaseTag,
|
||||
Version: version,
|
||||
VersionPrefix: versionPrefix,
|
||||
ManifestKey: manifestKey,
|
||||
ManifestUrl: s3.BuildPublicUrl(manifestKey),
|
||||
ChangedZipKey: changedZipKey,
|
||||
ChangedZipUrl: s3.BuildPublicUrl(changedZipKey),
|
||||
ChangedFolderKey: changedFolderKey,
|
||||
ChangedFolderUrl: s3.BuildPublicUrl(changedFolderKey),
|
||||
ChangedFileCount: changedFileCount);
|
||||
}
|
||||
|
||||
private static PlondsManifest LoadManifest(string manifestPath)
|
||||
{
|
||||
var json = File.ReadAllText(manifestPath);
|
||||
return JsonSerializer.Deserialize<PlondsManifest>(json, JsonOptions)
|
||||
?? throw new InvalidOperationException("PLONDS manifest is empty or invalid.");
|
||||
}
|
||||
|
||||
private static string NormalizePrefix(string value)
|
||||
{
|
||||
var normalized = Require(value, nameof(value)).Replace('\\', '/').Trim('/');
|
||||
if (normalized.Contains("..", StringComparison.Ordinal))
|
||||
{
|
||||
throw new ArgumentException($"Invalid S3 key prefix: {value}", nameof(value));
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static string ResolveContentType(string path)
|
||||
{
|
||||
return Path.GetExtension(path).ToLowerInvariant() switch
|
||||
{
|
||||
".json" => "application/json",
|
||||
".zip" => "application/zip",
|
||||
".dll" => "application/octet-stream",
|
||||
".exe" => "application/octet-stream",
|
||||
".pdb" => "application/octet-stream",
|
||||
".deps" => "application/json",
|
||||
".runtimeconfig" => "application/json",
|
||||
".txt" => "text/plain",
|
||||
".xml" => "application/xml",
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
}
|
||||
|
||||
private static string Require(string value, string name)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value)
|
||||
? throw new ArgumentException($"{name} is required.", name)
|
||||
: value.Trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Plonds.Core.Publishing;
|
||||
|
||||
public sealed class PlondsS3Client : IDisposable
|
||||
{
|
||||
private const string ServiceName = "s3";
|
||||
private const string EmptyPayloadHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
||||
|
||||
private readonly PlondsS3ClientOptions options;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly bool ownsHttpClient;
|
||||
|
||||
public PlondsS3Client(PlondsS3ClientOptions options, HttpClient? httpClient = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
this.options = options with
|
||||
{
|
||||
Endpoint = NormalizeEndpoint(options.Endpoint),
|
||||
Region = Require(options.Region, nameof(options.Region)),
|
||||
Bucket = Require(options.Bucket, nameof(options.Bucket)),
|
||||
AccessKey = Require(options.AccessKey, nameof(options.AccessKey)),
|
||||
SecretKey = Require(options.SecretKey, nameof(options.SecretKey)),
|
||||
PublicBaseUrl = Require(options.PublicBaseUrl, nameof(options.PublicBaseUrl)).TrimEnd('/'),
|
||||
PublicBaseKeyPrefix = NormalizeOptionalKeyPrefix(options.PublicBaseKeyPrefix)
|
||||
};
|
||||
|
||||
this.httpClient = httpClient ?? new HttpClient();
|
||||
ownsHttpClient = httpClient is null;
|
||||
}
|
||||
|
||||
public async Task UploadFileAsync(PlondsS3ObjectUpload upload, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(upload);
|
||||
|
||||
var sourcePath = Path.GetFullPath(upload.SourcePath);
|
||||
if (!File.Exists(sourcePath))
|
||||
{
|
||||
throw new FileNotFoundException("S3 upload source file not found.", sourcePath);
|
||||
}
|
||||
|
||||
var key = NormalizeKey(upload.Key);
|
||||
var payloadHash = PayloadUtilities.ComputeSha256(sourcePath);
|
||||
var contentLength = new FileInfo(sourcePath).Length;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var requestUri = BuildObjectUri(key);
|
||||
|
||||
using var content = new StreamContent(File.OpenRead(sourcePath));
|
||||
content.Headers.ContentType = new MediaTypeHeaderValue(string.IsNullOrWhiteSpace(upload.ContentType)
|
||||
? "application/octet-stream"
|
||||
: upload.ContentType);
|
||||
content.Headers.ContentLength = contentLength;
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Put, requestUri)
|
||||
{
|
||||
Content = content
|
||||
};
|
||||
|
||||
SignRequest(request, key, payloadHash, now);
|
||||
|
||||
using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
throw new InvalidOperationException($"S3 upload failed for {key}: HTTP {(int)response.StatusCode} {response.ReasonPhrase}. {Truncate(body, 512)}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EnsureObjectExistsAsync(string key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var normalizedKey = NormalizeKey(key);
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var requestUri = BuildObjectUri(normalizedKey);
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Head, requestUri);
|
||||
SignRequest(request, normalizedKey, EmptyPayloadHash, now);
|
||||
|
||||
using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new InvalidOperationException($"S3 object verification failed for {normalizedKey}: HTTP {(int)response.StatusCode} {response.ReasonPhrase}.");
|
||||
}
|
||||
}
|
||||
|
||||
public string BuildPublicUrl(string key)
|
||||
{
|
||||
var normalizedKey = NormalizeKey(key);
|
||||
if (!string.IsNullOrWhiteSpace(options.PublicBaseKeyPrefix) &&
|
||||
(string.Equals(normalizedKey, options.PublicBaseKeyPrefix, StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizedKey.StartsWith($"{options.PublicBaseKeyPrefix}/", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
normalizedKey = normalizedKey[options.PublicBaseKeyPrefix.Length..].TrimStart('/');
|
||||
}
|
||||
|
||||
return $"{options.PublicBaseUrl}/{normalizedKey}";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (ownsHttpClient)
|
||||
{
|
||||
httpClient.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void SignRequest(HttpRequestMessage request, string key, string payloadHash, DateTimeOffset now)
|
||||
{
|
||||
var amzDate = now.UtcDateTime.ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture);
|
||||
var dateStamp = now.UtcDateTime.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
|
||||
var credentialScope = $"{dateStamp}/{options.Region}/{ServiceName}/aws4_request";
|
||||
var canonicalUri = BuildCanonicalUri(key);
|
||||
var host = request.RequestUri?.IsDefaultPort == true
|
||||
? request.RequestUri.Host
|
||||
: request.RequestUri?.Authority;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
{
|
||||
throw new InvalidOperationException("Cannot sign an S3 request without a host.");
|
||||
}
|
||||
|
||||
request.Headers.Host = host;
|
||||
request.Headers.TryAddWithoutValidation("x-amz-date", amzDate);
|
||||
request.Headers.TryAddWithoutValidation("x-amz-content-sha256", payloadHash);
|
||||
|
||||
var canonicalHeaders = new StringBuilder();
|
||||
canonicalHeaders.Append("host:").Append(host).Append('\n');
|
||||
canonicalHeaders.Append("x-amz-content-sha256:").Append(payloadHash).Append('\n');
|
||||
canonicalHeaders.Append("x-amz-date:").Append(amzDate).Append('\n');
|
||||
|
||||
var signedHeaders = "host;x-amz-content-sha256;x-amz-date";
|
||||
var canonicalRequest = string.Join('\n',
|
||||
[
|
||||
request.Method.Method,
|
||||
canonicalUri,
|
||||
string.Empty,
|
||||
canonicalHeaders.ToString(),
|
||||
signedHeaders,
|
||||
payloadHash
|
||||
]);
|
||||
|
||||
var stringToSign = string.Join('\n',
|
||||
[
|
||||
"AWS4-HMAC-SHA256",
|
||||
amzDate,
|
||||
credentialScope,
|
||||
Sha256Hex(canonicalRequest)
|
||||
]);
|
||||
|
||||
var signingKey = GetSignatureKey(options.SecretKey, dateStamp, options.Region, ServiceName);
|
||||
var signature = HmacSha256Hex(signingKey, stringToSign);
|
||||
var authorization = $"AWS4-HMAC-SHA256 Credential={options.AccessKey}/{credentialScope}, SignedHeaders={signedHeaders}, Signature={signature}";
|
||||
request.Headers.TryAddWithoutValidation("Authorization", authorization);
|
||||
}
|
||||
|
||||
private Uri BuildObjectUri(string key)
|
||||
{
|
||||
var bucketPrefix = Uri.EscapeDataString(options.Bucket).Replace("%2F", "/", StringComparison.OrdinalIgnoreCase);
|
||||
var path = $"{options.Endpoint.AbsolutePath.TrimEnd('/')}/{bucketPrefix}/{BuildCanonicalKey(key)}";
|
||||
var builder = new UriBuilder(options.Endpoint)
|
||||
{
|
||||
Path = path
|
||||
};
|
||||
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
private string BuildCanonicalUri(string key)
|
||||
{
|
||||
var bucketPrefix = Uri.EscapeDataString(options.Bucket).Replace("%2F", "/", StringComparison.OrdinalIgnoreCase);
|
||||
return $"{options.Endpoint.AbsolutePath.TrimEnd('/')}/{bucketPrefix}/{BuildCanonicalKey(key)}";
|
||||
}
|
||||
|
||||
private static string BuildCanonicalKey(string key)
|
||||
{
|
||||
return string.Join("/", NormalizeKey(key)
|
||||
.Split('/', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(Uri.EscapeDataString));
|
||||
}
|
||||
|
||||
private static string NormalizeKey(string value)
|
||||
{
|
||||
var normalized = value.Replace('\\', '/').Trim('/');
|
||||
if (string.IsNullOrWhiteSpace(normalized) || normalized.Contains("..", StringComparison.Ordinal))
|
||||
{
|
||||
throw new ArgumentException($"Invalid S3 object key: {value}", nameof(value));
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static string NormalizeOptionalKeyPrefix(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return NormalizeKey(value);
|
||||
}
|
||||
|
||||
private static Uri NormalizeEndpoint(Uri endpoint)
|
||||
{
|
||||
if (!endpoint.IsAbsoluteUri)
|
||||
{
|
||||
throw new ArgumentException("S3 endpoint must be an absolute URI.", nameof(endpoint));
|
||||
}
|
||||
|
||||
var builder = new UriBuilder(endpoint)
|
||||
{
|
||||
Path = endpoint.AbsolutePath.TrimEnd('/')
|
||||
};
|
||||
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
private static string Require(string value, string name)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value)
|
||||
? throw new ArgumentException($"{name} is required.", name)
|
||||
: value.Trim();
|
||||
}
|
||||
|
||||
private static string Sha256Hex(string value)
|
||||
{
|
||||
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(value))).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static byte[] HmacSha256(byte[] key, string data)
|
||||
{
|
||||
return HMACSHA256.HashData(key, Encoding.UTF8.GetBytes(data));
|
||||
}
|
||||
|
||||
private static string HmacSha256Hex(byte[] key, string data)
|
||||
{
|
||||
return Convert.ToHexString(HmacSha256(key, data)).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static byte[] GetSignatureKey(string key, string dateStamp, string regionName, string serviceName)
|
||||
{
|
||||
var kDate = HmacSha256(Encoding.UTF8.GetBytes($"AWS4{key}"), dateStamp);
|
||||
var kRegion = HmacSha256(kDate, regionName);
|
||||
var kService = HmacSha256(kRegion, serviceName);
|
||||
return HmacSha256(kService, "aws4_request");
|
||||
}
|
||||
|
||||
private static string Truncate(string value, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value) || value.Length <= maxLength)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return value[..maxLength];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Plonds.Core.Publishing;
|
||||
|
||||
public sealed record PlondsS3ClientOptions(
|
||||
Uri Endpoint,
|
||||
string Region,
|
||||
string Bucket,
|
||||
string AccessKey,
|
||||
string SecretKey,
|
||||
string PublicBaseUrl,
|
||||
string PublicBaseKeyPrefix = "");
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Plonds.Core.Publishing;
|
||||
|
||||
public sealed record PlondsS3ObjectUpload(
|
||||
string SourcePath,
|
||||
string Key,
|
||||
string ContentType);
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Plonds.Shared.Models;
|
||||
|
||||
public sealed record PlondsDownloadInfo(
|
||||
string ReleaseTag,
|
||||
[property: JsonPropertyName("github")]
|
||||
PlondsGitHubDownloadInfo GitHub,
|
||||
PlondsS3DownloadInfo S3);
|
||||
|
||||
public sealed record PlondsGitHubDownloadInfo(
|
||||
string ReleaseUrl,
|
||||
string ManifestUrl,
|
||||
string ChangedZipUrl);
|
||||
|
||||
public sealed record PlondsS3DownloadInfo(
|
||||
string Bucket,
|
||||
string Prefix,
|
||||
string ManifestKey,
|
||||
string ManifestUrl,
|
||||
string ChangedZipKey,
|
||||
string ChangedZipUrl,
|
||||
string ChangedFolderKey,
|
||||
string ChangedFolderUrl);
|
||||
@@ -11,4 +11,5 @@ public sealed record PlondsManifest(
|
||||
DateTimeOffset UpdatedAt,
|
||||
IReadOnlyDictionary<string, PlondsFileEntry> FilesMap,
|
||||
IReadOnlyDictionary<string, PlondsChangedFileEntry> ChangedFilesMap,
|
||||
IReadOnlyDictionary<string, string> Checksums);
|
||||
IReadOnlyDictionary<string, string> Checksums,
|
||||
PlondsDownloadInfo? Downloads = null);
|
||||
|
||||
@@ -4,12 +4,12 @@ return await PlondsCli.RunAsync(args);
|
||||
|
||||
internal static class PlondsCli
|
||||
{
|
||||
public static Task<int> RunAsync(string[] args)
|
||||
public static async Task<int> RunAsync(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
PrintUsage();
|
||||
return Task.FromResult(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
var command = args[0].Trim().ToLowerInvariant();
|
||||
@@ -21,23 +21,25 @@ internal static class PlondsCli
|
||||
{
|
||||
case "build-delta":
|
||||
RunBuildDelta(options);
|
||||
return Task.FromResult(0);
|
||||
return 0;
|
||||
case "build-delta-from-commits":
|
||||
RunBuildDeltaFromCommits(options);
|
||||
return Task.FromResult(0);
|
||||
return 0;
|
||||
case "publish-s3":
|
||||
return await RunPublishS3Async(options).ConfigureAwait(false);
|
||||
case "pack-payload":
|
||||
RunPackPayload(options);
|
||||
return Task.FromResult(0);
|
||||
return 0;
|
||||
default:
|
||||
Console.Error.WriteLine($"Unknown command: {command}");
|
||||
PrintUsage();
|
||||
return Task.FromResult(1);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine(ex.Message);
|
||||
return Task.FromResult(1);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +95,34 @@ internal static class PlondsCli
|
||||
Console.WriteLine(outputZip);
|
||||
}
|
||||
|
||||
private static async Task<int> RunPublishS3Async(Dictionary<string, string> options)
|
||||
{
|
||||
var publisher = new PlondsPublisher();
|
||||
var result = await publisher.PublishAsync(new PlondsPublishOptions(
|
||||
ReleaseTag: Require(options, "release-tag"),
|
||||
Repository: Require(options, "repository"),
|
||||
ManifestPath: Require(options, "manifest"),
|
||||
ChangedZipPath: Require(options, "changed-zip"),
|
||||
WorkDir: Get(options, "work-dir", "plonds-publish-work") ?? "plonds-publish-work",
|
||||
S3KeyPrefix: Get(options, "s3-prefix", "lanmountain/update/plonds") ?? "lanmountain/update/plonds",
|
||||
S3: new PlondsS3ClientOptions(
|
||||
Endpoint: new Uri(Require(options, "s3-endpoint"), UriKind.Absolute),
|
||||
Region: Get(options, "s3-region", "us-east-1") ?? "us-east-1",
|
||||
Bucket: Require(options, "s3-bucket"),
|
||||
AccessKey: Require(options, "s3-access-key"),
|
||||
SecretKey: Require(options, "s3-secret-key"),
|
||||
PublicBaseUrl: Require(options, "s3-public-base-url"),
|
||||
PublicBaseKeyPrefix: Get(options, "s3-public-base-key-prefix", string.Empty) ?? string.Empty))).ConfigureAwait(false);
|
||||
|
||||
Console.WriteLine($"Published PLONDS release {result.ReleaseTag}:");
|
||||
Console.WriteLine($" Prefix: {result.VersionPrefix}");
|
||||
Console.WriteLine($" Manifest: {result.ManifestUrl}");
|
||||
Console.WriteLine($" ChangedZip: {result.ChangedZipUrl}");
|
||||
Console.WriteLine($" ChangedFolder: {result.ChangedFolderUrl}");
|
||||
Console.WriteLine($" ChangedFileCount: {result.ChangedFileCount}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> ParseOptions(string[] args)
|
||||
{
|
||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -163,5 +193,20 @@ internal static class PlondsCli
|
||||
Console.WriteLine(" pack-payload Pack a directory into a payload zip");
|
||||
Console.WriteLine(" --source-dir <dir> Source directory");
|
||||
Console.WriteLine(" --output-zip <file> Output zip path");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(" publish-s3 Publish PLONDS.json and changed.zip to S3");
|
||||
Console.WriteLine(" --release-tag <tag> GitHub release tag");
|
||||
Console.WriteLine(" --repository <owner/repo> GitHub repository");
|
||||
Console.WriteLine(" --manifest <file> PLONDS.json path");
|
||||
Console.WriteLine(" --changed-zip <file> changed.zip path");
|
||||
Console.WriteLine(" --s3-endpoint <url> S3-compatible endpoint");
|
||||
Console.WriteLine(" --s3-region <region> S3 signing region");
|
||||
Console.WriteLine(" --s3-bucket <bucket> S3 bucket");
|
||||
Console.WriteLine(" --s3-access-key <key> S3 access key");
|
||||
Console.WriteLine(" --s3-secret-key <key> S3 secret key");
|
||||
Console.WriteLine(" --s3-public-base-url <url> Public URL prefix for uploaded keys");
|
||||
Console.WriteLine(" [--s3-public-base-key-prefix <prefix>] Key prefix already represented by public URL");
|
||||
Console.WriteLine(" [--s3-prefix <prefix>] Object key prefix (default: lanmountain/update/plonds)");
|
||||
Console.WriteLine(" [--work-dir <dir>] Temporary publish work directory");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user