Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Core.sln
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessUtilsSampleApp", "samples\ProcessUtilsSampleApp\ProcessUtilsSampleApp.csproj", "{58E94E6E-9A9A-4E3F-ACB5-89E47CE67C39}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cli", "Cli", "{CDCF2469-1668-4EB0-A73F-692115CF6776}"
ProjectSection(SolutionItems) = preProject
source\Cli\README.md = source\Cli\README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreativeCoders.Cli.Core", "source\Cli\CreativeCoders.Cli.Core\CreativeCoders.Cli.Core.csproj", "{4E628A1A-953A-4274-B4EF-F33E943A650D}"
EndProject
Expand Down
149 changes: 149 additions & 0 deletions source/CakeBuild/CreativeCoders.CakeBuild/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# CreativeCoders.CakeBuild

A reusable build automation framework built on [Cake Frosting](https://cakebuild.net/docs/running-builds/runners/cake-frosting) for .NET projects. Provides a fluent builder API with pre-built CI/CD tasks — clean, build, test, pack, publish, create GitHub releases, and more — so you can set up a complete build pipeline with minimal code.

## Features

- 🏗️ **Fluent Builder API** — Configure your build pipeline with `CakeHostBuilder` in just a few lines
- 📦 **Pre-built Tasks** — Standard CI/CD tasks out of the box: Clean, Restore, Build, Test, Pack, Publish, NuGet Push, Code Coverage, GitHub Releases, Distribution Packages
- ⚙️ **Settings Interfaces** — Customize task behavior by implementing strongly-typed settings interfaces
- 🔍 **Auto-Discovery** — Automatically finds Git root, solution files, and test projects
- 🏷️ **GitVersion Integration** — Semantic versioning via GitVersion with static fallback
- 🐙 **GitHub Actions Support** — Log grouping and build server integration

## Getting Started

### Prerequisites

- [.NET 10 SDK](https://dotnet.microsoft.com/download) or later

### Setup

Create a new console application and reference the `CreativeCoders.CakeBuild` package:

```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

<ProjectReference Include="CreativeCoders.CakeBuild" Version="LATEST" />
</Project>
```

### Minimal Example

```csharp
using CreativeCoders.CakeBuild;

CakeHostBuilder.Create()
.UseBuildContext<MyBuildContext>()
.AddDefaultTasks()
.AddBuildServerIntegration()
.InstallTools(
new DotNetToolInstallation("GitVersion.Tool", "6.5.1"),
new DotNetToolInstallation("dotnet-reportgenerator-globaltool", "5.5.1"))
.Build()
.Run(args);
```

## Usage

### Custom Build Context

Extend `CakeBuildContext` and implement the settings interfaces for the tasks you want to configure:

```csharp
public class MyBuildContext(ICakeContext context) : CakeBuildContext(context),
IDefaultTaskSettings,
ICreateDistPackagesTaskSettings
{
public string Copyright => $"{DateTime.Now.Year} My Company";

public string PackageProjectUrl => "https://github.com/my-org/my-repo";

public string PackageLicenseExpression => PackageLicenseExpressions.Apache20;

public string NuGetFeedUrl => "https://api.nuget.org/v3/index.json";

public IEnumerable<DistPackage> DistPackages =>
[
new("my-app-linux-x64", "artifacts/publish/my-app/linux-x64", DistPackageFormat.TarGz),
new("my-app-win-x64", "artifacts/publish/my-app/win-x64", DistPackageFormat.Zip)
];
}
```

### Available Tasks

All default tasks are registered via `AddDefaultTasks()` and execute in dependency order:

| Task | Description | Depends On |
|------|-------------|------------|
| **Clean** | Removes `bin/`, `obj/`, and artifact directories | — |
| **Restore** | Restores NuGet packages | Clean |
| **Build** | Builds the solution with version info from GitVersion | Restore |
| **Test** | Runs tests with optional code coverage collection | Build |
| **CodeCoverage** | Generates coverage reports via ReportGenerator | Test |
| **Pack** | Creates NuGet packages with metadata | Build |
| **NuGetPush** | Pushes packages to a NuGet feed | Pack |
| **Publish** | Publishes applications to output directories | Build |
| **CreateDistPackages** | Creates `.tar.gz` / `.zip` distribution archives | Publish |
| **CreateGitHubRelease** | Creates a GitHub release with assets via Octokit | — |

### Settings Interfaces

Each task reads its configuration from a settings interface. Implement only the ones you need:

| Interface | Configures |
|-----------|------------|
| `ICleanTaskSettings` | Directories to clean |
| `ITestTaskSettings` | Test projects, coverage options |
| `ICodeCoverageTaskSettings` | Report types and file patterns |
| `IPackTaskSettings` | Package output, metadata (URL, license, copyright) |
| `INuGetPushTaskSettings` | Feed URL, API key, skip flag |
| `IPublishTaskSettings` | Per-project publish configuration (runtime, self-contained) |
| `ICreateDistPackagesTaskSettings` | Distribution package definitions and output path |
| `ICreateGitHubReleaseTaskSettings` | Release metadata, assets, GitHub token |

> [!TIP]
> Implement `IDefaultTaskSettings` to get all standard settings interfaces in one go.

### Tool Installation

Register external tools via the builder:

```csharp
CakeHostBuilder.Create()
.InstallTools(
new DotNetToolInstallation("GitVersion.Tool", "6.5.1"),
new DotNetToolInstallation("dotnet-reportgenerator-globaltool", "5.5.1"))
// ...
```

### GitHub Actions Integration

Enable log grouping for GitHub Actions:

```csharp
CakeHostBuilder.Create()
.AddBuildServerIntegration()
// ...
```

This registers task setup/teardown hooks that create collapsible log groups in GitHub Actions.

### Generic Task Templates

For advanced scenarios, use the generic task templates (`BuildTask<T>`, `TestTask<T>`, etc.) with a custom context type instead of the default `CakeBuildContext`:

```csharp
[TaskName("Build")]
[IsDependentOn(typeof(RestoreTask<MyContext>))]
public class MyBuildTask : BuildTask<MyContext> { }
```

## Sample

See [`samples/CakeBuildSample`](../../../samples/CakeBuildSample) for a complete working example that demonstrates the full pipeline setup with custom context, publishing, and distribution package creation.
20 changes: 20 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/CliCommandAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,34 @@

namespace CreativeCoders.Cli.Core;

/// <summary>
/// Marks a class as a CLI command and specifies the command path used to invoke it.
/// </summary>
/// <param name="commands">The command path segments used to invoke this command.</param>
[AttributeUsage(AttributeTargets.Class)]
public class CliCommandAttribute(string[] commands) : Attribute
{
/// <summary>
/// Gets or sets the display name of the command.
/// </summary>
/// <value>The display name of the command. The default is <see cref="string.Empty"/>.</value>
public string Name { get; set; } = string.Empty;

/// <summary>
/// Gets the command path segments used to invoke this command.
/// </summary>
/// <value>An array of strings representing the command path.</value>
public string[] Commands { get; } = Ensure.NotNull(commands);

/// <summary>
/// Gets or sets the description of the command displayed in help output.
/// </summary>
/// <value>The description text. The default is <see cref="string.Empty"/>.</value>
public string Description { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the alternative command path segments that can also invoke this command.
/// </summary>
/// <value>An array of alternative command path segments. The default is an empty array.</value>
public string[] AlternativeCommands { get; init; } = [];
}
5 changes: 5 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/CliCommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

namespace CreativeCoders.Cli.Core;

/// <summary>
/// Provides the default implementation of <see cref="ICliCommandContext"/>.
/// </summary>
[PublicAPI]
public class CliCommandContext : ICliCommandContext
{
/// <inheritdoc />
public string[] AllArgs { get; set; } = [];

/// <inheritdoc />
public string[] OptionsArgs { get; set; } = [];
}
13 changes: 13 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/CliCommandGroupAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
namespace CreativeCoders.Cli.Core;

/// <summary>
/// Defines a command group that organizes related CLI commands under a common path.
/// </summary>
/// <param name="commands">The command path segments that identify this group.</param>
/// <param name="description">The description of the command group displayed in help output.</param>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class CliCommandGroupAttribute(string[] commands, string description) : Attribute
{
/// <summary>
/// Gets the command path segments that identify this group.
/// </summary>
/// <value>An array of strings representing the group command path.</value>
public string[] Commands { get; } = commands;

/// <summary>
/// Gets the description of the command group displayed in help output.
/// </summary>
/// <value>The description text.</value>
public string Description { get; } = description;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
namespace CreativeCoders.Cli.Core;

/// <summary>
/// Specifies when a CLI pre-processor or post-processor should be executed.
/// </summary>
public enum CliProcessorExecutionCondition
{
/// <summary>
/// The processor is executed for every CLI invocation.
/// </summary>
Always,

/// <summary>
/// The processor is executed only when help output is displayed.
/// </summary>
OnlyOnHelp,

/// <summary>
/// The processor is executed only when a CLI command is run.
/// </summary>
OnlyOnCommand
}
8 changes: 8 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/CliResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

namespace CreativeCoders.Cli.Core;

/// <summary>
/// Represents the result of a CLI application execution.
/// </summary>
/// <param name="exitCode">The exit code of the CLI execution.</param>
[PublicAPI]
public class CliResult(int exitCode)
{
/// <summary>
/// Gets or sets the exit code of the CLI execution.
/// </summary>
/// <value>The exit code.</value>
public int ExitCode { get; set; } = exitCode;
}
16 changes: 16 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/CommandResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,31 @@ public class CommandResult
/// </summary>
public static CommandResult Success { get; } = new CommandResult();

/// <summary>
/// Initializes a new instance of the <see cref="CommandResult"/> class with an exit code of 0.
/// </summary>
public CommandResult() { }

/// <summary>
/// Initializes a new instance of the <see cref="CommandResult"/> class with the specified exit code.
/// </summary>
/// <param name="exitCode">The exit code for the command result.</param>
public CommandResult(int exitCode)
{
ExitCode = exitCode;
}

/// <summary>
/// Gets the exit code of the command execution.
/// </summary>
/// <value>The exit code. The default is 0.</value>
public int ExitCode { get; init; }

/// <summary>
/// Implicitly converts an integer exit code to a <see cref="CommandResult"/>.
/// </summary>
/// <param name="exitCode">The exit code to convert.</param>
/// <returns>A <see cref="CommandResult"/> representing the exit code. Returns <see cref="Success"/> if the exit code is 0.</returns>
public static implicit operator CommandResult(int exitCode)
=> exitCode == 0
? Success
Expand Down
16 changes: 16 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/ICliCommand.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
namespace CreativeCoders.Cli.Core;

/// <summary>
/// Defines a CLI command that accepts options of type <typeparamref name="TOptions"/>.
/// </summary>
/// <typeparam name="TOptions">The type of the options passed to the command.</typeparam>
public interface ICliCommand<in TOptions>
where TOptions : class
{
/// <summary>
/// Executes the CLI command asynchronously with the specified options.
/// </summary>
/// <param name="options">The options for the command.</param>
/// <returns>A <see cref="CommandResult"/> representing the outcome of the command execution.</returns>
Task<CommandResult> ExecuteAsync(TOptions options);
}

/// <summary>
/// Defines a CLI command without options.
/// </summary>
public interface ICliCommand
{
/// <summary>
/// Executes the CLI command asynchronously.
/// </summary>
/// <returns>A <see cref="CommandResult"/> representing the outcome of the command execution.</returns>
Task<CommandResult> ExecuteAsync();
}
12 changes: 12 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/ICliPostProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@

namespace CreativeCoders.Cli.Core;

/// <summary>
/// Defines a post-processor that executes after a CLI command has completed.
/// </summary>
[PublicAPI]
public interface ICliPostProcessor
{
/// <summary>
/// Executes the post-processor asynchronously with the result of the CLI command.
/// </summary>
/// <param name="cliResult">The result of the CLI command execution.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task ExecuteAsync(CliResult cliResult);

/// <summary>
/// Gets the condition that determines when this post-processor is executed.
/// </summary>
/// <value>One of the enumeration values that specifies the execution condition.</value>
CliProcessorExecutionCondition ExecutionCondition { get; }
}
12 changes: 12 additions & 0 deletions source/Cli/CreativeCoders.Cli.Core/ICliPreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@

namespace CreativeCoders.Cli.Core;

/// <summary>
/// Defines a pre-processor that executes before a CLI command is processed.
/// </summary>
[PublicAPI]
public interface ICliPreProcessor
{
/// <summary>
/// Executes the pre-processor asynchronously with the provided command line arguments.
/// </summary>
/// <param name="args">The command line arguments passed to the CLI application.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task ExecuteAsync(string[] args);

/// <summary>
/// Gets the condition that determines when this pre-processor is executed.
/// </summary>
/// <value>One of the enumeration values that specifies the execution condition.</value>
CliProcessorExecutionCondition ExecutionCondition { get; }
}
Loading
Loading