Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,8 @@ jobs:
- name: Run check-deps in Demo Application
run: dotnet run --project Neolution.DotNet.Console.Demo --no-build --configuration '${{ env.BUILD_CONFIGURATION }}' -- check-deps

- name: Test
run: dotnet test --no-build --verbosity normal --configuration '${{ env.BUILD_CONFIGURATION }}'
- name: Unit Tests
run: dotnet test --no-build --verbosity normal --configuration '${{ env.BUILD_CONFIGURATION }}' --filter FullyQualifiedName~UnitTests

- name: Integration Tests
run: dotnet test --no-build --verbosity normal --configuration '${{ env.BUILD_CONFIGURATION }}' --filter FullyQualifiedName~IntegrationTests
42 changes: 39 additions & 3 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,39 @@ jobs:
git config user.email release-bot@neolution.ch

- name: install release-it with plugins
run: npm install -g release-it @release-it/keep-a-changelog
run: npm install -g release-it@16.3.0 @release-it/keep-a-changelog@2.1.0

- name: Check Node.js version
run: |
node --version
npm --version
echo "=== PACKAGE.JSON DEBUG ==="
if [ -f package.json ]; then
echo "package.json exists:"
cat package.json
else
echo "No package.json found"
fi
echo "=== RELEASE-IT CONFIG DEBUG ==="
if [ -f .release-it.json ]; then
echo ".release-it.json contents:"
cat .release-it.json
else
echo "No .release-it.json found"
fi
echo "================================"

- name: run release-it
run: |
echo "=== WORKFLOW INPUTS DEBUG ==="
echo "bump_version_number: '${{ github.event.inputs.bump_version_number }}'"
echo "versioning_phase: '${{ github.event.inputs.versioning_phase }}'"
echo "is_dry_run: '${{ github.event.inputs.is_dry_run }}'"
echo "GITHUB_TOKEN (first 10 chars): ${GITHUB_TOKEN:0:10}..."
echo "Node.js version: $(node --version)"
echo "release-it version: $(release-it --version)"
echo "================================"

params=()

if [[ ${{ github.event.inputs.bump_version_number }} != "consecutive" ]]; then
Expand All @@ -62,17 +91,24 @@ jobs:

if [[ ${{ github.event.inputs.versioning_phase }} != "stable" ]]; then
params+=(--preRelease=${{ github.event.inputs.versioning_phase }})
params+=(--plugins.@release-it/keep-a-changelog.keepUnreleased)
params+=(--no-plugins.@release-it/keep-a-changelog.strictLatest)
fi

if [[ ${{ github.event.inputs.is_dry_run }} == "true" ]]; then
params+=(--dry-run)
fi

params+=(--ci)
params+=(--verbose)

echo "=== RELEASE-IT EXECUTION ==="
echo "command: release-it ${params[@]}"
echo "Working directory: $(pwd)"
echo "Git status:"
git status --porcelain
echo "Git tags (last 5):"
git tag --sort=-version:refname | head -5
echo "================================"

