From 72541734480dd76eaf21511d76c32d6cc9369d83 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Wed, 11 Sep 2024 09:43:05 -0700 Subject: [PATCH] feat: add --verify parameter to cli --- docs/stacks/agnostic/generation.md | 8 +++++--- playground/Coalesce.Web.Vue3/src/models.g.ts | 4 +++- .../Generation/Cleaners/DirectoryCleaner.cs | 1 + .../Generation/Cleaners/FileCleaner.cs | 1 + .../Generation/GenerationContext.cs | 8 ++++++++ .../Generation/GenerationExecutor.cs | 4 +++- .../Generation/Generators/FileGenerator.cs | 1 + .../Generation/Generators/Generator.cs | 4 ++++ .../Generation/Generators/IGenerator.cs | 1 + .../Generators/StringBuilderCSharpGenerator.cs | 2 -- src/IntelliTect.Coalesce.DotnetTool/Program.cs | 10 ++++++++++ 11 files changed, 37 insertions(+), 7 deletions(-) diff --git a/docs/stacks/agnostic/generation.md b/docs/stacks/agnostic/generation.md index 8e1d7bbf6..80d8062db 100644 --- a/docs/stacks/agnostic/generation.md +++ b/docs/stacks/agnostic/generation.md @@ -34,9 +34,11 @@ All configuration of the way that Coalesce interacts with your projects, includi There are a couple of extra options which are only available as CLI parameters to ``dotnet coalesce``. These options do not affect the behavior of the code generation - only the behavior of the CLI itself. -``--debug`` - When this flag is specified when running ``dotnet coalesce``, Coalesce will wait for a debugger to be attached to its process before starting code generation. - -``-v|--verbosity `` - Set the verbosity of the output. Options are ``trace``, ``debug``, ``information``, ``warning``, ``error``, ``critical``, and ``none``. +- `` - First positional parameter. Path to the `coalesce.json` configuration file. If not specified, will search upwards from the current folder for a file named `coalesce.json`. +- ``--debug`` - When this flag is specified when running ``dotnet coalesce``, Coalesce will wait for a debugger to be attached to its process before starting code generation. +- `--what-if|-WhatIf` - Runs all code generation, but does not make changes to disk. +- `--verify` - Assert that the code generation does not have any pending changes to its output. Useful in CI builds when combined with `--what-if` to ensure that developers haven't forgotten to run code gen before committing changes. +- ``-v|--verbosity `` - Set the verbosity of the output. Options are ``trace``, ``debug``, ``information``, ``warning``, ``error``, ``critical``, and ``none``. ## Generated Code diff --git a/playground/Coalesce.Web.Vue3/src/models.g.ts b/playground/Coalesce.Web.Vue3/src/models.g.ts index 6b988146d..c2c9f62d7 100644 --- a/playground/Coalesce.Web.Vue3/src/models.g.ts +++ b/playground/Coalesce.Web.Vue3/src/models.g.ts @@ -811,12 +811,14 @@ export class WeatherData { declare module "coalesce-vue/lib/model" { - interface ModelTypeLookup { + interface EnumTypeLookup { AuditEntryState: AuditEntryState Genders: Genders SkyConditions: SkyConditions Statuses: Statuses Titles: Titles + } + interface ModelTypeLookup { AuditLog: AuditLog AuditLogProperty: AuditLogProperty Case: Case diff --git a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Cleaners/DirectoryCleaner.cs b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Cleaners/DirectoryCleaner.cs index faf5355d2..01da68b50 100644 --- a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Cleaners/DirectoryCleaner.cs +++ b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Cleaners/DirectoryCleaner.cs @@ -41,6 +41,7 @@ public Task CleanupAsync(ICollection knownGoodFiles) { if (!knownGoodFiles.Any(goodFile => Path.GetFullPath(goodFile) == Path.GetFullPath(file))) { + Owner.ActionPerformed(); logger.LogWarning( (DryRun ? " What if: " : "") + $"Deleting {file} because it was not in the generation outputs."); diff --git a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Cleaners/FileCleaner.cs b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Cleaners/FileCleaner.cs index 791dc9b65..8ab9c13fd 100644 --- a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Cleaners/FileCleaner.cs +++ b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Cleaners/FileCleaner.cs @@ -26,6 +26,7 @@ public Task CleanupAsync(ICollection knownGoodFiles) logger.LogTrace($"Cleaning {this}"); if (File.Exists(TargetPath)) { + Owner.ActionPerformed(); logger.LogWarning( (DryRun ? " What if: " : "") + $"Deleting {TargetPath} because it was explicitly flagged for removal." diff --git a/src/IntelliTect.Coalesce.CodeGeneration/Generation/GenerationContext.cs b/src/IntelliTect.Coalesce.CodeGeneration/Generation/GenerationContext.cs index da6efb86a..8879709a3 100644 --- a/src/IntelliTect.Coalesce.CodeGeneration/Generation/GenerationContext.cs +++ b/src/IntelliTect.Coalesce.CodeGeneration/Generation/GenerationContext.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace IntelliTect.Coalesce.CodeGeneration.Generation @@ -26,5 +27,12 @@ public GenerationContext(CoalesceConfiguration config) public string AreaName => CoalesceConfiguration.Output.AreaName; public string TypescriptModulePrefix => CoalesceConfiguration.Output.TypescriptModulePrefix; + + private int actionCount = 0; + public void ActionPerformed() + { + Interlocked.Increment(ref actionCount); + } + public int ActionsPerformedCount => actionCount; } } diff --git a/src/IntelliTect.Coalesce.CodeGeneration/Generation/GenerationExecutor.cs b/src/IntelliTect.Coalesce.CodeGeneration/Generation/GenerationExecutor.cs index 36dcf3bc1..b10feb273 100644 --- a/src/IntelliTect.Coalesce.CodeGeneration/Generation/GenerationExecutor.cs +++ b/src/IntelliTect.Coalesce.CodeGeneration/Generation/GenerationExecutor.cs @@ -50,6 +50,8 @@ public GenerationExecutor(CoalesceConfiguration config, LogLevel logLevel) public ILogger Logger { get; private set; } public ServiceProvider ServiceProvider { get; private set; } + public GenerationContext GenerationContext => ServiceProvider.GetRequiredService(); + public Task GenerateAsync() where TGenerator : IRootGenerator { @@ -85,7 +87,7 @@ public async Task GenerateAsync(Type rootGenerator) Logger = ServiceProvider.GetRequiredService>(); - var genContext = ServiceProvider.GetRequiredService(); + var genContext = GenerationContext; Logger.LogInformation("Loading Projects:"); await LoadProjects(Logger, genContext); diff --git a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/FileGenerator.cs b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/FileGenerator.cs index 4bffe4080..e6c5a147c 100644 --- a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/FileGenerator.cs +++ b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/FileGenerator.cs @@ -74,6 +74,7 @@ public override async Task GenerateAsync() Uri relPath = new Uri(Environment.CurrentDirectory + Path.DirectorySeparatorChar).MakeRelativeUri(new Uri(outputFile)); + this.ActionPerformed(); Logger?.LogInformation( (DryRun ? " What if: " : "") + $"{(isRegen ? "Reg" : "G")}enerated: {Uri.UnescapeDataString(relPath.OriginalString)}" diff --git a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/Generator.cs b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/Generator.cs index 45b98228e..c4ea2042f 100644 --- a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/Generator.cs +++ b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/Generator.cs @@ -15,11 +15,13 @@ public abstract class Generator : IGenerator protected Generator(GeneratorServices services) { + Services = services; Logger = services.LoggerFactory.CreateLogger(GetType().Name); Configure(services.CoalesceConfiguration); DryRun = services.CoalesceConfiguration.DryRun; } + protected GeneratorServices Services { get; } protected ILogger Logger { get; } public bool DryRun { get; private set; } @@ -76,5 +78,7 @@ public virtual void Configure(JObject obj) } } } + + public void ActionPerformed() => Services.GenerationContext.ActionPerformed(); } } \ No newline at end of file diff --git a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/IGenerator.cs b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/IGenerator.cs index cab6d2498..93d8aae78 100644 --- a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/IGenerator.cs +++ b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/IGenerator.cs @@ -20,6 +20,7 @@ public interface IGenerator Task GenerateAsync(); void Configure(JObject obj); + void ActionPerformed(); } public interface IGenerator : IGenerator diff --git a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/StringBuilderCSharpGenerator.cs b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/StringBuilderCSharpGenerator.cs index 522013203..979626048 100644 --- a/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/StringBuilderCSharpGenerator.cs +++ b/src/IntelliTect.Coalesce.CodeGeneration/Generation/Generators/StringBuilderCSharpGenerator.cs @@ -15,10 +15,8 @@ public abstract class StringBuilderCSharpGenerator : StringBuilderFileGe { public StringBuilderCSharpGenerator(GeneratorServices services) : base(services) { - Services = services; } - public GeneratorServices Services { get; } public GenerationContext GenerationContext => Services.GenerationContext; // TODO: Should we remove AreaName entirely? Pretty sure nobody uses it. diff --git a/src/IntelliTect.Coalesce.DotnetTool/Program.cs b/src/IntelliTect.Coalesce.DotnetTool/Program.cs index abd244310..ee57c021d 100644 --- a/src/IntelliTect.Coalesce.DotnetTool/Program.cs +++ b/src/IntelliTect.Coalesce.DotnetTool/Program.cs @@ -31,6 +31,10 @@ public class Program Description = "Do not write output files to disk.", LongName = "what-if", ShortName = "WhatIf")] public bool DryRun { get; } + [Option(CommandOptionType.NoValue, + Description = "Verify that no output changes have been made. Use in CI builds to ensure that codegen has not been forgotten.", LongName = "verify", ShortName = "")] + public bool Verify { get; } + [Argument(0, "config", Description = "Path to a coalesce.json configuration file that will drive generation. If not specified, it will search in current folder.")] public string ConfigFile { get; } @@ -123,6 +127,12 @@ private async Task OnExecuteAsync(CommandLineApplication app) return -1; } + if (Verify && executor.GenerationContext.ActionsPerformedCount > 0) + { + executor.Logger.LogError("Output has uncommitted changes. Run `dotnet coalesce` and commit the changes."); + return -1; + } + return 0; }