@go-to-k/cdkd 0.268.1 → 0.268.3

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 WorkGraph, $t as MissingCdkCliError, A as slowCcOperationTimeoutMs, At as warnDeprecatedNoPrefixCliFlag, B as applyRoleArnIfSet, Bt as resolveBucketRegion, C as yellow, Ct as resolveAutoAssetStorage, D as ProviderRegistry, Dt as resolveStateBucketWithDefaultAndSource, E as clearOnUpdateRemoval, Et as resolveStateBucketWithDefault, F as refStateLookupFromResource, Ft as uploadCfnTemplate, G as DagBuilder, H as describeTypeWithThrottleRetry, Ht as getAwsClients, I as WAFv2WebACLProvider, It as expectedOwnerParam, J as S3StateBackend, K as TemplateParser, Kt as CdkdError, L as normalizeAwsTagsToCfn, Lt as AssemblyReader, M as isTerminationProtectionPropagationError, Mt as CFN_TEMPLATE_URL_LIMIT, N as IntrinsicFunctionResolver, Nt as MIGRATE_TMP_PREFIX, O as findActionableSilentDrops, Ot as resolveUseCdkBootstrapAssets, P as cfnRefValueFromPhysicalId, Pt as findLargeInlineResources, Q as stringifyValue, R as resolveExplicitPhysicalId, Rt as processStackMessages, S as red, St as resolveApp, T as collectInlinePolicyNamesManagedBySiblings, Tt as resolveSkipPrefix, U as withRetry, Ut as resetAwsClients, V as DiffCalculator, Vt as AwsClients, W as isRetryableTransientError, Wt as setAwsClients, X as shouldRetainResource, Xt as LocalMigrateError, Y as rebuildClientForBucketRegion, Yt as LocalInvokeBuildError$1, Z as AssetPublisher, Zt as LocalStartServiceError, _ as formatResourceLine, _t as getDockerImageBySourceHash, a as DeploymentEventsStore, an as StackHasActiveImportsError, at as BOOTSTRAP_MARKER_PREFIX, b as gray, bt as getDefaultStateBucketName, c as replayFailedOperations, ct as parseBootstrapMarker, d as IMPLICIT_DELETE_DEPENDENCIES, dn as normalizeAwsError, dt as buildDockerImage, en as NestedStackChildDirectDestroyError, et as buildAssetRedirectMap, f as computeImplicitDeleteEdges, fn as withErrorHandling, ft as formatDockerLoginError, g as renderStatefulReason, gt as AssetManifestLoader, h as isStatefulRecreateTargetSync, ht as runDockerStreaming, i as DeploymentEventsReader, in as ResourceUpdateNotSupportedError, it as AssetModeResolver, j as disableInstanceApiTermination, jt as CFN_TEMPLATE_BODY_LIMIT, k as CloudControlProvider, kt as stateBucketExistenceConfirmed, l as replayRollback, lt as validateAssetBucketName, m as MULTI_REGION_RECREATE_BLOCKED_TYPES, mt as runDockerForeground, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as ProvisioningError, nt as loadPublishableAssetManifest, o as planFailedOps, on as StackTerminationProtectionError, ot as ensureAssetStorage, p as extractDeploymentEventError, pt as getDockerCmd, q as LockManager, r as DeployEngine, rn as ResourceTimeoutError, rt as rewriteTemplateAssetReferences, s as planRollback, st as getBootstrapMarkerKey, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as PartialFailureError, tt as createAssetRedirectResolver, u as withResourceDeadline, ut as validateContainerRepoName, v as bold, vt as Synthesizer, w as IAMRoleProvider, wt as resolveCaptureObservedState, x as green, xt as getLegacyStateBucketName, y as cyan, yt as synthesisStatusMessage, z as assertRegionMatch } from "./deploy-engine-A2CeJkZr.js";
2
+ import { $ as WorkGraph, A as slowCcOperationTimeoutMs, At as warnDeprecatedNoPrefixCliFlag, B as applyRoleArnIfSet, Bt as resolveBucketRegion, C as yellow, Ct as resolveAutoAssetStorage, D as ProviderRegistry, Dt as resolveStateBucketWithDefaultAndSource, E as clearOnUpdateRemoval, Et as resolveStateBucketWithDefault, F as refStateLookupFromResource, Ft as uploadCfnTemplate, G as DagBuilder, H as describeTypeWithThrottleRetry, Ht as getAwsClients, I as WAFv2WebACLProvider, It as expectedOwnerParam, J as S3StateBackend, K as TemplateParser, Kt as CdkdError, L as normalizeAwsTagsToCfn, Lt as AssemblyReader, M as isTerminationProtectionPropagationError, Mt as CFN_TEMPLATE_URL_LIMIT, N as IntrinsicFunctionResolver, Nt as MIGRATE_TMP_PREFIX, O as findActionableSilentDrops, Ot as resolveUseCdkBootstrapAssets, P as cfnRefValueFromPhysicalId, Pt as findLargeInlineResources, Q as stringifyValue, Qt as LocalStartServiceError, R as resolveExplicitPhysicalId, Rt as processStackMessages, S as red, St as resolveApp, T as collectInlinePolicyNamesManagedBySiblings, Tt as resolveSkipPrefix, U as withRetry, Ut as resetAwsClients, V as DiffCalculator, Vt as AwsClients, W as isRetryableTransientError, Wt as setAwsClients, X as shouldRetainResource, Xt as LocalInvokeBuildError$1, Y as rebuildClientForBucketRegion, Yt as DeployCancelledError, Z as AssetPublisher, Zt as LocalMigrateError, _ as formatResourceLine, _t as getDockerImageBySourceHash, a as DeploymentEventsStore, an as ResourceUpdateNotSupportedError, at as BOOTSTRAP_MARKER_PREFIX, b as gray, bt as getDefaultStateBucketName, c as replayFailedOperations, ct as parseBootstrapMarker, d as IMPLICIT_DELETE_DEPENDENCIES, dt as buildDockerImage, en as MissingCdkCliError, et as buildAssetRedirectMap, f as computeImplicitDeleteEdges, fn as normalizeAwsError, ft as formatDockerLoginError, g as renderStatefulReason, gt as AssetManifestLoader, h as isStatefulRecreateTargetSync, ht as runDockerStreaming, i as DeploymentEventsReader, in as ResourceTimeoutError, it as AssetModeResolver, j as disableInstanceApiTermination, jt as CFN_TEMPLATE_BODY_LIMIT, k as CloudControlProvider, kt as stateBucketExistenceConfirmed, l as replayRollback, lt as validateAssetBucketName, m as MULTI_REGION_RECREATE_BLOCKED_TYPES, mt as runDockerForeground, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as PartialFailureError, nt as loadPublishableAssetManifest, o as planFailedOps, on as StackHasActiveImportsError, ot as ensureAssetStorage, p as extractDeploymentEventError, pn as withErrorHandling, pt as getDockerCmd, q as LockManager, r as DeployEngine, rn as ProvisioningError, rt as rewriteTemplateAssetReferences, s as planRollback, sn as StackTerminationProtectionError, st as getBootstrapMarkerKey, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as NestedStackChildDirectDestroyError, tt as createAssetRedirectResolver, u as withResourceDeadline, ut as validateContainerRepoName, v as bold, vt as Synthesizer, w as IAMRoleProvider, wt as resolveCaptureObservedState, x as green, xt as getLegacyStateBucketName, y as cyan, yt as synthesisStatusMessage, z as assertRegionMatch } from "./deploy-engine-DEi5-8x-.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as withSkipPrefix, i as runStackBuffered, n as getLogger, o as PATTERN_B_NAME_OPTIONS, p as withStackName, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-BYMEE-BS.js";
4
- import { t as ASGProvider } from "./asg-provider-_vnp1pfL.js";
4
+ import { t as ASGProvider } from "./asg-provider-bxS6B6Ja.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, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, 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";
@@ -15617,30 +15617,14 @@ var EC2Provider = class {
15617
15617
  VpcId: properties["VpcId"] ?? void 0
15618
15618
  }))).GroupId;