release-it "${params[@]}"
env:
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
8 changes: 5 additions & 3 deletions .release-it.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
"skipChecks": true
},
"github": {
"release": true
"release": true,
"releaseName": "Release ${version}"
},
"plugins": {
"@release-it/keep-a-changelog": {
"filename": "CHANGELOG.md",
"addVersionUrl": true,
"strictLatest": false,
"addUnreleased": true,
"strictLatest": false
"head": "Unreleased",
"keepUnreleased": "${preRelease}"
}
},
"hooks": {
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Fixed loading of `appsettings.Development.json` during `check-deps` runs by forcing the Development environment for dependency validation.
- Updated Scrutor to v6.1.0.
- Updated Microsoft.Extensions packages to latest patch versions.

Expand Down
2 changes: 0 additions & 2 deletions Neolution.DotNet.Console.Demo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ public static async Task Main(string[] args)
try
{
var builder = DotNetConsole.CreateDefaultBuilder(args);
DotNetConsoleLogger.Initialize(builder.Configuration);

var startup = new Startup(builder.Environment, builder.Configuration);
startup.ConfigureServices(builder.Services);
var console = builder.Build();
Expand Down
84 changes: 84 additions & 0 deletions Neolution.DotNet.Console.IntegrationTests/CheckDepsConsoleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
namespace Neolution.DotNet.Console.IntegrationTests
{
using System.Diagnostics;
using System.Threading.Tasks;
using Shouldly;
using Xunit;

/// <summary>
/// Integration tests for the CheckDepsConsole scenario.
/// </summary>
public class CheckDepsConsoleTests : IClassFixture<SolutionDirectoryFixture>
{
/// <summary>
/// The fixture that provides solution and project paths.
/// </summary>
private readonly SolutionDirectoryFixture fixture;

/// <summary>
/// Initializes a new instance of the <see cref="CheckDepsConsoleTests"/> class.
/// </summary>
/// <param name="fixture">The solution directory fixture.</param>
public CheckDepsConsoleTests(SolutionDirectoryFixture fixture)
{
this.fixture = fixture;
}

/// <summary>
/// Given the Demo app, when run with 'check-deps', then it prints the expected DI validation message.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
[Fact]
public async Task GivenDemoApp_WhenRunWithCheckDeps_ThenPrintsDependencyInjectionValidationSucceeded()
{
// Arrange
var restorePsi = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"restore \"{this.fixture.DemoProjectPath}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};

using (var restoreProcess = Process.Start(restorePsi))
{
if (restoreProcess is null)
{
throw new System.InvalidOperationException("Failed to start dotnet restore for Demo app.");
}

await restoreProcess.StandardOutput.ReadToEndAsync();
await restoreProcess.StandardError.ReadToEndAsync();
await restoreProcess.WaitForExitAsync();
restoreProcess.ExitCode.ShouldBe(0, "dotnet restore failed for Demo app");
}

var psi = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"run --project \"{this.fixture.DemoProjectPath}\" check-deps",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};

// Act
using var process = Process.Start(psi);
if (process is null)
{
throw new System.InvalidOperationException("Failed to start process for Demo app.");
}

var output = await process.StandardOutput.ReadToEndAsync();
var error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();

// Assert
output.ShouldContain("Dependency injection validation succeeded. All registered services can be constructed and no DI issues were found.");
process.ExitCode.ShouldBe(0, $"Process exited with code {process.ExitCode}. Error: {error}");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Neolution.CodeAnalysis.TestsRuleset" Version="3.3.0-beta.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Shouldly" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Neolution.DotNet.Console.IntegrationTests
{
using System.IO;

/// <summary>
/// Provides the solution directory and Demo project path for integration tests.
/// </summary>
public class SolutionDirectoryFixture
{
/// <summary>
/// Initializes a new instance of the <see cref="SolutionDirectoryFixture"/> class.
/// </summary>
public SolutionDirectoryFixture()
{
var dir = Directory.GetCurrentDirectory();
while (dir != null && !File.Exists(Path.Combine(dir, "Neolution.DotNet.Console.sln")))
{
dir = Path.GetDirectoryName(dir);
}

// Set the solution directory to the one containing the solution file
this.SolutionDirectory = dir ?? throw new DirectoryNotFoundException("Could not find solution directory.");

// Set the Demo project path relative to the solution directory
this.DemoProjectPath = Path.Combine(this.SolutionDirectory, "Neolution.DotNet.Console.Demo", "Neolution.DotNet.Console.Demo.csproj");
if (!File.Exists(this.DemoProjectPath))
{
throw new FileNotFoundException($"Demo project file not found: {this.DemoProjectPath}");
}
}

/// <summary>
/// Gets the solution directory path.
/// </summary>
public string SolutionDirectory { get; }

/// <summary>
/// Gets the Demo project file path.
/// </summary>
public string DemoProjectPath { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class DotNetConsoleBuilderTests
/// <summary>
/// The argument string for the internal check-deps command
/// </summary>
private const string CheckDependenciesArgumentString = "check-deps";
private const string CheckDependenciesArgumentString = DotNetConsoleDefaults.CheckDependenciesCommand;

/// <summary>
/// Given a mistyped verb, when a default verb is defined, then should throw on console building.
Expand Down
6 changes: 6 additions & 0 deletions Neolution.DotNet.Console.sln
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neolution.DotNet.Console.Un
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neolution.DotNet.Console.Demo", "Neolution.DotNet.Console.Demo\Neolution.DotNet.Console.Demo.csproj", "{3F17699A-2864-0EEC-AC50-93648D6E5BDE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neolution.DotNet.Console.IntegrationTests", "Neolution.DotNet.Console.IntegrationTests\Neolution.DotNet.Console.IntegrationTests.csproj", "{8C6D9105-CEF3-66FD-11A3-73BFF67273F6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -40,6 +42,10 @@ Global
{3F17699A-2864-0EEC-AC50-93648D6E5BDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F17699A-2864-0EEC-AC50-93648D6E5BDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F17699A-2864-0EEC-AC50-93648D6E5BDE}.Release|Any CPU.Build.0 = Release|Any CPU
{8C6D9105-CEF3-66FD-11A3-73BFF67273F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C6D9105-CEF3-66FD-11A3-73BFF67273F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C6D9105-CEF3-66FD-11A3-73BFF67273F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C6D9105-CEF3-66FD-11A3-73BFF67273F6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
23 changes: 16 additions & 7 deletions Neolution.DotNet.Console/DotNetConsoleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@ public IDotNetConsole Build()

if (this.checkDependencies)
{
// Use development environment before building because that's where ValidateScopes and ValidateOnBuild are enabled.
// Ensure development environment is used for dependency checking
// Note: The environment should already be set to Development during CreateConsoleEnvironment
// but we explicitly set it here as well to ensure ValidateScopes and ValidateOnBuild are enabled.
this.hostBuilder.UseEnvironment("Development");
this.hostBuilder.Build();

// If build was successful and did not throw an exception, return a console that does nothing and then terminates.
return new NoOperationConsole();
// If build was successful and did not throw an exception, return a console that logs a success message and then terminates.
return new CheckDepsConsole();
}

var host = this.hostBuilder.Build();
Expand All @@ -113,13 +115,19 @@ internal static DotNetConsoleBuilder CreateBuilderInternal(Assembly assembly, Ty
var environment = DotNetConsoleDefaults.CreateConsoleEnvironment(args);
var configuration = DotNetConsoleDefaults.CreateConsoleConfiguration(assembly, args, environment);

// Create a HostBuilder
// Initialize NLog logger from configuration with fallback; any config errors are handled in Initialize
DotNetConsoleLogger.Initialize(configuration);

// Create a HostBuilder and configure logging and services
var builder = Host.CreateDefaultBuilder(args)
.UseContentRoot(environment.ContentRootPath)
.ConfigureLogging((context, logging) =>
.ConfigureLogging((_, logging) =>
{
// Remove default providers and add core providers for debug and event sources
AdjustDefaultBuilderLoggingProviders(logging);
logging.AddNLog(context.Configuration);

// Add NLog provider using existing LogManager configuration
logging.AddNLog();
})
.ConfigureServices((_, services) =>
{
Expand All @@ -137,7 +145,8 @@ internal static DotNetConsoleBuilder CreateBuilderInternal(Assembly assembly, Ty
var parsedArguments = Parser.Default.ParseArguments(args, verbTypes);
var consoleBuilder = new DotNetConsoleBuilder(builder, parsedArguments, environment, configuration);

if (args.Length == 1 && string.Equals(args[0], "check-deps", StringComparison.OrdinalIgnoreCase))
// Determine if this is a check-deps run: only DI validation should run
if (DotNetConsoleDefaults.IsCheckDependenciesRun(args))
{
consoleBuilder.checkDependencies = true;
return consoleBuilder;
Expand Down
25 changes: 24 additions & 1 deletion Neolution.DotNet.Console/DotNetConsoleDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@
/// </summary>
internal static class DotNetConsoleDefaults
{
/// <summary>
/// The command argument used to trigger dependency validation.
/// </summary>
internal const string CheckDependenciesCommand = "check-deps";

/// <summary>
/// Determines if the given arguments represent a check-deps run.
/// </summary>
/// <param name="args">The command line arguments.</param>
/// <returns>True if this is a check-deps run, false otherwise.</returns>
public static bool IsCheckDependenciesRun(string[] args)
{
return args.Length == 1 && string.Equals(args[0], CheckDependenciesCommand, StringComparison.OrdinalIgnoreCase);
}

/// <summary>
/// Creates the console environment.
/// </summary>
Expand All @@ -27,9 +42,17 @@ internal static DotNetConsoleEnvironment CreateConsoleEnvironment(string[] args)
// The apps root directory is where the appsettings.json are located
var appRootDirectory = AppContext.BaseDirectory;

// Check if this is a check-deps run - if so, always use Development environment
var isCheckDepsRun = IsCheckDependenciesRun(args);

// Default to Production for normal runs, matching ASP.NET Core behavior
// For check-deps, always use Development to ensure appsettings.Development.json is loaded
// Environment can be overridden via DOTNET_ENVIRONMENT or command line arguments
var defaultEnvironment = isCheckDepsRun ? Environments.Development : Environments.Production;

return new DotNetConsoleEnvironment
{
EnvironmentName = configuration[HostDefaults.EnvironmentKey] ?? Environments.Production,
EnvironmentName = isCheckDepsRun ? Environments.Development : (configuration[HostDefaults.EnvironmentKey] ?? defaultEnvironment),
ApplicationName = AppDomain.CurrentDomain.FriendlyName,
ContentRootPath = appRootDirectory,
ContentRootFileProvider = new PhysicalFileProvider(appRootDirectory),
Expand Down
Loading