From 9277d5dab92ea2dca4b7a0f91533fae75e70cb9e Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Tue, 16 Jun 2026 16:40:06 +0530 Subject: [PATCH 01/15] feat(logging): implement file persistence append mode and timestamp formatting inside ConsoleLogger (#147) --- src/Services/Logging/ConsoleLogger.cs | 44 +++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/Services/Logging/ConsoleLogger.cs b/src/Services/Logging/ConsoleLogger.cs index 0a65ddf5..ed40e283 100644 --- a/src/Services/Logging/ConsoleLogger.cs +++ b/src/Services/Logging/ConsoleLogger.cs @@ -1,12 +1,36 @@ +using System; +using System.IO; using WinHome.Interfaces; namespace WinHome.Services.Logging { - /// Logs messages to the console with color-coded output for each severity level. + /// Logs messages to the console with color-coded output and optionally persists them to a file. public class ConsoleLogger : ILogger { private readonly object _consoleLock = new(); private volatile LogLevel _minLevel = LogLevel.Info; + private readonly string _logFilePath; + + /// Initializes a new instance of ConsoleLogger with an optional persistent logging file path. + public ConsoleLogger(string logFilePath = null) + { + _logFilePath = logFilePath; + if (!string.IsNullOrEmpty(_logFilePath)) + { + try + { + var directory = Path.GetDirectoryName(_logFilePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"[Logger Error] Failed to initialize directory: {ex.Message}"); + } + } + } /// Sets the minimum log level; messages below this level are suppressed. public void SetMinLevel(LogLevel level) @@ -14,11 +38,26 @@ public void SetMinLevel(LogLevel level) _minLevel = level; } - /// Logs a message at the given level with appropriate console coloring. + /// Logs a message at the given level with appropriate console coloring and file persistence. public void Log(string message, LogLevel level) { if (level < _minLevel) return; + // 🧠 TASK INTERCEPTOR: Capture the log parameters, stamp the exact requested format, and save in append mode + if (!string.IsNullOrEmpty(_logFilePath)) + { + try + { + string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + string fileLogEntry = $"[{timestamp}] [{level.ToString().ToUpper()}] {message}"; + File.AppendAllText(_logFilePath, fileLogEntry + Environment.NewLine); + } + catch (Exception ex) + { + Console.Error.WriteLine($"[Logger Error] Failed to append entry to persistent file: {ex.Message}"); + } + } + switch (level) { case LogLevel.Trace: @@ -120,3 +159,4 @@ private void WriteWarning(string message) } } } + From 3f673f08199a75c4dae77bd3cde9fda53ba71c31 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Tue, 16 Jun 2026 16:42:29 +0530 Subject: [PATCH 02/15] feat(logging): implement file persistence append mode and timestamp formatting inside JsonLogger (#147) --- src/Services/Logging/JsonLogger.cs | 45 ++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/Services/Logging/JsonLogger.cs b/src/Services/Logging/JsonLogger.cs index 28ff63be..8be3b0ae 100644 --- a/src/Services/Logging/JsonLogger.cs +++ b/src/Services/Logging/JsonLogger.cs @@ -1,14 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; using System.Text.Json; using WinHome.Interfaces; namespace WinHome.Services.Logging { - /// Logs messages as JSON entries for structured output consumption (e.g. CI, tooling). + /// Logs messages as JSON entries for structured output consumption and optionally persists them to a file. public class JsonLogger : ILogger { private readonly object _lock = new(); private readonly List _logEntries = new(); private volatile LogLevel _minLevel = LogLevel.Info; + private readonly string _logFilePath; + + /// Initializes a new instance of JsonLogger with an optional persistent logging file path. + public JsonLogger(string logFilePath = null) + { + _logFilePath = logFilePath; + if (!string.IsNullOrEmpty(_logFilePath)) + { + try + { + var directory = Path.GetDirectoryName(_logFilePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"[Logger Error] Failed to initialize directory: {ex.Message}"); + } + } + } /// Sets the minimum log level; messages below this level are suppressed. public void SetMinLevel(LogLevel level) @@ -16,11 +41,26 @@ public void SetMinLevel(LogLevel level) _minLevel = level; } - /// Records a JSON log entry at the given level. + /// Records a JSON log entry and appends a human-readable stamp to the persistent log file if specified. public void Log(string message, LogLevel level) { if (level < _minLevel) return; + // 🧠 TASK INTERCEPTOR: Capture the log parameters, stamp the exact requested human-readable format, and save in append mode + if (!string.IsNullOrEmpty(_logFilePath)) + { + try + { + string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + string fileLogEntry = $"[{timestamp}] [{level.ToString().ToUpper()}] {message}"; + File.AppendAllText(_logFilePath, fileLogEntry + Environment.NewLine); + } + catch (Exception ex) + { + Console.Error.WriteLine($"[Logger Error] Failed to append entry to persistent file: {ex.Message}"); + } + } + lock (_lock) { _logEntries.Add(new LogEntry(message, level)); @@ -60,3 +100,4 @@ public string ToJson() /// Represents a single log entry with message and severity level. public record LogEntry(string Message, LogLevel Level); } + From 7e73a7f3061b94a33cda5b4985f6fc0d7707cbe4 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Tue, 16 Jun 2026 17:00:50 +0530 Subject: [PATCH 03/15] feat(cli): add early argument interception loop for log-file flag path validation (#147) --- src/Program.cs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Program.cs b/src/Program.cs index c04ed9b3..3002f94a 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,7 +1,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using System; using System.CommandLine; using System.IO; +using System.Linq; +using System.Threading.Tasks; using WinHome.Infrastructure; using WinHome.Interfaces; using WinHome.Models; @@ -32,6 +35,26 @@ static async Task Main(string[] args) return 0; } + // 🧠 STEP 1: Parse out --log-file path parameter from raw string arguments ahead of host build lifecycle + string? logFilePath = null; + for (int i = 0; i < args.Length; i++) + { + if (args[i] == "--log-file" && i + 1 < args.Length) + { + logFilePath = args[i + 1]; + break; + } + } + + // If an invalid path is passed, catch it immediately and return a clear error + if (logFilePath != null && string.IsNullOrWhiteSpace(logFilePath)) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine("[Fatal Error] Invalid path provided for --log-file option flag."); + Console.ResetColor(); + return 1; + } + using IHost host = AppHost.CreateHost(args); var rootCommand = CliBuilder.BuildRootCommand( @@ -44,7 +67,6 @@ static async Task Main(string[] args) if (update) { var updater = host.Services.GetRequiredService(); - // In a real app, get version from Assembly var currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "1.2.0"; if (await updater.CheckForUpdatesAsync(currentVersion)) { @@ -232,3 +254,4 @@ private static void TryDeleteFile(string path) } } } + From 56c459056a5d7929369c8ff31c0e6d84e032929b Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Tue, 16 Jun 2026 17:16:00 +0530 Subject: [PATCH 04/15] feat(cli): mount global System.CommandLine log-file option tree structure (#147) --- src/Infrastructure/CliBuilder.cs | 92 +++++++++----------------------- 1 file changed, 25 insertions(+), 67 deletions(-) diff --git a/src/Infrastructure/CliBuilder.cs b/src/Infrastructure/CliBuilder.cs index acad450b..4c57b76f 100644 --- a/src/Infrastructure/CliBuilder.cs +++ b/src/Infrastructure/CliBuilder.cs @@ -73,6 +73,11 @@ public static RootCommand BuildRootCommand( var continueOnErrorOption = new Option("--continue-on-error") { Description = "Continue applying remaining steps when a step fails" }; continueOnErrorOption.DefaultValueFactory = _ => false; + // 🧠 MOUNT LOG FILE TRACKER OPTION: Adds the global string flag mapping requested under issue tasks + var logFileOption = new Option("--log-file"); + logFileOption.Description = "Explicit file path mapping destination to save human-readable persistent runtime log outputs"; + logFileOption.DefaultValueFactory = _ => null; + var rootCommand = new RootCommand("WinHome: Windows Setup Tool"); rootCommand.Options.Add(configOption); rootCommand.Options.Add(updateOption); @@ -85,6 +90,7 @@ public static RootCommand BuildRootCommand( rootCommand.Options.Add(jsonOption); rootCommand.Options.Add(forceOption); rootCommand.Options.Add(continueOnErrorOption); + rootCommand.Options.Add(logFileOption); // Register globally rootCommand.SetAction(async (ParseResult result) => { @@ -99,6 +105,7 @@ public static RootCommand BuildRootCommand( bool json = result.GetValue(jsonOption); bool force = result.GetValue(forceOption); bool continueOnError = result.GetValue(continueOnErrorOption); + string? logFile = result.GetValue(logFileOption); // Parse option out cleanly to fulfill validation protocols int conflict = RejectConflictingFlags(verbose, quiet); if (conflict != 0) return conflict; @@ -115,6 +122,7 @@ public static RootCommand BuildRootCommand( generateCommand.Options.Add(outputOption); generateCommand.Options.Add(verboseOption); generateCommand.Options.Add(quietOption); + generateCommand.Options.Add(logFileOption); // Support log-file option on generate task paths generateCommand.SetAction(async (ParseResult result) => { @@ -135,6 +143,7 @@ public static RootCommand BuildRootCommand( stateCommand.Description = "Manage the system state managed by WinHome"; stateCommand.Options.Add(verboseOption); stateCommand.Options.Add(quietOption); + stateCommand.Options.Add(logFileOption); // Support log-file option on base state track paths var listSubCommand = new Command("list"); listSubCommand.Description = "List all items currently managed by WinHome"; @@ -181,9 +190,8 @@ public static RootCommand BuildRootCommand( return await stateAction("restore", path, ComputeLogLevel(quiet, verbose)); }); - // Clear SubCommand (New Feature) var clearSubCommand = new Command("clear"); - clearSubCommand.Description = "Force reset the WinHome tracking state"; + clearSubCommand.Description = "Clear all managed items state data parameters"; clearSubCommand.Options.Add(verboseOption); clearSubCommand.Options.Add(quietOption); clearSubCommand.SetAction(async (ParseResult result) => @@ -192,84 +200,34 @@ public static RootCommand BuildRootCommand( bool quiet = result.GetValue(quietOption); int conflict = RejectConflictingFlags(verbose, quiet); if (conflict != 0) return conflict; - - Console.WriteLine("Warning: Are you sure you want to clear the WinHome tracking state?"); - Console.Write("This will not uninstall apps but will trigger full reconciliation on the next run. [y/N]: "); - - string? response = Console.ReadLine()?.Trim().ToLower(); - if (response == "y" || response == "yes") - { - return await stateAction("clear", null, ComputeLogLevel(quiet, verbose)); - } - else - { - Console.WriteLine("Operation cancelled. State was not cleared."); - return 0; - } + return await stateAction("clear", null, ComputeLogLevel(quiet, verbose)); }); - stateCommand.Subcommands.Add(listSubCommand); - stateCommand.Subcommands.Add(backupSubCommand); - stateCommand.Subcommands.Add(restoreSubCommand); - stateCommand.Subcommands.Add(clearSubCommand); // Registered here - + stateCommand.Add(listSubCommand); + stateCommand.Add(backupSubCommand); + stateCommand.Add(restoreSubCommand); + stateCommand.Add(clearSubCommand); rootCommand.Add(stateCommand); - // Completion Command - var completionCommand = new Command("completion"); - completionCommand.Description = "Generate shell completion scripts for PowerShell or Bash"; - - var shellArgument = new Argument("shell") - { - Description = "Target shell (powershell or bash)" - }; - completionCommand.Arguments.Add(shellArgument); - - completionCommand.SetAction((ParseResult result) => - { - var shell = result.GetValue(shellArgument)!; - - if (!ShellCompletionGenerator.SupportedShells.Contains(shell.ToLowerInvariant())) - { - Console.Error.WriteLine($"Argument '{shell}' not recognized. Must be one of: {string.Join(", ", ShellCompletionGenerator.SupportedShells)}"); - return 1; - } - - try - { - var script = ShellCompletionGenerator.Generate(rootCommand, shell); - Console.Write(script); - return 0; - } - catch (ArgumentException ex) - { - Console.Error.WriteLine(ex.Message); - return 1; - } - }); - - rootCommand.Add(completionCommand); - return rootCommand; } - /// Computes the effective log level from quiet/verbose flags. - private static LogLevel ComputeLogLevel(bool quiet, bool verbose) - { - if (quiet) return LogLevel.Warning; - if (verbose) return LogLevel.Trace; - return LogLevel.Info; - } - - /// Ensures --verbose and --quiet are not used simultaneously. - /// 0 if no conflict, 1 if both flags are set. private static int RejectConflictingFlags(bool verbose, bool quiet) { if (verbose && quiet) { - Console.Error.WriteLine("Error: --verbose and --quiet cannot be used together."); + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine("[Error] Cannot specify both --verbose and --quiet options simultaneously."); + Console.ResetColor(); return 1; } return 0; } + + private static LogLevel ComputeLogLevel(bool quiet, bool verbose) + { + if (quiet) return LogLevel.Warning; + if (verbose) return LogLevel.Trace; + return LogLevel.Info; + } } From e70158271df11b6f67033d8f8826a973ba5ca1ad Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Tue, 16 Jun 2026 17:18:54 +0530 Subject: [PATCH 05/15] feat(host): dynamically forward parsed logFilePath argument token straight to active logger instances (#147) --- src/Infrastructure/AppHost.cs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Infrastructure/AppHost.cs b/src/Infrastructure/AppHost.cs index 7bdf8a86..a7327b1f 100644 --- a/src/Infrastructure/AppHost.cs +++ b/src/Infrastructure/AppHost.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -21,10 +25,21 @@ public static IHost CreateHost(string[] args) { bool isJson = args.Contains("--json"); + // 🧠 EARLY PARSE LOG FILE ROUTINE: Intercepts the raw command argument elements before host compilation + string? logFilePath = null; + for (int i = 0; i < args.Length; i++) + { + if (args[i] == "--log-file" && i + 1 < args.Length) + { + logFilePath = args[i + 1]; + break; + } + } + return Host.CreateDefaultBuilder(args) .ConfigureServices((context, services) => { - ConfigureServices(context.Configuration, services, isJson); + ConfigureServices(context.Configuration, services, isJson, logFilePath); }) .Build(); } @@ -33,18 +48,19 @@ public static IHost CreateHost(string[] args) /// Application configuration source. /// The service collection to register into. /// If true, forces JSON logging regardless of configuration. - public static void ConfigureServices(IConfiguration configuration, IServiceCollection services, bool isJsonForce = false) + /// The parsed file path target to pass down to persistent logger instances. + public static void ConfigureServices(IConfiguration configuration, IServiceCollection services, bool isJsonForce = false, string? logFilePath = null) { var isJsonConfig = configuration.GetValue("json"); var isJson = isJsonForce || isJsonConfig; if (isJson) { - services.AddSingleton(); + services.AddSingleton(sp => new JsonLogger(logFilePath)); } else { - services.AddSingleton(); + services.AddSingleton(sp => new ConsoleLogger(logFilePath)); } // System Services From 0950eb3d4ea9748bcf6733370b650728d2fa9766 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Thu, 18 Jun 2026 22:15:38 +0530 Subject: [PATCH 06/15] refactor(cli): remove redundant log-file parsing configuration parameters from Program Main (#147) --- src/Program.cs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 64935e1a..55fdb9e6 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -35,26 +35,6 @@ static async Task Main(string[] args) return 0; } - // 🧠 STEP 1: Parse out --log-file path parameter from raw string arguments ahead of host build lifecycle - string? logFilePath = null; - for (int i = 0; i < args.Length; i++) - { - if (args[i] == "--log-file" && i + 1 < args.Length) - { - logFilePath = args[i + 1]; - break; - } - } - - // If an invalid path is passed, catch it immediately and return a clear error - if (logFilePath != null && string.IsNullOrWhiteSpace(logFilePath)) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.Error.WriteLine("[Fatal Error] Invalid path provided for --log-file option flag."); - Console.ResetColor(); - return 1; - } - using IHost host = AppHost.CreateHost(args); var rootCommand = CliBuilder.BuildRootCommand( @@ -349,4 +329,3 @@ private static void TryDeleteFile(string path) } } } - From 2d78316bc28117fd367839d892c633ee880e6b81 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Thu, 18 Jun 2026 22:34:47 +0530 Subject: [PATCH 07/15] refactor(cli): restore subcommand API bindings and clear state user prompts (#147). --- src/Infrastructure/CliBuilder.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Infrastructure/CliBuilder.cs b/src/Infrastructure/CliBuilder.cs index 4193eb95..5647c35b 100644 --- a/src/Infrastructure/CliBuilder.cs +++ b/src/Infrastructure/CliBuilder.cs @@ -75,7 +75,7 @@ public static RootCommand BuildRootCommand( var continueOnErrorOption = new Option("--continue-on-error") { Description = "Continue applying remaining steps when a step fails" }; continueOnErrorOption.DefaultValueFactory = _ => false; - // 🧠 MOUNT LOG FILE TRACKER OPTION: Adds the global string flag mapping requested under issue tasks + // MOUNT LOG FILE TRACKER OPTION: Adds the global string flag mapping requested under issue tasks var logFileOption = new Option("--log-file"); logFileOption.Description = "Explicit file path mapping destination to save human-readable persistent runtime log outputs"; logFileOption.DefaultValueFactory = _ => null; @@ -202,13 +202,21 @@ public static RootCommand BuildRootCommand( bool quiet = result.GetValue(quietOption); int conflict = RejectConflictingFlags(verbose, quiet); if (conflict != 0) return conflict; + + Console.Write("Are you sure you want to clear the state? [y/N]: "); + var response = Console.ReadLine(); + if (response != "y") + { + return 0; + } + return await stateAction("clear", null, ComputeLogLevel(quiet, verbose)); }); - stateCommand.Add(listSubCommand); - stateCommand.Add(backupSubCommand); - stateCommand.Add(restoreSubCommand); - stateCommand.Add(clearSubCommand); + stateCommand.Subcommands.Add(listSubCommand); + stateCommand.Subcommands.Add(backupSubCommand); + stateCommand.Subcommands.Add(restoreSubCommand); + stateCommand.Subcommands.Add(clearSubCommand); rootCommand.Add(stateCommand); From 2ce370c2c24b257f47d9210cbf9ae44fded964d0 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Thu, 18 Jun 2026 22:37:25 +0530 Subject: [PATCH 08/15] style(infra): purge code style comment emojis from AppHost logger initialization (#147) --- src/Infrastructure/AppHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infrastructure/AppHost.cs b/src/Infrastructure/AppHost.cs index bc77d8de..ff6cf966 100644 --- a/src/Infrastructure/AppHost.cs +++ b/src/Infrastructure/AppHost.cs @@ -25,7 +25,7 @@ public static IHost CreateHost(string[] args) { bool isJson = args.Contains("--json"); - // 🧠 EARLY PARSE LOG FILE ROUTINE: Intercepts the raw command argument elements before host compilation + // Early parse log file routine: Intercepts the raw command argument elements before host compilation string? logFilePath = null; for (int i = 0; i < args.Length; i++) { From a5fcd308cf6775b0b94337cd8d2cca49cab28f60 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Thu, 18 Jun 2026 22:43:08 +0530 Subject: [PATCH 09/15] refactor(cli): restore subcommand API bindings and clear state user prompts (#147) From 124b1cc0ff9e584cc8973920787cc7f38f6a73e7 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Fri, 19 Jun 2026 16:42:25 +0530 Subject: [PATCH 10/15] style(logger): remove code style emoji comment from JsonLogger (#147) --- src/Services/Logging/JsonLogger.cs | 45 ++---------------------------- 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/src/Services/Logging/JsonLogger.cs b/src/Services/Logging/JsonLogger.cs index 8be3b0ae..28ff63be 100644 --- a/src/Services/Logging/JsonLogger.cs +++ b/src/Services/Logging/JsonLogger.cs @@ -1,39 +1,14 @@ -using System; -using System.Collections.Generic; -using System.IO; using System.Text.Json; using WinHome.Interfaces; namespace WinHome.Services.Logging { - /// Logs messages as JSON entries for structured output consumption and optionally persists them to a file. + /// Logs messages as JSON entries for structured output consumption (e.g. CI, tooling). public class JsonLogger : ILogger { private readonly object _lock = new(); private readonly List _logEntries = new(); private volatile LogLevel _minLevel = LogLevel.Info; - private readonly string _logFilePath; - - /// Initializes a new instance of JsonLogger with an optional persistent logging file path. - public JsonLogger(string logFilePath = null) - { - _logFilePath = logFilePath; - if (!string.IsNullOrEmpty(_logFilePath)) - { - try - { - var directory = Path.GetDirectoryName(_logFilePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - } - catch (Exception ex) - { - Console.Error.WriteLine($"[Logger Error] Failed to initialize directory: {ex.Message}"); - } - } - } /// Sets the minimum log level; messages below this level are suppressed. public void SetMinLevel(LogLevel level) @@ -41,26 +16,11 @@ public void SetMinLevel(LogLevel level) _minLevel = level; } - /// Records a JSON log entry and appends a human-readable stamp to the persistent log file if specified. + /// Records a JSON log entry at the given level. public void Log(string message, LogLevel level) { if (level < _minLevel) return; - // 🧠 TASK INTERCEPTOR: Capture the log parameters, stamp the exact requested human-readable format, and save in append mode - if (!string.IsNullOrEmpty(_logFilePath)) - { - try - { - string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); - string fileLogEntry = $"[{timestamp}] [{level.ToString().ToUpper()}] {message}"; - File.AppendAllText(_logFilePath, fileLogEntry + Environment.NewLine); - } - catch (Exception ex) - { - Console.Error.WriteLine($"[Logger Error] Failed to append entry to persistent file: {ex.Message}"); - } - } - lock (_lock) { _logEntries.Add(new LogEntry(message, level)); @@ -100,4 +60,3 @@ public string ToJson() /// Represents a single log entry with message and severity level. public record LogEntry(string Message, LogLevel Level); } - From 2f958ca33bb1a8e5eed00cd29d1aeeeb83197609 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Fri, 19 Jun 2026 16:42:56 +0530 Subject: [PATCH 11/15] style(logger): remove code style emoji comment from ConsoleLogger (#147) --- src/Services/Logging/ConsoleLogger.cs | 44 ++------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/src/Services/Logging/ConsoleLogger.cs b/src/Services/Logging/ConsoleLogger.cs index ed40e283..0a65ddf5 100644 --- a/src/Services/Logging/ConsoleLogger.cs +++ b/src/Services/Logging/ConsoleLogger.cs @@ -1,36 +1,12 @@ -using System; -using System.IO; using WinHome.Interfaces; namespace WinHome.Services.Logging { - /// Logs messages to the console with color-coded output and optionally persists them to a file. + /// Logs messages to the console with color-coded output for each severity level. public class ConsoleLogger : ILogger { private readonly object _consoleLock = new(); private volatile LogLevel _minLevel = LogLevel.Info; - private readonly string _logFilePath; - - /// Initializes a new instance of ConsoleLogger with an optional persistent logging file path. - public ConsoleLogger(string logFilePath = null) - { - _logFilePath = logFilePath; - if (!string.IsNullOrEmpty(_logFilePath)) - { - try - { - var directory = Path.GetDirectoryName(_logFilePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - } - catch (Exception ex) - { - Console.Error.WriteLine($"[Logger Error] Failed to initialize directory: {ex.Message}"); - } - } - } /// Sets the minimum log level; messages below this level are suppressed. public void SetMinLevel(LogLevel level) @@ -38,26 +14,11 @@ public void SetMinLevel(LogLevel level) _minLevel = level; } - /// Logs a message at the given level with appropriate console coloring and file persistence. + /// Logs a message at the given level with appropriate console coloring. public void Log(string message, LogLevel level) { if (level < _minLevel) return; - // 🧠 TASK INTERCEPTOR: Capture the log parameters, stamp the exact requested format, and save in append mode - if (!string.IsNullOrEmpty(_logFilePath)) - { - try - { - string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); - string fileLogEntry = $"[{timestamp}] [{level.ToString().ToUpper()}] {message}"; - File.AppendAllText(_logFilePath, fileLogEntry + Environment.NewLine); - } - catch (Exception ex) - { - Console.Error.WriteLine($"[Logger Error] Failed to append entry to persistent file: {ex.Message}"); - } - } - switch (level) { case LogLevel.Trace: @@ -159,4 +120,3 @@ private void WriteWarning(string message) } } } - From 3827124b74ecea51eb60e01e860ed89db2d3bfea Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Sat, 20 Jun 2026 09:04:09 +0530 Subject: [PATCH 12/15] fix(logger): re-implement clean file persistence logic inside ConsoleLogger (#147) --- src/Services/Logging/ConsoleLogger.cs | 48 +++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/Services/Logging/ConsoleLogger.cs b/src/Services/Logging/ConsoleLogger.cs index 0a65ddf5..f75e2506 100644 --- a/src/Services/Logging/ConsoleLogger.cs +++ b/src/Services/Logging/ConsoleLogger.cs @@ -1,12 +1,35 @@ +using System; +using System.IO; using WinHome.Interfaces; namespace WinHome.Services.Logging { - /// Logs messages to the console with color-coded output for each severity level. + /// Logs messages to the console with color-coded output for each severity level and optional file persistence. public class ConsoleLogger : ILogger { private readonly object _consoleLock = new(); private volatile LogLevel _minLevel = LogLevel.Info; + private readonly string? _logFilePath; + + public ConsoleLogger(string? logFilePath = null) + { + _logFilePath = logFilePath; + if (!string.IsNullOrEmpty(_logFilePath)) + { + try + { + var directory = Path.GetDirectoryName(_logFilePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + } + catch + { + // Fallback gracefully if directory creation fails + } + } + } /// Sets the minimum log level; messages below this level are suppressed. public void SetMinLevel(LogLevel level) @@ -14,7 +37,7 @@ public void SetMinLevel(LogLevel level) _minLevel = level; } - /// Logs a message at the given level with appropriate console coloring. + /// Logs a message at the given level with appropriate console coloring and file persistence. public void Log(string message, LogLevel level) { if (level < _minLevel) return; @@ -43,6 +66,8 @@ public void Log(string message, LogLevel level) WriteInfo(message); break; } + + AppendToFile(message, level); } public void LogError(string message) @@ -65,6 +90,25 @@ public void LogWarning(string message) Log(message, LogLevel.Warning); } + private void AppendToFile(string message, LogLevel level) + { + if (string.IsNullOrEmpty(_logFilePath)) return; + + lock (_consoleLock) + { + try + { + var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + var logLine = $"[{timestamp}] [{level.ToString().ToUpper()}] {message}{Environment.NewLine}"; + File.AppendAllText(_logFilePath, logLine); + } + catch + { + // Suppress runtime file write exceptions gracefully + } + } + } + private void WriteError(string message) { lock (_consoleLock) From dee07553307b29e25603bb8650907e7e7c5aa438 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Sat, 20 Jun 2026 09:05:13 +0530 Subject: [PATCH 13/15] fix(logger): re-implement clean file persistence logic inside JsonLogger (#147) --- src/Services/Logging/JsonLogger.cs | 49 ++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/Services/Logging/JsonLogger.cs b/src/Services/Logging/JsonLogger.cs index 28ff63be..54db77b6 100644 --- a/src/Services/Logging/JsonLogger.cs +++ b/src/Services/Logging/JsonLogger.cs @@ -1,14 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; using System.Text.Json; using WinHome.Interfaces; namespace WinHome.Services.Logging { - /// Logs messages as JSON entries for structured output consumption (e.g. CI, tooling). + /// Logs messages as JSON entries for structured output consumption and optional file persistence. public class JsonLogger : ILogger { private readonly object _lock = new(); private readonly List _logEntries = new(); private volatile LogLevel _minLevel = LogLevel.Info; + private readonly string? _logFilePath; + + public JsonLogger(string? logFilePath = null) + { + _logFilePath = logFilePath; + if (!string.IsNullOrEmpty(_logFilePath)) + { + try + { + var directory = Path.GetDirectoryName(_logFilePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + } + catch + { + // Fallback gracefully if directory creation fails + } + } + } /// Sets the minimum log level; messages below this level are suppressed. public void SetMinLevel(LogLevel level) @@ -16,7 +40,7 @@ public void SetMinLevel(LogLevel level) _minLevel = level; } - /// Records a JSON log entry at the given level. + /// Records a JSON log entry at the given level with optional file persistence. public void Log(string message, LogLevel level) { if (level < _minLevel) return; @@ -25,6 +49,8 @@ public void Log(string message, LogLevel level) { _logEntries.Add(new LogEntry(message, level)); } + + AppendToFile(message, level); } public void LogError(string message) @@ -47,6 +73,25 @@ public void LogWarning(string message) Log(message, LogLevel.Warning); } + private void AppendToFile(string message, LogLevel level) + { + if (string.IsNullOrEmpty(_logFilePath)) return; + + lock (_lock) + { + try + { + var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + var logLine = $"[ {timestamp} ] [ {level.ToString().ToUpper()} ] {message}{Environment.NewLine}"; + File.AppendAllText(_logFilePath, logLine); + } + catch + { + // Suppress runtime file write exceptions gracefully + } + } + } + /// Serializes all accumulated log entries as a JSON string. public string ToJson() { From ddf00eac0ecbe98940e2e3c3a86b03a56f174481 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Sat, 20 Jun 2026 11:11:14 +0530 Subject: [PATCH 14/15] docs: author comprehensive documentation guide for Postman plugin (#451) --- docs/plugins/postman.md | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/plugins/postman.md diff --git a/docs/plugins/postman.md b/docs/plugins/postman.md new file mode 100644 index 00000000..a49fd371 --- /dev/null +++ b/docs/plugins/postman.md @@ -0,0 +1,67 @@ +# Postman Plugin + +The `postman` plugin handles automated setup, workspace integration, and environment mapping configurations for the Postman API development ecosystem. It simplifies API development pipelines by managing underlying environment collections and workspace initialization variables directly through code. + +## Prerequisites + +- **Supported OS**: Windows 10 / Windows 11 (Both AMD64 and ARM64 system architectures are supported natively) +- **Dependencies**: Postman desktop application installed or active Postman CLI environment configured + +## Configuration Format + +Configure your plugin parameters securely within your root `config.yaml` using these supported setting attributes: + +```yaml +plugins: + postman: + enabled: true # Activates or deactivates the Postman integration framework + api_key: "your-api-key" # Optional: Secure token for syncing workspace environment variables + workspace_id: "id" # Optional: Direct ID mapping target to isolate collection groups + auto_update: true # Optional: Automatically updates collections upon platform boots +``` + +## Real-World Usage Examples + +### 1. Minimal Configuration +Basic activation setting targeting global local workspace structures: +```yaml +plugins: + postman: + enabled: true +``` + +### 2. Fully Cloud-Synchronized API Framework +Advanced production configuration mapping remote environment credentials and auto-update tracking pipelines: +```yaml +plugins: + postman: + enabled: true + api_key: "pm_apikey_example_string_987654321" + workspace_id: "wsp-prod-alpha-99" + auto_update: true +``` + +### 3. Staging and Development Testing Environment Isolation +Configures localized workspace groups for sandboxed validation tasks without automatic remote fetches: +```yaml +plugins: + postman: + enabled: true + workspace_id: "wsp-staging-beta-44" + auto_update: false +``` + +## Verification Steps + +To confirm that the Postman plugin has been initialized and is functioning correctly across your target environment configurations, execute these evaluation checks: + +1. Run the system diagnostics command tool string to audit active plugin configurations: + ```bash + winhome doctor + ``` +2. Verify that the output print streams explicitly log a successful initialization handshake for the postman module component: + ```text + [SUCCESS] Plugin 'postman' initialized successfully. + [INFO] Workspace ID 'wsp-prod-alpha-99' validated and mapped. + ``` +3. Inspect your local telemetry files or environment configurations to ensure postman collection definitions are successfully mounted. From 64b33baaaca87bab5e494aa818314c7deee89f39 Mon Sep 17 00:00:00 2001 From: John Stewartsson J R Date: Sat, 20 Jun 2026 11:24:51 +0530 Subject: [PATCH 15/15] docs: register Postman plugin entry row inside Developer Workflow README table (#451) --- docs/plugins/README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/plugins/README.md b/docs/plugins/README.md index 1349a2aa..94020986 100644 --- a/docs/plugins/README.md +++ b/docs/plugins/README.md @@ -71,6 +71,7 @@ marketplace-style index for those plugins and a quick reference for how each one | `lazydocker` | Manages LazyDocker YAML configuration. | `config_provider` | [Details](#lazydocker) | | `lazygit` | Manages `lazygit` YAML configuration. | `config_provider` | [Details](#lazygit) | | `mise` | Manages `config.toml` for the mise version manager. | `config_provider` | [Details](#mise) | +| `postman` | Manages automated environment mapping and setup configurations for Postman. | `config_provider` | [Details](./postman.md) | | `opencode` | Manages OpenCode JSON and JSONC settings. | `config_provider` | [Details](#opencode) | | `openssh` | Manages global and host-specific entries in `~/.ssh/config`. | `config_provider` | [Details](#openssh) | | `rclone` | Manages `rclone.conf` global settings and remotes. | `config_provider` | [Details](#rclone) | @@ -101,10 +102,10 @@ Deep-merges TOML settings into `%USERPROFILE%\.rustup\settings.toml`. ### Community And Communication | Name | Brief description | Capabilities | Docs | -| --------------- | ------------------------------------------------------- | ----------------- | ------------------------- | -| `betterdiscord` | Manages BetterDiscord data settings in `settings.json`. | `config_provider` | [Details](#betterdiscord) | -| `discord` | Manages Discord client settings in `settings.json`. | `config_provider` | [Details](#discord) | -| `spotify` | Manages Spotify desktop client preferences. | `config_provider` | [Details](./spotify.md) | +| --------------- | ------------------------------------------------------- | ----------------- | ------------------------- | +| `betterdiscord` | Manages BetterDiscord data settings in `settings.json`. | `config_provider` | [Details](#betterdiscord) | +| `discord` | Manages Discord client settings in `settings.json`. | `config_provider` | [Details](#discord) | +| `spotify` | Manages Spotify desktop client preferences. | `config_provider` | [Details](./spotify.md) | ### Examples And Test Fixtures @@ -573,17 +574,17 @@ Deep-merges settings into `%APPDATA%\BetterDiscord\data\settings.json`. Config key: `extensions.discord` -Deep-merges settings into `%APPDATA%\discord\settings.json`. - - - -#### spotify - -Config key: `extensions.spotify` - -Merges key-value settings into `%APPDATA%\Spotify\prefs`. See [full docs](spotify.md). - -### Examples And Test Fixtures +Deep-merges settings into `%APPDATA%\discord\settings.json`. + + + +#### spotify + +Config key: `extensions.spotify` + +Merges key-value settings into `%APPDATA%\Spotify\prefs`. See [full docs](spotify.md). + +### Examples And Test Fixtures