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
2 changes: 2 additions & 0 deletions src/PPDS.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- **PluginRegistrationService refactored to use early-bound entities** - Replaced all magic string attribute access with strongly-typed `PPDS.Dataverse.Generated` classes (`PluginAssembly`, `PluginPackage`, `PluginType`, `SdkMessageProcessingStep`, `SdkMessageProcessingStepImage`, `SdkMessage`, `SdkMessageFilter`, `SystemUser`). Provides compile-time type safety and IntelliSense for all Dataverse entity operations. ([#56](https://github.com/joshsmithxrm/ppds-sdk/issues/56))
- **`PluginRegistrationService` now requires logger** - Constructor now requires `ILogger<PluginRegistrationService>` for diagnostic output. ([#61](https://github.com/joshsmithxrm/ppds-sdk/issues/61))

### Fixed

- **Improved exception handling in `GetComponentTypeAsync`** - Replaced generic catch clause with specific `FaultException<OrganizationServiceFault>` and `FaultException` handlers. Logs failures at Debug level for troubleshooting while maintaining graceful degradation behavior. ([#61](https://github.com/joshsmithxrm/ppds-sdk/issues/61))
- **Environment resolution for service principals** - `ppds env select` now works with full URLs for service principals by trying direct Dataverse connection first, before falling back to Global Discovery (which requires user auth). ([#89](https://github.com/joshsmithxrm/ppds-sdk/issues/89))
- **`auth update --environment` now validates and resolves** - Previously only parsed the URL string without connecting. Now performs full resolution with org metadata population. ([#88](https://github.com/joshsmithxrm/ppds-sdk/issues/88))
- **`env select` validates connection before saving** - Now performs actual WhoAmI request to verify user has access before saving environment selection. Previously resolved metadata but didn't validate access. ([#91](https://github.com/joshsmithxrm/ppds-sdk/issues/91))
Expand Down
4 changes: 3 additions & 1 deletion src/PPDS.Cli/Commands/Plugins/CleanCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PPDS.Cli.Infrastructure;
using PPDS.Cli.Plugins.Models;
using PPDS.Cli.Plugins.Registration;
Expand Down Expand Up @@ -95,8 +96,9 @@ private static async Task<int> ExecuteAsync(
cancellationToken);

var pool = serviceProvider.GetRequiredService<IDataverseConnectionPool>();
var logger = serviceProvider.GetRequiredService<ILogger<PluginRegistrationService>>();
await using var client = await pool.GetClientAsync(cancellationToken: cancellationToken);
var registrationService = new PluginRegistrationService(client);
var registrationService = new PluginRegistrationService(client, logger);

if (outputFormat != OutputFormat.Json)
{
Expand Down
4 changes: 3 additions & 1 deletion src/PPDS.Cli/Commands/Plugins/DeployCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text.Json.Serialization;
using System.Xml.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PPDS.Cli.Infrastructure;
using PPDS.Cli.Plugins.Models;
using PPDS.Cli.Plugins.Registration;
Expand Down Expand Up @@ -109,8 +110,9 @@ private static async Task<int> ExecuteAsync(
cancellationToken);

var pool = serviceProvider.GetRequiredService<IDataverseConnectionPool>();
var logger = serviceProvider.GetRequiredService<ILogger<PluginRegistrationService>>();
await using var client = await pool.GetClientAsync(cancellationToken: cancellationToken);
var registrationService = new PluginRegistrationService(client);
var registrationService = new PluginRegistrationService(client, logger);

if (outputFormat != OutputFormat.Json)
{
Expand Down
4 changes: 3 additions & 1 deletion src/PPDS.Cli/Commands/Plugins/DiffCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PPDS.Cli.Infrastructure;
using PPDS.Cli.Plugins.Models;
using PPDS.Cli.Plugins.Registration;
Expand Down Expand Up @@ -86,8 +87,9 @@ private static async Task<int> ExecuteAsync(
cancellationToken);

var pool = serviceProvider.GetRequiredService<IDataverseConnectionPool>();
var logger = serviceProvider.GetRequiredService<ILogger<PluginRegistrationService>>();
await using var client = await pool.GetClientAsync(cancellationToken: cancellationToken);
var registrationService = new PluginRegistrationService(client);
var registrationService = new PluginRegistrationService(client, logger);

if (outputFormat != OutputFormat.Json)
{
Expand Down
4 changes: 3 additions & 1 deletion src/PPDS.Cli/Commands/Plugins/ListCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PPDS.Cli.Infrastructure;
using PPDS.Cli.Plugins.Registration;
using PPDS.Dataverse.Pooling;
Expand Down Expand Up @@ -73,8 +74,9 @@ private static async Task<int> ExecuteAsync(
cancellationToken);

var pool = serviceProvider.GetRequiredService<IDataverseConnectionPool>();
var logger = serviceProvider.GetRequiredService<ILogger<PluginRegistrationService>>();
await using var client = await pool.GetClientAsync(cancellationToken: cancellationToken);
var registrationService = new PluginRegistrationService(client);
var registrationService = new PluginRegistrationService(client, logger);

if (outputFormat != OutputFormat.Json)
{
Expand Down
29 changes: 26 additions & 3 deletions src/PPDS.Cli/Plugins/Registration/PluginRegistrationService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Concurrent;
using System.ServiceModel;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Extensions.Logging;
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
Expand All @@ -17,6 +19,7 @@ public sealed class PluginRegistrationService
{
private readonly IOrganizationService _service;
private readonly IOrganizationServiceAsync2? _asyncService;
private readonly ILogger<PluginRegistrationService> _logger;

// Cache for entity type codes (ETCs) - some like pluginpackage vary by environment
private readonly ConcurrentDictionary<string, int> _entityTypeCodeCache = new();
Expand All @@ -42,9 +45,15 @@ public sealed class PluginRegistrationService

#endregion

public PluginRegistrationService(IOrganizationService service)
/// <summary>
/// Creates a new instance of the plugin registration service.
/// </summary>
/// <param name="service">The Dataverse organization service.</param>
/// <param name="logger">Logger for diagnostic output.</param>
public PluginRegistrationService(IOrganizationService service, ILogger<PluginRegistrationService> logger)
{
_service = service;
_logger = logger;
// Use native async when available (ServiceClient implements IOrganizationServiceAsync2)
_asyncService = service as IOrganizationServiceAsync2;
}
Expand Down Expand Up @@ -769,9 +778,23 @@ private async Task<int> GetComponentTypeAsync(string entityLogicalName)
_entityTypeCodeCache[entityLogicalName] = objectTypeCode;
return objectTypeCode;
}
catch
catch (FaultException<OrganizationServiceFault> ex)
{
// Entity doesn't exist or user lacks metadata read permissions - skip solution addition
_logger.LogDebug(
"Could not retrieve component type for entity '{EntityLogicalName}': {ErrorMessage} (ErrorCode: {ErrorCode})",
entityLogicalName,
ex.Detail?.Message ?? ex.Message,
ex.Detail?.ErrorCode);
return 0;
}
catch (FaultException ex)
{
// Entity doesn't exist or can't be queried - return 0 to skip solution addition
// Generic SOAP fault - entity may not exist in this environment
_logger.LogDebug(
"Could not retrieve component type for entity '{EntityLogicalName}': {ErrorMessage}",
entityLogicalName,
ex.Message);
return 0;
}
}
Expand Down
1 change: 1 addition & 0 deletions tests/PPDS.Cli.Tests/PPDS.Cli.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Moq" Version="4.20.*" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
Expand Down
Loading
Loading