@go-to-k/cdkd 0.108.0 → 0.109.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -35,7 +35,7 @@ import { Command, Option } from "commander";
35
35
  import { writeFileSync as writeFileSync$1 } from "fs";
36
36
  import { join as join$1 } from "path";
37
37
  import * as zlib from "node:zlib";
38
- import { ApplicationAutoScalingClient, DescribeScalableTargetsCommand, DescribeScalingPoliciesCommand } from "@aws-sdk/client-application-auto-scaling";
38
+ import { ApplicationAutoScalingClient, DeleteScalingPolicyCommand, DeregisterScalableTargetCommand, DescribeScalableTargetsCommand, DescribeScalingPoliciesCommand, PutScalingPolicyCommand, RegisterScalableTargetCommand } from "@aws-sdk/client-application-auto-scaling";
39
39
  import { ApiGatewayV2Client, CreateApiCommand, CreateAuthorizerCommand as CreateAuthorizerCommand$1, CreateIntegrationCommand, CreateRouteCommand as CreateRouteCommand$1, CreateStageCommand as CreateStageCommand$1, DeleteApiCommand, DeleteAuthorizerCommand as DeleteAuthorizerCommand$1, DeleteIntegrationCommand, DeleteRouteCommand as DeleteRouteCommand$1, DeleteStageCommand as DeleteStageCommand$1, GetApiCommand, GetApisCommand, GetAuthorizerCommand as GetAuthorizerCommand$1, GetIntegrationCommand, GetRouteCommand, GetStageCommand as GetStageCommand$1, NotFoundException as NotFoundException$3, UpdateApiCommand, UpdateAuthorizerCommand as UpdateAuthorizerCommand$1, UpdateIntegrationCommand, UpdateRouteCommand, UpdateStageCommand as UpdateStageCommand$1 } from "@aws-sdk/client-apigatewayv2";
40
40
  import { CreateStateMachineCommand, DeleteStateMachineCommand, DescribeStateMachineCommand, ListStateMachinesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$9, SFNClient, StateMachineDoesNotExist, TagResourceCommand as TagResourceCommand$10, UntagResourceCommand as UntagResourceCommand$9, UpdateStateMachineCommand } from "@aws-sdk/client-sfn";
41
41
  import { CreateClusterCommand, CreateServiceCommand, DeleteClusterCommand, DeleteServiceCommand, DeregisterTaskDefinitionCommand, DescribeClustersCommand, DescribeServicesCommand, DescribeTaskDefinitionCommand, ECSClient, ListClustersCommand, ListServicesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$10, PutClusterCapacityProvidersCommand, RegisterTaskDefinitionCommand, TagResourceCommand as TagResourceCommand$11, UntagResourceCommand as UntagResourceCommand$10, UpdateClusterCommand, UpdateServiceCommand } from "@aws-sdk/client-ecs";
@@ -7984,6 +7984,20 @@ var DynamoDBGlobalTableProvider = class {
7984
7984
  return client;
7985
7985
  }