15619
15619
  try {
15620
- await this.applyTags(groupId, properties, logicalId);
15621
15620
  const ingressRules = properties["SecurityGroupIngress"];
15622
- if (ingressRules && Array.isArray(ingressRules)) for (const rule of ingressRules) await this.ec2Client.send(new AuthorizeSecurityGroupIngressCommand({
15623
- GroupId: groupId,
15624
- IpPermissions: [this.buildIpPermission(rule)]
15625
- }));
15626
15621
  const egressRules = properties["SecurityGroupEgress"];
15627
- if (egressRules && Array.isArray(egressRules)) {
15628
- try {
15629
- await this.ec2Client.send(new RevokeSecurityGroupEgressCommand({
15630
- GroupId: groupId,
15631
- IpPermissions: [{
15632
- IpProtocol: "-1",
15633
- IpRanges: [{ CidrIp: "0.0.0.0/0" }]
15634
- }]
15635
- }));
15636
- } catch (error) {
15637
- if (!this.isNotFoundError(error)) throw error;
15638
- }
15639
- for (const rule of egressRules) await this.ec2Client.send(new AuthorizeSecurityGroupEgressCommand({
15640
- GroupId: groupId,
15641
- IpPermissions: [this.buildIpPermission(rule, "egress")]
15642
- }));
15643
- }
15622
+ const firstRejection = (await Promise.allSettled([
15623
+ this.applyTags(groupId, properties, logicalId),
15624
+ this.authorizeInlineIngress(groupId, ingressRules),
15625
+ this.applyInlineEgress(groupId, egressRules)
15626
+ ])).find((r) => r.status === "rejected");
15627
+ if (firstRejection) throw firstRejection.reason;
15644
15628
  } catch (innerError) {
15645
15629
  try {
15646
15630
  await this.ec2Client.send(new DeleteSecurityGroupCommand({ GroupId: groupId }));
@@ -15663,6 +15647,79 @@ var EC2Provider = class {
15663
15647
  throw new ProvisioningError(`Failed to create SecurityGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
15664
15648
  }
15665
15649
  }
15650
+ /**
15651
+ * Authorize a freshly-created security group's inline
15652
+ * `SecurityGroupIngress` rules in a SINGLE `AuthorizeSecurityGroupIngress`
15653
+ * call.
15654
+ *
15655
+ * The API takes an `IpPermissions` ARRAY; the previous per-rule loop paid
15656
+ * one round trip per rule for no benefit. AWS applies the whole list
15657
+ * atomically, so the resulting rule set is byte-identical — the only
15658
+ * behavior difference is that a bad rule now rejects the batch instead of
15659
+ * being preceded by its already-applied siblings, and the caller deletes
15660
+ * the half-wired group in either case.
15661
+ */
15662
+ async authorizeInlineIngress(groupId, ingressRules) {
15663
+ if (!Array.isArray(ingressRules) || ingressRules.length === 0) return;
15664
+ await this.ec2Client.send(new AuthorizeSecurityGroupIngressCommand({
15665
+ GroupId: groupId,
15666
+ IpPermissions: ingressRules.map((rule) => this.buildIpPermission(rule))
15667
+ }));
15668
+ }
15669
+ /**
15670
+ * Apply a freshly-created security group's inline `SecurityGroupEgress`
15671
+ * rules.
15672
+ *
15673
+ * When a template specifies egress explicitly, CloudFormation replaces the
15674
+ * AWS-default "allow all egress" rule (`-1` / `0.0.0.0/0`) with the
15675
+ * supplied set, so cdkd revokes the default first and then authorizes the
15676
+ * template's rules. Both steps are preserved, including for an EMPTY
15677
+ * `SecurityGroupEgress: []` — that is a deliberate "no egress at all",
15678
+ * which requires the revoke and no authorize.
15679
+ *
15680
+ * Two round trips are saved where they are provably redundant:
15681
+ *
15682
+ * - The whole revoke+authorize pair is SKIPPED when the templated egress
15683
+ * is exactly the rule AWS already created by default. `isDefaultEgressRule`
15684
+ * is the exact comparator (protocol `-1`, `CidrIp 0.0.0.0/0`, no IPv6
15685
+ * range / peer group / prefix list / description, ports absent or `-1`)
15686
+ * and is the SAME predicate the drift reverse-mapper uses to recognize
15687
+ * the AWS default, so the two cannot disagree about what "default" means.
15688
+ * AWS creates exactly ONE default egress rule and it is IPv4-only (there
15689
+ * is no `::/0` default), which is why a single-element match is the right
15690
+ * shape. NOTE this deliberately does NOT fire for the CDK L2
15691
+ * `allowAllOutbound: true` shape, which stamps
15692
+ * `Description: 'Allow all outbound traffic by default'` — the AWS
15693
+ * default rule carries no description, so skipping there would leave the
15694
+ * group in a state CloudFormation would not produce AND would surface as
15695
+ * permanent phantom drift (`readCurrentState` would report no
15696
+ * Description while state records one).
15697
+ * - The remaining authorize is a SINGLE batched call rather than one per
15698
+ * rule (see {@link authorizeInlineIngress}).
15699
+ */
15700
+ async applyInlineEgress(groupId, egressRules) {
15701
+ if (!Array.isArray(egressRules)) return;
15702
+ if (egressRules.length === 1 && isDefaultEgressRule(egressRules[0])) {
15703
+ this.logger.debug(`SecurityGroup ${groupId} egress equals the AWS default allow-all rule; skipping the redundant revoke + re-authorize`);
15704
+ return;
15705
+ }
15706
+ try {
15707
+ await this.ec2Client.send(new RevokeSecurityGroupEgressCommand({
15708
+ GroupId: groupId,
15709
+ IpPermissions: [{
15710
+ IpProtocol: "-1",
15711
+ IpRanges: [{ CidrIp: "0.0.0.0/0" }]
15712
+ }]
15713
+ }));
15714
+ } catch (error) {
15715
+ if (!this.isNotFoundError(error)) throw error;
15716
+ }
15717
+ if (egressRules.length === 0) return;
15718
+ await this.ec2Client.send(new AuthorizeSecurityGroupEgressCommand({
15719
+ GroupId: groupId,
15720
+ IpPermissions: egressRules.map((rule) => this.buildIpPermission(rule, "egress"))
15721
+ }));
15722
+ }
15666
15723
  async updateSecurityGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
15667
15724
  this.logger.debug(`Updating SecurityGroup ${logicalId}: ${physicalId}`);
15668
15725
  try {
@@ -19991,6 +20048,14 @@ var CloudFrontOACProvider = class {
19991
20048
  * `assertRegionMatch()` confirms the client is pointed at the region the
19992
20049
  * resource was created in (a region mismatch would otherwise silently
19993
20050
  * "succeed" while leaving the real resource behind).
20051
+ *
20052
+ * A `Get` that succeeds but carries no `ETag` is a hard error here for the
20053
+ * same reason it is in {@link update}: `IfMatch: undefined` reaches AWS as
20054
+ * a missing required parameter, and the resulting SDK/AWS rejection says
20055
+ * nothing about the `Get` being what came back short. On the DELETE path
20056
+ * that opacity is worse than on update — the failure surfaces during
20057
+ * `cdkd destroy`, where the user's next question is "is the OAC still
20058
+ * there?", so the message has to name the actual cause.
19994
20059
  */
19995
20060
  async delete(logicalId, physicalId, resourceType, _properties, context) {
19996
20061
  this.logger.debug(`Deleting CloudFront Origin Access Control ${logicalId}: ${physicalId}`);
@@ -20006,6 +20071,7 @@ var CloudFrontOACProvider = class {
20006
20071
  }
20007
20072
  throw error;
20008
20073
  }
20074
+ if (!etag) throw new Error("GetOriginAccessControl did not return ETag");
20009
20075
  await this.cloudFrontClient.send(new DeleteOriginAccessControlCommand({
20010
20076
  Id: physicalId,
20011
20077
  IfMatch: etag
@@ -20099,6 +20165,18 @@ var CloudFrontOACProvider = class {
20099
20165
  * copy that exists to (a) fail loudly when a required field is missing
20100
20166
  * rather than letting the SDK reject with an opaque serialization error,
20101
20167
  * and (b) keep unknown extra keys out of the request.
20168
+ *
20169
+ * All four required fields are checked for a NON-EMPTY string, not merely
20170
+ * for `typeof === 'string'`. `''` is the shape an unresolved intrinsic or a
20171
+ * `Fn::Sub` over a missing value most often collapses into, and CloudFront
20172
+ * rejects it just as surely as it rejects an absent field — so letting it
20173
+ * through would defeat the whole point of (a): the user would get AWS's
20174
+ * `InvalidArgument` instead of a message naming the field cdkd could see
20175
+ * was blank. The three type/behavior fields are additionally CFn enums
20176
+ * (`s3` / `mediastore` / `lambda` / `mediapackagev2`; `always` / `never` /
20177
+ * `no-override`; `sigv4`), for which the empty string is never a member.
20178
+ * `Description` is deliberately not in this set — it is genuinely optional
20179
+ * and `''` is a legitimate value that clears it.
20102
20180
  */
20103
20181
  toSdkConfig(value, logicalId, resourceType, physicalId) {
20104
20182
  const config = value ?? {};
@@ -20106,11 +20184,12 @@ var CloudFrontOACProvider = class {
20106
20184
  const originType = config["OriginAccessControlOriginType"];
20107
20185
  const signingBehavior = config["SigningBehavior"];
20108
20186
  const signingProtocol = config["SigningProtocol"];
20187
+ const nonEmpty = (value) => typeof value === "string" && value.length > 0;
20109
20188
  const missing = [
20110
- typeof name === "string" && name.length > 0 ? void 0 : "Name",
20111
- typeof originType === "string" ? void 0 : "OriginAccessControlOriginType",
20112
- typeof signingBehavior === "string" ? void 0 : "SigningBehavior",
20113
- typeof signingProtocol === "string" ? void 0 : "SigningProtocol"
20189
+ nonEmpty(name) ? void 0 : "Name",
20190
+ nonEmpty(originType) ? void 0 : "OriginAccessControlOriginType",
20191
+ nonEmpty(signingBehavior) ? void 0 : "SigningBehavior",
20192
+ nonEmpty(signingProtocol) ? void 0 : "SigningProtocol"
20114
20193
  ].filter((field) => field !== void 0);
20115
20194
  if (missing.length > 0) throw new ProvisioningError(`CloudFront Origin Access Control ${logicalId}: OriginAccessControlConfig is missing required field(s): ${missing.join(", ")}`, resourceType, logicalId, physicalId);
20116
20195
  const description = config["Description"];
@@ -22393,19 +22472,19 @@ var ECSProvider = class {
22393
22472
  try {
22394
22473
  await this.settleService(logicalId, service.serviceName ?? service.serviceArn, properties["Cluster"]);
22395
22474
  } catch (waitError) {
22475
+ const cleanupCluster = properties["Cluster"];
22476
+ const clusterArg = cleanupCluster ? ` --cluster ${cleanupCluster}` : "";
22396
22477
  try {
22397
22478
  await client.send(new DeleteServiceCommand({
22398
22479
  cluster: properties["Cluster"],
22399
22480
  service: service.serviceArn,
22400
22481
  force: true
22401
22482
  }));
22402
- this.logger.debug(`Cleaned up partially-created ECS service ${logicalId} (${service.serviceArn}) after the steady-state wait failed`);
22483
+ this.logger.warn(`Deleted partially-created ECS service ${logicalId} (${service.serviceArn}) after the steady-state wait failed, so the next deploy's CreateService does not collide on the name. Its stopped tasks remain inspectable for about an hour: aws ecs list-tasks${clusterArg} --desired-status STOPPED`);
22403
22484
  } catch (cleanupError) {
22404
- const cleanupCluster = properties["Cluster"];
22405
- const clusterArg = cleanupCluster ? ` --cluster ${cleanupCluster}` : "";
22406
22485
  this.logger.warn(`Failed to clean up partially-created ECS service ${logicalId} (${service.serviceArn}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required before the next deploy: aws ecs delete-service${clusterArg} --service ${service.serviceArn} --force`);
22407
22486
  }
22408
- throw waitError;
22487
+ throw new Error(`ECS service ${logicalId} did not reach steady state under --full-wait: ${waitError instanceof Error ? waitError.message : String(waitError)}. Inspect why its tasks stopped (stopped tasks stay visible for about an hour): aws ecs list-tasks${clusterArg} --desired-status STOPPED, then aws ecs describe-tasks${clusterArg} --tasks <task-arn> --query 'tasks[].[stoppedReason,containers[].reason]'`, { cause: waitError });
22409
22488
  }
22410
22489
  return {
22411
22490
  physicalId: service.serviceArn,
@@ -42953,6 +43032,39 @@ async function promptMigrationConfirm(renames, opts) {
42953
43032
  rl.close();
42954
43033
  }
42955
43034
  }
43035
+ /**
43036
+ * Build the `DeployEngineOptions.onCurrentStateLoaded` gate that runs the
43037
+ * prefix-migration check, or `undefined` when the check is inactive
43038
+ * (`skipPrefix === false`, i.e. the user opted back in to legacy prefixing).
43039
+ *
43040
+ * Why a gate rather than a pre-lock `getState`: prefix-skipping has been the
43041
+ * default since v0.94.0, so the previous shape issued an S3 GET on EVERY
43042
+ * deploy of EVERY stack — including brand-new stacks with no state at all —
43043
+ * and the deploy engine then read the very same object again immediately
43044
+ * after taking the lock. Running as the engine's post-lock gate reuses that
43045
+ * read. The contract the check actually depends on ("surface this before any
43046
+ * provider call runs") is preserved: the engine invokes the gate immediately
43047
+ * after loading state and before parsing, diffing, or provisioning anything.
43048
+ * Reading under the lock is also strictly more authoritative — no concurrent
43049
+ * deploy can mutate the state between the check and the diff it predicts.
43050
+ *
43051
+ * Declining the prompt raises {@link DeployCancelledError}, which the deploy
43052
+ * CLI unwinds quietly (no error output, no FAILED run event) — matching the
43053
+ * plain early `return` the pre-lock version used.
43054
+ *
43055
+ * The gate is scoped to `stackName`: nested-stack children inherit the parent
43056
+ * engine's option object, and the check is deliberately top-level-only (it was
43057
+ * never applied to children).
43058
+ */
43059
+ function createPrefixMigrationGate(opts) {
43060
+ if (!opts.skipPrefix) return void 0;
43061
+ return async (gateStackName, state) => {
43062
+ if (gateStackName !== opts.stackName) return;
43063
+ const pending = findPendingPrefixRenames(opts.stackName, state);
43064
+ if (pending.length === 0) return;
43065
+ if (!await promptMigrationConfirm(pending, { ...opts.yes !== void 0 && { yes: opts.yes } })) throw new DeployCancelledError();
43066
+ };
43067
+ }
42956
43068
 
42957
43069
  //#endregion
42958
43070
  //#region src/cli/commands/deploy.ts
@@ -43190,13 +43302,11 @@ async function deployCommand(stacks, options) {
43190
43302
  });
43191
43303
  let runResult = "SUCCEEDED";
43192
43304
  try {
43193
- if (skipPrefix) {
43194
- const existing = await stackStateBackend.getState(stackInfo.stackName, stackRegion);
43195
- const pending = findPendingPrefixRenames(stackInfo.stackName, existing?.state);
43196
- if (pending.length > 0) {
43197
- if (!await promptMigrationConfirm(pending, { yes: options.yes })) return;
43198
- }
43199
- }
43305
+ const migrationGate = createPrefixMigrationGate({
43306
+ stackName: stackInfo.stackName,
43307
+ skipPrefix,
43308
+ yes: options.yes
43309
+ });
43200
43310
  let recreateViaCcApiTargets;
43201
43311
  let recreateViaSdkProviderTargets;
43202
43312
  if (options.recreateViaCcApi?.length || options.recreateViaSdkProvider?.length) {
@@ -43248,6 +43358,7 @@ async function deployCommand(stacks, options) {
43248
43358
  ...options.roleArn && { roleArn: options.roleArn },
43249
43359
  ...assetRedirect && { assetRedirect },
43250
43360
  ...eventRecorder && { eventRecorder },
43361
+ ...migrationGate && { onCurrentStateLoaded: migrationGate },
43251
43362
  ...recreateViaCcApiTargets && recreateViaCcApiTargets.size > 0 && { recreateViaCcApiTargets },
43252
43363
  ...recreateViaSdkProviderTargets && recreateViaSdkProviderTargets.size > 0 && { recreateViaSdkProviderTargets },
43253
43364
  ...options.replace && { replace: true },
@@ -43296,6 +43407,7 @@ async function deployCommand(stacks, options) {
43296
43407
  deleted: deployResult.deleted
43297
43408
  }, deployResult.durationMs);
43298
43409
  } catch (deployError) {
43410
+ if (deployError instanceof DeployCancelledError) return;
43299
43411
  runResult = "FAILED";
43300
43412
  recordRunFailed(eventRecorder, stackInfo.stackName, deployError);
43301
43413
  throw deployError;
@@ -62613,7 +62725,7 @@ function createMigrateCommand() {
62613
62725
  */
62614
62726
  function buildProgram() {
62615
62727
  const program = new Command();
62616
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.268.1");
62728
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.268.3");
62617
62729
  program.hook("preAction", (_thisCommand, actionCommand) => {
62618
62730
  const { profile } = actionCommand.optsWithGlobals();
62619
62731
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;