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
9 changes: 5 additions & 4 deletions AssemblyAnalyzer/Reader/LocalReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task<AssemblyInfo> ReadAssemblyAsync(string assemblyDllPath, string
}

logger.LogDebug("Reading assembly from {AssemblyDllPath}", assemblyDllPath);
var assemblyInfo = await ReadAssemblyInternalAsync(assemblyDllPath, publisherPrefix, sharedOptions.Value.ConfigName, cancellationToken);
var assemblyInfo = await ReadAssemblyInternalAsync(assemblyDllPath, publisherPrefix, sharedOptions.Value.ProfileName, cancellationToken);

// Cache the assembly info
assemblyCache[assemblyDllPath] = assemblyInfo;
Expand Down Expand Up @@ -78,7 +78,7 @@ public List<WebresourceDefinition> ReadWebResourceFolder(string folderPath, stri
];
}

private async Task<AssemblyInfo> ReadAssemblyInternalAsync(string assemblyDllPath, string publisherPrefix, string configName, CancellationToken cancellationToken)
private async Task<AssemblyInfo> ReadAssemblyInternalAsync(string assemblyDllPath, string publisherPrefix, string? configName, CancellationToken cancellationToken)
{
var (filename, args) = await GetExecutionInfoAsync(assemblyDllPath, publisherPrefix, configName, cancellationToken);

Expand All @@ -97,9 +97,10 @@ private async Task<AssemblyInfo> ReadAssemblyInternalAsync(string assemblyDllPat
return assemblyInfo ?? throw new AnalysisException("Failed to read plugin type information from assembly");
}

private async Task<(string filename, string args)> GetExecutionInfoAsync(string assemblyDllPath, string publisherPrefix, string configName, CancellationToken cancellationToken)
private async Task<(string filename, string args)> GetExecutionInfoAsync(string assemblyDllPath, string publisherPrefix, string? configName, CancellationToken cancellationToken)
{
var baseArgs = $"analyze --assembly \"{assemblyDllPath}\" --prefix \"{publisherPrefix}\" --config \"{configName}\"";
var configArg = !string.IsNullOrWhiteSpace(configName) ? $" --profile \"{configName}\"" : "";
var baseArgs = $"analyze --assembly \"{assemblyDllPath}\" --prefix \"{publisherPrefix}\"{configArg}";

#if DEBUG
// In debug, try to invoke the currently executing assembly first
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### Unreleased
* Refactor: The configuration format has been updated
* Remove: `--save-config` and `--save-config-to` options - configuration files should be created manually

### v1.0.0-preview.14 - 06 November 2025
* Add: Validation rule to prevent duplicate webresource creation

Expand Down
44 changes: 39 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,45 @@ The solution is organized into distinct layers with clear separation of concerns
5. Execute operations via `IWebresourceWriter`

**Configuration System**:
- Hierarchical configuration under `XrmSync` section in `appsettings.json`
- Named configurations support (e.g., "default", "dev", "prod")
- CLI options override configuration file values
- `--save-config` flag generates/updates configuration files from CLI arguments
- Root command can execute multiple sub-commands from a single configuration
- Profile-based configuration under `XrmSync` section in `appsettings.json`
- Global settings (DryRun, LogLevel, CiMode) apply to all profiles
- Each profile contains a solution name and list of sync items (Plugin, PluginAnalysis, Webresource)
- Profile support (e.g., "default", "dev", "prod") via `--profile` flag
- CLI options override configuration file values for standalone execution
- Root command can execute all sync items in a profile sequentially

**Configuration Format**:
```json
{
"XrmSync": {
"DryRun": false,
"LogLevel": "Information",
"CiMode": false,
"Profiles": [
{
"Name": "dev",
"SolutionName": "MySolution",
"Sync": [
{
"Type": "Plugin",
"AssemblyPath": "../path/to/plugin.dll"
},
{
"Type": "Webresource",
"FolderPath": "../path/to/webresources"
},
{
"Type": "PluginAnalysis",
"AssemblyPath": "../path/to/plugin.dll",
"PublisherPrefix": "new",
"PrettyPrint": true
}
]
}
]
}
}
```

**Command Architecture**:
- All commands implement `IXrmSyncCommand` and extend `XrmSyncCommandBase`
Expand Down
2 changes: 1 addition & 1 deletion Dataverse/CustomApiWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace XrmSync.Dataverse;

internal class CustomApiWriter(IDataverseWriter writer, IOptions<PluginSyncOptions> configuration) : ICustomApiWriter
internal class CustomApiWriter(IDataverseWriter writer, IOptions<PluginSyncCommandOptions> configuration) : ICustomApiWriter
{
private Dictionary<string, object> Parameters { get; } = new() {
{ "SolutionUniqueName", configuration.Value.SolutionName }
Expand Down
2 changes: 1 addition & 1 deletion Dataverse/DataverseWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal sealed class DataverseWriter : IDataverseWriter
private readonly ServiceClient serviceClient;
private readonly ILogger<DataverseWriter> logger;

public DataverseWriter(ServiceClient serviceClient, ILogger<DataverseWriter> logger, IOptions<ExecutionOptions> configuration)
public DataverseWriter(ServiceClient serviceClient, ILogger<DataverseWriter> logger, IOptions<ExecutionModeOptions> configuration)
{
if (configuration.Value.DryRun)
{
Expand Down
2 changes: 1 addition & 1 deletion Dataverse/DryRunDataverseWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal class DryRunDataverseWriter : IDataverseWriter
{
private readonly ILogger<DryRunDataverseWriter> logger;

public DryRunDataverseWriter(IOptions<ExecutionOptions> configuration, ILogger<DryRunDataverseWriter> logger)
public DryRunDataverseWriter(IOptions<ExecutionModeOptions> configuration, ILogger<DryRunDataverseWriter> logger)
{
if (!configuration.Value.DryRun)
{
Expand Down
2 changes: 1 addition & 1 deletion Dataverse/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static IServiceCollection AddDataverseConnection(this IServiceCollection
services.AddSingleton<IDataverseReader, DataverseReader>();
services.AddSingleton<IDataverseWriter>((sp) =>
{
var options = sp.GetRequiredService<IOptions<ExecutionOptions>>();
var options = sp.GetRequiredService<IOptions<ExecutionModeOptions>>();

return options.Value.DryRun
? ActivatorUtilities.CreateInstance<DryRunDataverseWriter>(sp)
Expand Down
2 changes: 1 addition & 1 deletion Dataverse/PluginAssemblyWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace XrmSync.Dataverse;

internal class PluginAssemblyWriter(IDataverseWriter writer, IOptions<PluginSyncOptions> configuration) : IPluginAssemblyWriter
internal class PluginAssemblyWriter(IDataverseWriter writer, IOptions<PluginSyncCommandOptions> configuration) : IPluginAssemblyWriter
{
private Dictionary<string, object> Parameters { get; } = new() {
{ "SolutionUniqueName", configuration.Value.SolutionName }
Expand Down
2 changes: 1 addition & 1 deletion Dataverse/PluginWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace XrmSync.Dataverse;

internal class PluginWriter(IMessageReader messageReader, IDataverseWriter writer, IOptions<PluginSyncOptions> configuration) : IPluginWriter
internal class PluginWriter(IMessageReader messageReader, IDataverseWriter writer, IOptions<PluginSyncCommandOptions> configuration) : IPluginWriter
{
private Dictionary<string, object> Parameters { get; } = new() {
{ "SolutionUniqueName", configuration.Value.SolutionName }
Expand Down
2 changes: 1 addition & 1 deletion Dataverse/WebresourceWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace XrmSync.Dataverse;

internal class WebresourceWriter(IDataverseWriter writer, IOptions<WebresourceSyncOptions> configuration) : IWebresourceWriter
internal class WebresourceWriter(IDataverseWriter writer, IOptions<WebresourceSyncCommandOptions> configuration) : IWebresourceWriter
{
private Dictionary<string, object> Parameters { get; } = new() {
{ "SolutionUniqueName", configuration.Value.SolutionName }
Expand Down
65 changes: 46 additions & 19 deletions Model/XrmSyncOptions.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,77 @@
using Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;

[assembly: InternalsVisibleTo("Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
namespace XrmSync.Model;

public record XrmSyncConfiguration(PluginOptions Plugin, WebresourceOptions Webresource, LoggerOptions Logger, ExecutionOptions Execution)
public record XrmSyncConfiguration(bool DryRun, LogLevel LogLevel, bool CiMode, List<ProfileConfiguration> Profiles)
{
public static XrmSyncConfiguration Empty => new (PluginOptions.Empty, WebresourceOptions.Empty, LoggerOptions.Empty, ExecutionOptions.Empty);
public static XrmSyncConfiguration Empty => new (false, LogLevel.Information, false, new List<ProfileConfiguration>());
}
public record PluginOptions(PluginSyncOptions Sync, PluginAnalysisOptions Analysis)

public record ProfileConfiguration(string Name, string SolutionName, List<SyncItem> Sync)
{
public static PluginOptions Empty => new(PluginSyncOptions.Empty, PluginAnalysisOptions.Empty);
public static ProfileConfiguration Empty => new (string.Empty, string.Empty, new List<SyncItem>());
}

public record WebresourceOptions(WebresourceSyncOptions Sync)
[JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")]
[JsonDerivedType(typeof(PluginSyncItem), typeDiscriminator: "Plugin")]
[JsonDerivedType(typeof(PluginAnalysisSyncItem), typeDiscriminator: "PluginAnalysis")]
[JsonDerivedType(typeof(WebresourceSyncItem), typeDiscriminator: "Webresource")]
public abstract record SyncItem
{
public static WebresourceOptions Empty => new (WebresourceSyncOptions.Empty);
[JsonIgnore]
public abstract string SyncType { get; }
}

public record PluginSyncOptions(string AssemblyPath, string SolutionName)
public record PluginSyncItem(string AssemblyPath) : SyncItem
{
public static PluginSyncOptions Empty => new (string.Empty, string.Empty);
public static PluginSyncItem Empty => new (string.Empty);

[JsonIgnore]
public override string SyncType => "Plugin";
}

public record PluginAnalysisOptions(string AssemblyPath, string PublisherPrefix, bool PrettyPrint)
public record PluginAnalysisSyncItem(string AssemblyPath, string PublisherPrefix, bool PrettyPrint) : SyncItem
{
public static PluginAnalysisOptions Empty => new (string.Empty, "new", false);
public static PluginAnalysisSyncItem Empty => new (string.Empty, "new", false);

[JsonIgnore]
public override string SyncType => "PluginAnalysis";
}

public record WebresourceSyncOptions(string FolderPath, string SolutionName)
public record WebresourceSyncItem(string FolderPath) : SyncItem
{
public static WebresourceSyncOptions Empty => new (string.Empty, string.Empty);
public static WebresourceSyncItem Empty => new (string.Empty);

[JsonIgnore]
public override string SyncType => "Webresource";
}

public record LoggerOptions(LogLevel LogLevel, bool CiMode)
public record SharedOptions(string? ProfileName)
{
public static LoggerOptions Empty => new (LogLevel.Information, false);
public static SharedOptions Empty => new((string?)null);
}

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

public record SharedOptions(bool SaveConfig, string? SaveConfigTo, string ConfigName)
public record PluginAnalysisCommandOptions(string AssemblyPath, string PublisherPrefix, bool PrettyPrint)
{
public static SharedOptions Empty => new(false, null, string.Empty);
}
public static PluginAnalysisCommandOptions Empty => new(string.Empty, "new", false);
}

public record WebresourceSyncCommandOptions(string FolderPath, string SolutionName)
{
public static WebresourceSyncCommandOptions Empty => new(string.Empty, string.Empty);
}

public record ExecutionModeOptions(bool DryRun)
{
public static ExecutionModeOptions Empty => new(false);
}
Loading