7986
7986
  /**
7987
+ * Lazy-init + cache the local-region `ApplicationAutoScalingClient`.
7988
+ * Previously `applyAutoScalingDiff` constructed a fresh client per
7989
+ * call when no `client` arg was passed, leaking SDK clients (each
7990
+ * holds its own HTTP agent) on multi-stack runs with many
7991
+ * GlobalTables (PR #403 review minor #4).
7992
+ */
7993
+ localAutoScalingClient;
7994
+ async getLocalAutoScalingClient() {
7995
+ if (this.localAutoScalingClient) return this.localAutoScalingClient;
7996
+ const region = await this.dynamoDBClient.config.region() ?? "";
7997
+ this.localAutoScalingClient = new ApplicationAutoScalingClient({ region });
7998
+ return this.localAutoScalingClient;
7999
+ }
8000
+ /**
7987
8001
  * Construct the regional table ARN for a cross-region replica of a
7988
8002
  * GlobalTable. AWS replicates the same `TableName` across every
7989
8003
  * replica region, with each replica's ARN differing only in the
@@ -8237,10 +8251,19 @@ var DynamoDBGlobalTableProvider = class {
8237
8251
  await this.dynamoDBClient.send(new UpdateTableCommand(billingUpdate));
8238
8252
  await this.waitForTableActiveAfterUpdate(physicalId, logicalId);
8239
8253
  }
8254
+ const writeAutoScalingOld = (previousProperties["WriteProvisionedThroughputSettings"] ?? {})["WriteCapacityAutoScalingSettings"];
8255
+ const writeAutoScalingNew = (properties["WriteProvisionedThroughputSettings"] ?? {})["WriteCapacityAutoScalingSettings"];
8256
+ const effectiveNewAutoScaling = oldBilling === "PROVISIONED" && newBilling === "PAY_PER_REQUEST" ? void 0 : writeAutoScalingNew;
8257
+ if ((newBilling === "PROVISIONED" || oldBilling === "PROVISIONED") && !deepEqual$1(writeAutoScalingOld, effectiveNewAutoScaling)) await this.applyAutoScalingDiff(physicalId, "dynamodb:table:WriteCapacityUnits", writeAutoScalingOld, effectiveNewAutoScaling);
8240
8258
  const replicaDiff = diffReplicas(previousProperties["Replicas"] ?? [], properties["Replicas"] ?? []);
8241
8259
  for (const replica of replicaDiff.removed) {
8242
8260
  const region = replica["Region"];
8243
8261
  if (!region || region === currentRegion) continue;
8262
+ const removedReadAutoScaling = (replica["ReadProvisionedThroughputSettings"] ?? {})["ReadCapacityAutoScalingSettings"];
8263
+ if (removedReadAutoScaling) {
8264
+ const regionalAutoScalingClient = this.getRegionalAutoScalingClient(region);
8265
+ await this.applyAutoScalingDiff(physicalId, "dynamodb:table:ReadCapacityUnits", removedReadAutoScaling, void 0, regionalAutoScalingClient);
8266
+ }
8244
8267
  await this.dynamoDBClient.send(new UpdateTableCommand({
8245
8268
  TableName: physicalId,
8246
8269
  ReplicaUpdates: [{ Delete: { RegionName: region } }]
@@ -8251,11 +8274,17 @@ var DynamoDBGlobalTableProvider = class {
8251
8274
  const region = replica["Region"];
8252
8275
  if (!region || region === currentRegion) continue;
8253
8276
  await this.addReplica(physicalId, replica, region, logicalId);
8277
+ const newReadAutoScaling = (replica["ReadProvisionedThroughputSettings"] ?? {})["ReadCapacityAutoScalingSettings"];
8278
+ if (newBilling === "PROVISIONED" && newReadAutoScaling) {
8279
+ const regionalAutoScalingClient = this.getRegionalAutoScalingClient(region);
8280
+ await this.applyAutoScalingDiff(physicalId, "dynamodb:table:ReadCapacityUnits", void 0, newReadAutoScaling, regionalAutoScalingClient);
8281
+ }
8254
8282
  }
8255
8283
  for (const replica of replicaDiff.modified) {
8256
8284
  const region = replica["Region"];
8257
8285
  if (!region || region === currentRegion) continue;
8258
- const oldReplicaTags = (previousProperties["Replicas"] ?? []).find((r) => r["Region"] === region)?.["Tags"];
8286
+ const oldReplica = (previousProperties["Replicas"] ?? []).find((r) => r["Region"] === region);
8287
+ const oldReplicaTags = oldReplica?.["Tags"];
8259
8288
  const newReplicaTags = replica["Tags"];
8260
8289
  if (!deepEqual$1(oldReplicaTags, newReplicaTags)) if (tableArn) {
8261
8290
  const replicaArn = this.replicaArnForRegion(tableArn, region);
@@ -8267,6 +8296,13 @@ var DynamoDBGlobalTableProvider = class {
8267
8296
  }
8268
8297
  else this.logger.warn(`Could not derive replica ARN for region ${region} from ${tableArn} — skipping Tags propagation for ${physicalId}`);
8269
8298
  } else this.logger.warn(`Local DescribeTable returned no TableArn — cannot propagate Tags to cross-region replica ${region} of ${physicalId}`);
8299
+ const oldReadAutoScaling = (oldReplica?.["ReadProvisionedThroughputSettings"] ?? {})["ReadCapacityAutoScalingSettings"];
8300
+ const newReadAutoScaling = (replica["ReadProvisionedThroughputSettings"] ?? {})["ReadCapacityAutoScalingSettings"];
8301
+ const effectiveNewReadAutoScaling = newBilling === "PAY_PER_REQUEST" ? void 0 : newReadAutoScaling;
8302
+ if ((newBilling === "PROVISIONED" || oldBilling === "PROVISIONED") && !deepEqual$1(oldReadAutoScaling, effectiveNewReadAutoScaling)) {
8303
+ const regionalAutoScalingClient = this.getRegionalAutoScalingClient(region);
8304
+ await this.applyAutoScalingDiff(physicalId, "dynamodb:table:ReadCapacityUnits", oldReadAutoScaling, effectiveNewReadAutoScaling, regionalAutoScalingClient);
8305
+ }
8270
8306
  const updateAction = { RegionName: region };
8271
8307
  if (replica["KMSMasterKeyId"] !== void 0) updateAction.KMSMasterKeyId = replica["KMSMasterKeyId"];
8272
8308
  if (replica["GlobalSecondaryIndexes"]) updateAction.GlobalSecondaryIndexes = replica["GlobalSecondaryIndexes"];
@@ -8405,6 +8441,127 @@ var DynamoDBGlobalTableProvider = class {
8405
8441
  }
8406
8442
  }
8407
8443
  /**
8444
+ * Apply an application-autoscaling diff for one (tableName, dimension)
8445
+ * pair (Issue #402 / PR closing Issue #395's deferred items).
8446
+ *
8447
+ * Dimension is either:
8448
+ * - `dynamodb:table:WriteCapacityUnits` (table-level write capacity),
8449
+ * paired with the local-region autoscaling client.
8450
+ * - `dynamodb:table:ReadCapacityUnits` (per-replica read capacity),
8451
+ * paired with a region-scoped autoscaling client returned by
8452
+ * `getRegionalAutoScalingClient`.
8453
+ *
8454
+ * Diff semantics (idempotent upsert, per Issue #402 spec):
8455
+ * - new auto-scaling settings present (Min/Max + TargetValue) →
8456
+ * `RegisterScalableTarget` (upsert) + `PutScalingPolicy` (upsert).
8457
+ * AWS's `RegisterScalableTarget` accepts no-op Min/Max changes
8458
+ * silently, and `PutScalingPolicy` is idempotent on the same
8459
+ * policy name (`DynamoDB{Write,Read}CapacityUtilization:table/<name>`).
8460
+ * - old auto-scaling settings present but new ones absent (= null) →
8461
+ * `DeleteScalingPolicy` + `DeregisterScalableTarget`. AWS rejects
8462
+ * `DeregisterScalableTarget` on a still-policy-attached target,
8463
+ * so the policy must be deleted FIRST.
8464
+ * - neither side has auto-scaling settings → no-op.
8465
+ *
8466
+ * **Best-effort**: any AWS error is logged at WARN with the per-API
8467
+ * recovery command and the deploy continues — auto-scaling drift is
8468
+ * recoverable on the next deploy, and an aborted deploy is much worse
8469
+ * UX than a transient auto-scaling miss. This matches the cross-region
8470
+ * Tags propagation contract (PR #393).
8471
+ *
8472
+ * **Policy naming** matches AWS's CDK / console default:
8473
+ * `DynamoDBWriteCapacityUtilization:table/<table-name>` (write)
8474
+ * `DynamoDBReadCapacityUtilization:table/<table-name>` (read)
8475
+ * so re-imports against a console-created target also match.
8476
+ *
8477
+ * **`SeedCapacity` is intentionally NOT forwarded** — it is a CFn
8478
+ * create-only field with no corresponding `RegisterScalableTarget`
8479
+ * surface; `readAutoScalingSettings` also explicitly skips it.
8480
+ */
8481
+ async applyAutoScalingDiff(tableName, dimension, oldSettings, newSettings, client) {
8482
+ const isWrite = dimension === "dynamodb:table:WriteCapacityUnits";
8483
+ const policyName = isWrite ? `DynamoDBWriteCapacityUtilization:table/${tableName}` : `DynamoDBReadCapacityUtilization:table/${tableName}`;
8484
+ const metricType = isWrite ? "DynamoDBWriteCapacityUtilization" : "DynamoDBReadCapacityUtilization";
8485
+ const resourceId = `table/${tableName}`;
8486
+ const asClient = client ?? await this.getLocalAutoScalingClient();
8487
+ const oldEnabled = oldSettings !== void 0 && oldSettings !== null;
8488
+ const newEnabled = newSettings !== void 0 && newSettings !== null;
8489
+ if (!oldEnabled && !newEnabled) return;
8490
+ if (newEnabled) {
8491
+ const minCapacity = Number(newSettings["MinCapacity"] ?? 0);
8492
+ const maxCapacity = Number(newSettings["MaxCapacity"] ?? 0);
8493
+ if (!Number.isFinite(minCapacity) || !Number.isFinite(maxCapacity)) {
8494
+ this.logger.warn(`Cannot apply auto-scaling diff on ${tableName} (${dimension}): MinCapacity / MaxCapacity must be numbers, got ${String(newSettings["MinCapacity"])} / ${String(newSettings["MaxCapacity"])}`);
8495
+ return;
8496
+ }
8497
+ try {
8498
+ await asClient.send(new RegisterScalableTargetCommand({
8499
+ ServiceNamespace: "dynamodb",
8500
+ ResourceId: resourceId,
8501
+ ScalableDimension: dimension,
8502
+ MinCapacity: minCapacity,
8503
+ MaxCapacity: maxCapacity
8504
+ }));
8505
+ } catch (err) {
8506
+ this.logger.warn(`Could not register auto-scaling target on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling register-scalable-target --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension} --min-capacity ${minCapacity} --max-capacity ${maxCapacity}`);
8507
+ return;
8508
+ }
8509
+ const tttCfg = newSettings["TargetTrackingScalingPolicyConfiguration"] ?? {};
8510
+ const targetValue = Number(tttCfg["TargetValue"]);
8511
+ if (!Number.isFinite(targetValue)) {
8512
+ this.logger.warn(`Auto-scaling target registered on ${tableName} (${dimension}) but TargetValue is missing or non-numeric — skipping PutScalingPolicy. Provide TargetTrackingScalingPolicyConfiguration.TargetValue in the template.`);
8513
+ return;
8514
+ }
8515
+ const targetTrackingConfig = {
8516
+ PredefinedMetricSpecification: { PredefinedMetricType: metricType },
8517
+ TargetValue: targetValue
8518
+ };
8519
+ if (tttCfg["ScaleInCooldown"] !== void 0) targetTrackingConfig["ScaleInCooldown"] = Number(tttCfg["ScaleInCooldown"]);
8520
+ if (tttCfg["ScaleOutCooldown"] !== void 0) targetTrackingConfig["ScaleOutCooldown"] = Number(tttCfg["ScaleOutCooldown"]);
8521
+ if (tttCfg["DisableScaleIn"] !== void 0) targetTrackingConfig["DisableScaleIn"] = Boolean(tttCfg["DisableScaleIn"]);
8522
+ try {
8523
+ await asClient.send(new PutScalingPolicyCommand({
8524
+ PolicyName: policyName,
8525
+ ServiceNamespace: "dynamodb",
8526
+ ResourceId: resourceId,
8527
+ ScalableDimension: dimension,
8528
+ PolicyType: "TargetTrackingScaling",
8529
+ TargetTrackingScalingPolicyConfiguration: targetTrackingConfig
8530
+ }));
8531
+ this.logger.debug(`Upserted auto-scaling policy ${policyName} on ${tableName} (${dimension})`);
8532
+ } catch (err) {
8533
+ this.logger.warn(`Could not put auto-scaling policy on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling put-scaling-policy --policy-name ${policyName} --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension} --policy-type TargetTrackingScaling`);
8534
+ }
8535
+ return;
8536
+ }
8537
+ const isObjectNotFound = (err) => {
8538
+ if (!(err instanceof Error)) return false;
8539
+ const name = err.name ?? "";
8540
+ const msg = err.message ?? "";
8541
+ return name === "ObjectNotFoundException" || msg.includes("No scaling policy found") || msg.includes("No scalable target found");
8542
+ };
8543
+ try {
8544
+ await asClient.send(new DeleteScalingPolicyCommand({
8545
+ PolicyName: policyName,
8546
+ ServiceNamespace: "dynamodb",
8547
+ ResourceId: resourceId,
8548
+ ScalableDimension: dimension
8549
+ }));
8550
+ } catch (err) {
8551
+ if (!isObjectNotFound(err)) this.logger.warn(`Could not delete auto-scaling policy on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling delete-scaling-policy --policy-name ${policyName} --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension}`);
8552
+ }
8553
+ try {
8554
+ await asClient.send(new DeregisterScalableTargetCommand({
8555
+ ServiceNamespace: "dynamodb",
8556
+ ResourceId: resourceId,
8557
+ ScalableDimension: dimension
8558
+ }));
8559
+ this.logger.debug(`Deregistered auto-scaling target ${resourceId} (${dimension})`);
8560
+ } catch (err) {
8561
+ if (!isObjectNotFound(err)) this.logger.warn(`Could not deregister auto-scaling target on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling deregister-scalable-target --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension}`);
8562
+ }
8563
+ }
8564
+ /**
8408
8565
  * Delete a DynamoDB Global Table.
8409
8566
  *
8410
8567
  * Order is load-bearing:
@@ -8450,6 +8607,7 @@ var DynamoDBGlobalTableProvider = class {
8450
8607
  const region = replica.RegionName;
8451
8608
  if (!region || region === currentRegion) continue;
8452
8609
  try {
8610
+ await this.applyAutoScalingDiff(physicalId, "dynamodb:table:ReadCapacityUnits", {}, void 0, this.getRegionalAutoScalingClient(region));
8453
8611
  await this.dynamoDBClient.send(new UpdateTableCommand({
8454
8612
  TableName: physicalId,
8455
8613
  ReplicaUpdates: [{ Delete: { RegionName: region } }]
@@ -8459,6 +8617,9 @@ var DynamoDBGlobalTableProvider = class {
8459
8617
  if (!(replicaErr instanceof ResourceNotFoundException$1)) throw replicaErr;
8460
8618
  }
8461
8619
  }
8620
+ const localAsClient = await this.getLocalAutoScalingClient();
8621
+ await this.applyAutoScalingDiff(physicalId, "dynamodb:table:ReadCapacityUnits", {}, void 0, localAsClient);
8622
+ await this.applyAutoScalingDiff(physicalId, "dynamodb:table:WriteCapacityUnits", {}, void 0, localAsClient);
8462
8623
  } catch (describeErr) {
8463
8624
  if (!(describeErr instanceof ResourceNotFoundException$1)) {
8464
8625
  const cause = describeErr instanceof Error ? describeErr : void 0;
@@ -18396,7 +18557,7 @@ var RDSProvider = class {
18396
18557
  //#endregion
18397
18558
  //#region src/provisioning/providers/rds-dbproxy-provider.ts
18398
18559
  const POLL_INTERVAL_MS$1 = 5e3;
18399
- const POLL_TIMEOUT_MS$1 = 900 * 1e3;
18560
+ const POLL_TIMEOUT_MS$1 = 1800 * 1e3;
18400
18561
  /**
18401
18562
  * AWS RDS DBProxy Provider
18402
18563
  *
@@ -18541,8 +18702,8 @@ var RDSDBProxyProvider = class {
18541
18702
  throw this.wrapError(error, "UPDATE", resourceType, logicalId, physicalId);
18542
18703
  }
18543
18704
  }
18544
- await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"], resourceType, logicalId);
18545
18705
  this.invalidateAttributeCache(physicalId);
18706
+ await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"], resourceType, logicalId);
18546
18707
  return {
18547
18708
  physicalId,
18548
18709
  wasReplaced: false
@@ -18664,6 +18825,9 @@ var RDSDBProxyProvider = class {
18664
18825
  return result;
18665
18826
  }
18666
18827
  async applyTagDiff(physicalId, oldTags, newTags, resourceType, logicalId) {
18828
+ const oldMap = this.toTagMap(oldTags);
18829
+ const newMap = this.toTagMap(newTags);
18830
+ if (oldMap.size === newMap.size && [...oldMap.keys()].every((k) => newMap.has(k)) && [...oldMap.entries()].every(([k, v]) => newMap.get(k) === v)) return;
18667
18831
  const client = this.getClient();
18668
18832
  const arnCacheKey = `${physicalId}:DBProxyArn`;
18669
18833
  let arn = this.attributeCache.get(arnCacheKey);
@@ -18675,8 +18839,6 @@ var RDSDBProxyProvider = class {
18675
18839
  return;
18676
18840
  }
18677
18841
  if (!arn) return;
18678
- const oldMap = this.toTagMap(oldTags);
18679
- const newMap = this.toTagMap(newTags);
18680
18842
  const toRemove = [];
18681
18843
  const toAdd = [];
18682
18844
  for (const k of oldMap.keys()) if (!newMap.has(k)) toRemove.push(k);
@@ -18750,7 +18912,7 @@ var RDSDBProxyProvider = class {
18750
18912
  //#endregion
18751
18913
  //#region src/provisioning/providers/rds-dbproxy-endpoint-provider.ts
18752
18914
  const POLL_INTERVAL_MS = 5e3;
18753
- const POLL_TIMEOUT_MS = 900 * 1e3;
18915
+ const POLL_TIMEOUT_MS = 1800 * 1e3;
18754
18916
  /**
18755
18917
  * AWS RDS DBProxyEndpoint Provider
18756
18918
  *
@@ -18832,11 +18994,9 @@ var RDSDBProxyEndpointProvider = class {
18832
18994
  let status;
18833
18995
  while (Date.now() < deadline) {
18834
18996
  try {
18835
- const ep = (await client.send(new DescribeDBProxyEndpointsCommand({
18836
- DBProxyName: dbProxyName,
18837
- DBProxyEndpointName: dbProxyEndpointName
18838
- }))).DBProxyEndpoints?.[0];
18997
+ const ep = (await client.send(new DescribeDBProxyEndpointsCommand({ DBProxyEndpointName: dbProxyEndpointName }))).DBProxyEndpoints?.[0];
18839
18998
  status = ep?.Status;
18999
+ this.logger.debug(`DBProxyEndpoint ${dbProxyEndpointName} poll: status=${status ?? "not-yet-visible"}`);
18840
19000
  if (status === "available") {
18841
19001
  endpoint = ep?.Endpoint;
18842
19002
  arn = ep?.DBProxyEndpointArn;
@@ -18883,8 +19043,8 @@ var RDSDBProxyEndpointProvider = class {
18883
19043
  throw this.wrapError(error, "UPDATE", resourceType, logicalId, physicalId);
18884
19044
  }
18885
19045
  }
18886
- await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"], resourceType, logicalId);
18887
19046
  this.invalidateAttributeCache(physicalId);
19047
+ await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"], resourceType, logicalId);
18888
19048
  return {
18889
19049
  physicalId,
18890
19050
  wasReplaced: false
@@ -45026,7 +45186,7 @@ function reorderArgs(argv) {
45026
45186
  */
45027
45187
  async function main() {
45028
45188
  const program = new Command();
45029
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.108.0");
45189
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.109.1");
45030
45190
  program.addCommand(createBootstrapCommand());
45031
45191
  program.addCommand(createSynthCommand());
45032
45192
  program.addCommand(createListCommand());