diff --git a/src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseEntityMetadataService.cs b/src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseEntityMetadataService.cs
index 63c1481..b7d0683 100644
--- a/src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseEntityMetadataService.cs
+++ b/src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseEntityMetadataService.cs
@@ -24,7 +24,9 @@ public sealed record EntityAttributeRecord(
bool IsPrimaryId,
bool IsPrimaryName,
int? MaxLength,
- string? Description);
+ string? Description,
+ string? OptionSetName = null,
+ string? OptionValues = null);
///
/// Relationship summary for an entity, returned by
diff --git a/src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseOptionSetService.cs b/src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseOptionSetService.cs
index b8cb895..bb7153a 100644
--- a/src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseOptionSetService.cs
+++ b/src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseOptionSetService.cs
@@ -31,29 +31,29 @@ Task CreateGlobalOptionSetAsync(
CancellationToken ct);
///
- /// Inserts a new option value into a local or global option set.
+ /// Inserts a new option value into an option set.
/// For a local option set, provide and .
- /// For a global option set, provide .
+ /// For a global option set, provide .
///
Task InsertOptionAsync(
string? profileName,
string? entityName,
string? attributeName,
- string? globalOptionSetName,
+ string? optionSetName,
string label,
int? value,
CancellationToken ct);
///
- /// Deletes an option value from a local or global option set.
+ /// Deletes an option value from an option set.
/// For a local option set, provide and .
- /// For a global option set, provide .
+ /// For a global option set, provide .
///
Task DeleteOptionAsync(
string? profileName,
string? entityName,
string? attributeName,
- string? globalOptionSetName,
+ string? optionSetName,
int value,
CancellationToken ct);
@@ -71,9 +71,65 @@ Task DeleteGlobalOptionSetAsync(
Task> ListGlobalOptionSetsAsync(
string? profileName,
CancellationToken ct);
+
+ ///
+ /// Describes a specific global option set — returns its options (value + label pairs).
+ ///
+ /// Optional LCID for label language (e.g. 1033=English, 1029=Czech). Null = user's language.
+ Task DescribeGlobalOptionSetAsync(
+ string? profileName,
+ string optionSetName,
+ int? languageCode,
+ CancellationToken ct);
}
///
/// Input DTO for a single option (label + optional value) used when creating option sets.
///
-public sealed record OptionMetadataInput(string Label, int Value);
+public sealed record OptionMetadataInput(string Label, int Value)
+{
+ ///
+ /// Parses a comma-separated options string into items.
+ /// Supports "Label:Value" pairs or plain "Label" (auto-valued starting at 100000000).
+ ///
+ public static OptionMetadataInput[] ParseCsv(string csv)
+ {
+ var entries = csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ if (entries.Length == 0)
+ return Array.Empty();
+
+ var results = new OptionMetadataInput[entries.Length];
+ int autoValue = 100_000_000;
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var parts = entries[i].Split(':', 2);
+ if (parts.Length == 2 && int.TryParse(parts[1].Trim(), out int v))
+ {
+ results[i] = new OptionMetadataInput(parts[0].Trim(), v);
+ }
+ else
+ {
+ results[i] = new OptionMetadataInput(parts[0].Trim(), autoValue++);
+ }
+ }
+
+ return results;
+ }
+};
+
+///
+/// Detail record for a global option set, including all option values and labels.
+///
+public sealed record GlobalOptionSetDetailRecord(
+ string Name,
+ string? DisplayName,
+ string? Description,
+ string OptionSetType,
+ bool IsCustomOptionSet,
+ IReadOnlyList Options);
+
+///
+/// A single option value within an option set.
+///
+public sealed record OptionValueRecord(int Value, string? Label, string? Description);
diff --git a/src/TALXIS.CLI.Features.Environment/Data/Bulk/EnvDataBulkCreateCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Data/Bulk/EnvDataBulkCreateCliCommand.cs
index ce57782..c9cb232 100644
--- a/src/TALXIS.CLI.Features.Environment/Data/Bulk/EnvDataBulkCreateCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Data/Bulk/EnvDataBulkCreateCliCommand.cs
@@ -14,7 +14,7 @@ namespace TALXIS.CLI.Features.Environment.Data.Bulk;
[CliIdempotent]
[CliCommand(
Name = "create",
- Description = "Creates multiple Dataverse records in a single batch request on the LIVE connected environment. Requires an active profile. Accepts JSON array via --data or --file."
+ Description = "Creates multiple Dataverse records in a single batch request on the LIVE connected environment. Requires an active profile. Accepts JSON array via --data or --file. Column types are auto-detected: option sets accept plain integers (e.g. 375970000), money fields accept decimals, lookups accept {Id,LogicalName} objects or a bare GUID string (single-target lookups)."
)]
public class EnvDataBulkCreateCliCommand : ProfiledCliCommand
{
diff --git a/src/TALXIS.CLI.Features.Environment/Data/Record/EnvDataRecordCreateCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Data/Record/EnvDataRecordCreateCliCommand.cs
index ba5a39e..33414c9 100644
--- a/src/TALXIS.CLI.Features.Environment/Data/Record/EnvDataRecordCreateCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Data/Record/EnvDataRecordCreateCliCommand.cs
@@ -14,7 +14,7 @@ namespace TALXIS.CLI.Features.Environment.Data.Record;
[CliIdempotent]
[CliCommand(
Name = "create",
- Description = "Creates a single Dataverse record in the LIVE connected environment from inline JSON or file. Requires an active profile. For LOCAL component scaffolding, use 'workspace component create' instead."
+ Description = "Creates a single Dataverse record in the LIVE connected environment from inline JSON or file. Requires an active profile. Column types are auto-detected: option sets accept plain integers (e.g. 375970000), money fields accept decimals, lookups accept {Id,LogicalName} objects or a bare GUID string (single-target lookups). For LOCAL component scaffolding, use 'workspace component create' instead."
)]
#pragma warning disable TXC003
public class EnvDataRecordCreateCliCommand : StagedCliCommand
diff --git a/src/TALXIS.CLI.Features.Environment/Entity/EntityCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Entity/EntityCliCommand.cs
index 2b162b1..b9ad966 100644
--- a/src/TALXIS.CLI.Features.Environment/Entity/EntityCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Entity/EntityCliCommand.cs
@@ -9,7 +9,7 @@ namespace TALXIS.CLI.Features.Environment.Entity;
[CliCommand(
Name = "entity",
Description = "Entity discovery and schema metadata for the live environment.",
- Children = new[] { typeof(EntityListCliCommand), typeof(EntityDescribeCliCommand), typeof(EntityGetCliCommand), typeof(EntityUpdateCliCommand), typeof(EntityCreateCliCommand), typeof(EntityDeleteCliCommand), typeof(EntityAttributeCliCommand), typeof(EntityRelationshipCliCommand), typeof(EntityOptionSetCliCommand) }
+ Children = new[] { typeof(EntityListCliCommand), typeof(EntityDescribeCliCommand), typeof(EntityGetCliCommand), typeof(EntityUpdateCliCommand), typeof(EntityCreateCliCommand), typeof(EntityDeleteCliCommand), typeof(EntityAttributeCliCommand), typeof(EntityRelationshipCliCommand) }
)]
public class EntityCliCommand
{
diff --git a/src/TALXIS.CLI.Features.Environment/Entity/EntityDescribeCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Entity/EntityDescribeCliCommand.cs
index 06ac735..fa026fe 100644
--- a/src/TALXIS.CLI.Features.Environment/Entity/EntityDescribeCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Entity/EntityDescribeCliCommand.cs
@@ -52,6 +52,10 @@ private static void PrintAttributesTable(IReadOnlyList ro
int pkWidth = 2;
int nameWidth = 4;
int maxLenWidth = 10;
+ bool hasOptionSets = rows.Any(r => r.OptionSetName is not null);
+ int optSetWidth = hasOptionSets
+ ? Math.Clamp(rows.Where(r => r.OptionSetName is not null).Max(r => r.OptionSetName!.Length), 9, 30)
+ : 0;
string header =
$"{"Logical Name".PadRight(logicalWidth)} | " +
@@ -61,6 +65,8 @@ private static void PrintAttributesTable(IReadOnlyList ro
$"{"PK".PadRight(pkWidth)} | " +
$"{"Name".PadRight(nameWidth)} | " +
$"{"Max Length".PadRight(maxLenWidth)}";
+ if (hasOptionSets)
+ header += $" | {"OptionSet".PadRight(optSetWidth)}";
OutputWriter.WriteLine(header);
OutputWriter.WriteLine(new string('-', header.Length));
@@ -74,14 +80,17 @@ private static void PrintAttributesTable(IReadOnlyList ro
string name = r.IsPrimaryName ? "*" : "";
string maxLen = r.MaxLength.HasValue ? r.MaxLength.Value.ToString() : "";
- OutputWriter.WriteLine(
+ string line =
$"{logical.PadRight(logicalWidth)} | " +
$"{type.PadRight(typeWidth)} | " +
$"{display.PadRight(displayWidth)} | " +
$"{custom.PadRight(customWidth)} | " +
$"{pk.PadRight(pkWidth)} | " +
$"{name.PadRight(nameWidth)} | " +
- $"{maxLen.PadRight(maxLenWidth)}");
+ $"{maxLen.PadRight(maxLenWidth)}";
+ if (hasOptionSets)
+ line += $" | {Truncate(r.OptionSetName ?? "", optSetWidth).PadRight(optSetWidth)}";
+ OutputWriter.WriteLine(line);
}
}
#pragma warning restore TXC003
diff --git a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetCliCommand.cs
deleted file mode 100644
index 8b5bf39..0000000
--- a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetCliCommand.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using DotMake.CommandLine;
-
-namespace TALXIS.CLI.Features.Environment.Entity;
-
-///
-/// Parent command for option set operations.
-/// Usage: txc environment entity optionset [global|option]
-///
-[CliCommand(
- Name = "optionset",
- Description = "Create and manage option sets (choices).",
- Children = new[]
- {
- typeof(EntityOptionSetGlobalCliCommand),
- typeof(EntityOptionSetOptionCliCommand)
- },
- ShortFormAutoGenerate = CliNameAutoGenerate.None
-)]
-public class EntityOptionSetCliCommand
-{
- public void Run(CliContext context)
- {
- context.ShowHelp();
- }
-}
-
-///
-/// Group command for global option set operations.
-/// Usage: txc environment entity optionset global [create|delete|list]
-///
-[CliCommand(
- Name = "global",
- Description = "Manage global option sets.",
- Children = new[]
- {
- typeof(EntityOptionSetCreateGlobalCliCommand),
- typeof(EntityOptionSetDeleteGlobalCliCommand),
- typeof(EntityOptionSetListGlobalCliCommand)
- },
- ShortFormAutoGenerate = CliNameAutoGenerate.None
-)]
-public class EntityOptionSetGlobalCliCommand
-{
- public void Run(CliContext context)
- {
- context.ShowHelp();
- }
-}
-
-///
-/// Group command for individual option (value) operations.
-/// Usage: txc environment entity optionset option [add|delete]
-///
-[CliCommand(
- Name = "option",
- Description = "Add or remove individual options (values) in an option set.",
- Children = new[]
- {
- typeof(EntityOptionSetAddOptionCliCommand),
- typeof(EntityOptionSetDeleteOptionCliCommand)
- },
- ShortFormAutoGenerate = CliNameAutoGenerate.None
-)]
-public class EntityOptionSetOptionCliCommand
-{
- public void Run(CliContext context)
- {
- context.ShowHelp();
- }
-}
diff --git a/src/TALXIS.CLI.Features.Environment/EnvironmentCliCommand.cs b/src/TALXIS.CLI.Features.Environment/EnvironmentCliCommand.cs
index baf0d7b..7b3fd0b 100644
--- a/src/TALXIS.CLI.Features.Environment/EnvironmentCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/EnvironmentCliCommand.cs
@@ -6,7 +6,7 @@ namespace TALXIS.CLI.Features.Environment;
Name = "environment",
Alias = "env",
Description = "Manage the footprint of your project in a live target environment (packages, solutions, deployment history).",
- Children = new[] { typeof(Package.PackageCliCommand), typeof(Solution.SolutionCliCommand), typeof(Deployment.DeploymentCliCommand), typeof(Data.EnvDataCliCommand), typeof(Entity.EntityCliCommand), typeof(Setting.SettingCliCommand), typeof(Changeset.ChangesetCliCommand), typeof(Component.ComponentCliCommand), typeof(Publisher.PublisherCliCommand) },
+ Children = new[] { typeof(Package.PackageCliCommand), typeof(Solution.SolutionCliCommand), typeof(Deployment.DeploymentCliCommand), typeof(Data.EnvDataCliCommand), typeof(Entity.EntityCliCommand), typeof(OptionSet.OptionSetCliCommand), typeof(Setting.SettingCliCommand), typeof(Changeset.ChangesetCliCommand), typeof(Component.ComponentCliCommand), typeof(Publisher.PublisherCliCommand) },
ShortFormAutoGenerate = CliNameAutoGenerate.None
)]
public class EnvironmentCliCommand
diff --git a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetAddOptionCliCommand.cs b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetAddOptionCliCommand.cs
similarity index 53%
rename from src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetAddOptionCliCommand.cs
rename to src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetAddOptionCliCommand.cs
index f11c947..1496ef9 100644
--- a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetAddOptionCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetAddOptionCliCommand.cs
@@ -6,57 +6,55 @@
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
-namespace TALXIS.CLI.Features.Environment.Entity;
+namespace TALXIS.CLI.Features.Environment.OptionSet;
///
-/// Adds an option value to a local or global option set.
-/// Usage: txc environment entity optionset option add --label <text> (--global-optionset <name> | --entity <name> --attribute <name>) [--value <int>]
+/// Adds an option value to a global or local option set.
+/// Global: txc environment optionset add-option --name <schema-name> --label <text>
+/// Local: txc environment optionset add-option --entity <name> --attribute <name> --label <text>
///
[CliIdempotent]
[CliCommand(
Name = "add",
- Description = "Add an option value to a local or global option set."
+ Description = "Add an option value to a global or local option set."
)]
#pragma warning disable TXC003
-public class EntityOptionSetAddOptionCliCommand : StagedCliCommand
+public class OptionSetAddOptionCliCommand : StagedCliCommand
{
- protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(EntityOptionSetAddOptionCliCommand));
+ protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(OptionSetAddOptionCliCommand));
- [CliOption(Name = "--entity", Description = "The logical name of the entity (for local option sets).", Required = false)]
+ [CliOption(Name = "--name", Description = "Schema name of the global option set.", Required = false)]
+ public string? Name { get; set; }
+
+ [CliOption(Name = "--entity", Description = "Entity logical name (for local option sets).", Required = false)]
public string? Entity { get; set; }
- [CliOption(Name = "--attribute", Description = "The logical name of the attribute (for local option sets).", Required = false)]
+ [CliOption(Name = "--attribute", Description = "Attribute logical name (for local option sets).", Required = false)]
public string? Attribute { get; set; }
- [CliOption(Name = "--global-optionset", Description = "The name of the global option set (mutually exclusive with --entity/--attribute).", Required = false)]
- public string? GlobalOptionset { get; set; }
-
- [CliOption(Name = "--label", Description = "The label for the new option.", Required = true)]
+ [CliOption(Name = "--label", Description = "Label for the new option.", Required = true)]
public string Label { get; set; } = null!;
- [CliOption(Name = "--value", Description = "The integer value for the new option (auto-generated if not provided).", Required = false)]
+ [CliOption(Name = "--value", Description = "Integer value for the new option (auto-generated if omitted).", Required = false)]
public int? Value { get; set; }
protected override async Task ExecuteAsync()
{
ValidateExecutionMode();
- // Validate mutually exclusive options.
- bool hasGlobal = !string.IsNullOrWhiteSpace(GlobalOptionset);
+ bool hasGlobal = !string.IsNullOrWhiteSpace(Name);
bool hasLocal = !string.IsNullOrWhiteSpace(Entity) || !string.IsNullOrWhiteSpace(Attribute);
if (hasGlobal && hasLocal)
{
- Logger.LogError("Specify either --global-optionset or --entity/--attribute, not both.");
+ Logger.LogError("Specify either --name (global) or --entity + --attribute (local), not both.");
return ExitError;
}
-
if (!hasGlobal && !hasLocal)
{
- Logger.LogError("Specify --global-optionset for a global option set, or --entity and --attribute for a local one.");
+ Logger.LogError("Specify --name for a global option set, or --entity and --attribute for a local one.");
return ExitError;
}
-
if (hasLocal && (string.IsNullOrWhiteSpace(Entity) || string.IsNullOrWhiteSpace(Attribute)))
{
Logger.LogError("Both --entity and --attribute are required for local option sets.");
@@ -65,20 +63,20 @@ protected override async Task ExecuteAsync()
if (Stage)
{
- string stageTarget = hasGlobal ? GlobalOptionset! : $"{Entity}.{Attribute}";
+ string stageTarget = hasGlobal ? Name! : $"{Entity}.{Attribute}";
var store = TxcServices.Get();
store.Add(new StagedOperation
{
Category = "schema",
OperationType = "CREATE",
- TargetType = "optionset",
+ TargetType = "optionset-option",
TargetDescription = stageTarget,
Details = $"add option: \"{Label}\"" + (Value.HasValue ? $" ({Value})" : ""),
Parameters = new Dictionary
{
["entity"] = Entity,
["attribute"] = Attribute,
- ["globalOptionset"] = GlobalOptionset,
+ ["name"] = Name,
["label"] = Label,
["value"] = Value
}
@@ -89,10 +87,10 @@ protected override async Task ExecuteAsync()
var service = TxcServices.Get();
await service.InsertOptionAsync(
- Profile, Entity, Attribute, GlobalOptionset, Label, Value, CancellationToken.None
+ Profile, Entity, Attribute, Name, Label, Value, CancellationToken.None
).ConfigureAwait(false);
- string target = hasGlobal ? $"global option set '{GlobalOptionset}'" : $"attribute '{Attribute}' on entity '{Entity}'";
+ string target = hasGlobal ? $"global option set '{Name}'" : $"attribute '{Attribute}' on entity '{Entity}'";
OutputWriter.WriteLine($"Option '{Label}' added to {target}.");
return ExitSuccess;
}
diff --git a/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetCliCommand.cs b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetCliCommand.cs
new file mode 100644
index 0000000..7bf6474
--- /dev/null
+++ b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetCliCommand.cs
@@ -0,0 +1,51 @@
+using DotMake.CommandLine;
+
+namespace TALXIS.CLI.Features.Environment.OptionSet;
+
+///
+/// Parent command for option set (choice) operations.
+/// Covers both global option sets and local (entity-attribute) option sets.
+/// Usage: txc environment optionset [list|describe|create|delete|add-option|remove-option]
+///
+[CliCommand(
+ Name = "optionset",
+ Description = "Manage option sets (choices) — global and local.",
+ Children = new[]
+ {
+ typeof(OptionSetListCliCommand),
+ typeof(OptionSetShowCliCommand),
+ typeof(OptionSetCreateCliCommand),
+ typeof(OptionSetDeleteCliCommand),
+ typeof(OptionSetOptionCliCommand)
+ },
+ ShortFormAutoGenerate = CliNameAutoGenerate.None
+)]
+public class OptionSetCliCommand
+{
+ public void Run(CliContext context)
+ {
+ context.ShowHelp();
+ }
+}
+
+///
+/// Sub-resource for individual option values within an option set.
+/// Usage: txc environment optionset option [add|remove]
+///
+[CliCommand(
+ Name = "option",
+ Description = "Add or remove individual values in an option set.",
+ Children = new[]
+ {
+ typeof(OptionSetAddOptionCliCommand),
+ typeof(OptionSetRemoveOptionCliCommand)
+ },
+ ShortFormAutoGenerate = CliNameAutoGenerate.None
+)]
+public class OptionSetOptionCliCommand
+{
+ public void Run(CliContext context)
+ {
+ context.ShowHelp();
+ }
+}
diff --git a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetCreateGlobalCliCommand.cs b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetCreateCliCommand.cs
similarity index 50%
rename from src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetCreateGlobalCliCommand.cs
rename to src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetCreateCliCommand.cs
index 59e022a..144f2a6 100644
--- a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetCreateGlobalCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetCreateCliCommand.cs
@@ -6,11 +6,11 @@
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
-namespace TALXIS.CLI.Features.Environment.Entity;
+namespace TALXIS.CLI.Features.Environment.OptionSet;
///
/// Creates a new global option set (choice) in Dataverse.
-/// Usage: txc environment entity optionset global create --name <schema-name> --display-name <label> --options <csv> [--description <text>] [--solution <name>]
+/// Usage: txc environment optionset create --name <schema-name> --display-name <label> --options <csv>
///
[CliIdempotent]
[CliCommand(
@@ -18,23 +18,23 @@ namespace TALXIS.CLI.Features.Environment.Entity;
Description = "Create a new global option set (choice)."
)]
#pragma warning disable TXC003
-public class EntityOptionSetCreateGlobalCliCommand : StagedCliCommand
+public class OptionSetCreateCliCommand : StagedCliCommand
{
- protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(EntityOptionSetCreateGlobalCliCommand));
+ protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(OptionSetCreateCliCommand));
- [CliOption(Name = "--name", Description = "The schema name of the global option set.", Required = true)]
+ [CliOption(Name = "--name", Description = "Schema name of the global option set.", Required = true)]
public string Name { get; set; } = null!;
- [CliOption(Name = "--display-name", Description = "The display name (label) for the option set.", Required = true)]
+ [CliOption(Name = "--display-name", Description = "Display name (label) for the option set.", Required = true)]
public string DisplayName { get; set; } = null!;
[CliOption(Name = "--options", Description = "Comma-separated options: \"Label1:100000000,Label2:100000001\" or \"Label1,Label2\" (auto-value).", Required = true)]
public string Options { get; set; } = null!;
- [CliOption(Name = "--description", Description = "The description for the option set.", Required = false)]
+ [CliOption(Name = "--description", Description = "Description for the option set.", Required = false)]
public string? Description { get; set; }
- [CliOption(Name = "--solution", Description = "The unique name of the solution to add the option set to.", Required = false)]
+ [CliOption(Name = "--solution", Description = "Solution unique name to register the option set in.", Required = false)]
public string? Solution { get; set; }
protected override async Task ExecuteAsync()
@@ -64,45 +64,14 @@ protected override async Task ExecuteAsync()
return ExitSuccess;
}
- OptionMetadataInput[] parsed = ParseOptions(Options);
+ OptionMetadataInput[] parsed = OptionMetadataInput.ParseCsv(Options);
var service = TxcServices.Get();
await service.CreateGlobalOptionSetAsync(
Profile, Name, DisplayName, Description, parsed, Solution, CancellationToken.None
).ConfigureAwait(false);
- OutputWriter.WriteLine($"Global option set '{Name}' created successfully.");
+ OutputWriter.WriteLine($"Global option set '{Name}' created.");
return ExitSuccess;
}
-
- ///
- /// Parses a comma-separated options string into items.
- /// Supports "Label:Value" pairs or plain "Label" (auto-valued starting at 100000000).
- ///
- internal static OptionMetadataInput[] ParseOptions(string csv)
- {
- var entries = csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
- if (entries.Length == 0)
- throw new FormatException("--options must contain at least one option.");
-
- var results = new OptionMetadataInput[entries.Length];
- int autoValue = 100_000_000;
-
- for (int i = 0; i < entries.Length; i++)
- {
- var parts = entries[i].Split(':', 2);
- if (parts.Length == 2)
- {
- if (!int.TryParse(parts[1].Trim(), out int v))
- throw new FormatException($"Invalid option value '{parts[1].Trim()}' in '{entries[i]}'. Expected an integer.");
- results[i] = new OptionMetadataInput(parts[0].Trim(), v);
- }
- else
- {
- results[i] = new OptionMetadataInput(parts[0].Trim(), autoValue++);
- }
- }
-
- return results;
- }
}
diff --git a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetDeleteGlobalCliCommand.cs b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetDeleteCliCommand.cs
similarity index 60%
rename from src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetDeleteGlobalCliCommand.cs
rename to src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetDeleteCliCommand.cs
index 311a11b..1a8199b 100644
--- a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetDeleteGlobalCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetDeleteCliCommand.cs
@@ -6,26 +6,22 @@
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
-namespace TALXIS.CLI.Features.Environment.Entity;
+namespace TALXIS.CLI.Features.Environment.OptionSet;
-///
-/// Deletes an existing global option set (choice) from Dataverse.
-/// Usage: txc environment entity optionset global delete --name <schema-name> [-p profile] --apply
-///
-[CliDestructive("Permanently deletes the global option set from the remote environment.")]
+[CliDestructive("Permanently deletes the global option set from the environment.")]
[CliCommand(
Name = "delete",
Description = "Delete a global option set (choice)."
)]
#pragma warning disable TXC003
-public class EntityOptionSetDeleteGlobalCliCommand : StagedCliCommand, IDestructiveCommand
+public class OptionSetDeleteCliCommand : StagedCliCommand, IDestructiveCommand
{
- protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(EntityOptionSetDeleteGlobalCliCommand));
+ protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(OptionSetDeleteCliCommand));
- [CliOption(Name = "--yes", Description = "Skip interactive confirmation for this destructive operation.", Required = false)]
+ [CliOption(Name = "--yes", Description = "Skip interactive confirmation.", Required = false)]
public bool Yes { get; set; }
- [CliOption(Name = "--name", Description = "The schema name of the global option set to delete.", Required = true)]
+ [CliOption(Name = "--name", Description = "Schema name of the global option set to delete.", Required = true)]
public string Name { get; set; } = null!;
protected override async Task ExecuteAsync()
@@ -42,21 +38,16 @@ protected override async Task ExecuteAsync()
TargetType = "optionset",
TargetDescription = Name,
Details = $"global optionset: \"{Name}\"",
- Parameters = new Dictionary
- {
- ["name"] = Name
- }
+ Parameters = new Dictionary { ["name"] = Name }
});
OutputWriter.WriteLine($"Staged: DELETE global optionset '{Name}'");
return ExitSuccess;
}
var service = TxcServices.Get();
- await service.DeleteGlobalOptionSetAsync(
- Profile, Name, CancellationToken.None
- ).ConfigureAwait(false);
+ await service.DeleteGlobalOptionSetAsync(Profile, Name, CancellationToken.None).ConfigureAwait(false);
- OutputWriter.WriteLine($"Global option set '{Name}' deleted successfully.");
+ OutputWriter.WriteLine($"Global option set '{Name}' deleted.");
return ExitSuccess;
}
}
diff --git a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetListGlobalCliCommand.cs b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetListCliCommand.cs
similarity index 84%
rename from src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetListGlobalCliCommand.cs
rename to src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetListCliCommand.cs
index 94014d8..a350f1e 100644
--- a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetListGlobalCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetListCliCommand.cs
@@ -1,16 +1,15 @@
using DotMake.CommandLine;
using Microsoft.Extensions.Logging;
using TALXIS.CLI.Core;
-using TALXIS.CLI.Core.Abstractions;
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
-namespace TALXIS.CLI.Features.Environment.Entity;
+namespace TALXIS.CLI.Features.Environment.OptionSet;
///
/// Lists all global option sets in the environment.
-/// Usage: txc environment entity optionset global list [--format json]
+/// Usage: txc environment optionset list [--format json]
///
[CliReadOnly]
[CliCommand(
@@ -18,9 +17,9 @@ namespace TALXIS.CLI.Features.Environment.Entity;
Description = "List all global option sets in the environment."
)]
#pragma warning disable TXC003
-public class EntityOptionSetListGlobalCliCommand : ProfiledCliCommand
+public class OptionSetListCliCommand : ProfiledCliCommand
{
- protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(EntityOptionSetListGlobalCliCommand));
+ protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(OptionSetListCliCommand));
protected override async Task ExecuteAsync()
{
@@ -42,8 +41,8 @@ private static void PrintOptionSetsTable(IReadOnlyList r.Name.Length), 4, 60);
int displayWidth = Math.Clamp(rows.Max(r => (r.DisplayName ?? "").Length), 12, 48);
int typeWidth = Math.Clamp(rows.Max(r => r.OptionSetType.Length), 4, 16);
- int countWidth = 7; // "Options"
- int customWidth = 6; // "Custom"
+ int countWidth = 7;
+ int customWidth = 6;
string header =
$"{"Name".PadRight(nameWidth)} | " +
@@ -71,7 +70,6 @@ private static void PrintOptionSetsTable(IReadOnlyListTruncate a string to fit the column width, appending a dot if trimmed.
private static string Truncate(string value, int maxWidth) =>
value.Length > maxWidth ? value[..(maxWidth - 1)] + "." : value;
}
diff --git a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetDeleteOptionCliCommand.cs b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetRemoveOptionCliCommand.cs
similarity index 50%
rename from src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetDeleteOptionCliCommand.cs
rename to src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetRemoveOptionCliCommand.cs
index 28bebba..c5f818e 100644
--- a/src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetDeleteOptionCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetRemoveOptionCliCommand.cs
@@ -6,57 +6,55 @@
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
-namespace TALXIS.CLI.Features.Environment.Entity;
+namespace TALXIS.CLI.Features.Environment.OptionSet;
///
-/// Deletes an option value from a local or global option set.
-/// Usage: txc environment entity optionset option delete --value <int> (--global-optionset <name> | --entity <name> --attribute <name>)
+/// Removes an option value from a global or local option set.
+/// Global: txc environment optionset remove-option --name <schema-name> --value <int>
+/// Local: txc environment optionset remove-option --entity <name> --attribute <name> --value <int>
///
-[CliDestructive("Permanently deletes the option from the option set.")]
+[CliDestructive("Permanently removes the option value from the option set.")]
[CliCommand(
- Name = "delete",
- Description = "Delete an option value from a local or global option set."
+ Name = "remove",
+ Description = "Remove an option value from a global or local option set."
)]
#pragma warning disable TXC003
-public class EntityOptionSetDeleteOptionCliCommand : StagedCliCommand, IDestructiveCommand
+public class OptionSetRemoveOptionCliCommand : StagedCliCommand, IDestructiveCommand
{
- protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(EntityOptionSetDeleteOptionCliCommand));
+ protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(OptionSetRemoveOptionCliCommand));
- [CliOption(Name = "--yes", Description = "Skip interactive confirmation for this destructive operation.", Required = false)]
+ [CliOption(Name = "--yes", Description = "Skip interactive confirmation.", Required = false)]
public bool Yes { get; set; }
- [CliOption(Name = "--entity", Description = "The logical name of the entity (for local option sets).", Required = false)]
+ [CliOption(Name = "--name", Description = "Schema name of the global option set.", Required = false)]
+ public string? Name { get; set; }
+
+ [CliOption(Name = "--entity", Description = "Entity logical name (for local option sets).", Required = false)]
public string? Entity { get; set; }
- [CliOption(Name = "--attribute", Description = "The logical name of the attribute (for local option sets).", Required = false)]
+ [CliOption(Name = "--attribute", Description = "Attribute logical name (for local option sets).", Required = false)]
public string? Attribute { get; set; }
- [CliOption(Name = "--global-optionset", Description = "The name of the global option set (mutually exclusive with --entity/--attribute).", Required = false)]
- public string? GlobalOptionset { get; set; }
-
- [CliOption(Name = "--value", Description = "The integer value of the option to remove.", Required = true)]
+ [CliOption(Name = "--value", Description = "Integer value of the option to remove.", Required = true)]
public int Value { get; set; }
protected override async Task ExecuteAsync()
{
ValidateExecutionMode();
- // Validate mutually exclusive options.
- bool hasGlobal = !string.IsNullOrWhiteSpace(GlobalOptionset);
+ bool hasGlobal = !string.IsNullOrWhiteSpace(Name);
bool hasLocal = !string.IsNullOrWhiteSpace(Entity) || !string.IsNullOrWhiteSpace(Attribute);
if (hasGlobal && hasLocal)
{
- Logger.LogError("Specify either --global-optionset or --entity/--attribute, not both.");
+ Logger.LogError("Specify either --name (global) or --entity + --attribute (local), not both.");
return ExitError;
}
-
if (!hasGlobal && !hasLocal)
{
- Logger.LogError("Specify --global-optionset for a global option set, or --entity and --attribute for a local one.");
+ Logger.LogError("Specify --name for a global option set, or --entity and --attribute for a local one.");
return ExitError;
}
-
if (hasLocal && (string.IsNullOrWhiteSpace(Entity) || string.IsNullOrWhiteSpace(Attribute)))
{
Logger.LogError("Both --entity and --attribute are required for local option sets.");
@@ -65,33 +63,33 @@ protected override async Task ExecuteAsync()
if (Stage)
{
- string stageTarget = hasGlobal ? GlobalOptionset! : $"{Entity}.{Attribute}";
+ string stageTarget = hasGlobal ? Name! : $"{Entity}.{Attribute}";
var store = TxcServices.Get();
store.Add(new StagedOperation
{
Category = "schema",
OperationType = "DELETE",
- TargetType = "optionset",
+ TargetType = "optionset-option",
TargetDescription = stageTarget,
Details = $"remove option value: {Value}",
Parameters = new Dictionary
{
["entity"] = Entity,
["attribute"] = Attribute,
- ["globalOptionset"] = GlobalOptionset,
+ ["name"] = Name,
["value"] = Value
}
});
- OutputWriter.WriteLine($"Staged: DELETE option {Value} from {stageTarget}");
+ OutputWriter.WriteLine($"Staged: REMOVE option {Value} from {stageTarget}");
return ExitSuccess;
}
var service = TxcServices.Get();
await service.DeleteOptionAsync(
- Profile, Entity, Attribute, GlobalOptionset, Value, CancellationToken.None
+ Profile, Entity, Attribute, Name, Value, CancellationToken.None
).ConfigureAwait(false);
- string target = hasGlobal ? $"global option set '{GlobalOptionset}'" : $"attribute '{Attribute}' on entity '{Entity}'";
+ string target = hasGlobal ? $"global option set '{Name}'" : $"attribute '{Attribute}' on entity '{Entity}'";
OutputWriter.WriteLine($"Option value {Value} removed from {target}.");
return ExitSuccess;
}
diff --git a/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetShowCliCommand.cs b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetShowCliCommand.cs
new file mode 100644
index 0000000..77c443e
--- /dev/null
+++ b/src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetShowCliCommand.cs
@@ -0,0 +1,201 @@
+using DotMake.CommandLine;
+using Microsoft.Extensions.Logging;
+using TALXIS.CLI.Core;
+using TALXIS.CLI.Core.Contracts.Dataverse;
+using TALXIS.CLI.Core.DependencyInjection;
+using TALXIS.CLI.Logging;
+
+namespace TALXIS.CLI.Features.Environment.OptionSet;
+
+///
+/// Shows all values and labels for an option set — global or local.
+/// Usage: txc environment optionset describe --name <schema-name>
+/// Usage: txc environment optionset describe --entity <name> --attribute <name>
+///
+[CliReadOnly]
+[CliCommand(
+ Name = "show",
+ Description = "Show values and labels for a global or local option set."
+)]
+#pragma warning disable TXC003
+public class OptionSetShowCliCommand : ProfiledCliCommand
+{
+ protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(OptionSetShowCliCommand));
+
+ [CliOption(Name = "--name", Description = "Schema name of a global option set.", Required = false)]
+ public string? Name { get; set; }
+
+ [CliOption(Name = "--entity", Description = "Entity logical name (for local option sets).", Required = false)]
+ public string? Entity { get; set; }
+
+ [CliOption(Name = "--attribute", Description = "Attribute logical name (for local option sets).", Required = false)]
+ public string? Attribute { get; set; }
+
+ [CliOption(Name = "--language", Description = "Language code (LCID) for labels, e.g. 1033 (English), 1029 (Czech). Defaults to the connection user's language.", Required = false)]
+ public int? Language { get; set; }
+
+ [CliOption(Name = "--label", Description = "Look up the integer value for a specific label text (case-insensitive). Outputs only the value for piping.", Required = false)]
+ public string? LabelLookup { get; set; }
+
+ [CliOption(Name = "--value", Description = "Look up the label text for a specific integer value. Outputs only the label for piping.", Required = false)]
+ public int? ValueLookup { get; set; }
+
+ protected override async Task ExecuteAsync()
+ {
+ bool hasGlobal = !string.IsNullOrWhiteSpace(Name);
+ bool hasLocal = !string.IsNullOrWhiteSpace(Entity) || !string.IsNullOrWhiteSpace(Attribute);
+
+ if (hasGlobal && hasLocal)
+ {
+ Logger.LogError("Specify either --name (global) or --entity + --attribute (local), not both.");
+ return ExitError;
+ }
+ if (!hasGlobal && !hasLocal)
+ {
+ Logger.LogError("Specify --name for a global option set, or --entity and --attribute for a local one.");
+ return ExitError;
+ }
+ if (hasLocal && (string.IsNullOrWhiteSpace(Entity) || string.IsNullOrWhiteSpace(Attribute)))
+ {
+ Logger.LogError("Both --entity and --attribute are required for local option sets.");
+ return ExitError;
+ }
+
+ // Collect options from global or local source
+ IReadOnlyList options;
+
+ if (hasGlobal)
+ {
+ var service = TxcServices.Get();
+ var detail = await service.DescribeGlobalOptionSetAsync(Profile, Name!, Language, CancellationToken.None).ConfigureAwait(false);
+ options = detail.Options;
+
+ if (LabelLookup is null && ValueLookup is null)
+ {
+ OutputFormatter.WriteData(detail, PrintGlobalDetail);
+ return ExitSuccess;
+ }
+ }
+ else
+ {
+ var metaService = TxcServices.Get();
+ var attrDetail = await metaService.GetAttributeDetailAsync(Profile, Entity!, Attribute!, CancellationToken.None).ConfigureAwait(false);
+
+ // Extract options from attribute detail
+ options = ExtractOptionsFromAttributeDetail(attrDetail);
+
+ if (LabelLookup is null && ValueLookup is null)
+ {
+ OutputFormatter.WriteData(attrDetail, PrintAttributeDetail);
+ return ExitSuccess;
+ }
+ }
+
+ // Label → Value lookup
+ if (LabelLookup is not null)
+ {
+ var match = options.FirstOrDefault(o =>
+ string.Equals(o.Label, LabelLookup, StringComparison.OrdinalIgnoreCase));
+ if (match is null)
+ {
+ Logger.LogError("No option found with label '{Label}'.", LabelLookup);
+ return ExitError;
+ }
+ OutputWriter.WriteLine(match.Value.ToString());
+ return ExitSuccess;
+ }
+
+ // Value → Label lookup
+ if (ValueLookup is not null)
+ {
+ var match = options.FirstOrDefault(o => o.Value == ValueLookup.Value);
+ if (match is null)
+ {
+ Logger.LogError("No option found with value {Value}.", ValueLookup.Value);
+ return ExitError;
+ }
+ OutputWriter.WriteLine(match.Label ?? "");
+ return ExitSuccess;
+ }
+
+ return ExitSuccess;
+ }
+
+ private static void PrintGlobalDetail(GlobalOptionSetDetailRecord detail)
+ {
+ OutputWriter.WriteLine($"Name: {detail.Name}");
+ OutputWriter.WriteLine($"Display Name: {detail.DisplayName ?? "-"}");
+ OutputWriter.WriteLine($"Description: {detail.Description ?? "-"}");
+ OutputWriter.WriteLine($"Type: {detail.OptionSetType}");
+ OutputWriter.WriteLine($"Custom: {detail.IsCustomOptionSet}");
+ OutputWriter.WriteLine();
+ PrintOptions(detail.Options);
+ }
+
+ private static void PrintAttributeDetail(Dictionary detail)
+ {
+ if (detail.TryGetValue("Logical Name", out var ln)) OutputWriter.WriteLine($"Attribute: {ln}");
+ if (detail.TryGetValue("Option Set Name", out var osn)) OutputWriter.WriteLine($"Option Set: {osn}");
+ if (detail.TryGetValue("Is Global Option Set", out var ig)) OutputWriter.WriteLine($"Global: {ig}");
+ OutputWriter.WriteLine();
+
+ if (detail.TryGetValue("Options", out var opts) && opts is IEnumerable