@go-to-k/cdkd 0.284.54 → 0.284.56

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $n as clearBucketRegionCache, Dr as isCdkdError, Er as formatError, Ht as DiffCalculator, Jt as LockManager, Kt as DagBuilder, Or as normalizeAwsError, Tr as SynthesisError, W as IAMRoleProvider, Xt as S3StateBackend, Zn as AssemblyReader, ar as AssetError, er as resolveBucketRegion, hr as LockError, ir as setAwsClients, kn as Synthesizer, lr as DependencyError, nn as AssetPublisher, nr as getAwsClients, nt as CloudControlProvider, or as CdkdError, q as ProviderRegistry, qt as TemplateParser, r as DeployEngine, rr as resetAwsClients, sr as ConfigError, tr as AwsClients, wr as StateError, yr as ProvisioningError } from "./deploy-engine-B9SrF2-R.js";
1
+ import { An as Synthesizer, Dr as formatError, Er as SynthesisError, Jt as TemplateParser, Or as isCdkdError, Qn as AssemblyReader, Tr as StateError, Ut as DiffCalculator, W as IAMRoleProvider, Yt as LockManager, Zt as S3StateBackend, ar as setAwsClients, br as ProvisioningError, cr as ConfigError, er as clearBucketRegionCache, gr as LockError, ir as resetAwsClients, kr as normalizeAwsError, nr as AwsClients, or as AssetError, q as ProviderRegistry, qt as DagBuilder, r as DeployEngine, rn as AssetPublisher, rr as getAwsClients, rt as CloudControlProvider, sr as CdkdError, tr as resolveBucketRegion, ur as DependencyError } from "./deploy-engine-CNYFUw4D.js";
2
2
  import { n as getLogger, r as setLogger, t as ConsoleLogger } from "./logger-zRrlbaQt.js";
3
3
 
4
4
  export { AssemblyReader, AssetError, AssetPublisher, AwsClients, CdkdError, CloudControlProvider, ConfigError, ConsoleLogger, DagBuilder, DependencyError, DeployEngine, DiffCalculator, IAMRoleProvider, LockError, LockManager, ProviderRegistry, ProvisioningError, S3StateBackend, StateError, SynthesisError, Synthesizer, TemplateParser, clearBucketRegionCache, formatError, getAwsClients, getLogger, isCdkdError, normalizeAwsError, resetAwsClients, resolveBucketRegion, setAwsClients, setLogger };
