diff --git a/developer-cli/Commands/BuildCommand.cs b/developer-cli/Commands/BuildCommand.cs index 8c883ff90f..eda6203e2b 100644 --- a/developer-cli/Commands/BuildCommand.cs +++ b/developer-cli/Commands/BuildCommand.cs @@ -108,7 +108,7 @@ private static void Execute(bool backend, bool frontend, bool emails, bool devel if (quiet) { - Console.WriteLine("Build succeeded."); + Console.WriteLine($"Build succeeded in {Stopwatch.GetElapsedTime(startTime).Format()}."); } else { diff --git a/developer-cli/Commands/DeployCommand.cs b/developer-cli/Commands/DeployCommand.cs index cef772b370..83abf5d350 100644 --- a/developer-cli/Commands/DeployCommand.cs +++ b/developer-cli/Commands/DeployCommand.cs @@ -577,7 +577,7 @@ [bold]Please review planned changes before continuing.[/] [yellow]** All variables can be changed on the GitHub Settings page. For example, if you want to deploy production or staging to different locations.[/] - 4. Disable the reusable GitHub workflows [blue]Deploy Container[/] and [blue]Plan and Deploy Infrastructure[/]. + 4. Disable the reusable GitHub workflows [blue]Deploy Container[/], [blue]Deploy Infrastructure[/], and [blue]Migrate Database[/]. 5. The [blue]Cloud Infrastructure - Deployment[/] GitHub Actions will be triggered deployment of Azure Infrastructure. This will take [yellow]between 15 and 45 minutes[/]. diff --git a/developer-cli/Commands/End2EndCommand.cs b/developer-cli/Commands/End2EndCommand.cs index 551455f2af..f972743288 100644 --- a/developer-cli/Commands/End2EndCommand.cs +++ b/developer-cli/Commands/End2EndCommand.cs @@ -226,7 +226,14 @@ private static void Execute( stopwatch.Stop(); - if (!quiet) + if (quiet) + { + Console.WriteLine(overallSuccess + ? $"All tests completed in {stopwatch.Elapsed.TotalSeconds:F1}s." + : $"Some tests failed in {stopwatch.Elapsed.TotalSeconds:F1}s." + ); + } + else { AnsiConsole.MarkupLine(overallSuccess ? $"[green]All tests completed in {stopwatch.Elapsed.TotalSeconds:F1} seconds[/]" diff --git a/developer-cli/Commands/FormatCommand.cs b/developer-cli/Commands/FormatCommand.cs index e4e085c205..4f0695d478 100644 --- a/developer-cli/Commands/FormatCommand.cs +++ b/developer-cli/Commands/FormatCommand.cs @@ -52,6 +52,21 @@ private static void Execute(bool backend, bool frontend, bool developerCli, stri try { + const string cacheKey = "format"; + if (SourceStateCache.IsUpToDate(cacheKey)) + { + if (quiet) + { + Console.WriteLine("No changes since last format run, skipping."); + } + else + { + AnsiConsole.MarkupLine("[green]No changes since last format run, skipping.[/]"); + } + + return; + } + var initialUncommittedFiles = quiet ? null : GitHelper.GetChangedFiles(); if (!quiet && initialUncommittedFiles!.Count > 0) { @@ -84,9 +99,11 @@ private static void Execute(bool backend, bool frontend, bool developerCli, stri developerCliTime = Stopwatch.GetElapsedTime(startTime) - backendTime - frontendTime; } + SourceStateCache.Save(cacheKey); + if (quiet) { - Console.WriteLine("Code formatted successfully."); + Console.WriteLine($"Code formatted successfully in {Stopwatch.GetElapsedTime(startTime).Format()}."); } else { diff --git a/developer-cli/Commands/LintCommand.cs b/developer-cli/Commands/LintCommand.cs index a38b95ecc3..6237186605 100644 --- a/developer-cli/Commands/LintCommand.cs +++ b/developer-cli/Commands/LintCommand.cs @@ -53,6 +53,21 @@ private static void Execute(bool backend, bool frontend, bool developerCli, stri try { + const string cacheKey = "lint"; + if (SourceStateCache.IsUpToDate(cacheKey)) + { + if (quiet) + { + Console.WriteLine("No changes since last lint run, skipping."); + } + else + { + AnsiConsole.MarkupLine("[green]No changes since last lint run, skipping.[/]"); + } + + return; + } + var startTime = Stopwatch.GetTimestamp(); var backendTime = TimeSpan.Zero; var frontendTime = TimeSpan.Zero; @@ -82,6 +97,8 @@ private static void Execute(bool backend, bool frontend, bool developerCli, stri developerCliTime = Stopwatch.GetElapsedTime(startTime) - backendTime - frontendTime; } + if (!hasIssues) SourceStateCache.Save(cacheKey); + if (quiet) { if (hasIssues) @@ -90,7 +107,7 @@ private static void Execute(bool backend, bool frontend, bool developerCli, stri Environment.Exit(1); } - Console.WriteLine("Linting completed successfully. No issues found."); + Console.WriteLine($"Linting completed successfully in {Stopwatch.GetElapsedTime(startTime).Format()}. No issues found."); } else { diff --git a/developer-cli/Utilities/SourceStateCache.cs b/developer-cli/Utilities/SourceStateCache.cs new file mode 100644 index 0000000000..fab934af67 --- /dev/null +++ b/developer-cli/Utilities/SourceStateCache.cs @@ -0,0 +1,51 @@ +using System.Security.Cryptography; +using System.Text; +using DeveloperCli.Installation; + +namespace DeveloperCli.Utilities; + +public static class SourceStateCache +{ + private static readonly string CacheDirectory = Path.Combine(Configuration.WorkspaceFolder, "developer-cli", "cache"); + + public static bool IsUpToDate(string cacheKey) + { + var cachePath = GetCachePath(cacheKey); + if (!File.Exists(cachePath)) return false; + + try + { + var savedState = File.ReadAllText(cachePath).Trim(); + return savedState == ComputeStateKey(); + } + catch + { + return false; + } + } + + public static void Save(string cacheKey) + { + try + { + Directory.CreateDirectory(CacheDirectory); + File.WriteAllText(GetCachePath(cacheKey), ComputeStateKey()); + } + catch + { + // Caching is best-effort; failures should not break the workflow + } + } + + private static string GetCachePath(string cacheKey) + { + return Path.Combine(CacheDirectory, $"{cacheKey}.hash"); + } + + private static string ComputeStateKey() + { + var head = ProcessHelper.StartProcess("git rev-parse HEAD", Configuration.SourceCodeFolder, true, exitOnError: false).Trim(); + var stash = ProcessHelper.StartProcess("git stash create", Configuration.SourceCodeFolder, true, exitOnError: false).Trim(); + return Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes($"{head}:{stash}"))); + } +}