feat(transport): export site/instance selection end-to-end via CLI + ManagementActor (M8 B4)

This commit is contained in:
Joseph Doherty
2026-06-18 06:14:39 -04:00
parent d7dae24355
commit bdbf5cdab0
16 changed files with 450 additions and 12 deletions
@@ -65,6 +65,10 @@ public static class BundleCommands
// --api-keys option. Re-create keys and re-grant their method scopes on the
// destination via the admin UI/CLI.
var apiMethodsOption = NameListOption("--api-methods", "Comma-separated API-method names");
// M8 (B4): site/instance-scoped export. Sites accept a SiteIdentifier
// (preferred) or friendly Name per token; instances accept a UniqueName.
var sitesOption = NameListOption("--sites", "Comma-separated site identifiers or names");
var instancesOption = NameListOption("--instances", "Comma-separated instance unique-names");
var includeDepsOption = new Option<bool>("--include-dependencies")
{
Description = "Pull transitive dependencies (referenced shared scripts, parents, composed members) into the bundle.",
@@ -88,6 +92,8 @@ public static class BundleCommands
cmd.Add(notificationListsOption);
cmd.Add(smtpConfigsOption);
cmd.Add(apiMethodsOption);
cmd.Add(sitesOption);
cmd.Add(instancesOption);
cmd.Add(includeDepsOption);
cmd.Add(sourceEnvOption);
@@ -110,7 +116,9 @@ public static class BundleCommands
ApiMethodNames: result.GetValue(apiMethodsOption),
IncludeDependencies: includeDeps,
Passphrase: passphrase,
SourceEnvironment: sourceEnv);
SourceEnvironment: sourceEnv,
SiteNames: result.GetValue(sitesOption),
InstanceNames: result.GetValue(instancesOption));
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption,
@@ -359,14 +367,26 @@ public static class BundleCommands
{
Description = description,
CustomParser = arg =>
{
var token = arg.Tokens.Count == 0 ? null : arg.Tokens[0].Value;
if (string.IsNullOrWhiteSpace(token)) return null;
return token
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToArray();
},
ParseNameList(arg.Tokens.Count == 0 ? null : arg.Tokens[0].Value),
};
return opt;
}
/// <summary>
/// Splits a comma-separated CLI value into a trimmed, empty-entry-free name
/// list (the shared shape used by every <c>--templates</c>/<c>--sites</c>/…
/// option). Returns <c>null</c> for a null/blank token so the management
/// command sees "not specified" rather than an empty list. Exposed
/// <c>internal</c> so the flag-parse tests can assert the split semantics
/// without reaching into the per-command local options.
/// </summary>
/// <param name="token">The raw comma-separated option value, or <c>null</c>.</param>
/// <returns>The parsed name array, or <c>null</c> when the token is null/blank.</returns>
internal static IReadOnlyList<string>? ParseNameList(string? token)
{
if (string.IsNullOrWhiteSpace(token)) return null;
return token
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToArray();
}
}