Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions AssemblyAnalyzer/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using ExecutionContext = XrmSync.Model.ExecutionContext;
2 changes: 1 addition & 1 deletion AssemblyAnalyzer/Reader/LocalReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace XrmSync.Analyzer.Reader;

internal class LocalReader(ILogger<LocalReader> logger, IOptions<SharedOptions> sharedOptions) : ILocalReader
internal class LocalReader(ILogger<LocalReader> logger, IOptions<ExecutionContext> sharedOptions) : ILocalReader
{
private readonly Dictionary<string, AssemblyInfo> assemblyCache = [];

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### v1.0.0-preview.19 - 24 April 2026
* Refactor / Fix: Complete rewrite of the option passing logic

### v1.0.0-preview.18 - 22 April 2026
* Fix: Handling of passing boolean arguments through from CLI to final command
* Refactor: Only print the header and Dataverse connection once during execution
Expand Down
1 change: 1 addition & 0 deletions Dataverse/CustomApiWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using XrmSync.Dataverse.Interfaces;
using XrmSync.Model;
using XrmSync.Model.CustomApi;
using XrmSync.Model.Plugin;

namespace XrmSync.Dataverse;

Expand Down
4 changes: 2 additions & 2 deletions Dataverse/DataverseWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ internal sealed class DataverseWriter : IDataverseWriter
private readonly IOrganizationService service;
private readonly ILogger<DataverseWriter> logger;

public DataverseWriter(IOrganizationServiceProvider serviceProvider, ILogger<DataverseWriter> logger, IOptions<ExecutionModeOptions> configuration)
public DataverseWriter(IOrganizationServiceProvider serviceProvider, ILogger<DataverseWriter> logger, IOptions<ExecutionContext> configuration)
{
if (configuration.Value.DryRun)
if (configuration.Value.DryRun == true)
{
throw new XrmSyncException("Cannot perform write operations in dry run mode. Please disable dry run to proceed with writing to Dataverse.");
}
Expand Down
4 changes: 2 additions & 2 deletions Dataverse/DryRunDataverseWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ internal class DryRunDataverseWriter : IDataverseWriter
{
private readonly ILogger<DryRunDataverseWriter> logger;

public DryRunDataverseWriter(IOptions<ExecutionModeOptions> configuration, ILogger<DryRunDataverseWriter> logger)
public DryRunDataverseWriter(IOptions<ExecutionContext> configuration, ILogger<DryRunDataverseWriter> logger)
{
if (!configuration.Value.DryRun)
if (configuration.Value.DryRun != true)
{
throw new XrmSyncException("This writer is intended for dry runs only.");
}
Expand Down
5 changes: 2 additions & 3 deletions Dataverse/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using XrmSync.Dataverse.Interfaces;
using XrmSync.Model;

namespace XrmSync.Dataverse.Extensions;

Expand All @@ -17,9 +16,9 @@ public static IServiceCollection AddDataverseServices(this IServiceCollection se
services.AddSingleton<IDataverseReader, DataverseReader>();
services.AddSingleton<IDataverseWriter>((sp) =>
{
var options = sp.GetRequiredService<IOptions<ExecutionModeOptions>>();
var options = sp.GetRequiredService<IOptions<ExecutionContext>>();

return options.Value.DryRun
return options.Value.DryRun == true
? ActivatorUtilities.CreateInstance<DryRunDataverseWriter>(sp)
: ActivatorUtilities.CreateInstance<DataverseWriter>(sp);
});
Expand Down
1 change: 1 addition & 0 deletions Dataverse/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using ExecutionContext = XrmSync.Model.ExecutionContext;
1 change: 1 addition & 0 deletions Dataverse/PluginAssemblyWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using XrmSync.Dataverse.Interfaces;
using XrmSync.Model;
using XrmSync.Model.Exceptions;
using XrmSync.Model.Plugin;

namespace XrmSync.Dataverse;

Expand Down
17 changes: 17 additions & 0 deletions Model/ExecutionContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.Extensions.Logging;

namespace XrmSync.Model;

/// <summary>
/// Carries execution parameters resolved from configuration and CLI overrides.
/// Used as the single DI-registered context replacing both SharedOptions and ExecutionModeOptions.
/// </summary>
public record ExecutionContext(
string? SolutionName,
bool? DryRun,
bool? CiMode,
LogLevel? LogLevel,
string? ProfileName)
{
public static ExecutionContext Empty => new(null, null, null, null, null);
}
7 changes: 7 additions & 0 deletions Model/Identity/IdentityCommandOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace XrmSync.Model.Identity;

public record IdentityCommandOptions(IdentityOperation Operation, string AssemblyPath, string SolutionName, string ClientId, string TenantId)
{
public static IdentityCommandOptions Empty => new(IdentityOperation.Remove, string.Empty, string.Empty, string.Empty, string.Empty);
}

7 changes: 7 additions & 0 deletions Model/Plugin/PluginAnalysisCommandOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace XrmSync.Model.Plugin;

public record PluginAnalysisCommandOptions(string AssemblyPath, string PublisherPrefix, bool PrettyPrint)
{
public static PluginAnalysisCommandOptions Empty => new(string.Empty, "new", false);
}

8 changes: 8 additions & 0 deletions Model/Plugin/PluginSyncCommandOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace XrmSync.Model.Plugin;

// Command-specific options that can be populated from CLI or profile
public record PluginSyncCommandOptions(string AssemblyPath, string SolutionName)
{
public static PluginSyncCommandOptions Empty => new(string.Empty, string.Empty);
}

7 changes: 7 additions & 0 deletions Model/Webresource/WebresourceSyncCommandOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace XrmSync.Model.Webresource;

public record WebresourceSyncCommandOptions(string FolderPath, string SolutionName, List<string>? FileExtensions = null)
{
public static WebresourceSyncCommandOptions Empty => new(string.Empty, string.Empty);
}

32 changes: 1 addition & 31 deletions Model/XrmSyncOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,34 +92,13 @@ public record WebresourceSyncItem(string FolderPath, List<string>? FileExtension
public override string SyncType => TypeName;
}

public record SharedOptions(string? ProfileName)
{
public static SharedOptions Empty => new((string?)null);
}

// Command-specific options that can be populated from CLI or profile
public record PluginSyncCommandOptions(string AssemblyPath, string SolutionName)
{
public static PluginSyncCommandOptions Empty => new(string.Empty, string.Empty);
}

public record PluginAnalysisCommandOptions(string AssemblyPath, string PublisherPrefix, bool PrettyPrint)
{
public static PluginAnalysisCommandOptions Empty => new(string.Empty, "new", false);
}

public record WebresourceSyncCommandOptions(string FolderPath, string SolutionName, List<string>? FileExtensions = null)
{
public static WebresourceSyncCommandOptions Empty => new(string.Empty, string.Empty);
}

public enum IdentityOperation
{
Remove,
Ensure
}

public record IdentitySyncItem(IdentityOperation? Operation = null, string AssemblyPath = "", string? ClientId = null, string? TenantId = null) : SyncItem
public record IdentitySyncItem(IdentityOperation? Operation = null, string AssemblyPath = "", string ClientId = "", string TenantId = "") : SyncItem
{
public const string TypeName = "Identity";
public static IdentitySyncItem Empty => new(AssemblyPath: string.Empty);
Expand All @@ -128,12 +107,3 @@ public record IdentitySyncItem(IdentityOperation? Operation = null, string Assem
public override string SyncType => Operation.HasValue ? $"{TypeName} ({Operation})" : TypeName;
}

public record IdentityCommandOptions(IdentityOperation Operation, string AssemblyPath, string SolutionName, string? ClientId = null, string? TenantId = null)
{
public static IdentityCommandOptions Empty => new(IdentityOperation.Remove, string.Empty, string.Empty);
}

public record ExecutionModeOptions(bool DryRun)
{
public static ExecutionModeOptions Empty => new(false);
}
4 changes: 2 additions & 2 deletions SyncService/Difference/PrintService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ namespace XrmSync.SyncService.Difference;

internal class PrintService(
ILogger<PrintService> log,
IOptions<ExecutionModeOptions> configuration
IOptions<ExecutionContext> configuration
) : IPrintService
{
private readonly LogLevel LogLevel = configuration.Value.DryRun ? LogLevel.Information : LogLevel.Debug;
private readonly LogLevel LogLevel = configuration.Value.DryRun == true ? LogLevel.Information : LogLevel.Debug;

public void Print<TEntity>(Difference<TEntity> differences, string title, Func<TEntity, string> namePicker)
where TEntity : EntityBase
Expand Down
1 change: 1 addition & 0 deletions SyncService/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using ExecutionContext = XrmSync.Model.ExecutionContext;
2 changes: 1 addition & 1 deletion SyncService/IdentitySyncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using XrmSync.Dataverse.Interfaces;
using XrmSync.Model;
using XrmSync.Model.Exceptions;
using XrmSync.SyncService.Difference;
using XrmSync.Model.Identity;

namespace XrmSync.SyncService;

Expand Down
2 changes: 0 additions & 2 deletions SyncService/WebresourceSyncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
using Microsoft.Extensions.Options;
using XrmSync.Analyzer.Reader;
using XrmSync.Dataverse.Interfaces;
using XrmSync.Model;
using XrmSync.Model.Exceptions;
using XrmSync.Model.Webresource;
using XrmSync.SyncService.Difference;
using XrmSync.SyncService.Exceptions;
using XrmSync.SyncService.Validation;

Expand Down
1 change: 1 addition & 0 deletions Tests.Integration/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using ExecutionContext = XrmSync.Model.ExecutionContext;
5 changes: 3 additions & 2 deletions Tests.Integration/Infrastructure/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
using NSubstitute;
using XrmSync.Dataverse.Extensions;
using XrmSync.Dataverse.Interfaces;
using XrmSync.Model;
using XrmSync.Model.Plugin;
using XrmSync.Model.Webresource;

namespace Tests.Integration.Infrastructure;

Expand Down Expand Up @@ -70,7 +71,7 @@ private IServiceCollection BuildBaseServices()
services.AddSingleton(ServiceProvider);

// Configure execution options (not dry run for tests)
services.AddSingleton(Options.Create(new ExecutionModeOptions(DryRun: false)));
services.AddSingleton(Options.Create(new ExecutionContext(null, false, null, null, null)));

// Reuse all reader/writer registrations from production code
services.AddDataverseServices();
Expand Down
14 changes: 7 additions & 7 deletions Tests/Config/ConfigValidateCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public async Task ConfigValidationOutputWithValidConfigurationOutputsSuccessfull
{
var configReader = new TestConfigReader(tempFile);
var configuration = configReader.GetConfiguration();
var sharedOptions = Options.Create(SharedOptions.Empty);
var sharedOptions = Options.Create(ExecutionContext.Empty);
var builder = new XrmSyncConfigurationBuilder(configuration);
var config = builder.Build();
var configOptions = Options.Create(config);
Expand Down Expand Up @@ -129,7 +129,7 @@ public async Task ConfigValidationOutputWithMultipleConfigsListsAllConfiguration
{
var configReader = new TestConfigReader(tempFile);
var configuration = configReader.GetConfiguration();
var sharedOptions = Options.Create(SharedOptions.Empty);
var sharedOptions = Options.Create(ExecutionContext.Empty);
var builder = new XrmSyncConfigurationBuilder(configuration);
var config = builder.Build();
var configOptions = Options.Create(config);
Expand Down Expand Up @@ -162,7 +162,7 @@ public async Task ConfigValidationOutputWithNoConfigurationHandlesGracefully()
{
var configReader = new TestConfigReader(tempFile);
var configuration = configReader.GetConfiguration();
var sharedOptions = Options.Create(SharedOptions.Empty);
var sharedOptions = Options.Create(ExecutionContext.Empty);
var builder = new XrmSyncConfigurationBuilder(configuration);
var config = builder.Build();
var configOptions = Options.Create(config);
Expand All @@ -186,7 +186,7 @@ public async Task OutputAllValidationResultsWithNoProfilesHandlesGracefully()
var configuration = new ConfigurationBuilder().Build();
var config = XrmSyncConfiguration.Empty;
var configOptions = Options.Create(config);
var sharedOptions = Options.Create(SharedOptions.Empty);
var sharedOptions = Options.Create(ExecutionContext.Empty);

var output = new ConfigValidationOutput(configuration, configOptions, sharedOptions);

Expand All @@ -210,7 +210,7 @@ public async Task OutputAllValidationResultsWithSingleProfileOutputsValidationRe
]
);
var configOptions = Options.Create(config);
var sharedOptions = Options.Create(SharedOptions.Empty);
var sharedOptions = Options.Create(ExecutionContext.Empty);

var output = new ConfigValidationOutput(configuration, configOptions, sharedOptions);

Expand All @@ -235,7 +235,7 @@ public async Task OutputAllValidationResultsWithMultipleProfilesOutputsAllProfil
]
);
var configOptions = Options.Create(config);
var sharedOptions = Options.Create(SharedOptions.Empty);
var sharedOptions = Options.Create(ExecutionContext.Empty);

var output = new ConfigValidationOutput(configuration, configOptions, sharedOptions);

Expand All @@ -260,7 +260,7 @@ public async Task OutputAllValidationResultsWithInvalidProfileReportsFailure()
]
);
var configOptions = Options.Create(config);
var sharedOptions = Options.Create(SharedOptions.Empty);
var sharedOptions = Options.Create(ExecutionContext.Empty);

var output = new ConfigValidationOutput(configuration, configOptions, sharedOptions);

Expand Down
4 changes: 2 additions & 2 deletions Tests/Config/NamedConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,8 @@ public void IdentityRemoveSyncItemParsesFromConfig()
var identitySync = Assert.IsType<IdentitySyncItem>(profile.Sync[0]);
Assert.Equal(IdentityOperation.Remove, identitySync.Operation);
Assert.Equal("plugins.dll", identitySync.AssemblyPath);
Assert.Null(identitySync.ClientId);
Assert.Null(identitySync.TenantId);
Assert.Empty(identitySync.ClientId);
Assert.Empty(identitySync.TenantId);
}
finally
{
Expand Down
12 changes: 6 additions & 6 deletions Tests/Config/OptionsValidationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ namespace Tests.Config;

public class OptionsValidationTests
{
private static SharedOptions CreateSharedOptions(string profileName = "default") =>
new SharedOptions(profileName);
private static ExecutionContext CreateSharedOptions(string profileName = "default") =>
new ExecutionContext(null, null, null, null, profileName);

[Fact]
public void PluginSyncValidatorValidOptionsPassesValidation()
Expand Down Expand Up @@ -579,7 +579,7 @@ public void IdentityEnsureValidatorMissingClientIdThrowsValidationException()
{
new("default", "TestSolution", new List<SyncItem>
{
new IdentitySyncItem(IdentityOperation.Ensure, dllPath, null, "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d")
new IdentitySyncItem(IdentityOperation.Ensure, dllPath, string.Empty, "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d")
})
}
);
Expand Down Expand Up @@ -618,7 +618,7 @@ public void IdentityEnsureValidatorMissingTenantIdThrowsValidationException()
{
new("default", "TestSolution", new List<SyncItem>
{
new IdentitySyncItem(IdentityOperation.Ensure, dllPath, "d3b5e6a1-2c4f-4a8b-9e1d-7f3c6b8a2e4d", null)
new IdentitySyncItem(IdentityOperation.Ensure, dllPath, "d3b5e6a1-2c4f-4a8b-9e1d-7f3c6b8a2e4d", string.Empty)
})
}
);
Expand Down Expand Up @@ -696,7 +696,7 @@ public void IdentityRemoveValidatorDoesNotRequireClientIdOrTenantId()
{
new("default", "TestSolution", new List<SyncItem>
{
new IdentitySyncItem(IdentityOperation.Remove, dllPath, null, null)
new IdentitySyncItem(IdentityOperation.Remove, dllPath, string.Empty, string.Empty)
})
}
);
Expand Down Expand Up @@ -767,7 +767,7 @@ public void ValidatorNoProfilesAndNoProfileNamePassesValidation()
// Act & Assert - Should not throw (CLI mode, no profile validation needed)
var validator = new XrmSyncConfigurationValidator(
Options.Create(config),
Options.Create(new SharedOptions(null)));
Options.Create(new ExecutionContext(null, null, null, null, null)));
validator.Validate(ConfigurationScope.PluginSync);
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/CustomApis/DifferenceCalculatorCustomApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public DifferenceCalculatorCustomApiTests()
{
var logger = new LoggerFactory().CreateLogger<PrintService>();
var description = new Description();
var options = new ExecutionModeOptions(true);
var options = new ExecutionContext(null, true, null, null, null);
differenceCalculator = new DifferenceCalculator(
new PluginDefinitionComparer(),
new PluginStepComparer(),
Expand Down
1 change: 1 addition & 0 deletions Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using ExecutionContext = XrmSync.Model.ExecutionContext;
Loading