@go-to-k/cdkd 0.281.21 → 0.281.23

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as configStringRefusal, $t as CFN_TEMPLATE_URL_LIMIT, A as green, At as validateContainerRepoName, B as slowCcOperationTimeoutMs, Bt as synthesisStatusMessage, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as PartialFailureError, Ct as rewriteTemplateAssetReferences, D as bold, Dn as StackHasActiveImportsError, Dt as getBootstrapMarkerKey, E as formatResourceLine, En as ResourceUpdateNotSupportedError, Et as ensureAssetStorage, F as clearOnUpdateRemoval, Ft as runDockerForeground, G as getAccountInfo, Gt as resolveCaptureObservedState, H as isTerminationProtectionPropagationError, Ht as getLegacyStateBucketName, I as ProviderRegistry, It as runDockerStreaming, J as normalizeAwsTagsToCfn, Jt as resolveStateBucketWithDefaultAndSource, K as refStateLookupFromResource, Kt as resolveSkipPrefix, L as findActionableSilentDrops, Lt as AssetManifestLoader, M as yellow, Mt as buildDockerImage, N as IAMRoleProvider, Nn as normalizeAwsError, Nt as formatDockerLoginError, O as cyan, On as StackTerminationProtectionError, Ot as parseBootstrapMarker, P as collectInlinePolicyNamesManagedBySiblings, Pn as withErrorHandling, Pt as getDockerCmd, Q as configBooleanRefusal, Qt as CFN_TEMPLATE_BODY_LIMIT, R as findSilentDropProperties, Rt as getDockerImageBySourceHash, S as extractDeploymentEventError, Sn as NestedStackChildDirectDestroyError, St as loadPublishableAssetManifest, T as renderStatefulReason, Tn as ResourceTimeoutError, Tt as BOOTSTRAP_MARKER_PREFIX, U as IntrinsicFunctionResolver, Ut as resolveApp, V as disableInstanceApiTermination, Vt as getDefaultStateBucketName, W as cfnRefValueFromPhysicalId, Wt as resolveAutoAssetStorage, X as assertRegionMatch, Xt as stateBucketExistenceConfirmed, Y as resolveExplicitPhysicalId, Yt as resolveUseCdkBootstrapAssets, Z as coerceCfnBoolean, Zt as warnDeprecatedNoPrefixCliFlag, _ as createPreDeleteFinalSnapshot, _n as LocalInvokeBuildError$1, _t as AssetPublisher, a as DeploymentEventsStore, an as processStackMessages, at as applyRoleArnIfSet, b as unsupportedFinalSnapshotError, bt as buildAssetRedirectMap, c as replayFailedOperations, cn as AwsClients, ct as withRetry, d as IMPLICIT_DELETE_DEPENDENCIES, dn as setAwsClients, dt as DagBuilder, en as MIGRATE_TMP_PREFIX, et as readConfigString, f as computeImplicitDeleteEdges, ft as TemplateParser, g as ccRoutedFinalSnapshotError, gn as DeployCancelledError, gt as shouldRetainResource, h as buildFinalSnapshotIdentifier, ht as rebuildClientForBucketRegion, i as DeploymentEventsReader, in as AssemblyReader, it as requireConfigString, j as red, jt as derivePartitionAndUrlSuffix, k as gray, kt as validateAssetBucketName, l as replayRollback, ln as getAwsClients, lt as isRetryableTransientError, m as PRE_DELETE_SNAPSHOT_TYPES, mt as S3StateBackend, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as uploadCfnTemplate, nt as requireConfigArray, o as planFailedOps, ot as DiffCalculator, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as CdkdError, pt as LockManager, q as WAFv2WebACLProvider, qt as resolveStateBucketWithDefault, r as DeployEngine, rn as expectedOwnerParam, rt as requireConfigObject, s as planRollback, sn as resolveBucketRegion, st as describeTypeWithThrottleRetry, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as findLargeInlineResources, tt as replayWarn, u as withResourceDeadline, un as resetAwsClients, ut as isThrottlingError, v as isFinalSnapshotError, vn as LocalMigrateError, vt as stringifyValue, w as isStatefulRecreateTargetSync, wn as ProvisioningError, wt as AssetModeResolver, x as makeCanonicalizePropertiesFn, xn as MissingCdkCliError, xt as createAssetRedirectResolver, y as refusesFinalSnapshot, yn as LocalStartServiceError, yt as WorkGraph, z as CloudControlProvider, zt as Synthesizer } from "./deploy-engine-Cx48pqGQ.js";
2
+ import { $ as configStringRefusal, $t as MIGRATE_TMP_PREFIX, A as green, At as validateContainerRepoName, B as slowCcOperationTimeoutMs, Bt as getDefaultStateBucketName, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as PartialFailureError, Ct as rewriteTemplateAssetReferences, D as bold, Dn as StackHasActiveImportsError, Dt as getBootstrapMarkerKey, E as formatResourceLine, En as ResourceUpdateNotSupportedError, Et as ensureAssetStorage, F as clearOnUpdateRemoval, Ft as runDockerStreaming, G as getAccountInfo, Gt as resolveSkipPrefix, H as isTerminationProtectionPropagationError, Ht as resolveApp, I as ProviderRegistry, It as AssetManifestLoader, J as normalizeAwsTagsToCfn, Jt as resolveUseCdkBootstrapAssets, K as refStateLookupFromResource, Kt as resolveStateBucketWithDefault, L as findActionableSilentDrops, Lt as getDockerImageBySourceHash, M as yellow, Mt as formatDockerLoginError, N as IAMRoleProvider, Nn as normalizeAwsError, Nt as getDockerCmd, O as cyan, On as StackTerminationProtectionError, Ot as parseBootstrapMarker, P as collectInlinePolicyNamesManagedBySiblings, Pn as withErrorHandling, Pt as runDockerForeground, Q as configBooleanRefusal, Qt as CFN_TEMPLATE_URL_LIMIT, R as findSilentDropProperties, Rt as Synthesizer, S as extractDeploymentEventError, Sn as NestedStackChildDirectDestroyError, St as loadPublishableAssetManifest, T as renderStatefulReason, Tn as ResourceTimeoutError, Tt as BOOTSTRAP_MARKER_PREFIX, U as IntrinsicFunctionResolver, Ut as resolveAutoAssetStorage, V as disableInstanceApiTermination, Vt as getLegacyStateBucketName, W as cfnRefValueFromPhysicalId, Wt as resolveCaptureObservedState, X as assertRegionMatch, Xt as warnDeprecatedNoPrefixCliFlag, Y as resolveExplicitPhysicalId, Yt as stateBucketExistenceConfirmed, Z as coerceCfnBoolean, Zt as CFN_TEMPLATE_BODY_LIMIT, _ as createPreDeleteFinalSnapshot, _n as LocalInvokeBuildError$1, _t as AssetPublisher, a as DeploymentEventsStore, an as processStackMessages, at as applyRoleArnIfSet, b as unsupportedFinalSnapshotError, bt as buildAssetRedirectMap, c as replayFailedOperations, cn as AwsClients, ct as withRetry, d as IMPLICIT_DELETE_DEPENDENCIES, dn as setAwsClients, dt as DagBuilder, en as findLargeInlineResources, et as readConfigString, f as computeImplicitDeleteEdges, ft as TemplateParser, g as ccRoutedFinalSnapshotError, gn as DeployCancelledError, gt as shouldRetainResource, h as buildFinalSnapshotIdentifier, ht as rebuildClientForBucketRegion, i as DeploymentEventsReader, in as AssemblyReader, it as requireConfigString, j as red, jt as buildDockerImage, k as gray, kt as validateAssetBucketName, l as replayRollback, ln as getAwsClients, lt as isRetryableTransientError, m as PRE_DELETE_SNAPSHOT_TYPES, mt as S3StateBackend, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as expectedOwnerParam, nt as requireConfigArray, o as planFailedOps, ot as DiffCalculator, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as CdkdError, pt as LockManager, q as WAFv2WebACLProvider, qt as resolveStateBucketWithDefaultAndSource, r as DeployEngine, rn as derivePartitionAndUrlSuffix, rt as requireConfigObject, s as planRollback, sn as resolveBucketRegion, st as describeTypeWithThrottleRetry, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as uploadCfnTemplate, tt as replayWarn, u as withResourceDeadline, un as resetAwsClients, ut as isThrottlingError, v as isFinalSnapshotError, vn as LocalMigrateError, vt as stringifyValue, w as isStatefulRecreateTargetSync, wn as ProvisioningError, wt as AssetModeResolver, x as makeCanonicalizePropertiesFn, xn as MissingCdkCliError, xt as createAssetRedirectResolver, y as refusesFinalSnapshot, yn as LocalStartServiceError, yt as WorkGraph, z as CloudControlProvider, zt as synthesisStatusMessage } from "./deploy-engine-uxo_FCje.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
4
- import { t as ASGProvider } from "./asg-provider-CH0YfbOn.js";
4
+ import { t as ASGProvider } from "./asg-provider-CCSQkGwO.js";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -12455,16 +12455,57 @@ function formatShape(segments) {
12455
12455
  * ## The `skipping` variant
12456
12456
  *
12457
12457
  * On the DELETE path the established policy is warn-and-continue, so a
12458
- * malformed record leaves the AWS resource ALIVE while `cdkd destroy` reports
12459
- * success. That policy is not changed here but the warning now says what the
12460
- * skip costs, because an orphan the user is never told about is one they cannot
12461
- * go and delete.
12458
+ * malformed record leaves the AWS resource ALIVE. That policy is unchanged —
12459
+ * the warning says what the skip costs, because an orphan the user is never
12460
+ * told about is one they cannot go and delete.
12461
+ *
12462
+ * Since issue [#1752](https://github.com/go-to-k/cdkd/issues/1752) the ACCOUNTING
12463
+ * around it is no longer a lie: an arm emitting this message must also return
12464
+ * `{ outcome: 'skipped', reason: COMPOSITE_ID_SKIP_REASON }` so `cdkd destroy`
12465
+ * prints a `⚠ … skipped` line instead of `✓ … deleted`, reports
12466
+ * `N deleted, 1 skipped`, KEEPS the state record, and exits non-zero. The
12467
+ * wording here changed accordingly — it used to promise unconditionally that
12468
+ * cdkd "will report this delete as successful", which is no longer true on the
12469
+ * destroy path. It is STILL true for the deploy engine's replacement / rollback
12470
+ * deletes, which discard the return value (issue
12471
+ * [#1762](https://github.com/go-to-k/cdkd/issues/1762)), so the message names
12472
+ * both callers rather than describing only the fixed one — a remedy of
12473
+ * "drop the record with 'cdkd state orphan'" is impossible on a path that has
12474
+ * already dropped it.
12462
12475
  */
12463
12476
  function compositeIdFormatMessage(format, logicalId, physicalId, options) {
12464
12477
  const accepted = format.alsoAccepts ? `"${formatShape(format.segments)}" or ${format.alsoAccepts}` : `"${formatShape(format.segments)}"`;
12465
12478
  const head = `Invalid physicalId format for ${format.label} ${logicalId}: expected ${accepted}, got "${physicalId}"`;
12466
12479
  if (!options?.skipping) return head;
12467
- return `${head}. Skipping the delete — the AWS resource is LEFT IN PLACE and cdkd will report this delete as successful, so delete it by hand (or repair the id in state.json and re-run) if it still exists. NOTE this arm is reached from cdkd destroy AND from the deploy engine's replacement / rollback deletes.`;
12480
+ return `${head}. Skipping the delete — no AWS call is issued, so the resource is LEFT IN PLACE. On 'cdkd destroy' / 'cdkd state destroy' the state record is KEPT and the run exits non-zero, so repair the id in state.json and re-run (or delete the resource by hand and drop the record with 'cdkd state orphan'). NOTE this arm is ALSO reached from cdkd deploy the plain DELETE of a resource removed from the template (the most common one), plus the replacement and rollback deletes — all of which still DROP the record and report success (https://github.com/go-to-k/cdkd/issues/1762) — there, delete it by hand.`;
12481
+ }
12482
+ /**
12483
+ * The short `ResourceDeleteResult.reason` every malformed-composite-id DELETE
12484
+ * skip reports (issue [#1752](https://github.com/go-to-k/cdkd/issues/1752)).
12485
+ *
12486
+ * Deliberately short and IDENTICAL across the five arms: it is rendered inline
12487
+ * on the per-resource status line (`⚠ MyTable (AWS::Glue::Table) skipped
12488
+ * (malformed physicalId in state — no delete issued)`), where the full
12489
+ * remediation sentence built by {@link compositeIdFormatMessage} would not fit.
12490
+ * The full text is still emitted, as the `logger.warn` right beside it.
12491
+ *
12492
+ * A shared constant rather than five literals so the five arms cannot drift and
12493
+ * a grep for the wording finds all of them.
12494
+ */
12495
+ const COMPOSITE_ID_SKIP_REASON = "malformed physicalId in state — no delete issued";
12496
+ /**
12497
+ * The `ResourceDeleteResult` a malformed-composite-id DELETE arm returns.
12498
+ *
12499
+ * Pairs with the `{ skipping: true }` warning: emit the warning, return this.
12500
+ * Providers call it as `return compositeIdSkipResult();` so the arm reads as
12501
+ * the deliberate skip it is rather than as the bare `return;` that made issue
12502
+ * #1752's mis-accounting possible.
12503
+ */
12504
+ function compositeIdSkipResult() {
12505
+ return {
12506
+ outcome: "skipped",
12507
+ reason: COMPOSITE_ID_SKIP_REASON
12508
+ };
12468
12509
  }
12469
12510
 
12470
12511
  //#endregion
@@ -21890,7 +21931,7 @@ var EC2Provider = class {
21890
21931
  const parts = physicalId.split("|");
21891
21932
  if (parts.length < 3) {
21892
21933
  this.logger.warn(compositeIdFormatMessage(EC2_NETWORK_ACL_ENTRY_ID_FORMAT, logicalId, physicalId, { skipping: true }));
21893
- return;
21934
+ return compositeIdSkipResult();
21894
21935
  }
21895
21936
  const networkAclId = parts[0];
21896
21937
  const ruleNumber = parseInt(parts[1], 10);
@@ -39028,7 +39069,7 @@ var AppSyncProvider = class {
39028
39069
  const [apiId, name] = physicalId.split("|");
39029
39070
  if (!apiId || !name) {
39030
39071
  this.logger.warn(compositeIdFormatMessage(APPSYNC_DATASOURCE_ID_FORMAT, logicalId, physicalId, { skipping: true }));
39031
- return;
39072
+ return compositeIdSkipResult();
39032
39073
  }
39033
39074
  try {
39034
39075
  await this.getClient().send(new DeleteDataSourceCommand({
@@ -39091,7 +39132,7 @@ var AppSyncProvider = class {
39091
39132
  const parts = physicalId.split("|");
39092
39133
  if (parts.length < 3) {
39093
39134
  this.logger.warn(compositeIdFormatMessage(APPSYNC_RESOLVER_ID_FORMAT, logicalId, physicalId, { skipping: true }));
39094
- return;
39135
+ return compositeIdSkipResult();
39095
39136
  }
39096
39137
  const [apiId, typeName, fieldName] = parts;
39097
39138
  try {
@@ -39147,7 +39188,7 @@ var AppSyncProvider = class {
39147
39188
  const [apiId, apiKeyId] = physicalId.split("|");
39148
39189
  if (!apiId || !apiKeyId) {
39149
39190
  this.logger.warn(compositeIdFormatMessage(APPSYNC_APIKEY_ID_FORMAT, logicalId, physicalId, { skipping: true }));
39150
- return;
39191
+ return compositeIdSkipResult();
39151
39192
  }
39152
39193
  try {
39153
39194
  await this.getClient().send(new DeleteApiKeyCommand({
@@ -39873,7 +39914,7 @@ var GlueProvider = class {
39873
39914
  const [databaseName, tableName] = physicalId.split("|");
39874
39915
  if (!databaseName || !tableName) {
39875
39916
  this.logger.warn(compositeIdFormatMessage(GLUE_TABLE_ID_FORMAT, logicalId, physicalId, { skipping: true }));
39876
- return;
39917
+ return compositeIdSkipResult();
39877
39918
  }
39878
39919
  const target = deleteCatalogId(properties);
39879
39920
  if (target.declaredButUnusable) this.logger.debug(`Glue Table ${logicalId}: CatalogId is not a usable value (likely an unresolved intrinsic), so DeleteTable targets ${describeCatalog(target.catalogId)}.`);
@@ -52076,6 +52117,12 @@ var ECRProvider = class {
52076
52117
  *
52077
52118
  * Exported for unit-test coverage of `countProtectedResources`.
52078
52119
  */
52120
+ /**
52121
+ * The nested-stack resource type. A skipped row of this type means the CHILD
52122
+ * stack was not destroyed, and the record a user must repair lives in the
52123
+ * child's own state file (`<parent>~<logicalId>`) — see `skippedStateTargets`.
52124
+ */
52125
+ const NESTED_STACK_TYPE = "AWS::CloudFormation::Stack";
52079
52126
  const PROTECTION_PROPERTY_BY_TYPE = {
52080
52127
  "AWS::Logs::LogGroup": "DeletionProtectionEnabled",
52081
52128
  "AWS::RDS::DBInstance": "DeletionProtection",
@@ -52179,6 +52226,7 @@ async function runDestroyForStack(stackName, state, ctx) {
52179
52226
  skippedEmpty: false,
52180
52227
  deletedCount: 0,
52181
52228
  retainedCount: 0,
52229
+ skippedCount: 0,
52182
52230
  errorCount: 0,
52183
52231
  interrupted: false
52184
52232
  };
@@ -52285,6 +52333,7 @@ async function runDestroyForStack(stackName, state, ctx) {
52285
52333
  }
52286
52334
  }
52287
52335
  const remainingResources = { ...state.resources };
52336
+ const skippedStateTargets = /* @__PURE__ */ new Set();
52288
52337
  const buildDestroySnapshot = () => {
52289
52338
  const { imports: _imports, outputReads: _outputReads, ...rest } = state;
52290
52339
  return {
@@ -52412,15 +52461,16 @@ async function runDestroyForStack(stackName, state, ctx) {
52412
52461
  const globalTimeoutMs = ctx.resourceTimeoutMs ?? 18e5;
52413
52462
  const slowTypeMinTimeoutMs = slowCcOperationTimeoutMs(resource.resourceType, "DELETE");
52414
52463
  const timeoutMs = ctx.resourceTimeoutByType?.[resource.resourceType] ?? Math.max(providerMinTimeoutMs, slowTypeMinTimeoutMs, globalTimeoutMs);
52464
+ let deleteResult;
52415
52465
  await withResourceDeadline(async () => {
52416
52466
  const maxAttempts = provider.disableOuterRetry ? 0 : 3;
52417
52467
  let lastDeleteError;
52418
52468
  for (let attempt = 0; attempt <= maxAttempts; attempt++) try {
52419
- await provider.delete(logicalId, resource.physicalId, resource.resourceType, resource.properties, {
52469
+ deleteResult = await provider.delete(logicalId, resource.physicalId, resource.resourceType, resource.properties, {
52420
52470
  ...state.region !== void 0 && { expectedRegion: state.region },
52421
52471
  ...ctx.removeProtection === true && { removeProtection: true },
52422
52472
  ...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
52423
- });
52473
+ }) ?? void 0;
52424
52474
  lastDeleteError = null;
52425
52475
  break;
52426
52476
  } catch (retryError) {
@@ -52445,6 +52495,23 @@ async function runDestroyForStack(stackName, state, ctx) {
52445
52495
  onTimeout: (elapsedMs) => new ResourceTimeoutError(logicalId, resource.resourceType, stackRegion, elapsedMs, "DELETE", timeoutMs)
52446
52496
  });
52447
52497
  renderer.removeTask(logicalId);
52498
+ if (deleteResult?.outcome === "skipped") {
52499
+ logger.info(` ${formatResourceLine("skipped", logicalId, resource.resourceType, `skipped (${deleteResult.reason})`)}`);
52500
+ result.skippedCount++;
52501
+ skippedStateTargets.add(resource.resourceType === NESTED_STACK_TYPE ? `${stackName}~${logicalId}` : stackName);
52502
+ ctx.eventRecorder?.record({
52503
+ eventType: "RESOURCE_SKIPPED",
52504
+ stackName,
52505
+ operation: "DELETE",
52506
+ logicalId,
52507
+ resourceType: resource.resourceType,
52508
+ ...resource.provisionedBy && { provisionedBy: resource.provisionedBy },
52509
+ ...resource.physicalId && { physicalId: resource.physicalId },
52510
+ reason: deleteResult.reason,
52511
+ durationMs: Date.now() - resourceStartedAt
52512
+ });
52513
+ return;
52514
+ }
52448
52515
  logger.info(` ${formatResourceLine("deleted", logicalId, resource.resourceType)}`);
52449
52516
  result.deletedCount++;
52450
52517
  ctx.eventRecorder?.record({
@@ -52513,7 +52580,7 @@ async function runDestroyForStack(stackName, state, ctx) {
52513
52580
  }
52514
52581
  result.interrupted = draining;
52515
52582
  await saveChain;
52516
- const preserveState = result.errorCount > 0 || result.interrupted;
52583
+ const preserveState = result.errorCount > 0 || result.interrupted || result.skippedCount > 0;
52517
52584
  if (!preserveState) {
52518
52585
  await ctx.stateBackend.deleteState(stackName, regionForState);
52519
52586
  logger.debug("State deleted");
@@ -52525,12 +52592,19 @@ async function runDestroyForStack(stackName, state, ctx) {
52525
52592
  logger.warn(`Failed to persist remaining state after partial destroy: ${error instanceof Error ? error.message : String(error)}. The state file may still list already-deleted resources; a re-run resolves them idempotently.`);
52526
52593
  }
52527
52594
  if (result.interrupted) logger.warn(`Destroy interrupted — ${Object.keys(remainingResources).length} resource(s) not deleted. State preserved.`);
52528
- else logger.warn(`${result.errorCount} resource(s) failed to delete. State preserved.`);
52595
+ else if (result.errorCount > 0) logger.warn(`${result.errorCount} resource(s) failed to delete. State preserved.`);
52596
+ else logger.warn(`${result.skippedCount} resource(s) skipped — cdkd could not address them, so no delete was issued and they may still exist in AWS. State preserved (the records are kept so the resources stay traceable).`);
52529
52597
  }
52530
52598
  const retainedSuffix = result.retainedCount > 0 ? `, ${result.retainedCount} retained` : "";
52599
+ const skippedSuffix = result.skippedCount > 0 ? `, ${yellow(result.skippedCount)} skipped` : "";
52531
52600
  if (!preserveState) logger.info(`\n${green("✓")} ${bold(`Stack ${stackName} destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}, ${result.errorCount} errors)`);
52532
- else if (result.interrupted && result.errorCount === 0) logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} destroy interrupted`)} (${green(result.deletedCount)} deleted${retainedSuffix}, ${result.errorCount} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to finish.`);
52533
- else logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}, ${red(result.errorCount)} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to clean up. If the same resource keeps failing, 'cdkd state orphan ${stackName}' is the last resort: it removes the state record without deleting AWS resources.`);
52601
+ else if (result.interrupted && result.errorCount === 0) logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} destroy interrupted`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}, ${result.errorCount} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to finish.`);
52602
+ else if (result.errorCount === 0) {
52603
+ const targets = [...skippedStateTargets];
52604
+ const showHint = targets.map((t) => `'cdkd state show ${t}'`).join(" / ");
52605
+ const orphanHint = targets.map((t) => `'cdkd state orphan ${t}'`).join(" / ");
52606
+ logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}, ${result.errorCount} errors). cdkd could not address the skipped resource(s), so they may still exist in AWS. Fix the physicalId in state.json (${showHint}) and re-run, or delete them by hand and drop the records with ${orphanHint}.`);
52607
+ } else logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}, ${red(result.errorCount)} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to clean up. If the same resource keeps failing, 'cdkd state orphan ${stackName}' is the last resort: it removes the state record without deleting AWS resources.`);
52534
52608
  } finally {
52535
52609
  process.removeListener("SIGINT", sigintHandler);
52536
52610
  renderer.stop();
@@ -52749,7 +52823,7 @@ var NestedStackProvider = class {
52749
52823
  }
52750
52824
  const resourceCount = Object.keys(childStateData.state.resources).length;
52751
52825
  this.logger.info(`Destroying nested stack ${childStackName} (logicalId=${logicalId}, ${resourceCount} resource(s))`);
52752
- await withNestedStackContext({
52826
+ const childResult = await withNestedStackContext({
52753
52827
  ...ctx,
52754
52828
  parentStackName: childStackName,
52755
52829
  parentRegion: childRegion,
@@ -52771,6 +52845,15 @@ var NestedStackProvider = class {
52771
52845
  ...ctx.destroyOptions?.resourceWarnAfterByType && { resourceWarnAfterByType: ctx.destroyOptions.resourceWarnAfterByType },
52772
52846
  ...ctx.destroyOptions?.resourceTimeoutByType && { resourceTimeoutByType: ctx.destroyOptions.resourceTimeoutByType }
52773
52847
  }));
52848
+ if (childResult.skippedCount > 0 || childResult.interrupted) {
52849
+ const causes = [];
52850
+ if (childResult.skippedCount > 0) causes.push(`skipped ${childResult.skippedCount} resource(s)`);
52851
+ if (childResult.interrupted) causes.push("was interrupted");
52852
+ return {
52853
+ outcome: "skipped",
52854
+ reason: `nested stack ${childStackName} ${causes.join(" and ")}`
52855
+ };
52856
+ }
52774
52857
  }
52775
52858
  async getAttribute(_physicalId, _resourceType, attributeName) {
52776
52859
  throw new Error(`AWS::CloudFormation::Stack: attribute '${attributeName}' is not in the recorded Outputs map. Only 'Outputs.<Key>' references to declared Output names on the child template are supported.`);
@@ -57689,6 +57772,7 @@ async function destroyCommand(stackArgs, options) {
57689
57772
  } else throw new Error("Could not determine which stacks belong to this app. Specify stack names explicitly, use --all, or ensure --app / cdk.json is configured.");
57690
57773
  const stackPatterns = stackArgs.length > 0 ? stackArgs : options.stack ? [options.stack] : [];
57691
57774
  let totalErrors = 0;
57775
+ let totalSkipped = 0;
57692
57776
  let interrupted = false;
57693
57777
  let stackNames;
57694
57778
  if (options.all) stackNames = candidateStacks.map((s) => s.stackName);
@@ -57813,8 +57897,9 @@ async function destroyCommand(stackArgs, options) {
57813
57897
  eventRecorder
57814
57898
  }));
57815
57899
  totalErrors += result.errorCount;
57900
+ totalSkipped += result.skippedCount;
57816
57901
  if (result.interrupted) interrupted = true;
57817
- destroyRunResult = result.errorCount > 0 ? "FAILED" : "SUCCEEDED";
57902
+ destroyRunResult = result.errorCount > 0 || result.skippedCount > 0 ? "FAILED" : "SUCCEEDED";
57818
57903
  eventRecorder.record({
57819
57904
  eventType: "RUN_FINISHED",
57820
57905
  stackName,
@@ -57823,7 +57908,8 @@ async function destroyCommand(stackArgs, options) {
57823
57908
  created: 0,
57824
57909
  updated: 0,
57825
57910
  deleted: result.deletedCount,
57826
- ...result.errorCount > 0 && { failed: result.errorCount }
57911
+ ...result.errorCount > 0 && { failed: result.errorCount },
57912
+ ...result.skippedCount > 0 && { skipped: result.skippedCount }
57827
57913
  }
57828
57914
  });
57829
57915
  } catch (destroyError) {
@@ -57842,6 +57928,7 @@ async function destroyCommand(stackArgs, options) {
57842
57928
  }
57843
57929
  if (totalErrors > 0) throw new PartialFailureError(`Destroy completed with ${totalErrors} resource error(s). State preserved — inspect 'cdkd state show <stack>' and re-run 'cdkd destroy' to retry. If the same resource keeps failing, 'cdkd state orphan <stack>' removes the state record without deleting AWS resources.`);
57844
57930
  if (interrupted) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd destroy' to finish.`);
57931
+ if (totalSkipped > 0) throw new PartialFailureError(`Destroy skipped ${totalSkipped} entr${totalSkipped === 1 ? "y" : "ies"} cdkd could not address, so the underlying resources may still exist in AWS. A skipped nested stack counts as ONE entry and may cover several of its own resources — the per-stack summaries above give the exact breakdown. State preserved (the records are kept). Repair the physicalId in state.json and re-run 'cdkd destroy', or delete the resources by hand and drop the records with 'cdkd state orphan <stack>'.`);
57845
57932
  } finally {
57846
57933
  unforwardSigterm();
57847
57934
  awsClients.destroy();
@@ -61356,6 +61443,7 @@ async function stateDestroyCommand(stackArgs, options) {
61356
61443
  }
61357
61444
  logger.info(`Found ${stackNames.length} stack(s) to destroy: ${stackNames.join(", ")}`);
61358
61445
  let totalErrors = 0;
61446
+ let totalSkipped = 0;
61359
61447
  let interrupted = false;
61360
61448
  for (const stackName of stackNames) {
61361
61449
  const refs = stateRefs.filter((r) => r.stackName === stackName);
@@ -61416,6 +61504,7 @@ async function stateDestroyCommand(stackArgs, options) {
61416
61504
  ...options.resourceTimeout?.perTypeMs && { resourceTimeoutByType: options.resourceTimeout.perTypeMs }
61417
61505
  }));
61418
61506
  totalErrors += result.errorCount;
61507
+ totalSkipped += result.skippedCount;
61419
61508
  if (result.interrupted) interrupted = true;
61420
61509
  if (interrupted) break;
61421
61510
  }
@@ -61423,6 +61512,7 @@ async function stateDestroyCommand(stackArgs, options) {
61423
61512
  }
61424
61513
  if (totalErrors > 0) throw new PartialFailureError(`Destroy completed with ${totalErrors} resource error(s). State preserved — inspect 'cdkd state show <stack>' and re-run 'cdkd state destroy' to retry. If the same resource keeps failing, 'cdkd state orphan <stack>' removes the state record without deleting AWS resources.`);
61425
61514
  if (interrupted) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd state destroy' to finish.`);
61515
+ if (totalSkipped > 0) throw new PartialFailureError(`Destroy skipped ${totalSkipped} entr${totalSkipped === 1 ? "y" : "ies"} cdkd could not address, so the underlying resources may still exist in AWS. A skipped nested stack counts as ONE entry and may cover several of its own resources — the per-stack summaries above give the exact breakdown. State preserved (the records are kept). Repair the physicalId in state.json and re-run 'cdkd state destroy', or delete the resources by hand and drop the records with 'cdkd state orphan <stack>'.`);
61426
61516
  } finally {
61427
61517
  unforwardSigterm();
61428
61518
  setup.dispose();
@@ -62492,6 +62582,11 @@ function printRunList(stackName, region, runs) {
62492
62582
  logger.info(gray(`\nUse 'cdkd events ${stackName} --run <runId>' to read one run's events.`));
62493
62583
  }
62494
62584
  /** Human-readable single-run event stream (in recorded order). */
62585
+ /**
62586
+ * Render one run's event stream. Exported for unit-test coverage — the
62587
+ * `counts.skipped` / `reason` rendering is the whole value of those fields
62588
+ * (issue #1752), so it needs a direct fence rather than only `--json` coverage.
62589
+ */
62495
62590
  function printRunEvents(stackName, region, runId, events) {
62496
62591
  const logger = getLogger();
62497
62592
  logger.info(`${bold("Events for run")} ${cyan(runId)} ${gray(`(${stackName}, ${region})`)}`);
@@ -62509,8 +62604,9 @@ function printRunEvents(stackName, region, runId, events) {
62509
62604
  if (e.cdkdVersion) parts.push(gray(`cdkd ${e.cdkdVersion}`));
62510
62605
  if (e.result) parts.push(e.result === "SUCCEEDED" ? green(e.result) : red(e.result));
62511
62606
  if (typeof e.durationMs === "number") parts.push(gray(`${e.durationMs}ms`));
62512
- if (e.counts) parts.push(gray(`+${e.counts.created}/~${e.counts.updated}/-${e.counts.deleted}` + (e.counts.failed ? ` !${e.counts.failed}` : "")));
62607
+ if (e.counts) parts.push(gray(`+${e.counts.created}/~${e.counts.updated}/-${e.counts.deleted}` + (e.counts.failed ? ` !${e.counts.failed}` : "") + (e.counts.skipped ? ` ⚠${e.counts.skipped}` : "")));
62513
62608
  logger.info(` ${parts.join(" ")}`);
62609
+ if (e.reason) logger.info(` ${yellow(e.reason)}`);
62514
62610
  if (e.error) {
62515
62611
  const code = e.error.awsErrorCode ? ` (${e.error.awsErrorCode})` : "";
62516
62612
  const reqId = e.error.requestId ? gray(` requestId=${e.error.requestId}`) : "";
@@ -62518,10 +62614,17 @@ function printRunEvents(stackName, region, runId, events) {
62518
62614
  }
62519
62615
  }
62520
62616
  }
62521
- /** Color the event-type token by its lifecycle phase. */
62617
+ /**
62618
+ * Color the event-type token by its lifecycle phase.
62619
+ *
62620
+ * Exported for unit-test coverage (matching `destroy-runner.ts`'s
62621
+ * `PROTECTION_PROPERTY_BY_TYPE` convention) — it is pure, so testing it
62622
+ * directly beats driving it through the renderer's log output.
62623
+ */
62522
62624
  function colorizeEventType(eventType) {
62523
62625
  if (eventType.endsWith("FAILED")) return red(eventType);
62524
62626
  if (eventType.endsWith("SUCCEEDED") || eventType === "RUN_FINISHED") return green(eventType);
62627
+ if (eventType === "RESOURCE_SKIPPED") return yellow(eventType);
62525
62628
  if (eventType.startsWith("ROLLBACK")) return yellow(eventType);
62526
62629
  return cyan(eventType);
62527
62630
  }
@@ -65827,6 +65930,61 @@ function notFoundError$1(target, stack, resources) {
65827
65930
  return new LocalInvokeResolutionError(msg.trimEnd());
65828
65931
  }
65829
65932
 
65933
+ //#endregion
65934
+ //#region src/utils/ecr-uri.ts
65935
+ /**
65936
+ * Matching the HOST half of an ECR image URI:
65937
+ * `<acct>.dkr.ecr.<region>.<urlSuffix>/`.
65938
+ *
65939
+ * The suffix is CAPTURED rather than spelled out (issue #1758): the previous
65940
+ * pattern hardcoded `amazonaws.com` with an optional `.cn` tail, so a `us-iso*`
65941
+ * registry (`c2s.ic.gov` / `sc2s.sgov.gov`) never matched and the caller
65942
+ * silently classified a real ECR image as a user-managed one — skipping the
65943
+ * `docker login` it needs.
65944
+ */
65945
+ const ECR_URI_HOST_REGEX = /^(\d{12})\.dkr\.ecr\.([^.]+)\.([^/]+)\//;
65946
+ /**
65947
+ * The account + region of an ECR image URI, or `undefined` when the URI is not
65948
+ * an ECR registry host for the region it names.
65949
+ *
65950
+ * Lives in `src/utils/` rather than beside its first caller because it has TWO
65951
+ * consumers in different layers — `src/local/ecr-puller.ts` (which needs the
65952
+ * `:tag` tail too) and `src/local/ecs-task-resolver.ts` (which classifies an
65953
+ * image that may carry a digest or no tag at all). Before issue #1758 they each
65954
+ * carried their own copy of the hardcoded commercial pattern, and fixing only
65955
+ * the pull path left `cdkd local run-task` broken outside the commercial
65956
+ * partition; ONE definition is what stops them drifting apart again.
65957
+ *
65958
+ * The module is deliberately free of AWS SDK imports, which is what lets
65959
+ * `ecs-task-resolver.ts` consume it without breaking the invariant that module
65960
+ * documents about itself (it resolves secrets through a separate module for
65961
+ * exactly that reason).
65962
+ */
65963
+ function parseEcrRegistryHost(imageUri) {
65964
+ const m = ECR_URI_HOST_REGEX.exec(imageUri);
65965
+ if (!m) return void 0;
65966
+ const region = m[2];
65967
+ if (m[3] !== derivePartitionAndUrlSuffix(region).urlSuffix) return void 0;
65968
+ return {
65969
+ accountId: m[1],
65970
+ region
65971
+ };
65972
+ }
65973
+ /**
65974
+ * True when the URI has the ECR HOST SHAPE but its suffix does not belong to
65975
+ * the region it names — i.e. exactly the case {@link parseEcrRegistryHost}
65976
+ * rejects for a reason the caller may want to report.
65977
+ *
65978
+ * The two rejections are worth telling apart at a call site that degrades
65979
+ * silently: a genuinely public image and a registry in a partition
65980
+ * `derivePartitionAndUrlSuffix` does not know yet (issue #1764) both come back
65981
+ * `undefined`, and only the second is a cdkd gap rather than a user choice.
65982
+ */
65983
+ function looksLikeEcrHostWithForeignSuffix(imageUri) {
65984
+ const m = ECR_URI_HOST_REGEX.exec(imageUri);
65985
+ return m !== null && m[3] !== derivePartitionAndUrlSuffix(m[2]).urlSuffix;
65986
+ }
65987
+
65830
65988
  //#endregion
65831
65989
  //#region src/local/ecs-task-resolver.ts
65832
65990
  function detectEcsImageResolutionNeeds(stack) {
@@ -66446,13 +66604,14 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
66446
66604
  if (substituted.includes("AWS::")) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${substituted}). cdkd could not resolve them: confirm AWS credentials are configured so STS GetCallerIdentity succeeds, and that --region / AWS_REGION names the target region. Workaround: build the image locally (ContainerImage.fromAsset) or pin a public image.`);
66447
66605
  throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an Image with unresolved \${...} placeholders (${substituted}). cdkd local run-task v1 only resolves AWS pseudo parameters and same-stack AWS::ECR::Repository refs.`);
66448
66606
  }
66449
- const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(substituted);
66450
- if (ecrMatch) return {
66607
+ const ecrHost = parseEcrRegistryHost(substituted);
66608
+ if (ecrHost) return {
66451
66609
  kind: "ecr",
66452
66610
  uri: substituted,
66453
- account: ecrMatch[1],
66454
- region: ecrMatch[2]
66611
+ account: ecrHost.accountId,
66612
+ region: ecrHost.region
66455
66613
  };
66614
+ if (looksLikeEcrHostWithForeignSuffix(substituted)) getLogger().child("ecs-task-resolver").debug(`Image '${substituted}' has an ECR host shape but a URL suffix that does not belong to its region's partition; treating it as a public image (no docker login).`);
66456
66615
  return {
66457
66616
  kind: "public",
66458
66617
  uri: substituted
@@ -66489,12 +66648,12 @@ function classifyResolvedImage(uri, cdkAssetContainerRepo) {
66489
66648
  if (hashMatch) out.assetHash = hashMatch[1];
66490
66649
  return out;
66491
66650
  }
66492
- const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(uri);
66493
- if (ecrMatch) return {
66651
+ const ecrHost = parseEcrRegistryHost(uri);
66652
+ if (ecrHost) return {
66494
66653
  kind: "ecr",
66495
66654
  uri,
66496
- account: ecrMatch[1],
66497
- region: ecrMatch[2]
66655
+ account: ecrHost.accountId,
66656
+ region: ecrHost.region
66498
66657
  };
66499
66658
  return {
66500
66659
  kind: "public",
@@ -67080,50 +67239,25 @@ async function buildContainerImage$1(...args) {
67080
67239
 
67081
67240
  //#endregion
67082
67241
  //#region src/local/ecr-puller.ts
67083
- /**
67084
- * ECR pull fallback for `cdkd local invoke` / `cdkd local start-api` /
67085
- * `cdkd local run-task`. When the image URI resolves to an ECR repo but
67086
- * doesn't match any cdk.out asset (typical when invoking a stack
67087
- * deployed elsewhere or sharing a centralized registry), cdkd
67088
- * authenticates against the target registry and runs `docker pull`.
67089
- *
67090
- * **Cross-account / cross-region** (#455):
67091
- * - Same-account, same-region: fast path. No STS hop. The default
67092
- * credential chain is used directly for `ecr:GetAuthorizationToken`.
67093
- * - `ecrRoleArn` is provided: `sts:AssumeRole` is issued via the
67094
- * default credential chain to obtain temporary credentials for the
67095
- * target account. The resulting credentials authenticate the ECR
67096
- * client (regardless of region — the ECR client is built for the
67097
- * URI's region, which can differ from the caller's profile region).
67098
- * - Cross-account, NO `ecrRoleArn`: cdkd falls through to the
67099
- * default credential chain. This works when the caller has been
67100
- * granted cross-account `ecr:GetAuthorizationToken` +
67101
- * `ecr:BatchGetImage` permissions on the target repository via an
67102
- * IAM policy; otherwise AWS rejects the call with `AccessDenied`
67103
- * and the user is pointed at `--ecr-role-arn`.
67104
- *
67105
- * The `--no-pull` semantics (C3 in the design doc):
67106
- * - When NOT set: `ecrLogin` + `docker pull <uri>`.
67107
- * - When set: skip `docker pull`. If the image isn't in the local
67108
- * cache, the subsequent `docker run` will fail; we surface a clearer
67109
- * "image not in local cache" error here so the user knows to drop
67110
- * `--no-pull` or pre-pull manually.
67111
- */
67112
- /** Regex matching the `<acct>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` shape. */
67113
- const ECR_URI_REGEX = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\/([^:]+):(.+)$/;
67242
+ /** The URL suffix an ECR registry host uses in `region`'s partition (issue #1758). */
67243
+ function ecrUrlSuffix(region) {
67244
+ return derivePartitionAndUrlSuffix(region).urlSuffix;
67245
+ }
67114
67246
  /**
67115
67247
  * Parse an ECR image URI. Returns `undefined` for non-ECR URIs (typically:
67116
67248
  * Docker Hub, public.ecr.aws, gcr.io, ...) — those are user-managed
67117
67249
  * images we don't try to authenticate against.
67118
67250
  */
67119
67251
  function parseEcrUri(imageUri) {
67120
- const m = ECR_URI_REGEX.exec(imageUri);
67252
+ const host = parseEcrRegistryHost(imageUri);
67253
+ if (!host) return void 0;
67254
+ const m = /^([^:]+):(.+)$/.exec(imageUri.slice(imageUri.indexOf("/") + 1));
67121
67255
  if (!m) return void 0;
67122
67256
  return {
67123
- accountId: m[1],
67124
- region: m[2],
67125
- repository: m[3],
67126
- tag: m[4]
67257
+ accountId: host.accountId,
67258
+ region: host.region,
67259
+ repository: m[1],
67260
+ tag: m[2]
67127
67261
  };
67128
67262
  }
67129
67263
  /**
@@ -67262,7 +67396,7 @@ async function ecrLogin(client, accountId, region) {
67262
67396
  if (!authData?.authorizationToken) throw new LocalInvokeBuildError$1("Failed to get ECR authorization token");
67263
67397
  const [username, password] = Buffer.from(authData.authorizationToken, "base64").toString().split(":");
67264
67398
  if (!username || password === void 0) throw new LocalInvokeBuildError$1("ECR authorization token has unexpected shape (missing username/password)");
67265
- const endpoint = authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.amazonaws.com`;
67399
+ const endpoint = authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.${ecrUrlSuffix(region)}`;
67266
67400
  try {
67267
67401
  await runDockerStreaming([
67268
67402
  "login",
@@ -74474,7 +74608,7 @@ function createMigrateCommand() {
74474
74608
  */
74475
74609
  function buildProgram() {
74476
74610
  const program = new Command();
74477
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.21");
74611
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.23");
74478
74612
  program.hook("preAction", (_thisCommand, actionCommand) => {
74479
74613
  const { profile } = actionCommand.optsWithGlobals();
74480
74614
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;