diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f87933f..fad4b29 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index 3a283d7..c8b6d95 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -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
@@ -62,8 +91,6 @@ 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
@@ -71,8 +98,17 @@ jobs:
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 }}
diff --git a/.release-it.json b/.release-it.json
index 3f22ba4..c2530de 100644
--- a/.release-it.json
+++ b/.release-it.json
@@ -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": {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96c89fc..f1f6272 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/Neolution.DotNet.Console.Demo/Program.cs b/Neolution.DotNet.Console.Demo/Program.cs
index 305c94d..eaeb986 100644
--- a/Neolution.DotNet.Console.Demo/Program.cs
+++ b/Neolution.DotNet.Console.Demo/Program.cs
@@ -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();
diff --git a/Neolution.DotNet.Console.IntegrationTests/CheckDepsConsoleTests.cs b/Neolution.DotNet.Console.IntegrationTests/CheckDepsConsoleTests.cs
new file mode 100644
index 0000000..2c87f27
--- /dev/null
+++ b/Neolution.DotNet.Console.IntegrationTests/CheckDepsConsoleTests.cs
@@ -0,0 +1,84 @@
+namespace Neolution.DotNet.Console.IntegrationTests
+{
+ using System.Diagnostics;
+ using System.Threading.Tasks;
+ using Shouldly;
+ using Xunit;
+
+ ///
+ /// Integration tests for the CheckDepsConsole scenario.
+ ///
+ public class CheckDepsConsoleTests : IClassFixture
+ {
+ ///
+ /// The fixture that provides solution and project paths.
+ ///
+ private readonly SolutionDirectoryFixture fixture;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The solution directory fixture.
+ public CheckDepsConsoleTests(SolutionDirectoryFixture fixture)
+ {
+ this.fixture = fixture;
+ }
+
+ ///
+ /// Given the Demo app, when run with 'check-deps', then it prints the expected DI validation message.
+ ///
+ /// A task representing the asynchronous operation.
+ [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}");
+ }
+ }
+}
diff --git a/Neolution.DotNet.Console.IntegrationTests/Neolution.DotNet.Console.IntegrationTests.csproj b/Neolution.DotNet.Console.IntegrationTests/Neolution.DotNet.Console.IntegrationTests.csproj
new file mode 100644
index 0000000..2d8635c
--- /dev/null
+++ b/Neolution.DotNet.Console.IntegrationTests/Neolution.DotNet.Console.IntegrationTests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net8.0
+ false
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/Neolution.DotNet.Console.IntegrationTests/SolutionDirectoryFixture.cs b/Neolution.DotNet.Console.IntegrationTests/SolutionDirectoryFixture.cs
new file mode 100644
index 0000000..f73d8dd
--- /dev/null
+++ b/Neolution.DotNet.Console.IntegrationTests/SolutionDirectoryFixture.cs
@@ -0,0 +1,42 @@
+namespace Neolution.DotNet.Console.IntegrationTests
+{
+ using System.IO;
+
+ ///
+ /// Provides the solution directory and Demo project path for integration tests.
+ ///
+ public class SolutionDirectoryFixture
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ 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}");
+ }
+ }
+
+ ///
+ /// Gets the solution directory path.
+ ///
+ public string SolutionDirectory { get; }
+
+ ///
+ /// Gets the Demo project file path.
+ ///
+ public string DemoProjectPath { get; }
+ }
+}
diff --git a/Neolution.DotNet.Console.UnitTests/DotNetConsoleBuilderTests.cs b/Neolution.DotNet.Console.UnitTests/DotNetConsoleBuilderTests.cs
index e3544f0..cd914b9 100644
--- a/Neolution.DotNet.Console.UnitTests/DotNetConsoleBuilderTests.cs
+++ b/Neolution.DotNet.Console.UnitTests/DotNetConsoleBuilderTests.cs
@@ -17,7 +17,7 @@ public class DotNetConsoleBuilderTests
///
/// The argument string for the internal check-deps command
///
- private const string CheckDependenciesArgumentString = "check-deps";
+ private const string CheckDependenciesArgumentString = DotNetConsoleDefaults.CheckDependenciesCommand;
///
/// Given a mistyped verb, when a default verb is defined, then should throw on console building.
diff --git a/Neolution.DotNet.Console.sln b/Neolution.DotNet.Console.sln
index 67bf04b..5e64a93 100644
--- a/Neolution.DotNet.Console.sln
+++ b/Neolution.DotNet.Console.sln
@@ -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
@@ -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
diff --git a/Neolution.DotNet.Console/DotNetConsoleBuilder.cs b/Neolution.DotNet.Console/DotNetConsoleBuilder.cs
index 442f4d0..fbe4316 100644
--- a/Neolution.DotNet.Console/DotNetConsoleBuilder.cs
+++ b/Neolution.DotNet.Console/DotNetConsoleBuilder.cs
@@ -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();
@@ -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) =>
{
@@ -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;
diff --git a/Neolution.DotNet.Console/DotNetConsoleDefaults.cs b/Neolution.DotNet.Console/DotNetConsoleDefaults.cs
index 6744df4..b4dd50a 100644
--- a/Neolution.DotNet.Console/DotNetConsoleDefaults.cs
+++ b/Neolution.DotNet.Console/DotNetConsoleDefaults.cs
@@ -12,6 +12,21 @@
///
internal static class DotNetConsoleDefaults
{
+ ///
+ /// The command argument used to trigger dependency validation.
+ ///
+ internal const string CheckDependenciesCommand = "check-deps";
+
+ ///
+ /// Determines if the given arguments represent a check-deps run.
+ ///
+ /// The command line arguments.
+ /// True if this is a check-deps run, false otherwise.
+ public static bool IsCheckDependenciesRun(string[] args)
+ {
+ return args.Length == 1 && string.Equals(args[0], CheckDependenciesCommand, StringComparison.OrdinalIgnoreCase);
+ }
+
///
/// Creates the console environment.
///
@@ -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),
diff --git a/Neolution.DotNet.Console/DotNetConsoleLogger.cs b/Neolution.DotNet.Console/DotNetConsoleLogger.cs
index 5692252..c579472 100644
--- a/Neolution.DotNet.Console/DotNetConsoleLogger.cs
+++ b/Neolution.DotNet.Console/DotNetConsoleLogger.cs
@@ -4,7 +4,6 @@
using Microsoft.Extensions.Configuration;
using NLog;
using NLog.Extensions.Logging;
- using NLog.Targets;
///
/// Provides static methods to initialize and manage a logger instance.
@@ -26,47 +25,39 @@ public static Logger Log
{
if (logger == null)
{
- throw new InvalidOperationException("Logger has not been initialized. Call Initialize(configuration) first.");
+ throw new InvalidOperationException("Logger has not yet been initialized.");
}
return logger;
}
}
+ ///
+ /// Ensures the logger flushes messages and shuts down internal timers.
+ ///
+ public static void Shutdown()
+ {
+ LogManager.Shutdown();
+ }
+
///
/// Initializes the logger based on the provided configuration.
///
/// The configuration used to initialize the logger.
- public static void Initialize(IConfiguration configuration)
+ internal static void Initialize(IConfiguration configuration)
{
- ConsoleTarget? consoleTarget = null;
try
{
- logger = LogManager.Setup().LoadConfigurationFromSection(configuration).GetCurrentClassLogger();
+ LogManager.Setup().LoadConfigurationFromSection(configuration);
+ logger = LogManager.GetCurrentClassLogger();
}
catch (Exception ex)
{
- // Create a simple NLog configuration that logs to the console
- var config = new NLog.Config.LoggingConfiguration();
- consoleTarget = new ConsoleTarget("console");
- config.AddRule(LogLevel.Trace, LogLevel.Fatal, consoleTarget);
-
- LogManager.Configuration = config;
+ // Fallback: minimal console logger setup using NLog fluent API
+ LogManager.Setup().LoadConfiguration(builder => builder.ForLogger().WriteToConsole());
logger = LogManager.GetCurrentClassLogger();
- logger.Error(ex, "Logger initialization failed");
+ logger.Error(ex, "Logger initialization failed.");
}
- finally
- {
- consoleTarget?.Dispose();
- }
- }
-
- ///
- /// Ensures the logger flushes messages and shuts down internal timers.
- ///
- public static void Shutdown()
- {
- LogManager.Shutdown();
}
}
}
diff --git a/Neolution.DotNet.Console/Internal/NoOperationConsole.cs b/Neolution.DotNet.Console/Internal/CheckDepsConsole.cs
similarity index 61%
rename from Neolution.DotNet.Console/Internal/NoOperationConsole.cs
rename to Neolution.DotNet.Console/Internal/CheckDepsConsole.cs
index 2e43ace..f65f1ec 100644
--- a/Neolution.DotNet.Console/Internal/NoOperationConsole.cs
+++ b/Neolution.DotNet.Console/Internal/CheckDepsConsole.cs
@@ -1,15 +1,16 @@
namespace Neolution.DotNet.Console.Internal
{
using System;
+ using System.Globalization;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Neolution.DotNet.Console.Abstractions;
///
- /// The no operation console application.
+ /// The check dependencies console application.
///
///
- public class NoOperationConsole : IDotNetConsole
+ internal class CheckDepsConsole : IDotNetConsole
{
///
public IServiceProvider Services => new ServiceCollection().BuildServiceProvider();
@@ -17,6 +18,7 @@ public class NoOperationConsole : IDotNetConsole
///
public Task RunAsync()
{
+ DotNetConsoleLogger.Log.Info(CultureInfo.InvariantCulture, message: "Dependency injection validation succeeded. All registered services can be constructed and no DI issues were found.");
return Task.CompletedTask;
}
}
diff --git a/Neolution.DotNet.Console/Properties/AssemblyInfo.cs b/Neolution.DotNet.Console/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..44efd0d
--- /dev/null
+++ b/Neolution.DotNet.Console/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Neolution.DotNet.Console.UnitTests")]