diff --git a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs
index 8c97bf40..2f6dbbca 100644
--- a/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs
+++ b/Source/Bake.Tests/ExplicitTests/GitHubReleaseCookTests.cs
@@ -25,7 +25,6 @@
using Bake.Services;
using Bake.Tests.Helpers;
using Bake.ValueObjects;
-using Bake.ValueObjects.Artifacts;
using Bake.ValueObjects.Recipes.GitHub;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -44,21 +43,14 @@ public async Task CreateRelease()
{
// Arrange
var recipe = new GitHubReleaseRecipe(
+ string.Empty,
new GitHubInformation(
"rasmus",
"testtest",
new Uri("https://github.com/rasmus/testtest"),
new Uri("https://api.guthub.com/")),
- SemVer.Random,
"a108d8a38b4ac154172cb7eeea8530e316ead798",
- new ReleaseNotes(SemVer.Random, "This is a test"),
- new Artifact[]
- {
- new ExecutableArtifact(
- "test_linux",
- Path.Combine(WorkingDirectory, "README.md"),
- new Platform(ExecutableOperatingSystem.Linux, ExecutableArchitecture.Intel64))
- });
+ []);
// Arrange
var result = await Sut.CookAsync(
diff --git a/Source/Bake.Tests/Files/nuget-config.xml b/Source/Bake.Tests/Files/nuget-config.xml
new file mode 100644
index 00000000..7e956965
--- /dev/null
+++ b/Source/Bake.Tests/Files/nuget-config.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Source/Bake.Tests/Helpers/BakeTest.cs b/Source/Bake.Tests/Helpers/BakeTest.cs
index 19ef2648..a025a8a5 100644
--- a/Source/Bake.Tests/Helpers/BakeTest.cs
+++ b/Source/Bake.Tests/Helpers/BakeTest.cs
@@ -33,11 +33,8 @@ namespace Bake.Tests.Helpers
{
public abstract class BakeTest : TestProject
{
- private CancellationTokenSource? _timeout;
-
- private List _releases = null!;
- protected IReadOnlyCollection Releases => _releases;
- protected CancellationToken Timeout => _timeout!.Token;
+ private List _releases = null!;
+ protected IReadOnlyCollection Releases => _releases;
protected BakeTest(string projectName) : base(projectName)
{
@@ -46,15 +43,7 @@ protected BakeTest(string projectName) : base(projectName)
[SetUp]
public void SetUpBakeTest()
{
- _timeout = new CancellationTokenSource(TimeSpan.FromMinutes(5));
- _releases = new List();
- }
-
- [TearDown]
- public void TearDownBakeTest()
- {
- _timeout?.Dispose();
- _timeout = null;
+ _releases = new List();
}
protected Task ExecuteAsync(
@@ -107,20 +96,20 @@ private static void ReplaceService(IServiceCollection serviceCollection, T in
private class TestGitHub : IGitHub
{
- private readonly List _releases;
+ private readonly List _releases;
public TestGitHub(
- List releases)
+ List releases)
{
_releases = releases;
}
public Task CreateReleaseAsync(
- Release release,
+ GitHubRelease gitHubRelease,
GitHubInformation gitHubInformation,
CancellationToken cancellationToken)
{
- _releases.Add(release);
+ _releases.Add(gitHubRelease);
return Task.CompletedTask;
}
diff --git a/Source/Bake.Tests/Helpers/TestFor.cs b/Source/Bake.Tests/Helpers/TestFor.cs
index 8be8706c..a02cc136 100644
--- a/Source/Bake.Tests/Helpers/TestFor.cs
+++ b/Source/Bake.Tests/Helpers/TestFor.cs
@@ -32,10 +32,11 @@ namespace Bake.Tests.Helpers
{
public class TestFor : TestIt
{
+ protected IServiceProvider ServiceProvider { get; private set; } = null!;
+ protected T Sut => _lazySut.Value;
+
private Lazy _lazySut = null!;
- private ServiceProvider _serviceProvider = null!;
private Logger _logger = null!;
- protected T Sut => _lazySut.Value;
[SetUp]
public void SetUpTestFor()
@@ -45,18 +46,18 @@ public void SetUpTestFor()
.MinimumLevel.Verbose()
.WriteTo.Sink(new LogSink(A()))
.CreateLogger();
- _serviceProvider = Configure(new ServiceCollection())
+ ServiceProvider = Configure(new ServiceCollection())
.AddLogging(b => b.AddSerilog(_logger))
.BuildServiceProvider();
- Inject(_serviceProvider.GetRequiredService>());
- Inject(_serviceProvider);
+ Inject(ServiceProvider.GetRequiredService>());
+ Inject(ServiceProvider);
}
[TearDown]
public void TearDownTestFor()
{
- _serviceProvider.Dispose();
+ ((IDisposable)ServiceProvider).Dispose();
_logger.Dispose();
}
diff --git a/Source/Bake.Tests/Helpers/TestIt.cs b/Source/Bake.Tests/Helpers/TestIt.cs
index 26c62f96..bd20d3e6 100644
--- a/Source/Bake.Tests/Helpers/TestIt.cs
+++ b/Source/Bake.Tests/Helpers/TestIt.cs
@@ -31,13 +31,16 @@ namespace Bake.Tests.Helpers
{
public abstract class TestIt
{
+ private CancellationTokenSource? _timeout;
private List _filesToDelete = null!;
protected IFixture Fixture { get; private set; } = null!;
+ protected CancellationToken Timeout => _timeout!.Token;
[SetUp]
public void SetUpTestIt()
{
+ _timeout = new CancellationTokenSource(TimeSpan.FromMinutes(5));
_filesToDelete = new List();
Fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
@@ -46,6 +49,9 @@ public void SetUpTestIt()
[TearDown]
public void TearDownTestIt()
{
+ _timeout?.Dispose();
+ _timeout = null;
+
foreach (var file in _filesToDelete)
{
if (File.Exists(file))
@@ -96,7 +102,7 @@ protected async Task ReadEmbeddedAsync(
var resourceName = resourceNames.Single(n => n.EndsWith(fileEnding, StringComparison.OrdinalIgnoreCase));
await using var stream = assembly.GetManifestResourceStream(resourceName);
using var streamReader = new StreamReader(stream!);
- return await streamReader.ReadToEndAsync();
+ return await streamReader.ReadToEndAsync(Timeout);
}
protected string Lines(
@@ -105,12 +111,17 @@ protected string Lines(
return string.Join(Environment.NewLine, lines);
}
+ protected void DeleteAfter(string filePath)
+ {
+ _filesToDelete.Add(filePath);
+ }
+
protected async Task WriteEmbeddedAsync(
string fileEnding)
{
var content = await ReadEmbeddedAsync(fileEnding);
var path = Path.GetTempFileName();
- await System.IO.File.WriteAllTextAsync(path, content);
+ await File.WriteAllTextAsync(path, content, Timeout);
_filesToDelete.Add(path);
return path;
}
diff --git a/Source/Bake.Tests/Helpers/TestProject.cs b/Source/Bake.Tests/Helpers/TestProject.cs
index 90e4a9cb..84fd6eed 100644
--- a/Source/Bake.Tests/Helpers/TestProject.cs
+++ b/Source/Bake.Tests/Helpers/TestProject.cs
@@ -46,7 +46,7 @@ protected TestProject(
}
[SetUp]
- public void SetUpTestProject()
+ public async Task SetUpTestProject()
{
_folder = Folder.New;
@@ -54,12 +54,19 @@ public void SetUpTestProject()
{
Sha = GitHelper.Create(_folder.Path);
+ var destination = Path.Join(_folder.Path, ProjectName);
+
DirectoryCopy(
Path.Combine(
ProjectHelper.GetRoot(),
"TestProjects",
ProjectName),
- Path.Join(_folder.Path, ProjectName));
+ destination);
+
+ var nugetConfig = await ReadEmbeddedAsync("nuget-config.xml");
+ await System.IO.File.WriteAllTextAsync(
+ Path.Combine(destination, "nuget.config"),
+ nugetConfig);
}
_previousCurrentDirectory = Directory.GetCurrentDirectory();
diff --git a/Source/Bake.Tests/Helpers/TestService.cs b/Source/Bake.Tests/Helpers/TestService.cs
new file mode 100644
index 00000000..a9645100
--- /dev/null
+++ b/Source/Bake.Tests/Helpers/TestService.cs
@@ -0,0 +1,41 @@
+// MIT License
+//
+// Copyright (c) 2021-2025 Rasmus Mikkelsen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Bake.Tests.Helpers
+{
+ public abstract class TestService : TestFor
+ where T : class
+ {
+ protected override T CreateSut()
+ {
+ return ServiceProvider.GetRequiredService();
+ }
+
+ protected override IServiceCollection Configure(IServiceCollection serviceCollection)
+ {
+ return base.Configure(serviceCollection)
+ .AddTransient();
+ }
+ }
+}
diff --git a/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs
new file mode 100644
index 00000000..d1e96cb4
--- /dev/null
+++ b/Source/Bake.Tests/UnitTests/Cooking/Cooks/ReleaseCookTests.cs
@@ -0,0 +1,198 @@
+// MIT License
+//
+// Copyright (c) 2021-2025 Rasmus Mikkelsen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using Bake.Cooking.Cooks.Release;
+using Bake.Core;
+using Bake.Tests.Helpers;
+using Bake.ValueObjects.Recipes.Release;
+using Bake.ValueObjects.Releases;
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
+using NUnit.Framework;
+using File = System.IO.File;
+
+namespace Bake.Tests.UnitTests.Cooking.Cooks
+{
+ public class ReleaseCookTests : TestService
+ {
+ [Test]
+ public async Task Empty()
+ {
+ // Arrange
+ var context = NewContext();
+
+ // Act
+ var success = await Sut.CookAsync(
+ context,
+ new ReleaseRecipe(
+ string.Empty,
+ []),
+ Timeout);
+
+ // Assert
+ success.Should().BeTrue();
+ }
+
+ [Test]
+ public async Task Files()
+ {
+ // Arrange
+ var context = NewContext();
+
+ // Act
+ var success = await Sut.CookAsync(
+ context,
+ new ReleaseRecipe(
+ string.Empty,
+ [
+ NewReleaseFile()
+ ]),
+ Timeout);
+
+ // Assert
+ success.Should().BeTrue();
+ }
+
+ [Test]
+ public async Task Directories()
+ {
+ // Arrange
+ var context = NewContext();
+
+ // Act
+ var success = await Sut.CookAsync(
+ context,
+ new ReleaseRecipe(
+ string.Empty,
+ [
+ NewReleaseDirectory()
+ ]),
+ Timeout);
+
+ // Assert
+ success.Should().BeTrue();
+ }
+
+ [Test]
+ public async Task Mixed()
+ {
+ // Arrange
+ var context = NewContext();
+
+ // Act
+ var success = await Sut.CookAsync(
+ context,
+ new ReleaseRecipe(
+ string.Empty,
+ [
+ NewMixedRelease(),
+ NewReleaseDirectory(),
+ NewReleaseFile(),
+ ]),
+ Timeout);
+
+ // Assert
+ success.Should().BeTrue();
+ }
+
+ private static Context NewContext()
+ {
+ return Context.New(ValueObjects.Ingredients.New(SemVer.Random, Path.GetTempPath()));
+ }
+
+ private ReleaseFile NewMixedRelease()
+ {
+ var fileName = $"{Guid.NewGuid():N}.zip";
+ var destinationPath = Path.Combine(Path.GetTempPath(), fileName);
+ DeleteAfter(destinationPath);
+
+ return new ReleaseFile(
+ fileName,
+ [NewDirectory(), NewFile(), NewFile(), NewDirectory()],
+ destinationPath);
+ }
+
+ private ReleaseFile NewReleaseDirectory()
+ {
+ var fileName = $"{Guid.NewGuid():N}.zip";
+ var destinationPath = Path.Combine(Path.GetTempPath(), fileName);
+ DeleteAfter(destinationPath);
+
+ return new ReleaseFile(
+ fileName,
+ [NewDirectory()],
+ destinationPath);
+ }
+
+ private ReleaseFile NewReleaseFile(int fileCount = 3)
+ {
+ var fileName = $"{Guid.NewGuid():N}.zip";
+ var destinationPath = Path.Combine(Path.GetTempPath(), fileName);
+ DeleteAfter(destinationPath);
+
+ return new ReleaseFile(
+ fileName,
+ Enumerable.Range(0, fileCount).Select(_ => NewFile()).ToArray(),
+ destinationPath);
+ }
+
+ private string NewDirectory(params string[] path)
+ {
+ var name = Guid.NewGuid().ToString("N");
+ path = path.Concat([name]).ToArray();
+ var directory = path.Aggregate(Path.GetTempPath(), Path.Combine);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ _ = Enumerable.Range(0, 3).Select(_ => NewFile(directory)).ToArray();
+
+ return directory;
+ }
+
+ private string NewFile(params string[] path)
+ {
+ var parentDirectory = path.Aggregate(Path.GetTempPath(), Path.Combine);
+ if (!Directory.Exists(parentDirectory))
+ {
+ Directory.CreateDirectory(parentDirectory);
+ }
+
+ var filePath = Path.Combine(
+ parentDirectory,
+ $"{Guid.NewGuid():N}.txt");
+
+ File.WriteAllText(filePath, "Hello there!");
+
+ DeleteAfter(filePath);
+
+ return filePath;
+ }
+
+ protected override IServiceCollection Configure(IServiceCollection serviceCollection)
+ {
+ return base.Configure(serviceCollection)
+ .AddTransient();
+ }
+ }
+}
diff --git a/Source/Bake.Tests/UnitTests/Services/ComposerOrderingTests.cs b/Source/Bake.Tests/UnitTests/Services/ComposerOrderingTests.cs
index d596257d..0fba6756 100644
--- a/Source/Bake.Tests/UnitTests/Services/ComposerOrderingTests.cs
+++ b/Source/Bake.Tests/UnitTests/Services/ComposerOrderingTests.cs
@@ -42,7 +42,7 @@ public void BasicOrdering()
var composers = new[]
{
DummyProducer("E", ArtifactType.Executable),
- Dummy("R", ArtifactType.Executable, ArtifactType.Release),
+ Dummy("R", ArtifactType.Executable, ArtifactType.GitHubRelease),
DummyProducer("E", ArtifactType.Executable),
};
@@ -94,12 +94,12 @@ private static IReadOnlyCollection GetNames(IEnumerable compo
private static IComposer Dummy(string name, ArtifactType consume, ArtifactType produce) => new DummyComposer(
name,
- new[] { consume },
- new[] { produce });
+ [consume],
+ [produce]);
private class DummyComposer : IComposer
{
- private static readonly Task> EmptyRecipes = Task.FromResult>(new Recipe[] { });
+ private static readonly Task> EmptyRecipes = Task.FromResult>([]);
public string Name { get; }
public IReadOnlyCollection Produces { get; }
diff --git a/Source/Bake/Context.cs b/Source/Bake/Context.cs
index 34ea0ad2..337df7e9 100644
--- a/Source/Bake/Context.cs
+++ b/Source/Bake/Context.cs
@@ -20,8 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System.Collections.Generic;
-using System.Linq;
using Bake.ValueObjects;
using Bake.ValueObjects.Artifacts;
diff --git a/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs b/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs
index f82b8792..84761623 100644
--- a/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs
+++ b/Source/Bake/Cooking/Composers/GitHubReleaseComposer.cs
@@ -33,17 +33,8 @@ public class GitHubReleaseComposer : Composer
{
private readonly IConventionInterpreter _conventionInterpreter;
- public override IReadOnlyCollection Consumes { get; } = new[]
- {
- ArtifactType.NuGet,
- ArtifactType.Executable,
- ArtifactType.DocumentationSite,
- ArtifactType.Container
- };
- public override IReadOnlyCollection Produces { get; } = new[]
- {
- ArtifactType.Release,
- };
+ public override IReadOnlyCollection Consumes { get; } = [ArtifactType.Release];
+ public override IReadOnlyCollection Produces { get; } = [ArtifactType.GitHubRelease];
public GitHubReleaseComposer(
IConventionInterpreter conventionInterpreter)
@@ -69,32 +60,25 @@ public override Task> ComposeAsync(
var gitHubDestination = context.Ingredients.Destinations
.OfType()
.SingleOrDefault();
-
if (gitHubDestination == null)
{
return Task.FromResult(EmptyRecipes);
}
- var artifacts = Enumerable.Empty()
- .Concat(context.GetArtifacts())
- .Concat(context.GetArtifacts())
- .Concat(context.GetArtifacts())
- .ToArray();
-
- if (!artifacts.Any())
+ var release = context.GetArtifacts().SingleOrDefault();
+ if (release == null)
{
return Task.FromResult(EmptyRecipes);
}
- return Task.FromResult>(new[]
- {
- new GitHubReleaseRecipe(
- context.Ingredients.GitHub,
- context.Ingredients.Version,
- context.Ingredients.Git.Sha,
- context.Ingredients.ReleaseNotes!,
- artifacts)
- });
+ return Task.FromResult>(
+ [
+ new GitHubReleaseRecipe(
+ release.Text,
+ context.Ingredients.GitHub,
+ context.Ingredients.Git.Sha,
+ release.Files)
+ ]);
}
}
}
diff --git a/Source/Bake/Cooking/Composers/ReleaseComposer.cs b/Source/Bake/Cooking/Composers/ReleaseComposer.cs
new file mode 100644
index 00000000..2ceb557d
--- /dev/null
+++ b/Source/Bake/Cooking/Composers/ReleaseComposer.cs
@@ -0,0 +1,264 @@
+// MIT License
+//
+// Copyright (c) 2021-2025 Rasmus Mikkelsen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using System.Collections.Concurrent;
+using System.Text;
+using Bake.Core;
+using Bake.ValueObjects;
+using Bake.ValueObjects.Artifacts;
+using Bake.ValueObjects.Recipes;
+using Bake.ValueObjects.Recipes.Release;
+using Bake.ValueObjects.Releases;
+using Microsoft.Extensions.Logging;
+using File = System.IO.File;
+
+namespace Bake.Cooking.Composers
+{
+ public class ReleaseComposer : Composer
+ {
+ private static readonly IReadOnlyDictionary NamingOs = new ConcurrentDictionary
+ {
+ [ExecutableOperatingSystem.Linux] = "linux",
+ [ExecutableOperatingSystem.MacOSX] = "macosx",
+ [ExecutableOperatingSystem.Windows] = "windows"
+ };
+ private static readonly IReadOnlyDictionary NamingArch = new ConcurrentDictionary
+ {
+ [ExecutableArchitecture.Intel32] = "x86",
+ [ExecutableArchitecture.Intel64] = "x64",
+ [ExecutableArchitecture.Arm32] = "arm32",
+ [ExecutableArchitecture.Arm64] = "arm64",
+ };
+
+ public override IReadOnlyCollection Consumes { get; } =
+ [
+ ArtifactType.Container,
+ ArtifactType.DocumentationSite,
+ ArtifactType.Executable,
+ ArtifactType.HelmChart,
+ ArtifactType.NuGet,
+ ];
+
+ public override IReadOnlyCollection Produces { get; } = [ArtifactType.Release];
+
+ private readonly ILogger _logger;
+ private readonly IDefaults _defaults;
+
+ public ReleaseComposer(
+ ILogger logger,
+ IDefaults defaults)
+ {
+ _logger = logger;
+ _defaults = defaults;
+ }
+
+ public override Task> ComposeAsync(
+ IContext context,
+ CancellationToken cancellationToken)
+ {
+ var artifacts = Enumerable.Empty()
+ .Concat(context.GetArtifacts())
+ .Concat(context.GetArtifacts())
+ .Concat(context.GetArtifacts())
+ .Concat(context.GetArtifacts())
+ .Concat(context.GetArtifacts())
+ .ToArray();
+
+ if (!artifacts.Any())
+ {
+ _logger.LogWarning("No artifacts found for release, skipping release creation!");
+ return Task.FromResult(EmptyRecipes);
+ }
+
+ var releaseTextBuilder = new StringBuilder();
+ AddReleaseNotes(context, releaseTextBuilder);
+ AddChangeLog(context, releaseTextBuilder);
+ AddArtifactDescriptions(context, artifacts, releaseTextBuilder);
+ AddGitHubChangeLink(context, releaseTextBuilder);
+
+ var releaseFiles = BuildReleaseFiles(context, artifacts);
+ var releaseText = releaseTextBuilder.ToString();
+
+ return Task.FromResult>(
+ [
+ new ReleaseRecipe(
+ releaseText,
+ releaseFiles.ToArray(),
+ new ReleaseArtifact(
+ releaseText,
+ releaseFiles.Select(f => f.Destination).ToArray()))
+ ]);
+ }
+
+ private List BuildReleaseFiles(
+ IContext context,
+ Artifact[] inputArtifacts)
+ {
+ var additionalSourceFiles = new[]
+ {
+ Path.Combine(context.Ingredients.WorkingDirectory, "README.md"),
+ Path.Combine(context.Ingredients.WorkingDirectory, "LICENSE"),
+ Path.Combine(context.Ingredients.WorkingDirectory, "RELEASE_NOTES.md"),
+ }
+ .Where(File.Exists)
+ .ToArray();
+
+ var releaseFiles = new List();
+
+ foreach (var g in inputArtifacts.GroupBy(a => a.GetType()))
+ {
+ switch (g.Key)
+ {
+ case { } t when t == typeof(DocumentationSiteArtifact):
+ {
+ foreach (var artifact in g)
+ {
+ var documentationSiteArtifact = (DocumentationSiteArtifact)artifact;
+ var fileName = $"documentation_v{context.Ingredients.Version}.zip";
+ releaseFiles.Add(new ReleaseFile(
+ fileName,
+ AppendFiles(documentationSiteArtifact.Path),
+ Path.Combine(_defaults.BakeReleaseOutputDirectory, fileName)));
+ }
+ }
+ break;
+
+ case { } t when t == typeof(ExecutableArtifact):
+ {
+ foreach (var artifact in g)
+ {
+ var executableArtifact = (ExecutableArtifact) artifact;
+ var fileName = CalculateArtifactFileName(executableArtifact);
+ releaseFiles.Add(new ReleaseFile(
+ fileName,
+ AppendFiles(executableArtifact.Path),
+ Path.Combine(_defaults.BakeReleaseOutputDirectory, fileName)));
+ }
+ }
+ break;
+ }
+ }
+
+ return releaseFiles;
+
+ string[] AppendFiles(params string[] paths)
+ {
+ return Enumerable.Empty()
+ .Concat(additionalSourceFiles)
+ .Concat(paths)
+ .OrderBy(p => p, StringComparer.OrdinalIgnoreCase)
+ .ToArray();
+ }
+ }
+
+ private static void AddReleaseNotes(IContext context, StringBuilder releaseText)
+ {
+ if (context.Ingredients.ReleaseNotes != null)
+ {
+ releaseText
+ .AppendLine("### Release notes")
+ .AppendLine(context.Ingredients.ReleaseNotes.Notes)
+ .AppendLine();
+ }
+ }
+
+ private static void AddArtifactDescriptions(
+ IContext _,
+ Artifact[] artifacts,
+ StringBuilder releaseText)
+ {
+ foreach (var g in artifacts.GroupBy(a => a.GetType()))
+ {
+ switch (g.Key)
+ {
+ case { } t when t == typeof(ContainerArtifact):
+ {
+ releaseText.AppendLine("### Containers");
+ foreach (var artifact in g)
+ {
+ var containerArtifact = (ContainerArtifact) artifact;
+ if (containerArtifact.Name.StartsWith("bake.local", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ releaseText.AppendLine($"* `{containerArtifact.Name}`");
+ foreach (var tag in containerArtifact.Tags)
+ {
+ releaseText.AppendLine($" * `{tag}`");
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private static void AddChangeLog(IContext context, StringBuilder releaseText)
+ {
+ if (context.Ingredients.Changelog == null || !context.Ingredients.Changelog.Changes.Any())
+ {
+ return;
+ }
+
+ foreach (var a in new[]
+ {
+ new {changeType = ChangeType.Other, title = "Changes"},
+ new {changeType = ChangeType.Dependency, title = "Updated dependencies"},
+ })
+ {
+ releaseText
+ .AppendLine($"#### {a.title}")
+ .AppendLine();
+
+ foreach (var change in context.Ingredients.Changelog.Changes[a.changeType])
+ {
+ releaseText.AppendLine($"* {change.Text}");
+ }
+ releaseText.AppendLine();
+ }
+
+ releaseText.AppendLine();
+ }
+
+ private static void AddGitHubChangeLink(IContext context, StringBuilder releaseText)
+ {
+ if (context.Ingredients is {GitHub: not null, Changelog: not null})
+ {
+ releaseText.AppendLine(
+ $"Full Changelog: {context.Ingredients.GitHub.Url.AbsoluteUri.TrimEnd('/')}/compare/{context.Ingredients.Changelog.PreviousReleaseTag.Sha}...{context.Ingredients.Git!.Sha}");
+ }
+ }
+
+ private static string CalculateArtifactFileName(ExecutableArtifact artifact)
+ {
+ var parts = new[]
+ {
+ artifact.Name,
+ NamingOs[artifact.Platform.Os],
+ NamingArch[artifact.Platform.Arch]
+ };
+
+ return $"{string.Join("_", parts)}.zip";
+ }
+ }
+}
diff --git a/Source/Bake/Cooking/Cooks/.gitignore b/Source/Bake/Cooking/Cooks/.gitignore
new file mode 100644
index 00000000..3a2535e1
--- /dev/null
+++ b/Source/Bake/Cooking/Cooks/.gitignore
@@ -0,0 +1 @@
+!Release
diff --git a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs
index 1a264021..47acd64b 100644
--- a/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs
+++ b/Source/Bake/Cooking/Cooks/GitHub/GitHubReleaseCook.cs
@@ -20,15 +20,10 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System.Collections.Concurrent;
-using System.IO.Compression;
-using System.Text;
using Bake.Core;
using Bake.Services;
using Bake.ValueObjects;
-using Bake.ValueObjects.Artifacts;
using Bake.ValueObjects.Recipes.GitHub;
-using Microsoft.Extensions.Logging;
// ReSharper disable StringLiteralTypo
@@ -36,28 +31,13 @@ namespace Bake.Cooking.Cooks.GitHub
{
public class GitHubReleaseCook : Cook
{
- private static readonly IReadOnlyDictionary NamingOs = new ConcurrentDictionary
- {
- [ExecutableOperatingSystem.Linux] = "linux",
- [ExecutableOperatingSystem.MacOSX] = "macosx",
- [ExecutableOperatingSystem.Windows] = "windows"
- };
- private static readonly IReadOnlyDictionary NamingArch = new ConcurrentDictionary
- {
- [ExecutableArchitecture.Intel32] = "x86",
- [ExecutableArchitecture.Intel64] = "x86_64",
- };
-
- private readonly ILogger _logger;
private readonly IGitHub _gitHub;
private readonly IFileSystem _fileSystem;
public GitHubReleaseCook(
- ILogger logger,
IGitHub gitHub,
IFileSystem fileSystem)
{
- _logger = logger;
_gitHub = gitHub;
_fileSystem = fileSystem;
}
@@ -67,155 +47,24 @@ protected override async Task CookAsync(
GitHubReleaseRecipe recipe,
CancellationToken cancellationToken)
{
- var additionalFiles = new[]
- {
- Path.Combine(context.Ingredients.WorkingDirectory, "README.md"),
- Path.Combine(context.Ingredients.WorkingDirectory, "LICENSE"),
- Path.Combine(context.Ingredients.WorkingDirectory, "RELEASE_NOTES.md"),
- }
- .Where(System.IO.File.Exists)
- .Select(p => _fileSystem.Open(p))
- .ToArray();
-
- var stringBuilder = new StringBuilder();
-
- if (recipe.ReleaseNotes != null)
- {
- stringBuilder
- .AppendLine("### Release notes")
- .AppendLine(recipe.ReleaseNotes.Notes)
- .AppendLine();
- }
-
- if (context.Ingredients.Changelog != null && context.Ingredients.Changelog.Changes.Any())
- {
- foreach (var a in new[]
- {
- new {changeType = ChangeType.Other, title = "Changes"},
- new {changeType = ChangeType.Dependency, title = "Updated dependencies"},
- })
- {
- stringBuilder
- .AppendLine($"#### {a.title}")
- .AppendLine();
-
- foreach (var change in context.Ingredients.Changelog.Changes[a.changeType])
- {
- stringBuilder.AppendLine($"* {change.Text}");
- }
-
- stringBuilder.AppendLine();
- }
-
- stringBuilder.AppendLine();
-
- if (context.Ingredients.GitHub != null)
- {
- stringBuilder.AppendLine(
- $"Full Changelog: {context.Ingredients.GitHub.Url.AbsoluteUri.TrimEnd('/')}/compare/{context.Ingredients.Changelog.PreviousReleaseTag.Sha}...{context.Ingredients.Git!.Sha}");
- }
- }
-
- var releaseFiles = (await CreateReleaseFilesAsync(additionalFiles, recipe, cancellationToken)).ToList();
-
- var documentationSite = recipe.Artifacts
- .OfType()
- .FirstOrDefault();
- if (documentationSite != null)
- {
- _logger.LogInformation("Documentation site built, packing it into a release file");
- var documentationZipFilePath = Path.Combine(
- Path.GetTempPath(),
- Guid.NewGuid().ToString("N"),
- "documentation.zip");
- Directory.CreateDirectory(Path.GetDirectoryName(documentationZipFilePath)!);
- ZipFile.CreateFromDirectory(documentationSite.Path, documentationZipFilePath);
- var file = _fileSystem.Open(documentationZipFilePath);
- releaseFiles.Add(new ReleaseFile(
- file,
- $"documentation_v{context.Ingredients.Version}.zip",
- await file.GetHashAsync(HashAlgorithm.SHA256, cancellationToken)));
- }
-
- var containerArtifacts = recipe.Artifacts
- .OfType()
+ var releaseFiles = recipe.Files
+ .Select(f => new GitHubReleaseFile(
+ _fileSystem.Get(f),
+ Path.GetFileName(f)))
.ToArray();
- if (containerArtifacts.Any())
- {
- stringBuilder.AppendLine("### Containers");
- foreach (var containerArtifact in containerArtifacts)
- {
- stringBuilder.AppendLine($"* `{containerArtifact.Name}`");
- foreach (var tag in containerArtifact.Tags)
- {
- stringBuilder.AppendLine($" * `{tag}`");
- }
- }
- }
-
- if (releaseFiles.Any())
- {
- stringBuilder.AppendLine("### Files");
- foreach (var releaseFile in releaseFiles)
- {
- stringBuilder.AppendLine($"* `{releaseFile.Destination}`");
- stringBuilder.AppendLine($" * SHA256: `{releaseFile.Sha256}`");
- }
- }
- var release = new Release(
- recipe.Version,
+ var gitHubRelease = new GitHubRelease(
+ context.Ingredients.Version,
recipe.Sha,
- stringBuilder.ToString(),
+ recipe.Text,
releaseFiles);
await _gitHub.CreateReleaseAsync(
- release,
+ gitHubRelease,
recipe.GitHubInformation,
cancellationToken);
return true;
}
-
- private async Task> CreateReleaseFilesAsync(
- IReadOnlyCollection additionalFiles,
- GitHubReleaseRecipe recipe,
- CancellationToken cancellationToken)
- {
- return await Task.WhenAll(recipe.Artifacts
- .OfType()
- .Select(async artifact =>
- {
- var file = _fileSystem.Open(artifact.Path);
- var fileName = CalculateArtifactFileName(artifact);
- var compressedFile = await _fileSystem.CompressAsync(
- fileName,
- CompressionAlgorithm.ZIP,
- Enumerable.Empty()
- .Concat(additionalFiles)
- .Concat(new[] {file,})
- .ToArray(),
- cancellationToken);
- var sha256 = await compressedFile.GetHashAsync(
- HashAlgorithm.SHA256,
- cancellationToken);
- return new ReleaseFile(
- compressedFile,
- fileName,
- sha256);
- }));
- }
-
- private static string CalculateArtifactFileName(ExecutableArtifact artifact)
- {
- var parts = new[]
- {
- artifact.Name,
- NamingOs[artifact.Platform.Os],
- NamingArch[artifact.Platform.Arch]
- };
-
- return $"{string.Join("_", parts)}.zip";
- }
}
}
diff --git a/Source/Bake/Cooking/Cooks/OctopusDeploy/OctopusDeployPackagePushCook.cs b/Source/Bake/Cooking/Cooks/OctopusDeploy/OctopusDeployPackagePushCook.cs
index b5ec1c0f..9926ca7b 100644
--- a/Source/Bake/Cooking/Cooks/OctopusDeploy/OctopusDeployPackagePushCook.cs
+++ b/Source/Bake/Cooking/Cooks/OctopusDeploy/OctopusDeployPackagePushCook.cs
@@ -86,7 +86,7 @@ private async Task UploadAsync(
CancellationToken cancellationToken)
{
url = new Uri(url, "/api/packages/raw?replace=false");
- var file = _fileSystem.Open(packagePath);
+ var file = _fileSystem.Get(packagePath);
await using var stream = await file.OpenReadAsync(cancellationToken);
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
diff --git a/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs
new file mode 100644
index 00000000..0df30d9a
--- /dev/null
+++ b/Source/Bake/Cooking/Cooks/Release/ReleaseCook.cs
@@ -0,0 +1,101 @@
+// MIT License
+//
+// Copyright (c) 2021-2025 Rasmus Mikkelsen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using Bake.Core;
+using Bake.ValueObjects.Recipes.Release;
+using Bake.ValueObjects.Releases;
+using Microsoft.Extensions.Logging;
+using System.IO.Compression;
+using Bake.Extensions;
+using File = System.IO.File;
+
+namespace Bake.Cooking.Cooks.Release
+{
+ public class ReleaseCook : Cook
+ {
+ private readonly ILogger _logger;
+ private readonly IFileSystem _fileSystem;
+
+ public ReleaseCook(
+ ILogger logger,
+ IFileSystem fileSystem)
+ {
+ _logger = logger;
+ _fileSystem = fileSystem;
+ }
+
+ protected override async Task CookAsync(
+ IContext context,
+ ReleaseRecipe recipe,
+ CancellationToken cancellationToken)
+ {
+ foreach (var releaseFile in recipe.Files)
+ {
+ if (!await CompressReleaseFilesAsync(releaseFile, cancellationToken))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private async Task CompressReleaseFilesAsync(ReleaseFile releaseFile, CancellationToken cancellationToken)
+ {
+ var tmpDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
+ _logger.LogInformation("Creating temporary directory at {TmpDirectory}", tmpDirectory);
+ foreach (var source in releaseFile.Sources)
+ {
+ if (File.Exists(source))
+ {
+ var fileName = Path.GetFileName(source);
+ var destination = Path.Combine(tmpDirectory, fileName);
+ _logger.LogInformation("Copying file from {Source} to {Destination}", source, destination);
+ await _fileSystem.CopyFileAsync(source, destination, cancellationToken);
+ }
+ else if (Directory.Exists(source))
+ {
+ _logger.LogInformation("Copying directory from {Source} to {Destination}", source, tmpDirectory);
+ await _fileSystem.CopyDirectoryAsync(source, tmpDirectory, cancellationToken);
+ }
+ else
+ {
+ _logger.LogError("The source {Source} does not exist", source);
+ return false;
+ }
+ }
+
+ var destinationDirectory = Path.GetDirectoryName(releaseFile.Destination);
+ if (!string.IsNullOrEmpty(destinationDirectory) && !Directory.Exists(destinationDirectory))
+ {
+ Directory.CreateDirectory(destinationDirectory);
+ }
+
+ _logger.LogInformation("Creating ZIP file at {Destination}", releaseFile.Destination);
+ ZipFile.CreateFromDirectory(tmpDirectory, releaseFile.Destination);
+ var fileInfo = new FileInfo(releaseFile.Destination);
+ _logger.LogInformation("Created ZIP file {Destination} with size {Size}", releaseFile.Destination, fileInfo.Length.BytesToString());
+
+ return true;
+ }
+ }
+}
diff --git a/Source/Bake/Cooking/Ingredients/Gathers/DynamicDestinationGather.cs b/Source/Bake/Cooking/Ingredients/Gathers/DynamicDestinationGather.cs
index cc8f0c0e..5eb6da44 100644
--- a/Source/Bake/Cooking/Ingredients/Gathers/DynamicDestinationGather.cs
+++ b/Source/Bake/Cooking/Ingredients/Gathers/DynamicDestinationGather.cs
@@ -77,7 +77,7 @@ public async Task GatherAsync(
await ExtractContainerDestinationAsync(ingredients, dynamicDestination);
break;
- case ArtifactType.Release:
+ case ArtifactType.GitHubRelease:
await ExtractReleaseDestinationAsync(ingredients, dynamicDestination);
break;
diff --git a/Source/Bake/Core/Defaults.cs b/Source/Bake/Core/Defaults.cs
index 75744052..c9e2c2d6 100644
--- a/Source/Bake/Core/Defaults.cs
+++ b/Source/Bake/Core/Defaults.cs
@@ -43,6 +43,7 @@ public class Defaults : IDefaults
public string DotNetRollForward { get; private set; } = "LatestMajor";
public TimeSpan BakeIngredientsGatherTimeout { get; private set; } = TimeSpan.FromMinutes(5);
public TimeSpan BakeComposeTimeout { get; private set; } = TimeSpan.FromMinutes(5);
+ public string BakeReleaseOutputDirectory { get; private set; } = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"), "bake-release");
public Defaults(
IEnvironmentVariables environmentVariables)
@@ -70,6 +71,7 @@ public async Task InitializeAsync(
DotNetRollForward = GetString(e, "dotnet_roll_forward", DotNetRollForward);
BakeIngredientsGatherTimeout = TimeSpan.FromSeconds(GetDouble(e, "bake_ingredients_gather_timeout_seconds", BakeIngredientsGatherTimeout.TotalSeconds));
BakeComposeTimeout = TimeSpan.FromSeconds(GetDouble(e, "bake_compose_timeout_seconds", BakeComposeTimeout.TotalSeconds));
+ BakeReleaseOutputDirectory = GetString(e, "bake_release_output_directory", BakeReleaseOutputDirectory);
}
private static bool GetBool(
diff --git a/Source/Bake/Core/File.cs b/Source/Bake/Core/File.cs
index 6cacbe62..93f3457e 100644
--- a/Source/Bake/Core/File.cs
+++ b/Source/Bake/Core/File.cs
@@ -20,17 +20,16 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
-using System.IO;
+using System.Collections.Concurrent;
using System.Security.Cryptography;
-using System.Threading;
-using System.Threading.Tasks;
using HashAlgorithm = Bake.ValueObjects.HashAlgorithm;
namespace Bake.Core
{
public class File : IFile
{
+ private static readonly ConcurrentDictionary>> CachedHashes = new();
+
public string Path { get; }
public string FileName => System.IO.Path.GetFileName(Path);
public long Size => new FileInfo(Path).Length;
@@ -72,16 +71,24 @@ public async Task GetHashAsync(
throw new ArgumentOutOfRangeException(nameof(hashAlgorithm));
}
- await using var stream = await OpenReadAsync(cancellationToken);
+ return await CachedHashes.GetOrAdd(
+ $"{hashAlgorithm}:{Path}",
+ _ => new Lazy>(
+ async () =>
+ {
+ await using var stream = await OpenReadAsync(cancellationToken);
- using var sha256 = SHA256.Create();
- var checksum = await sha256.ComputeHashAsync(stream, cancellationToken);
- return BitConverter.ToString(checksum).Replace("-", string.Empty);
+ using var sha256 = SHA256.Create();
+ var checksum = await sha256.ComputeHashAsync(stream, cancellationToken);
+ return BitConverter.ToString(checksum).Replace("-", string.Empty);
+ },
+ LazyThreadSafetyMode.ExecutionAndPublication)).Value;
}
public void Dispose()
{
System.IO.File.Delete(Path);
+ GC.SuppressFinalize(this);
}
}
}
diff --git a/Source/Bake/Core/FileSystem.cs b/Source/Bake/Core/FileSystem.cs
index e6f05371..815ec5c8 100644
--- a/Source/Bake/Core/FileSystem.cs
+++ b/Source/Bake/Core/FileSystem.cs
@@ -85,6 +85,50 @@ public async Task> FindFilesAsync(
return validPaths;
}
+ public async Task CopyFileAsync(
+ string sourcePath,
+ string destinationPath,
+ CancellationToken cancellationToken)
+ {
+ var destinationParentDirectory = Path.GetDirectoryName(destinationPath);
+ if (string.IsNullOrEmpty(destinationParentDirectory))
+ {
+ throw new ArgumentException($"Cannot determine parent directory of {destinationPath}");
+ }
+ if (!Directory.Exists(destinationPath))
+ {
+ Directory.CreateDirectory(destinationParentDirectory!);
+ }
+
+ await using var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);
+ await using var destinationStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true);
+ await sourceStream.CopyToAsync(destinationStream, 81920, cancellationToken);
+ }
+
+ public async Task CopyDirectoryAsync(
+ string sourcePath,
+ string destinationPath,
+ CancellationToken cancellationToken)
+ {
+ var directoryInfo = new DirectoryInfo(sourcePath);
+ var directoryInfos = directoryInfo.GetDirectories();
+
+ Directory.CreateDirectory(destinationPath);
+
+ var files = directoryInfo.GetFiles();
+ foreach (var file in files)
+ {
+ var tempPath = Path.Combine(destinationPath, file.Name);
+ await CopyFileAsync(file.FullName, tempPath, cancellationToken);
+ }
+
+ foreach (var subDirectory in directoryInfos)
+ {
+ var tempPath = Path.Combine(destinationPath, subDirectory.Name);
+ await CopyDirectoryAsync(subDirectory.FullName, tempPath, cancellationToken);
+ }
+ }
+
public async Task CompressAsync(
string fileName,
CompressionAlgorithm algorithm,
@@ -96,7 +140,7 @@ public async Task CompressAsync(
throw new ArgumentOutOfRangeException(nameof(algorithm));
}
- if (!files.Any())
+ if (files.Count == 0)
{
throw new ArgumentNullException(nameof(files));
}
@@ -152,7 +196,7 @@ public async Task ReadAllTextAsync(
return await streamReader.ReadToEndAsync();
}
- public IFile Open(string filePath)
+ public IFile Get(string filePath)
{
return new File(filePath);
}
diff --git a/Source/Bake/Core/IDefaults.cs b/Source/Bake/Core/IDefaults.cs
index f9d19024..ca77f518 100644
--- a/Source/Bake/Core/IDefaults.cs
+++ b/Source/Bake/Core/IDefaults.cs
@@ -45,6 +45,7 @@ public interface IDefaults
TimeSpan BakeIngredientsGatherTimeout { get; }
TimeSpan BakeComposeTimeout { get; }
+ string BakeReleaseOutputDirectory { get; }
Task InitializeAsync(
CancellationToken cancellationToken);
diff --git a/Source/Bake/Core/IFileSystem.cs b/Source/Bake/Core/IFileSystem.cs
index f7147f06..1036a102 100644
--- a/Source/Bake/Core/IFileSystem.cs
+++ b/Source/Bake/Core/IFileSystem.cs
@@ -40,7 +40,7 @@ Task ReadAllTextAsync(
string filePath,
CancellationToken cancellationToken);
- IFile Open(string filePath);
+ IFile Get(string filePath);
Task CompressAsync(
string fileName,
@@ -49,5 +49,15 @@ Task CompressAsync(
CancellationToken cancellationToken);
bool FileExists(string filePath);
+
+ Task CopyFileAsync(
+ string sourcePath,
+ string destinationPath,
+ CancellationToken cancellationToken);
+
+ Task CopyDirectoryAsync(
+ string sourcePath,
+ string destinationPath,
+ CancellationToken cancellationToken);
}
}
diff --git a/Source/Bake/Extensions/LongExtensions.cs b/Source/Bake/Extensions/LongExtensions.cs
index af03c568..f4ae9e09 100644
--- a/Source/Bake/Extensions/LongExtensions.cs
+++ b/Source/Bake/Extensions/LongExtensions.cs
@@ -20,7 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
using System.Globalization;
namespace Bake.Extensions
diff --git a/Source/Bake/Extensions/ServiceCollectionExtensions.cs b/Source/Bake/Extensions/ServiceCollectionExtensions.cs
index 2848fc31..f5292032 100644
--- a/Source/Bake/Extensions/ServiceCollectionExtensions.cs
+++ b/Source/Bake/Extensions/ServiceCollectionExtensions.cs
@@ -38,6 +38,7 @@
using Bake.Cooking.Cooks.OctopusDeploy;
using Bake.Cooking.Cooks.Pip;
using Bake.Cooking.Cooks.Python;
+using Bake.Cooking.Cooks.Release;
using Bake.Cooking.Ingredients.Gathers;
using Bake.Core;
using Bake.Services;
@@ -112,6 +113,7 @@ public static IServiceCollection AddBake(
.AddTransient()
.AddTransient()
.AddTransient()
+ .AddTransient()
// Cooks - .NET
.AddTransient()
@@ -141,6 +143,8 @@ public static IServiceCollection AddBake(
.AddTransient()
// Cooks - GitHub
.AddTransient()
+ // Cooks - Releases
+ .AddTransient()
// NodeJS / NPM
.AddTransient()
.AddTransient()
diff --git a/Source/Bake/Names.cs b/Source/Bake/Names.cs
index 2f4ef53b..277021f5 100644
--- a/Source/Bake/Names.cs
+++ b/Source/Bake/Names.cs
@@ -60,6 +60,7 @@ public static class Artifacts
public const string DirectoryArtifact = "directory-artifact";
public const string DocumentationSiteArtifact = "documentation-site-artifact";
public const string HelmChartArtifact = "helm-chart-artifact";
+ public const string ReleaseArtifact = "release-artifact";
public static readonly IReadOnlyDictionary PluralNames = new ConcurrentDictionary
{
@@ -70,6 +71,7 @@ public static class Artifacts
[NuGetArtifact] = "nuget packages",
[DocumentationSiteArtifact] = "documentation sites",
[HelmChartArtifact] = "helm charts",
+ [ReleaseArtifact] = "released files",
};
}
@@ -90,7 +92,7 @@ public static class ArtifactTypes
[ArtifactType.Dockerfile] = Dockerfile,
[ArtifactType.DotNetPublishedDirectory] = DotNetPublishedDirectory,
[ArtifactType.NuGet] = NuGet,
- [ArtifactType.Release] = Release,
+ [ArtifactType.GitHubRelease] = Release,
[ArtifactType.Executable] = Executable,
[ArtifactType.DocumentationSite] = DocumentationSite,
};
@@ -134,7 +136,7 @@ public static class Python
public static class GitHub
{
- public const string Release = "github-release";
+ public const string GitHubRelease = "github-release";
}
public static class Helm
@@ -144,6 +146,11 @@ public static class Helm
public const string DependenciesUpdate = "helm-dependencies-update";
}
+ public static class Releases
+ {
+ public const string Release = "release";
+ }
+
public static class MkDocs
{
public const string Release = "mkdocs-build";
diff --git a/Source/Bake/Services/ComposerOrdering.cs b/Source/Bake/Services/ComposerOrdering.cs
index b076d659..37f55dcc 100644
--- a/Source/Bake/Services/ComposerOrdering.cs
+++ b/Source/Bake/Services/ComposerOrdering.cs
@@ -20,9 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
-using System.Collections.Generic;
-using System.Linq;
using Bake.Cooking;
using Bake.Extensions;
using Microsoft.Extensions.Logging;
diff --git a/Source/Bake/Services/GitHub.cs b/Source/Bake/Services/GitHub.cs
index fc45ce89..3eabd776 100644
--- a/Source/Bake/Services/GitHub.cs
+++ b/Source/Bake/Services/GitHub.cs
@@ -30,7 +30,6 @@
using Author = Bake.ValueObjects.Author;
using Commit = Bake.ValueObjects.Commit;
using PullRequest = Bake.ValueObjects.PullRequest;
-using Release = Bake.ValueObjects.Release;
namespace Bake.Services
{
@@ -57,7 +56,7 @@ public GitHub(
}
public async Task CreateReleaseAsync(
- Release release,
+ GitHubRelease gitHubRelease,
GitHubInformation gitHubInformation,
CancellationToken cancellationToken)
{
@@ -68,33 +67,33 @@ public async Task CreateReleaseAsync(
"Could not create a GitHub release due to missing credentials");
}
- var tag = $"v{release.Version}";
+ var tag = $"v{gitHubRelease.Version}";
- var gitHubRelease = await gitHubClient.Repository.Release.Create(
+ var octoKitRelease = await gitHubClient.Repository.Release.Create(
gitHubInformation.Owner,
gitHubInformation.Repository,
new NewRelease(tag)
{
- Prerelease = release.Version.IsPrerelease,
- TargetCommitish = release.Sha,
- Body = release.Body,
+ Prerelease = gitHubRelease.Version.IsPrerelease,
+ TargetCommitish = gitHubRelease.Sha,
+ Body = gitHubRelease.Body,
Draft = true,
- Name = $"v{release.Version}",
+ Name = $"v{gitHubRelease.Version}",
});
- if (release.Files.Count != 0)
+ if (gitHubRelease.Files.Count != 0)
{
- var uploadTasks = release.Files
- .Select(f => UploadFileAsync(f, gitHubRelease, gitHubClient, cancellationToken));
+ var uploadTasks = gitHubRelease.Files
+ .Select(f => UploadFileAsync(f, octoKitRelease, gitHubClient, cancellationToken));
await Task.WhenAll(uploadTasks);
}
- var gitHubReleaseUpdate = gitHubRelease.ToUpdate();
+ var gitHubReleaseUpdate = octoKitRelease.ToUpdate();
gitHubReleaseUpdate.Draft = false;
await gitHubClient.Repository.Release.Edit(
gitHubInformation.Owner,
gitHubInformation.Repository,
- gitHubRelease.Id,
+ octoKitRelease.Id,
gitHubReleaseUpdate);
}
@@ -316,18 +315,18 @@ public async Task> GetPullRequestsAsync(
}
private async Task UploadFileAsync(
- ReleaseFile releaseFile,
- Octokit.Release gitHubRelease,
+ GitHubReleaseFile gitHubReleaseFile,
+ Release gitHubRelease,
IGitHubClient gitHubClient,
CancellationToken cancellationToken)
{
var stopwatch = Stopwatch.StartNew();
_logger.LogDebug(
"Uploading releaseFile {FileName} to GitHub release {ReleaseUrl}",
- releaseFile.Source.FileName,
+ gitHubReleaseFile.Source.FileName,
gitHubRelease.Url);
- await using var stream = await releaseFile.Source.OpenReadAsync(cancellationToken);
+ await using var stream = await gitHubReleaseFile.Source.OpenReadAsync(cancellationToken);
try
{
@@ -336,7 +335,7 @@ await gitHubClient.Repository.Release.UploadAsset(
new ReleaseAssetUpload
{
ContentType = "application/octet-stream",
- FileName = releaseFile.Destination,
+ FileName = gitHubReleaseFile.ReleaseFileName,
RawData = stream,
},
cancellationToken);
@@ -349,7 +348,7 @@ await gitHubClient.Repository.Release.UploadAsset(
_logger.LogInformation(
"Done uploading releaseFile {FileName} to GitHub release {ReleaseUrl} after {TotalSeconds} seconds",
- releaseFile.Source.FileName,
+ gitHubReleaseFile.Source.FileName,
gitHubRelease.Url,
stopwatch.Elapsed.TotalSeconds);
}
diff --git a/Source/Bake/Services/IGitHub.cs b/Source/Bake/Services/IGitHub.cs
index 4ed15b50..ee8a59d4 100644
--- a/Source/Bake/Services/IGitHub.cs
+++ b/Source/Bake/Services/IGitHub.cs
@@ -27,7 +27,7 @@ namespace Bake.Services
public interface IGitHub
{
Task CreateReleaseAsync(
- Release release,
+ GitHubRelease gitHubRelease,
GitHubInformation gitHubInformation,
CancellationToken cancellationToken);
diff --git a/Source/Bake/Services/Uploader.cs b/Source/Bake/Services/Uploader.cs
index 540c4922..970fa4ca 100644
--- a/Source/Bake/Services/Uploader.cs
+++ b/Source/Bake/Services/Uploader.cs
@@ -67,7 +67,7 @@ public async Task UploadAsync(
Uri url,
CancellationToken cancellationToken)
{
- var file = _fileSystem.Open(filePath);
+ var file = _fileSystem.Get(filePath);
var fileName = Path.GetFileName(filePath);
var fileExtension = Path.GetExtension(filePath).Trim('.');
var mediaType = MediaTypes.TryGetValue(fileExtension, out var t) ? t : DefaultMediaType;
diff --git a/Source/Bake/ValueObjects/.gitignore b/Source/Bake/ValueObjects/.gitignore
index 6a890271..5f6ad16e 100644
--- a/Source/Bake/ValueObjects/.gitignore
+++ b/Source/Bake/ValueObjects/.gitignore
@@ -1 +1,3 @@
!Artifacts
+!Release
+!Releases
diff --git a/Source/Bake/ValueObjects/Artifacts/Artifact.cs b/Source/Bake/ValueObjects/Artifacts/Artifact.cs
index 2b80a2bb..1a8fa4e8 100644
--- a/Source/Bake/ValueObjects/Artifacts/Artifact.cs
+++ b/Source/Bake/ValueObjects/Artifacts/Artifact.cs
@@ -24,7 +24,7 @@ namespace Bake.ValueObjects.Artifacts
{
public abstract class Artifact : ValueObject
{
- public static IReadOnlyCollection Empty { get; } = new Artifact[] { };
+ public static IReadOnlyCollection Empty { get; } = [];
public abstract IAsyncEnumerable ValidateAsync(
CancellationToken cancellationToken);
diff --git a/Source/Bake/ValueObjects/Artifacts/ArtifactAttribute.cs b/Source/Bake/ValueObjects/Artifacts/ArtifactAttribute.cs
index 906d617c..d21318a2 100644
--- a/Source/Bake/ValueObjects/Artifacts/ArtifactAttribute.cs
+++ b/Source/Bake/ValueObjects/Artifacts/ArtifactAttribute.cs
@@ -20,8 +20,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
-
namespace Bake.ValueObjects.Artifacts
{
public class ArtifactAttribute : Attribute, IYamlTag
diff --git a/Source/Bake/ValueObjects/Artifacts/ArtifactType.cs b/Source/Bake/ValueObjects/Artifacts/ArtifactType.cs
index 20bb7728..88d23789 100644
--- a/Source/Bake/ValueObjects/Artifacts/ArtifactType.cs
+++ b/Source/Bake/ValueObjects/Artifacts/ArtifactType.cs
@@ -33,6 +33,7 @@ public enum ArtifactType
HelmChart,
Container,
DocumentationSite,
+ GitHubRelease,
Release,
}
}
diff --git a/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs
new file mode 100644
index 00000000..df061ab0
--- /dev/null
+++ b/Source/Bake/ValueObjects/Artifacts/ReleaseArtifact.cs
@@ -0,0 +1,64 @@
+// MIT License
+//
+// Copyright (c) 2021-2025 Rasmus Mikkelsen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using YamlDotNet.Serialization;
+
+namespace Bake.ValueObjects.Artifacts
+{
+ [Artifact(Names.Artifacts.ReleaseArtifact)]
+ public class ReleaseArtifact : Artifact
+ {
+ [YamlMember]
+ public string Text { get; [Obsolete] set; } = null!;
+
+ [YamlMember]
+ public string[] Files { get; [Obsolete] set; } = null!;
+
+ [Obsolete]
+ public ReleaseArtifact() { }
+
+ public ReleaseArtifact(
+ string text,
+ string[] files)
+ {
+#pragma warning disable CS0612 // Type or member is obsolete
+ Text = text;
+ Files = files;
+#pragma warning restore CS0612 // Type or member is obsolete
+ }
+
+ public override IAsyncEnumerable ValidateAsync(CancellationToken cancellationToken)
+ {
+ var missingFiles = Files
+ .Where(file => !File.Exists(file))
+ .Select(f => $"File '{f}' is missing!")
+ .ToArray();
+
+ return missingFiles.ToAsyncEnumerable();
+ }
+
+ public override IEnumerable PrettyNames()
+ {
+ return Files;
+ }
+ }
+}
diff --git a/Source/Bake/ValueObjects/Release.cs b/Source/Bake/ValueObjects/GitHubRelease.cs
similarity index 87%
rename from Source/Bake/ValueObjects/Release.cs
rename to Source/Bake/ValueObjects/GitHubRelease.cs
index cb2e37f4..598f438a 100644
--- a/Source/Bake/ValueObjects/Release.cs
+++ b/Source/Bake/ValueObjects/GitHubRelease.cs
@@ -20,21 +20,20 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System.Collections.Generic;
using Bake.Core;
namespace Bake.ValueObjects
{
- public class Release : Tag
+ public class GitHubRelease : Tag
{
public string Body { get; }
- public IReadOnlyCollection Files { get; }
+ public IReadOnlyCollection Files { get; }
- public Release(
+ public GitHubRelease(
SemVer version,
string sha,
string body,
- IReadOnlyCollection files)
+ IReadOnlyCollection files)
: base(version, sha)
{
Body = body;
diff --git a/Source/Bake/ValueObjects/ReleaseFile.cs b/Source/Bake/ValueObjects/GitHubReleaseFile.cs
similarity index 84%
rename from Source/Bake/ValueObjects/ReleaseFile.cs
rename to Source/Bake/ValueObjects/GitHubReleaseFile.cs
index 82021540..ac625532 100644
--- a/Source/Bake/ValueObjects/ReleaseFile.cs
+++ b/Source/Bake/ValueObjects/GitHubReleaseFile.cs
@@ -24,19 +24,16 @@
namespace Bake.ValueObjects;
-public class ReleaseFile
+public class GitHubReleaseFile
{
public IFile Source { get; }
- public string Destination { get; }
- public string Sha256 { get; }
+ public string ReleaseFileName { get; }
- public ReleaseFile(
+ public GitHubReleaseFile(
IFile source,
- string destination,
- string sha256)
+ string releaseFileName)
{
Source = source;
- Destination = destination;
- Sha256 = sha256;
+ ReleaseFileName = releaseFileName;
}
}
diff --git a/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs b/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs
index 58796c40..8362a79f 100644
--- a/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs
+++ b/Source/Bake/ValueObjects/Recipes/GitHub/GitHubReleaseRecipe.cs
@@ -20,44 +20,39 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using Bake.Core;
-using Bake.ValueObjects.Artifacts;
using YamlDotNet.Serialization;
namespace Bake.ValueObjects.Recipes.GitHub
{
- [Recipe(Names.Recipes.GitHub.Release)]
+ [Recipe(Names.Recipes.GitHub.GitHubRelease)]
public class GitHubReleaseRecipe : Recipe
{
[YamlMember]
- public GitHubInformation GitHubInformation { get; [Obsolete] set; } = null!;
+ public string Text { get; [Obsolete] set; } = null!;
[YamlMember]
- public SemVer Version { get; [Obsolete] set; } = null!;
+ public GitHubInformation GitHubInformation { get; [Obsolete] set; } = null!;
[YamlMember]
public string Sha { get; [Obsolete] set; } = null!;
[YamlMember]
- public ReleaseNotes? ReleaseNotes { get; [Obsolete] set; }
+ public string[] Files { get; [Obsolete] set; } = null!;
[Obsolete]
public GitHubReleaseRecipe() { }
public GitHubReleaseRecipe(
+ string text,
GitHubInformation gitHubInformation,
- SemVer version,
string sha,
- ReleaseNotes? releaseNotes,
- Artifact[] artifacts)
- : base(artifacts)
+ string[] files)
{
#pragma warning disable CS0612 // Type or member is obsolete
+ Text = text;
GitHubInformation = gitHubInformation;
- Version = version;
Sha = sha;
- ReleaseNotes = releaseNotes;
- Artifacts = artifacts;
+ Files = files;
#pragma warning restore CS0612 // Type or member is obsolete
}
}
diff --git a/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs
new file mode 100644
index 00000000..78acdd1f
--- /dev/null
+++ b/Source/Bake/ValueObjects/Recipes/Release/ReleaseRecipe.cs
@@ -0,0 +1,54 @@
+// MIT License
+//
+// Copyright (c) 2021-2025 Rasmus Mikkelsen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using Bake.ValueObjects.Artifacts;
+using Bake.ValueObjects.Releases;
+using YamlDotNet.Serialization;
+
+namespace Bake.ValueObjects.Recipes.Release
+{
+ [Recipe(Names.Recipes.Releases.Release)]
+ public class ReleaseRecipe : Recipe
+ {
+ [YamlMember]
+ public string Text { get; [Obsolete] set; } = null!;
+
+ [YamlMember]
+ public ReleaseFile[] Files { get; [Obsolete] set; } = null!;
+
+ [Obsolete]
+ public ReleaseRecipe() { }
+
+ public ReleaseRecipe(
+ string text,
+ ReleaseFile[] files,
+ params Artifact[] artifacts)
+ : base(artifacts)
+ {
+#pragma warning disable CS0612 // Type or member is obsolete
+ Text = text;
+ Files = files;
+ Artifacts = artifacts;
+#pragma warning restore CS0612 // Type or member is obsolete
+ }
+ }
+}
diff --git a/Source/Bake/ValueObjects/Releases/ReleaseFile.cs b/Source/Bake/ValueObjects/Releases/ReleaseFile.cs
new file mode 100644
index 00000000..98e679ca
--- /dev/null
+++ b/Source/Bake/ValueObjects/Releases/ReleaseFile.cs
@@ -0,0 +1,53 @@
+// MIT License
+//
+// Copyright (c) 2021-2025 Rasmus Mikkelsen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using YamlDotNet.Serialization;
+
+namespace Bake.ValueObjects.Releases
+{
+ public class ReleaseFile
+ {
+ [YamlMember]
+ public string Name { get; [Obsolete] set; } = null!;
+
+ [YamlMember]
+ public string[] Sources { get; [Obsolete] set; } = null!;
+
+ [YamlMember]
+ public string Destination { get; [Obsolete] set; } = null!;
+
+ [Obsolete]
+ public ReleaseFile() { }
+
+ public ReleaseFile(
+ string name,
+ string[] sources,
+ string destination)
+ {
+#pragma warning disable CS0612 // Type or member is obsolete
+ Name = name;
+ Sources = sources;
+ Destination = destination;
+#pragma warning restore CS0612 // Type or member is obsolete
+ }
+ }
+}