@@ -1,7 +1,7 @@
1
- import { $ as endCommandInterruptScope, $t as importableOutputKeys, A as renderStatefulReason, An as synthesisStatusMessage, Ar as isMarkedNonRetryable, At as maskSecretsInError, B as exportAliasCollisionScrubWarning, Bn as stateBucketExistenceConfirmed, Bt as s3BucketWebsiteUrl, C as isFinalSnapshotError, Cn as getDockerCmd, Cr as StackTerminationProtectionError, Ct as STATE_SOURCED_CROSS_GENERATION_RULES, D as extractDeploymentEventError, Dn as AssetManifestLoader, Dt as dynamicReferenceTokens, E as makeCanonicalizePropertiesFn, En as runDockerStreaming, Et as createSecretMasker, F as green, Fn as resolveCaptureObservedState, Ft as producerRegionsFromState, G as collectInlinePolicyNamesManagedBySiblings, Gn as findLargeInlineResources, Gt as withRetry, H as secretBearingStateKeyWarning, Hn as CFN_TEMPLATE_BODY_LIMIT, Ht as DiffCalculator, I as red, In as resolveSkipPrefix, It as s3BucketArn, J as findActionableSilentDrops, Jn as PARTITION_TABLE, Jt as LockManager, K as clearOnUpdateRemoval, Kn as uploadCfnTemplate, Kt as DagBuilder, L as yellow, Ln as resolveStateBucketWithDefault, Lt as s3BucketDomainName, M as bold, Mn as getLegacyStateBucketName, Mr as isThrottlingError, Mt as redactSecretsForState, N as cyan, Nn as resolveApp, Nr as markNonRetryable, Nt as scrubResourceRecord, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as getDockerImageBySourceHash, Or as normalizeAwsError, Ot as errorCauseChain, P as gray, Pn as resolveAutoAssetStorage, Pt as classifyReplaySecretRegion, Q as beginCommandInterruptScope, Qn as processStackMessages, Qt as exportNamesCarriedFrom, R as collectDeclaredOutputNames, Rn as resolveStateBucketWithDefaultAndSource, Rt as s3BucketDualStackDomainName, S as createPreDeleteFinalSnapshot, Sn as formatDockerLoginError, Sr as StackHasActiveImportsError, St as requireConfigString, T as unsupportedFinalSnapshotError, Tn as runDockerForeground, Tt as TEMPLATE_SOURCED_RULES, U as stateKeySecretExposure, Un as CFN_TEMPLATE_URL_LIMIT, Ut as INTRINSIC_KEYS, V as isExportAliasCollision, Vn as warnDeprecatedNoPrefixCliFlag, Vt as applyRoleArnIfSet, W as IAMRoleProvider, Wn as MIGRATE_TMP_PREFIX, Wt as describeTypeWithThrottleRetry, X as CUSTOM_RESOURCE_RESPONSE_PREFIX, Xn as derivePartitionAndUrlSuffix, Xt as S3StateBackend, Y as findSilentDropProperties, Yn as canonicalizeRegion, Yt as displaySafe, Z as DEFAULT_STATE_PREFIX, Zn as AssemblyReader, Zt as rebuildClientForBucketRegion, _ as computeImplicitDeleteEdges, _n as validateAssetBucketName, _r as NestedStackChildDirectDestroyError, _t as configStringRefusal, a as DeploymentEventsStore, an as buildAssetRedirectMap, at as isTerminationProtectionPropagationError, b as buildFinalSnapshotIdentifier, bn as buildDockerImage, br as ResourceTimeoutError, bt as requireConfigArray, c as replayFailedOperations, cn as rewriteTemplateAssetReferences, cr as CrossAccountSecretRefusalError, ct as cfnRefValueFromPhysicalId, d as updatePartialReason, dn as AssetModeResolver, dr as DynamicReferenceRegionAmbiguousError, dt as WAFv2WebACLProvider, en as importableOutputs, er as resolveBucketRegion, et as isInterruptedWaitError, f as UNSPECIFIED_SKIP_REASON, fn as BOOTSTRAP_MARKER_PREFIX, fr as LocalInvokeBuildError$1, ft as normalizeAwsTagsToCfn, g as IMPLICIT_DELETE_DEPENDENCIES, gn as readBootstrapMarkerBody, gr as MissingCdkCliError, gt as configBooleanRefusal, h as maskingRetryLogger, hn as parseBootstrapMarker, ht as coerceCfnBoolean, i as DeploymentEventsReader, in as WorkGraph, ir as setAwsClients, it as disableInstanceApiTermination, j as formatResourceLine, jn as getDefaultStateBucketName, jr as isRetryableTransientError, jt as maskSecretsInText, k as isStatefulRecreateTargetSync, kn as Synthesizer, kr as withErrorHandling, kt as isSingleDynamicReferenceToken, l as replayRollback, ln as escapeRegExp, lt as getAccountInfo, m as withResourceDeadline, mn as getBootstrapMarkerKey, mr as LocalStartServiceError, mt as assertRegionMatch, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as AssetPublisher, nr as getAwsClients, nt as CloudControlProvider, o as planFailedOps, on as createAssetRedirectResolver, or as CdkdError, ot as IntrinsicFunctionResolver, p as deleteSkipReason, pn as ensureAssetStorage, pr as LocalMigrateError, pt as resolveExplicitPhysicalId, q as ProviderRegistry, qn as expectedOwnerParam, qt as TemplateParser, r as DeployEngine, rn as stringifyValue, rr as resetAwsClients, rt as slowCcOperationTimeoutMs, s as planRollback, sn as loadPublishableAssetManifest, st as carriesDynamicReference, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as shouldRetainResource, tr as AwsClients, tt as startInterruptWatch, u as updatePartialMessage, un as stripControlChars, ur as DeployCancelledError, ut as refStateLookupFromResource, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as validateContainerRepoName, vr as PartialFailureError, vt as readConfigString, w as refusesFinalSnapshot, wn as partitionSensitiveEnv, wt as STATE_SOURCED_READBACK_RULES, x as ccRoutedFinalSnapshotError, xn as dockerSpawnEnvWithSensitive, xr as ResourceUpdateNotSupportedError, xt as requireConfigObject, y as PRE_DELETE_SNAPSHOT_TYPES, yn as buildDenyExternalAccessPolicy, yr as ProvisioningError, yt as replayWarn, z as collectPublishedOutputNames, zn as resolveUseCdkBootstrapAssets, zt as s3BucketRegionalDomainName } from "./deploy-engine-B9SrF2-R.js";
1
+ import { $ as endCommandInterruptScope, $n as processStackMessages, $t as exportNamesCarriedFrom, A as renderStatefulReason, An as Synthesizer, Ar as withErrorHandling, At as isSingleDynamicReferenceToken, B as exportAliasCollisionScrubWarning, Bn as resolveUseCdkBootstrapAssets, Bt as s3BucketRegionalDomainName, C as isFinalSnapshotError, Cn as formatDockerLoginError, Cr as StackHasActiveImportsError, Ct as requireConfigString, D as extractDeploymentEventError, Dn as runDockerStreaming, Dt as createSecretMasker, E as makeCanonicalizePropertiesFn, En as runDockerForeground, Et as TEMPLATE_SOURCED_RULES, F as green, Fn as resolveAutoAssetStorage, Ft as classifyReplaySecretRegion, G as collectInlinePolicyNamesManagedBySiblings, Gn as MIGRATE_TMP_PREFIX, Gt as describeTypeWithThrottleRetry, H as secretBearingStateKeyWarning, Hn as warnDeprecatedNoPrefixCliFlag, Ht as applyRoleArnIfSet, I as red, In as resolveCaptureObservedState, It as producerRegionsFromState, J as findActionableSilentDrops, Jn as expectedOwnerParam, Jt as TemplateParser, K as clearOnUpdateRemoval, Kn as findLargeInlineResources, Kt as withRetry, L as yellow, Ln as resolveSkipPrefix, Lt as s3BucketArn, M as bold, Mn as getDefaultStateBucketName, Mr as isRetryableTransientError, Mt as maskSecretsInText, N as cyan, Nn as getLegacyStateBucketName, Nr as isThrottlingError, Nt as redactSecretsForState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as AssetManifestLoader, Ot as dynamicReferenceTokens, P as gray, Pn as resolveApp, Pr as markNonRetryable, Pt as scrubResourceRecord, Q as beginCommandInterruptScope, Qn as AssemblyReader, Qt as rebuildClientForBucketRegion, R as collectDeclaredOutputNames, Rn as resolveStateBucketWithDefault, Rt as s3BucketDomainName, S as createPreDeleteFinalSnapshot, Sn as dockerSpawnEnvWithSensitive, Sr as ResourceUpdateNotSupportedError, St as requireConfigObject, T as unsupportedFinalSnapshotError, Tn as partitionSensitiveEnv, Tt as STATE_SOURCED_READBACK_RULES, U as stateKeySecretExposure, Un as CFN_TEMPLATE_BODY_LIMIT, Ut as DiffCalculator, V as isExportAliasCollision, Vn as stateBucketExistenceConfirmed, Vt as s3BucketWebsiteUrl, W as IAMRoleProvider, Wn as CFN_TEMPLATE_URL_LIMIT, Wt as INTRINSIC_KEYS, X as CUSTOM_RESOURCE_RESPONSE_PREFIX, Xn as canonicalizeRegion, Xt as displaySafe, Y as findSilentDropProperties, Yn as PARTITION_TABLE, Yt as LockManager, Z as DEFAULT_STATE_PREFIX, Zn as derivePartitionAndUrlSuffix, Zt as S3StateBackend, _ as computeImplicitDeleteEdges, _n as readBootstrapMarkerBody, _r as MissingCdkCliError, _t as configBooleanRefusal, a as DeploymentEventsStore, an as WorkGraph, ar as setAwsClients, at as disableInstanceApiTermination, b as buildFinalSnapshotIdentifier, bn as buildDenyExternalAccessPolicy, br as ProvisioningError, bt as replayWarn, c as replayFailedOperations, cn as loadPublishableAssetManifest, ct as carriesDynamicReference, d as updatePartialReason, dn as stripControlChars, dr as DeployCancelledError, dt as refStateLookupFromResource, en as importableOutputKeys, et as interruptWatchListenerCount, f as UNSPECIFIED_SKIP_REASON, fn as AssetModeResolver, fr as DynamicReferenceRegionAmbiguousError, ft as WAFv2WebACLProvider, g as IMPLICIT_DELETE_DEPENDENCIES, gn as parseBootstrapMarker, gt as coerceCfnBoolean, h as maskingRetryLogger, hn as getBootstrapMarkerKey, hr as LocalStartServiceError, ht as assertRegionMatch, i as DeploymentEventsReader, in as stringifyValue, ir as resetAwsClients, it as slowCcOperationTimeoutMs, j as formatResourceLine, jn as synthesisStatusMessage, jr as isMarkedNonRetryable, jt as maskSecretsInError, k as isStatefulRecreateTargetSync, kn as getDockerImageBySourceHash, kr as normalizeAwsError, kt as errorCauseChain, l as replayRollback, ln as rewriteTemplateAssetReferences, lr as CrossAccountSecretRefusalError, lt as cfnRefValueFromPhysicalId, m as withResourceDeadline, mn as ensureAssetStorage, mr as LocalMigrateError, mt as resolveExplicitPhysicalId, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as shouldRetainResource, nr as AwsClients, nt as startInterruptWatch, o as planFailedOps, on as buildAssetRedirectMap, ot as isTerminationProtectionPropagationError, p as deleteSkipReason, pn as BOOTSTRAP_MARKER_PREFIX, pr as LocalInvokeBuildError$1, pt as normalizeAwsTagsToCfn, q as ProviderRegistry, qn as uploadCfnTemplate, qt as DagBuilder, r as DeployEngine, rn as AssetPublisher, rr as getAwsClients, rt as CloudControlProvider, s as planRollback, sn as createAssetRedirectResolver, sr as CdkdError, st as IntrinsicFunctionResolver, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as importableOutputs, tr as resolveBucketRegion, tt as isInterruptedWaitError, u as updatePartialMessage, un as escapeRegExp, ut as getAccountInfo, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as validateAssetBucketName, vr as NestedStackChildDirectDestroyError, vt as configStringRefusal, w as refusesFinalSnapshot, wn as getDockerCmd, wr as StackTerminationProtectionError, wt as STATE_SOURCED_CROSS_GENERATION_RULES, x as ccRoutedFinalSnapshotError, xn as buildDockerImage, xr as ResourceTimeoutError, xt as requireConfigArray, y as PRE_DELETE_SNAPSHOT_TYPES, yn as validateContainerRepoName, yr as PartialFailureError, yt as readConfigString, z as collectPublishedOutputNames, zn as resolveStateBucketWithDefaultAndSource, zt as s3BucketDualStackDomainName } from "./deploy-engine-CNYFUw4D.js";
2
2
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as getCurrentStackName, 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";
3
- import { t as getCdkdVersion } from "./version-DLRgymoe.js";
4
- import { t as ASGProvider } from "./asg-provider-BaNTIjU4.js";
3
+ import { t as getCdkdVersion } from "./version-oF2rSNET.js";
4
+ import { t as ASGProvider } from "./asg-provider-DoiQARH6.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";
@@ -2055,6 +2055,15 @@ function createListCommand() {
2055
2055
  */
2056
2056
  function forwardSigtermToSigint() {
2057
2057
  beginCommandInterruptScope();
2058
+ try {
2059
+ return installSigtermForwarder();
2060
+ } catch (error) {
2061
+ endCommandInterruptScope();
2062
+ throw error;
2063
+ }
2064
+ }
2065
+ /** The registration half of {@link forwardSigtermToSigint}, after its scope is open. */
2066
+ function installSigtermForwarder() {
2058
2067
  const handler = () => {
2059
2068
  if (process.listenerCount("SIGINT") === 0) process.exit(143);
2060
2069
  process.emit("SIGINT", "SIGINT");
@@ -2065,6 +2074,117 @@ function forwardSigtermToSigint() {
2065
2074
  endCommandInterruptScope();
2066
2075
  };
2067
2076
  }
2077
+ /**
2078
+ * Register a command-scoped SIGINT handler for the destroy commands
2079
+ * (issue #2117).
2080
+ *
2081
+ * `cdkd deploy` has had one of these since issue #1348 (`deploy.ts`'s
2082
+ * `topLevelSigintHandler`). The destroy side had none, so the ONLY channel
2083
+ * carrying "the user interrupted" from `runDestroyForStack` back to the
2084
+ * `--all` loop was `DestroyRunResult.interrupted` — a boolean assigned ONCE,
2085
+ * inside the runner's `try`, after its level loop. Every instant where the
2086
+ * runner's `draining` flag flips AFTER that assignment is an instant where
2087
+ * `cdkd destroy --all` went on to delete the NEXT stack after the user asked
2088
+ * it to stop.
2089
+ *
2090
+ * The runner's outer `finally` re-syncs `result.interrupted ||= draining` to
2091
+ * cover the widest such window (renderer teardown, state flush, lock release),
2092
+ * and that line is marked TACTICAL in its own comment: it narrows the window
2093
+ * rather than removing it. Two gaps survive it, and neither is reachable from
2094
+ * inside the runner at all:
2095
+ *
2096
+ * 1. The runner removes its SIGINT listener in its inner `finally`, BEFORE the
2097
+ * re-sync line and its `return`, so from there on nothing of the runner's is
2098
+ * armed. This watch covers that stretch — by RECORDING once a graceful owner
2099
+ * is gone and the stack is done, and by QUITTING in the mirror-image window
2100
+ * at the START of a stack, where the runner has not armed yet and could not
2101
+ * act on a recorded flag (see `gracefulOwnerArmed` below).
2102
+ * 2. Everything the `--all` loop itself does between two stacks — the
2103
+ * `RUN_FINISHED` event, `eventRecorder.finalize`, `purgeEventsAfterDestroy`
2104
+ * — runs with no runner handler armed at all.
2105
+ *
2106
+ * A handler that lives for the whole command closes both, because it is armed
2107
+ * in every window rather than in the ones the runner happens to span. The two
2108
+ * flags answer different questions and both are kept: `result.interrupted` is
2109
+ * the PER-STACK outcome (it decides that stack's state preservation, its
2110
+ * summary line and its `--purge-events` skip), while this one is the
2111
+ * COMMAND-level "stop" the loop wants.
2112
+ *
2113
+ * Note on the counterpart contract: `deploy-engine.ts` removes its own SIGINT
2114
+ * handler before releasing its lock, which is safe only because `deploy.ts`'s
2115
+ * top-level handler outlives it. The destroy side now has the same property
2116
+ * for the same reason — `destroy-runner.ts`'s `process.removeListener('SIGINT',
2117
+ * sigintHandler)` is no longer the last handler standing, because this one is
2118
+ * disposed only in the command's outermost `finally`.
2119
+ *
2120
+ * @param opts.command the user-facing command name used in the force-quit
2121
+ * notice (`cdkd destroy` / `cdkd state destroy`).
2122
+ */
2123
+ function watchCommandInterrupt(opts) {
2124
+ let interrupted = false;
2125
+ let stacksInFlight = 0;
2126
+ /**
2127
+ * Is some OTHER listener going to handle this signal gracefully?
2128
+ *
2129
+ * Being inside `runStack` is NOT the same question, and assuming it was is a
2130
+ * regression this fix already shipped once: `runDestroyForStack` registers
2131
+ * its own handler about two hundred lines in, AFTER the strong-reference
2132
+ * pre-flight scan (an S3 LIST plus a GET per stack) and the per-stack
2133
+ * confirm prompt. Staying silent across that window swallowed the signal
2134
+ * outright — no notice, no exit, no escalation on a repeat — and because
2135
+ * this watch IS a listener, `interrupt-watch.ts`'s last-listener force-quit
2136
+ * could no longer fire there either. The runner then began with `draining`
2137
+ * false and destroyed the whole stack the user had just asked to stop, which
2138
+ * is issue #2117's own defect one window further in.
2139
+ *
2140
+ * Counting listeners answers the real question, and the two exclusions are
2141
+ * what make the count mean "graceful owner":
2142
+ *
2143
+ * - our own handler, which is not an answer to itself;
2144
+ * - `interrupt-watch.ts`'s shared handler, which is a LATCH rather than a
2145
+ * graceful owner — deferring to it is exactly the swallow above.
2146
+ *
2147
+ * A provider's transient wait listener (CustomResource / ACM / CloudFront /
2148
+ * Route53) does count, and that is correct rather than a false positive:
2149
+ * those exist only DURING a provider wait, which is strictly inside the
2150
+ * runner handler's own lifetime, so one cannot be present while the runner's
2151
+ * is absent.
2152
+ */
2153
+ function gracefulOwnerArmed() {
2154
+ return process.listeners("SIGINT").filter((l) => l !== handler).length - interruptWatchListenerCount() > 0;
2155
+ }
2156
+ const handler = () => {
2157
+ if (stacksInFlight > 0 && gracefulOwnerArmed()) {
2158
+ interrupted = true;
2159
+ return;
2160
+ }
2161
+ if (interrupted) {
2162
+ process.stderr.write("\nForce-quit.\n");
2163
+ process.exit(130);
2164
+ }
2165
+ interrupted = true;
2166
+ if (stacksInFlight > 0) {
2167
+ process.stderr.write(`\nInterrupted before ${opts.command} armed its per-stack teardown — quitting now. Nothing was deleted and no stack lock is held.\n`);
2168
+ process.exit(130);
2169
+ }
2170
+ process.stderr.write(`\nInterrupted — ${opts.command} will stop before starting another stack (press Ctrl-C again to force-quit)...\n`);
2171
+ };
2172
+ process.on("SIGINT", handler);
2173
+ return {
2174
+ interrupted: () => interrupted,
2175
+ async runStack(fn) {
2176
+ stacksInFlight += 1;
2177
+ try {
2178
+ return await fn();
2179
+ } finally {
2180
+ stacksInFlight -= 1;
2181
+ }
2182
+ },
2183
+ dispose: () => {
2184
+ process.removeListener("SIGINT", handler);
2185
+ }
2186
+ };
2187
+ }
2068
2188
 
2069
2189
  //#endregion
2070
2190
  //#region src/deployment/recreate-targets.ts
@@ -15927,17 +16047,60 @@ var ProtectionFlipRegistry = class {
15927
16047
  /**
15928
16048
  * The record for `key`, creating it on first use and REUSING it on re-entry.
15929
16049
  *
15930
- * `reuseWithinMs` bounds how long an entry may be reused for and is passed
15931
- * the same `DELETE_BUDGET_REUSE_WINDOW_MS` the allowance uses: past the
15932
- * deadline the whole path is sized against, that deadline has certainly
15933
- * fired, so a caller arriving now belongs to a NEW operation and must not
15934
- * inherit a previous one's flip. Without it a retained record would make a
15935
- * much later destroy of the same table re-enable a guard it never touched.
16050
+ * `reuseWithinMs` bounds how long an entry may sit UNUSED, and the window
16051
+ * SLIDES: every reuse restarts the stopwatch, so what is bounded is the idle
16052
+ * time since the last `acquire` rather than the entry's total lifetime. It is
16053
+ * passed the same `DELETE_BUDGET_REUSE_WINDOW_MS` the allowance uses, and it
16054
+ * is still needed for the reason it was introduced: entries are RETAINED on a
16055
+ * throw, so without any bound a retained record would let a much LATER
16056
+ * destroy of the same table re-enable a guard it never touched — the inverse
16057
+ * hazard of the one this record exists for.
16058
+ *
16059
+ * Measuring from FIRST acquisition instead is what issue #2211 reported, and
16060
+ * it re-creates issue #1978's residue through this very mechanism. The retry
16061
+ * sequence is unbounded while the window is a wall clock, so a
16062
+ * `--resource-timeout` overshoot past `DELETE_BUDGET_REUSE_WINDOW_MS` lets a
16063
+ * LIVE sequence age out MID-flight: `acquire` drops the entry and hands the
16064
+ * next attempt a fresh `{ flippedOffByThisRun: false }`, whose pre-flip
16065
+ * `DescribeTable` then observes the guard already OFF — because attempt 1
16066
+ * turned it off — so the latch stays `false` and a terminal failure
16067
+ * compensates nothing.
16068
+ *
16069
+ * KNOWN BOUND, stated because the obvious reading overclaims it. `acquire` is
16070
+ * called ONCE per `delete()`, at the top, and nothing touches the entry's
16071
+ * idle STOPWATCH again (the record's fields are mutated throughout, and
16072
+ * `release` drops it) — so the interval this measures is not a gap BETWEEN
16073
+ * operations, it is the previous ATTEMPT's own duration plus the outer loop's
16074
+ * backoff.
16075
+ *
16076
+ * That is normally comfortable, and the arithmetic is worth having right.
16077
+ * `DELETE_BUDGET_REUSE_WINDOW_MS` is `DYNAMODB_DELETE_BUDGET_MS` (26 min)
16078
+ * plus `DYNAMODB_DELETE_DEADLINE_MARGIN_MS` (4 min). The 26 minutes is the
16079
+ * allowance for the WHOLE retry sequence, not for one attempt —
16080
+ * `ElapsedBudgetRegistry.acquire` hands back the SAME budget on re-entry — so
16081
+ * a budget-conforming attempt plus one backoff is at most ~26m20s against a
16082
+ * 30-minute window, and the margin exists precisely to price that backoff.
16083
+ *
16084
+ * What can still age out a successor is therefore an attempt that OVERRUNS
16085
+ * the allowance rather than one that merely spends it: `attemptsWithin` keeps
16086
+ * a `minAttempts` floor poll even at zero remaining, and the auto-scaling
16087
+ * teardown is unpriced (see `src/utils/elapsed-budget.ts`). The slide removes
16088
+ * the ACCUMULATION case — many attempts summing past the window, the common
16089
+ * shape and the one issue #2211 reported — not the overrun one.
16090
+ *
16091
+ * Closing the remainder means sliding at attempt END too (a `touch(key)` in
16092
+ * `delete()`'s catch, making the measured gap the loop backoff rather than
16093
+ * the attempt), or sizing this window independently of the delete budget.
16094
+ * Both are behaviour changes to the delete path and belong in their own
16095
+ * change rather than riding this one.
15936
16096
  */
15937
16097
  acquire(key, reuseWithinMs, clock = monotonicNowMs) {
15938
16098
  const existing = this.records.get(key);
15939
16099
  if (existing) {
15940
- if (existing.age.elapsedMs() <= reuseWithinMs) return existing.record;
16100
+ if (existing.idle.elapsedMs() <= reuseWithinMs) {
16101
+ existing.idle = new ElapsedBudget(reuseWithinMs, clock);
16102
+ return existing.record;
16103
+ }
15941
16104
  this.records.delete(key);
15942
16105
  }
15943
16106
  const created = {
@@ -15945,7 +16108,7 @@ var ProtectionFlipRegistry = class {
15945
16108
  flippedOffByThisRun: false,
15946
16109
  deleteAccepted: false
15947
16110
  },
15948
- age: new ElapsedBudget(reuseWithinMs, clock)
16111
+ idle: new ElapsedBudget(reuseWithinMs, clock)
15949
16112
  };
15950
16113
  this.records.set(key, created);
15951
16114
  return created.record;
@@ -16044,9 +16207,30 @@ async function compensateRemovedDeletionProtection(opts) {
16044
16207
  await opts.reEnable();
16045
16208
  opts.logger.warn(`DynamoDB ${opts.typeLabel} ${opts.logicalId}: the delete failed after --remove-protection had turned DeletionProtectionEnabled off, so it was re-enabled on ${opts.physicalId}. The delete failure below is the outcome.`);
16046
16209
  } catch (reEnableError) {
16047
- opts.logger.error(`DynamoDB ${opts.typeLabel} ${opts.logicalId}: could NOT re-enable DeletionProtectionEnabled on ${opts.physicalId} after the delete failed — that table is LIVE with its deletion protection still off. Restore it with: aws dynamodb update-table --table-name ${opts.physicalId} --deletion-protection-enabled. (${reEnableError instanceof Error ? reEnableError.message : String(reEnableError)})`);
16210
+ const detail = reEnableError instanceof Error ? reEnableError.message : String(reEnableError);
16211
+ if (isResourceNotFoundError(reEnableError)) {
16212
+ const regionArg = opts.region ? ` --region ${opts.region}` : "";
16213
+ opts.logger.warn(`DynamoDB ${opts.typeLabel} ${opts.logicalId}: could not re-enable DeletionProtectionEnabled on ${opts.physicalId} after the delete failed — DynamoDB answered ResourceNotFound. That most commonly means the table is gone or its status is not ACTIVE, and it can also mean the table is not in this region or account. If it still exists, its deletion protection is OFF. Check with: aws dynamodb describe-table --table-name ${opts.physicalId}${regionArg} and if it is there, restore it with: aws dynamodb update-table --table-name ${opts.physicalId}${regionArg} --deletion-protection-enabled. (${detail})`);
16214
+ return;
16215
+ }
16216
+ opts.logger.error(`DynamoDB ${opts.typeLabel} ${opts.logicalId}: could NOT re-enable DeletionProtectionEnabled on ${opts.physicalId} after the delete failed — that table is LIVE with its deletion protection still off. Restore it with: aws dynamodb update-table --table-name ${opts.physicalId} --deletion-protection-enabled. (${detail})`);
16048
16217
  }
16049
16218
  }
16219
+ /**
16220
+ * Whether `error` is DynamoDB's `ResourceNotFoundException`.
16221
+ *
16222
+ * Keyed on the SDK's `name` rather than on `instanceof`, so this module keeps
16223
+ * its (deliberate) freedom from an `@aws-sdk/client-dynamodb` import and so a
16224
+ * second client instance of the class still matches.
16225
+ *
16226
+ * Deliberately NOT a `cause`-chain walk: every `reEnable` callback handed to
16227
+ * {@link compensateRemovedDeletionProtection} is a bare `UpdateTable` send, so
16228
+ * the SDK error arrives unwrapped. If one ever wraps its call, this answers
16229
+ * `false` and the compensation keeps the LOUD error line — the safe direction.
16230
+ */
16231
+ function isResourceNotFoundError(error) {
16232
+ return typeof error === "object" && error !== null && error.name === "ResourceNotFoundException";
16233
+ }
16050
16234
 
16051
16235
  //#endregion
16052
16236
  //#region src/provisioning/providers/dynamodb-table-provider.ts
@@ -17007,6 +17191,7 @@ var DynamoDBTableProvider = class {
17007
17191
  physicalId,
17008
17192
  typeLabel: "table",
17009
17193
  logger: this.logger,
17194
+ ...context?.expectedRegion ? { region: context.expectedRegion } : {},
17010
17195
  reEnable: async () => {
17011
17196
  await this.dynamoDBClient.send(new UpdateTableCommand({
17012
17197
  TableName: physicalId,
@@ -19573,6 +19758,7 @@ var DynamoDBGlobalTableProvider = class {
19573
19758
  physicalId,
19574
19759
  typeLabel: "GlobalTable",
19575
19760
  logger: this.logger,
19761
+ ...context?.expectedRegion ? { region: context.expectedRegion } : {},
19576
19762
  reEnable: async () => {
19577
19763
  await this.dynamoDBClient.send(new UpdateTableCommand({
19578
19764
  TableName: physicalId,
@@ -68211,7 +68397,14 @@ async function destroyCommand(stackArgs, options) {
68211
68397
  ...options.profile && { profile: options.profile }
68212
68398
  });
68213
68399
  setAwsClients(awsClients);
68214
- const unforwardSigterm = forwardSigtermToSigint();
68400
+ const interruptWatch = watchCommandInterrupt({ command: "cdkd destroy" });
68401
+ let unforwardSigterm;
68402
+ try {
68403
+ unforwardSigterm = forwardSigtermToSigint();
68404
+ } catch (error) {
68405
+ interruptWatch.dispose();
68406
+ throw error;
68407
+ }
68215
68408
  try {
68216
68409
  const stateConfig = {
68217
68410
  bucket: stateBucket,
@@ -68271,6 +68464,7 @@ async function destroyCommand(stackArgs, options) {
68271
68464
  let totalErrors = 0;
68272
68465
  let totalSkipped = 0;
68273
68466
  let interrupted = false;
68467
+ const runInterrupted = () => interrupted || interruptWatch.interrupted();
68274
68468
  let stackNames;
68275
68469
  if (options.all) stackNames = candidateStacks.map((s) => s.stackName);
68276
68470
  else if (stackPatterns.length > 0) stackNames = matchStacks(candidateStacks, stackPatterns).map((s) => s.stackName);
@@ -68347,6 +68541,7 @@ async function destroyCommand(stackArgs, options) {
68347
68541
  totalErrors++;
68348
68542
  continue;
68349
68543
  }
68544
+ if (runInterrupted()) break;
68350
68545
  const eventRecorder = startRunRecorder({
68351
68546
  backend: stateBackend,
68352
68547
  stackName,
@@ -68355,7 +68550,7 @@ async function destroyCommand(stackArgs, options) {
68355
68550
  });
68356
68551
  let destroyRunResult = "SUCCEEDED";
68357
68552
  try {
68358
- const result = await withNestedStackContext({
68553
+ const result = await interruptWatch.runStack(() => withNestedStackContext({
68359
68554
  stateBackend,
68360
68555
  lockManager,
68361
68556
  providerRegistry,
@@ -68394,7 +68589,7 @@ async function destroyCommand(stackArgs, options) {
68394
68589
  ...options.resourceWarnAfter?.perTypeMs && { resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs },
68395
68590
  ...options.resourceTimeout?.perTypeMs && { resourceTimeoutByType: options.resourceTimeout.perTypeMs },
68396
68591
  eventRecorder
68397
- }));
68592
+ })));
68398
68593
  totalErrors += result.errorCount;
68399
68594
  totalSkipped += result.skippedCount;
68400
68595
  if (result.interrupted) interrupted = true;
@@ -68421,15 +68616,16 @@ async function destroyCommand(stackArgs, options) {
68421
68616
  await purgeEventsAfterDestroy(new DeploymentEventsReader(stateBackend), stackName, stackTargetRegion, {
68422
68617
  purgeEvents: options.purgeEvents,
68423
68618
  runResult: destroyRunResult,
68424
- interrupted
68619
+ interrupted: runInterrupted()
68425
68620
  }, logger);
68426
- if (interrupted) break;
68621
+ if (runInterrupted()) break;
68427
68622
  }
68428
68623
  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.`);
68429
- if (interrupted) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd destroy' to finish.`);
68624
+ if (runInterrupted()) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd destroy' to finish.`);
68430
68625
  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>'.`);
68431
68626
  } finally {
68432
68627
  unforwardSigterm();
68628
+ interruptWatch.dispose();
68433
68629
  awsClients.destroy();
68434
68630
  }
68435
68631
  }
@@ -72997,7 +73193,14 @@ async function stateDestroyCommand(stackArgs, options) {
72997
73193
  registerAllProviders(providerRegistry);
72998
73194
  providerRegistry.setCustomResourceResponseBucket(setup.bucket);
72999
73195
  if (options.allowUnsupportedTypes?.length) providerRegistry.allowUnsupportedTypes(options.allowUnsupportedTypes);
73000
- const unforwardSigterm = forwardSigtermToSigint();
73196
+ const interruptWatch = watchCommandInterrupt({ command: "cdkd state destroy" });
73197
+ let unforwardSigterm;
73198
+ try {
73199
+ unforwardSigterm = forwardSigtermToSigint();
73200
+ } catch (error) {
73201
+ interruptWatch.dispose();
73202
+ throw error;
73203
+ }
73001
73204
  try {
73002
73205
  const stateRefs = await setup.stateBackend.listStacks();
73003
73206
  const knownStackNames = new Set(stateRefs.map((r) => r.stackName));
@@ -73033,6 +73236,7 @@ async function stateDestroyCommand(stackArgs, options) {
73033
73236
  let totalErrors = 0;
73034
73237
  let totalSkipped = 0;
73035
73238
  let interrupted = false;
73239
+ const runInterrupted = () => interrupted || interruptWatch.interrupted();
73036
73240
  for (const stackName of stackNames) {
73037
73241
  const refs = stateRefs.filter((r) => r.stackName === stackName);
73038
73242
  let targets;
@@ -73054,7 +73258,8 @@ async function stateDestroyCommand(stackArgs, options) {
73054
73258
  logger.warn(`No state found for stack ${stackName}${ref.region ? ` in ${ref.region}` : ""}, skipping`);
73055
73259
  continue;
73056
73260
  }
73057
- const result = await withNestedStackContext({
73261
+ if (runInterrupted()) break;
73262
+ const result = await interruptWatch.runStack(() => withNestedStackContext({
73058
73263
  stateBackend: setup.stateBackend,
73059
73264
  lockManager: setup.lockManager,
73060
73265
  providerRegistry,
@@ -73092,19 +73297,20 @@ async function stateDestroyCommand(stackArgs, options) {
73092
73297
  ...options.resourceTimeout?.globalMs !== void 0 && { resourceTimeoutMs: options.resourceTimeout.globalMs },
73093
73298
  ...options.resourceWarnAfter?.perTypeMs && { resourceWarnAfterByType: options.resourceWarnAfter.perTypeMs },
73094
73299
  ...options.resourceTimeout?.perTypeMs && { resourceTimeoutByType: options.resourceTimeout.perTypeMs }
73095
- }));
73300
+ })));
73096
73301
  totalErrors += result.errorCount;
73097
73302
  totalSkipped += result.skippedCount;
73098
73303
  if (result.interrupted) interrupted = true;
73099
- if (interrupted) break;
73304
+ if (runInterrupted()) break;
73100
73305
  }
73101
- if (interrupted) break;
73306
+ if (runInterrupted()) break;
73102
73307
  }
73103
73308
  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.`);
73104
- if (interrupted) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd state destroy' to finish.`);
73309
+ if (runInterrupted()) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd state destroy' to finish.`);
73105
73310
  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>'.`);
73106
73311
  } finally {
73107
73312
  unforwardSigterm();
73313
+ interruptWatch.dispose();
73108
73314
  setup.dispose();
73109
73315
  }
73110
73316
  }
@@ -87364,4 +87570,4 @@ function buildProgram() {
87364
87570
 
87365
87571
  //#endregion
87366
87572
  export { buildProgram };
87367
- //# sourceMappingURL=program-Bpz-tAA5.js.map
87573
+ //# sourceMappingURL=program-BsvNn8Y3.js.map