@go-to-k/cdkd 0.284.61 → 0.284.63

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.
@@ -1,7 +1,7 @@
1
- import { $ as endCommandInterruptScope, $n as derivePartitionAndUrlSuffix, $t as exportNamesCarriedFrom, A as renderStatefulReason, An as AssetManifestLoader, At as isSingleDynamicReferenceToken, B as exportAliasCollisionScrubWarning, Bn as resolveStateBucketWithDefault, Bt as s3BucketRegionalDomainName, C as isFinalSnapshotError, Cn as buildDockerImage, Cr as ResourceTimeoutError, Ct as requireConfigString, D as extractDeploymentEventError, Dn as partitionSensitiveEnv, Dt as createSecretMasker, E as makeCanonicalizePropertiesFn, En as getDockerCmd, Er as StackTerminationProtectionError, Et as TEMPLATE_SOURCED_RULES, F as green, Fn as getLegacyStateBucketName, Fr as isThrottlingError, Ft as classifyReplaySecretRegion, G as collectInlinePolicyNamesManagedBySiblings, Gn as CFN_TEMPLATE_BODY_LIMIT, Gt as describeTypeWithThrottleRetry, H as secretBearingStateKeyWarning, Hn as resolveUseCdkBootstrapAssets, Ht as applyRoleArnIfSet, I as red, In as resolveApp, Ir as markNonRetryable, It as producerRegionsFromState, J as findActionableSilentDrops, Jn as findLargeInlineResources, Jt as TemplateParser, K as clearOnUpdateRemoval, Kn as CFN_TEMPLATE_URL_LIMIT, Kt as withRetry, L as yellow, Ln as resolveAutoAssetStorage, Lt as s3BucketArn, M as bold, Mn as Synthesizer, Mr as withErrorHandling, Mt as maskSecretsInText, N as cyan, Nn as synthesisStatusMessage, Nr as isMarkedNonRetryable, Nt as redactSecretsForState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as runDockerForeground, Ot as dynamicReferenceTokens, P as gray, Pn as getDefaultStateBucketName, Pr as isRetryableTransientError, Pt as scrubResourceRecord, Q as beginCommandInterruptScope, Qn as canonicalizeRegion, Qt as rebuildClientForBucketRegion, R as collectDeclaredOutputNames, Rn as resolveCaptureObservedState, Rt as s3BucketDomainName, S as createPreDeleteFinalSnapshot, Sn as buildDenyExternalAccessPolicy, Sr as ProvisioningError, St as requireConfigObject, T as unsupportedFinalSnapshotError, Tn as formatDockerLoginError, Tr as StackHasActiveImportsError, Tt as STATE_SOURCED_READBACK_RULES, U as stateKeySecretExposure, Un as stateBucketExistenceConfirmed, Ut as DiffCalculator, V as isExportAliasCollision, Vn as resolveStateBucketWithDefaultAndSource, Vt as s3BucketWebsiteUrl, W as IAMRoleProvider, Wn as warnDeprecatedNoPrefixCliFlag, Wt as INTRINSIC_KEYS, X as CUSTOM_RESOURCE_RESPONSE_PREFIX, Xn as expectedOwnerParam, Xt as displaySafe, Y as findSilentDropProperties, Yn as uploadCfnTemplate, Yt as LockManager, Z as DEFAULT_STATE_PREFIX, Zn as PARTITION_TABLE, Zt as S3StateBackend, _ as computeImplicitDeleteEdges, _n as isCrossRegionRedirect, _r as LocalStartServiceError, _t as configBooleanRefusal, a as DeploymentEventsStore, an as WorkGraph, ar as getAwsClients, at as disableInstanceApiTermination, b as buildFinalSnapshotIdentifier, bn as validateAssetBucketName, br as NestedStackChildDirectDestroyError, bt as replayWarn, c as replayFailedOperations, cn as loadPublishableAssetManifest, ct as carriesDynamicReference, d as updatePartialReason, dn as stripControlChars, dr as CrossAccountSecretRefusalError, dt as refStateLookupFromResource, en as importableOutputKeys, er as AssemblyReader, et as interruptWatchListenerCount, f as UNSPECIFIED_SKIP_REASON, fn as AssetModeResolver, ft as WAFv2WebACLProvider, g as IMPLICIT_DELETE_DEPENDENCIES, gn as getBootstrapMarkerKey, gr as LocalMigrateError, gt as coerceCfnBoolean, h as maskingRetryLogger, hn as ensureAssetStorage, hr as LocalInvokeBuildError$1, ht as assertRegionMatch, i as DeploymentEventsReader, in as stringifyValue, ir as AwsClients, it as slowCcOperationTimeoutMs, j as formatResourceLine, jn as getDockerImageBySourceHash, jr as normalizeAwsError, jt as maskSecretsInError, k as isStatefulRecreateTargetSync, kn as runDockerStreaming, kt as errorCauseChain, l as replayRollback, ln as rewriteTemplateAssetReferences, lr as CdkdError, lt as cfnRefValueFromPhysicalId, m as withResourceDeadline, mn as assertAssetBucketRegion, mr as DynamicReferenceRegionAmbiguousError, mt as resolveExplicitPhysicalId, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as shouldRetainResource, nt as startInterruptWatch, o as planFailedOps, on as buildAssetRedirectMap, or as resetAwsClients, ot as isTerminationProtectionPropagationError, p as deleteSkipReason, pn as BOOTSTRAP_MARKER_PREFIX, pr as DeployCancelledError, pt as normalizeAwsTagsToCfn, q as ProviderRegistry, qn as MIGRATE_TMP_PREFIX, qt as DagBuilder, r as DeployEngine, rn as AssetPublisher, rr as resolveBucketRegion, rt as CloudControlProvider, s as planRollback, sn as createAssetRedirectResolver, sr as setAwsClients, st as IntrinsicFunctionResolver, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as importableOutputs, tr as processStackMessages, tt as isInterruptedWaitError, u as updatePartialMessage, un as escapeRegExp, ut as getAccountInfo, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as parseBootstrapMarker, vt as configStringRefusal, w as refusesFinalSnapshot, wn as dockerSpawnEnvWithSensitive, wr as ResourceUpdateNotSupportedError, wt as STATE_SOURCED_CROSS_GENERATION_RULES, x as ccRoutedFinalSnapshotError, xn as validateContainerRepoName, xr as PartialFailureError, xt as requireConfigArray, y as PRE_DELETE_SNAPSHOT_TYPES, yn as readBootstrapMarkerBody, yr as MissingCdkCliError, yt as readConfigString, z as collectPublishedOutputNames, zn as resolveSkipPrefix, zt as s3BucketDualStackDomainName } from "./deploy-engine-0d-g0TT4.js";
1
+ import { $ as beginCommandInterruptScope, $n as PARTITION_TABLE, $t as S3StateBackend, A as renderStatefulReason, An as runDockerForeground, At as dynamicReferenceTokens, B as exportAliasCollisionScrubWarning, Bn as resolveCaptureObservedState, Bt as s3BucketDomainName, C as isFinalSnapshotError, Cn as validateContainerRepoName, Cr as PartialFailureError, Ct as requireConfigArray, D as extractDeploymentEventError, Dn as formatDockerLoginError, Dr as StackHasActiveImportsError, Dt as STATE_SOURCED_READBACK_RULES, E as makeCanonicalizePropertiesFn, En as dockerSpawnEnvWithSensitive, Er as ResourceUpdateNotSupportedError, Et as STATE_SOURCED_CROSS_GENERATION_RULES, F as green, Fn as synthesisStatusMessage, Fr as isMarkedNonRetryable, Ft as redactSecretsForState, G as IAMRoleProvider, Gn as stateBucketExistenceConfirmed, Gt as DiffCalculator, H as secretBearingStateKeyWarning, Hn as resolveStateBucketWithDefault, Ht as s3BucketRegionalDomainName, I as red, In as getDefaultStateBucketName, Ir as isRetryableTransientError, It as scrubResourceRecord, J as ProviderRegistry, Jn as CFN_TEMPLATE_URL_LIMIT, Jt as withRetry, K as collectInlinePolicyNamesManagedBySiblings, Kn as warnDeprecatedNoPrefixCliFlag, Kt as INTRINSIC_KEYS, L as yellow, Ln as getLegacyStateBucketName, Lr as isThrottlingError, Lt as classifyReplaySecretRegion, M as bold, Mn as AssetManifestLoader, Mt as isSingleDynamicReferenceToken, N as cyan, Nn as getDockerImageBySourceHash, Nr as normalizeAwsError, Nt as maskSecretsInError, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as getDockerCmd, Or as StackTerminationProtectionError, Ot as TEMPLATE_SOURCED_RULES, P as gray, Pn as Synthesizer, Pr as withErrorHandling, Pt as maskSecretsInText, Q as DEFAULT_STATE_PREFIX, Qn as expectedOwnerParam, Qt as displaySafe, R as collectDeclaredOutputNames, Rn as resolveApp, Rr as markNonRetryable, Rt as producerRegionsFromState, S as createPreDeleteFinalSnapshot, Sn as validateAssetBucketName, Sr as NestedStackChildDirectDestroyError, St as replayWarn, T as unsupportedFinalSnapshotError, Tn as buildDockerImage, Tr as ResourceTimeoutError, Tt as requireConfigString, U as stateKeySecretExposure, Un as resolveStateBucketWithDefaultAndSource, Ut as s3BucketWebsiteUrl, V as isExportAliasCollision, Vn as resolveSkipPrefix, Vt as s3BucketDualStackDomainName, W as getCurrentResourceSecrets, Wn as resolveUseCdkBootstrapAssets, Wt as applyRoleArnIfSet, X as findSilentDropProperties, Xn as findLargeInlineResources, Xt as TemplateParser, Y as findActionableSilentDrops, Yn as MIGRATE_TMP_PREFIX, Yt as DagBuilder, Z as CUSTOM_RESOURCE_RESPONSE_PREFIX, Zn as uploadCfnTemplate, Zt as LockManager, _ as computeImplicitDeleteEdges, _n as ensureAssetStorage, _r as LocalInvokeBuildError$1, _t as assertRegionMatch, a as DeploymentEventsStore, an as AssetPublisher, ar as resolveBucketRegion, at as slowCcOperationTimeoutMs, b as buildFinalSnapshotIdentifier, bn as parseBootstrapMarker, bt as configStringRefusal, c as replayFailedOperations, cn as buildAssetRedirectMap, cr as resetAwsClients, ct as IntrinsicFunctionResolver, d as updatePartialReason, dn as rewriteTemplateAssetReferences, dr as CdkdError, dt as getAccountInfo, en as rebuildClientForBucketRegion, er as canonicalizeRegion, et as endCommandInterruptScope, f as UNSPECIFIED_SKIP_REASON, fn as escapeRegExp, ft as parameterTypeMayLoseSecretIdentity, g as IMPLICIT_DELETE_DEPENDENCIES, gn as assertAssetBucketRegion, gr as DynamicReferenceRegionAmbiguousError, gt as resolveExplicitPhysicalId, h as maskingRetryLogger, hn as BOOTSTRAP_MARKER_PREFIX, hr as DeployCancelledError, ht as normalizeAwsTagsToCfn, i as DeploymentEventsReader, in as shouldRetainResource, it as CloudControlProvider, j as formatResourceLine, jn as runDockerStreaming, jt as errorCauseChain, k as isStatefulRecreateTargetSync, kn as partitionSensitiveEnv, kt as createSecretMasker, l as replayRollback, ln as createAssetRedirectResolver, lr as setAwsClients, lt as carriesDynamicReference, m as withResourceDeadline, mn as AssetModeResolver, mt as WAFv2WebACLProvider, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as importableOutputKeys, nr as AssemblyReader, nt as isInterruptedWaitError, o as planFailedOps, on as stringifyValue, or as AwsClients, ot as disableInstanceApiTermination, p as deleteSkipReason, pn as stripControlChars, pr as CrossAccountSecretRefusalError, pt as refStateLookupFromResource, q as clearOnUpdateRemoval, qn as CFN_TEMPLATE_BODY_LIMIT, qt as describeTypeWithThrottleRetry, r as DeployEngine, rn as importableOutputs, rr as processStackMessages, rt as startInterruptWatch, s as planRollback, sn as WorkGraph, sr as getAwsClients, st as isTerminationProtectionPropagationError, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as exportNamesCarriedFrom, tr as derivePartitionAndUrlSuffix, tt as interruptWatchListenerCount, u as updatePartialMessage, un as loadPublishableAssetManifest, ut as cfnRefValueFromPhysicalId, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as getBootstrapMarkerKey, vr as LocalMigrateError, vt as coerceCfnBoolean, w as refusesFinalSnapshot, wn as buildDenyExternalAccessPolicy, wr as ProvisioningError, wt as requireConfigObject, x as ccRoutedFinalSnapshotError, xn as readBootstrapMarkerBody, xr as MissingCdkCliError, xt as readConfigString, y as PRE_DELETE_SNAPSHOT_TYPES, yn as isCrossRegionRedirect, yr as LocalStartServiceError, yt as configBooleanRefusal, z as collectPublishedOutputNames, zn as resolveAutoAssetStorage, zt as s3BucketArn } from "./deploy-engine-B-RuozdO.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-iXYxz3AH.js";
4
- import { t as ASGProvider } from "./asg-provider-DTolzTHQ.js";
3
+ import { t as getCdkdVersion } from "./version-Cmw63bLc.js";
4
+ import { t as ASGProvider } from "./asg-provider-B9V61Fbq.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";
@@ -2032,7 +2032,9 @@ function createListCommand() {
2032
2032
  * cancellation, and GitLab CI / `docker stop` / Kubernetes send SIGTERM
2033
2033
  * directly. cdkd's graceful-interrupt handling (the top-level command
2034
2034
  * handlers, the deploy engine's partial-state save, and the per-provider
2035
- * poll-abort listeners in CustomResource / ACM / CloudFront / Route53) is
2035
+ * poll-abort listeners in CustomResource / CloudFront / ACM `grep -rn
2036
+ * "process.on('SIGINT'" src/provisioning/providers/` regenerates that closed
2037
+ * set, and Route53 is NOT in it) is
2036
2038
  * registered on SIGINT only, so an unhandled SIGTERM killed the process
2037
2039
  * mid-run, skipped every `finally`, and stranded the stack lock for its
2038
2040
  * full TTL.
@@ -2082,7 +2084,7 @@ function installSigtermForwarder() {
2082
2084
  * `cdkd deploy` has had one of these since issue #1348 (`deploy.ts`'s
2083
2085
  * `topLevelSigintHandler`). The destroy side had none, so the ONLY channel
2084
2086
  * carrying "the user interrupted" from `runDestroyForStack` back to the
2085
- * `--all` loop was `DestroyRunResult.interrupted` — a boolean assigned ONCE,
2087
+ * `--all` loop was `DestroyRunnerResult.interrupted` — a boolean assigned ONCE,
2086
2088
  * inside the runner's `try`, after its level loop. Every instant where the
2087
2089
  * runner's `draining` flag flips AFTER that assignment is an instant where
2088
2090
  * `cdkd destroy --all` went on to delete the NEXT stack after the user asked
@@ -2106,10 +2108,38 @@ function installSigtermForwarder() {
2106
2108
  *
2107
2109
  * A handler that lives for the whole command closes both, because it is armed
2108
2110
  * in every window rather than in the ones the runner happens to span. The two
2109
- * flags answer different questions and both are kept: `result.interrupted` is
2110
- * the PER-STACK outcome (it decides that stack's state preservation, its
2111
- * summary line and its `--purge-events` skip), while this one is the
2112
- * COMMAND-level "stop" the loop wants.
2111
+ * flags answer different questions and both are kept, one owner each:
2112
+ *
2113
+ * - PER-STACK, "did THIS stack finish, or is there work left in it?" —
2114
+ * `DestroyRunnerResult.interrupted`, owned by `destroy-runner.ts`. It has FOUR
2115
+ * consumers: that stack's state preservation, its summary line,
2116
+ * `destroy.ts`'s `--purge-events` skip, and — the one earlier revisions of
2117
+ * this list omitted — `NestedStackProvider.delete`, which reads the CHILD's
2118
+ * result to decide whether the child row is `{ outcome: 'skipped' }` on the
2119
+ * parent and whether its failure is `markNonRetryable`.
2120
+ *
2121
+ * That fourth consumer is why the `&& statePreserved` gate below is a
2122
+ * behavior change beyond the exit code: a fully-destroyed child that takes
2123
+ * a tail signal no longer reports `interrupted`, so the parent no longer
2124
+ * marks its row skipped. That is the CORRECT reading — `skipped` means
2125
+ * "cdkd could not address this, the resources may still exist in AWS",
2126
+ * which is false of a child whose every resource was deleted and whose
2127
+ * state record is gone; it would preserve the parent's row for a child that
2128
+ * no longer exists and exit 2 over a completed teardown. Stopping the
2129
+ * surrounding `--all` run is a DIFFERENT question, still answered by this
2130
+ * watch, which the parent command holds for its whole run.
2131
+ * - PER-RUN, "has the user asked this COMMAND to stop?" — this watch. Its
2132
+ * consumers are the loops' `break`s and the terminal exit-code verdict
2133
+ * (which pairs it with `stoppedEarly`, so a signal in a completed run's
2134
+ * tail does not report unfinished work there is none of).
2135
+ *
2136
+ * `--purge-events` moved onto the per-stack answer when the runner's outer
2137
+ * re-sync was gated on `statePreserved`. Before that the per-stack flag could
2138
+ * be true over a stack whose state was already DELETED, so the purge had to
2139
+ * read the command-level one to stay conservative — at the cost of the
2140
+ * opposite error: a tail signal on the LAST stack skipped the purge while the
2141
+ * command exited 0, reporting full success with the events left behind. With
2142
+ * the per-stack flag accurate, one read serves both directions.
2113
2143
  *
2114
2144
  * Note on the counterpart contract: `deploy-engine.ts` removes its own SIGINT
2115
2145
  * handler before releasing its lock, which is safe only because `deploy.ts`'s
@@ -2123,6 +2153,11 @@ function installSigtermForwarder() {
2123
2153
  */
2124
2154
  function watchCommandInterrupt(opts) {
2125
2155
  let interrupted = false;
2156
+ const promptAbort = new AbortController();
2157
+ function markInterrupted() {
2158
+ interrupted = true;
2159
+ promptAbort.abort();
2160
+ }
2126
2161
  let stacksInFlight = 0;
2127
2162
  /**
2128
2163
  * Is some OTHER listener going to handle this signal gracefully?
@@ -2145,27 +2180,30 @@ function watchCommandInterrupt(opts) {
2145
2180
  * - `interrupt-watch.ts`'s shared handler, which is a LATCH rather than a
2146
2181
  * graceful owner — deferring to it is exactly the swallow above.
2147
2182
  *
2148
- * A provider's transient wait listener (CustomResource / ACM / CloudFront /
2149
- * Route53) does count, and that is correct rather than a false positive:
2150
- * those exist only DURING a provider wait, which is strictly inside the
2151
- * runner handler's own lifetime, so one cannot be present while the runner's
2152
- * is absent.
2183
+ * A provider's transient wait listener does count, and that is correct
2184
+ * rather than a false positive: three providers install one grep
2185
+ * `process.on('SIGINT'` under `src/provisioning/providers/**` for the
2186
+ * closed set, today `acm-certificate-provider.ts`,
2187
+ * `cloudfront-distribution-provider.ts` and `custom-resource-provider.ts`,
2188
+ * NOT Route53, whose provider registers none — and each exists only
2189
+ * DURING a provider wait, which is strictly inside the runner handler's own
2190
+ * lifetime, so one cannot be present while the runner's is absent.
2153
2191
  */
2154
2192
  function gracefulOwnerArmed() {
2155
2193
  return process.listeners("SIGINT").filter((l) => l !== handler).length - interruptWatchListenerCount() > 0;
2156
2194
  }
2157
2195
  const handler = () => {
2158
2196
  if (stacksInFlight > 0 && gracefulOwnerArmed()) {
2159
- interrupted = true;
2197
+ markInterrupted();
2160
2198
  return;
2161
2199
  }
2162
2200
  if (interrupted) {
2163
- process.stderr.write("\nForce-quit.\n");
2201
+ process.stderr.write("\nForce-quit.\nIf the next run reports a stack lock, release it with: cdkd force-unlock <stack-name>\n");
2164
2202
  process.exit(130);
2165
2203
  }
2166
- interrupted = true;
2204
+ markInterrupted();
2167
2205
  if (stacksInFlight > 0) {
2168
- process.stderr.write(`\nInterrupted before ${opts.command} armed its per-stack teardown — quitting now. Nothing was deleted and no stack lock is held.\n`);
2206
+ process.stderr.write(`\nInterrupted before ${opts.command} armed its per-stack teardown — quitting now. No delete was issued for this stack; on an --all run, stacks processed earlier are already destroyed.\nIf the next run reports a stack lock, release it with: cdkd force-unlock <stack-name>\n`);
2169
2207
  process.exit(130);
2170
2208
  }
2171
2209
  process.stderr.write(`\nInterrupted — ${opts.command} will stop before starting another stack (press Ctrl-C again to force-quit)...\n`);
@@ -2173,6 +2211,7 @@ function watchCommandInterrupt(opts) {
2173
2211
  process.on("SIGINT", handler);
2174
2212
  return {
2175
2213
  interrupted: () => interrupted,
2214
+ signal: promptAbort.signal,
2176
2215
  async runStack(fn) {
2177
2216
  stacksInFlight += 1;
2178
2217
  try {
@@ -2186,6 +2225,22 @@ function watchCommandInterrupt(opts) {
2186
2225
  }
2187
2226
  };
2188
2227
  }
2228
+ /**
2229
+ * Did this rejection come from a prompt aborted through
2230
+ * {@link CommandInterruptWatch.signal}?
2231
+ *
2232
+ * Deliberately duck-typed rather than `instanceof`: what `readline/promises`
2233
+ * rejects with is an internal Node error class in some versions and a
2234
+ * `DOMException` in others, and neither is importable. Both spellings carry
2235
+ * `name === 'AbortError'` / `code === 'ABORT_ERR'`, and the caller pairs this
2236
+ * with its own `watch.interrupted()` check, so a same-named error from an
2237
+ * unrelated abort cannot be mistaken for the user's Ctrl-C.
2238
+ */
2239
+ function isPromptAbortError(error) {
2240
+ if (typeof error !== "object" || error === null) return false;
2241
+ const { name, code } = error;
2242
+ return name === "AbortError" || code === "ABORT_ERR";
2243
+ }
2189
2244
 
2190
2245
  //#endregion
2191
2246
  //#region src/deployment/recreate-targets.ts
@@ -59254,7 +59309,6 @@ async function runDestroyForStack(stackName, state, ctx) {
59254
59309
  }
59255
59310
  }
59256
59311
  result.skippedEmpty = true;
59257
- result.interrupted ||= emptyInterrupted;
59258
59312
  return result;
59259
59313
  }
59260
59314
  const needsStrongRefCheck = !!(state.outputs && Object.keys(state.outputs).length > 0);
@@ -59327,6 +59381,7 @@ async function runDestroyForStack(stackName, state, ctx) {
59327
59381
  const renderer = getLiveRenderer();
59328
59382
  let draining = false;
59329
59383
  let lockHeld = false;
59384
+ let statePreserved = false;
59330
59385
  const sigintHandler = () => {
59331
59386
  if (draining) {
59332
59387
  if (lockHeld) {
@@ -59643,6 +59698,7 @@ async function runDestroyForStack(stackName, state, ctx) {
59643
59698
  result.interrupted = draining;
59644
59699
  await saveChain;
59645
59700
  const preserveState = result.errorCount > 0 || result.interrupted || result.skippedCount > 0;
59701
+ statePreserved = preserveState;
59646
59702
  if (!preserveState) {
59647
59703
  await ctx.stateBackend.deleteState(stackName, regionForState);
59648
59704
  logger.debug("State deleted");
@@ -59690,7 +59746,7 @@ async function runDestroyForStack(stackName, state, ctx) {
59690
59746
  process.removeListener("SIGINT", sigintHandler);
59691
59747
  restoreBaseRegionAndClients();
59692
59748
  }
59693
- result.interrupted ||= draining;
59749
+ result.interrupted ||= draining && statePreserved;
59694
59750
  }
59695
59751
  return result;
59696
59752
  }
@@ -59907,7 +59963,7 @@ var NestedStackProvider = class {
59907
59963
  await this.runChildDeploy(ctx, logicalId, childStackName, childRegion, childTemplate, childParameters, grandchildTemplates);
59908
59964
  const attributes = await this.readChildOutputsAsAttributes(ctx, childStackName, childRegion);
59909
59965
  return {
59910
- physicalId: this.synthesizeArn(ctx.accountId, ctx.parentRegion, ctx.parentStackName, logicalId),
59966
+ physicalId: this.synthesizeArn(ctx.accountId, childRegion, ctx.parentStackName, logicalId),
59911
59967
  attributes
59912
59968
  };
59913
59969
  }
@@ -59982,9 +60038,11 @@ var NestedStackProvider = class {
59982
60038
  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.`);
59983
60039
  }
59984
60040
  async runChildDeploy(parentCtx, logicalId, childStackName, childRegion, childTemplate, childParameters, grandchildTemplates) {
60041
+ const inheritedSecrets = getCurrentResourceSecrets();
59985
60042
  const childEngine = new DeployEngine(parentCtx.stateBackend, parentCtx.lockManager, parentCtx.dagBuilder, parentCtx.diffCalculator, parentCtx.providerRegistry, {
59986
60043
  ...parentCtx.options ?? {},
59987
60044
  parameters: childParameters,
60045
+ ...inheritedSecrets && inheritedSecrets.size > 0 && { inheritedSecrets },
59988
60046
  parentStackInfo: {
59989
60047
  parentStack: parentCtx.parentStackName,
59990
60048
  parentLogicalId: logicalId,
@@ -60014,8 +60072,22 @@ var NestedStackProvider = class {
60014
60072
  deriveChildStackName(parentStackName, nestedLogicalId) {
60015
60073
  return `${parentStackName}~${nestedLogicalId}`;
60016
60074
  }
60017
- synthesizeArn(accountId, region, parentStackName, logicalId) {
60018
- return `arn:cdkd-local:${region}:${accountId}:nested-stack/${parentStackName}/${logicalId}`;
60075
+ /**
60076
+ * The synthesized `physicalId` recorded on the PARENT's
60077
+ * `AWS::CloudFormation::Stack` row.
60078
+ *
60079
+ * `childRegion` is the region the child stack was deployed into (its own
60080
+ * `StackState.region`), and the segment is LOAD-BEARING as of issue
60081
+ * [#2055](https://github.com/go-to-k/cdkd/issues/2055):
60082
+ * `IntrinsicFunctionResolver` reads it back as the producer region when a
60083
+ * parent property consumes `Fn::GetAtt: [<Child>, 'Outputs.<Name>']` and the
60084
+ * child's persisted output is a redacted `{{resolve:...}}` expression. A
60085
+ * secret NAME is regional, so resolving that expression anywhere but the
60086
+ * child's own region can answer with a different secret. Equal to the
60087
+ * parent's region today; do not collapse the two.
60088
+ */
60089
+ synthesizeArn(accountId, childRegion, parentStackName, logicalId) {
60090
+ return `arn:cdkd-local:${childRegion}:${accountId}:nested-stack/${parentStackName}/${logicalId}`;
60019
60091
  }
60020
60092
  extractParameters(properties) {
60021
60093
  const params = properties["Parameters"];
@@ -60057,6 +60129,42 @@ var NestedStackProvider = class {
60057
60129
  }
60058
60130
  return result;
60059
60131
  }
60132
+ /**
60133
+ * Surface the child's PERSISTED outputs as the parent's `Outputs.<Key>`
60134
+ * attributes, VERBATIM — including a secret-bearing one, which PR #1899
60135
+ * persists as its unresolved `{{resolve:...}}` expression.
60136
+ *
60137
+ * DELIBERATELY NOT the place the expression is re-resolved (issue
60138
+ * [#2055](https://github.com/go-to-k/cdkd/issues/2055)). Re-resolving here
60139
+ * would be easy — `readChildOutputsAsAttributes` is already async — and it
60140
+ * would trade one bug for a worse one: this map becomes the parent's
60141
+ * `ResourceState.attributes`, and every CONSUMER resource then reads the
60142
+ * plaintext through `Fn::GetAtt` with no entry in its OWN
60143
+ * `recordedSecretValues`, so the deploy engine's save choke point has nothing
60144
+ * to redact it back to and the consumer's `state.json` persists the decrypted
60145
+ * secret. Keeping the expression here keeps BOTH the parent's nested-stack row
60146
+ * and every consumer's record redacted.
60147
+ *
60148
+ * The re-resolution therefore lives at the READ site, in
60149
+ * `IntrinsicFunctionResolver.resolveGetAtt`, where the CONSUMER's resolver
60150
+ * context is in hand — the same seam and the same reason as the
60151
+ * `Fn::ImportValue` / `Fn::GetStackOutput` arms of issue #1934.
60152
+ *
60153
+ * "A read site risks missing a caller" is answered for the RESOLUTION path by
60154
+ * there being exactly one: the flat-key attribute lookup, which
60155
+ * `getAttribute()` below exists to prove is the only route (it throws for
60156
+ * anything that falls through it).
60157
+ *
60158
+ * It is NOT answered for every reader of `ResourceState.attributes` in the
60159
+ * codebase, and an earlier revision of this note claimed it was.
60160
+ * `AttributeFetcher.cacheFallback` in `src/analyzer/orphan-rewriter.ts` reads
60161
+ * `orphan.attributes?.[attribute]` directly under `cdkd orphan --force` and
60162
+ * has no resolver context to re-resolve with, so it splices the token
60163
+ * verbatim into the referring resource's state and warns about exactly that.
60164
+ * The seam decision above is unchanged — putting the plaintext here would
60165
+ * make EVERY consumer persist it — but the count is "one resolution path",
60166
+ * not "one reader".
60167
+ */
60060
60168
  buildOutputsAttributes(outputs) {
60061
60169
  const attributes = {};
60062
60170
  for (const [key, value] of Object.entries(outputs)) attributes[`Outputs.${key}`] = value;
@@ -62641,6 +62749,55 @@ function isStackNotFoundError(err) {
62641
62749
  //#endregion
62642
62750
  //#region src/cli/commands/diff-recursive.ts
62643
62751
  const logger = getLogger().child("DiffRecursive");
62752
+ /** Every `Ref` target reachable from a value, collected in one walk. */
62753
+ function collectRefTargets(value, refs) {
62754
+ if (Array.isArray(value)) {
62755
+ for (const element of value) collectRefTargets(element, refs);
62756
+ return;
62757
+ }
62758
+ if (!value || typeof value !== "object") return;
62759
+ const obj = value;
62760
+ if (typeof obj["Ref"] === "string") refs.add(obj["Ref"]);
62761
+ for (const nested of Object.values(obj)) collectRefTargets(nested, refs);
62762
+ }
62763
+ /**
62764
+ * Whether ANY condition in `template` references one of `tokenParameterNames`
62765
+ * (issue #1903, review round 2).
62766
+ *
62767
+ * WHY THIS EXISTS AND WHY THE BROAD FORM IS NOT MERELY CONSERVATIVE. The
62768
+ * token-parameter guard originally skipped condition evaluation for the WHOLE
62769
+ * template as soon as any bound parameter was a redacted token, on the argument
62770
+ * that the cost is "a condition-false resource is diffed rather than pruned".
62771
+ * That is only half of it: leaving `conditions` UNDEFINED also means `resolveIf`
62772
+ * finds no entry, warns, and takes the FALSE branch for every `Fn::If` in every
62773
+ * property value — so a condition-TRUE property diffs as a spurious UPDATE
62774
+ * (perpetual, and `--fail` exits 1) and condition-gated resources surface as
62775
+ * phantom CREATEs. And it fired whenever ANY parameter was token-valued, even
62776
+ * when not one condition mentioned it.
62777
+ *
62778
+ * So the skip is narrowed to what it can actually get wrong: a condition whose
62779
+ * verdict DEPENDS on a value this side does not have.
62780
+ *
62781
+ * NO TRANSITIVE `{Condition: X}` WALK, and its absence is a reachability
62782
+ * argument rather than an omission. An `Fn::And` over a NAMED condition (issue
62783
+ * #840's shape) does inherit that condition's dependency — but the named
62784
+ * condition is itself an entry in this same `Conditions` map, and this scan
62785
+ * visits EVERY entry, so the dependency is already found directly on the
62786
+ * referenced condition. A chain walk was written first and measured dead: with
62787
+ * it removed, a template whose `IsDev` wraps `{Condition: IsProd}` while only
62788
+ * `IsProd` names the token parameter still answers `true`.
62789
+ */
62790
+ function conditionsDependOnTokenParameters(template, tokenParameterNames) {
62791
+ const templateConditions = template.Conditions;
62792
+ if (!templateConditions || typeof templateConditions !== "object") return false;
62793
+ if (tokenParameterNames.size === 0) return false;
62794
+ for (const definition of Object.values(templateConditions)) {
62795
+ const refs = /* @__PURE__ */ new Set();
62796
+ collectRefTargets(definition, refs);
62797
+ for (const ref of refs) if (tokenParameterNames.has(ref)) return true;
62798
+ }
62799
+ return false;
62800
+ }
62644
62801
  /** Empty template used to diff a removed nested child's state → all DELETE. */
62645
62802
  const EMPTY_TEMPLATE = { Resources: {} };
62646
62803
  /**
@@ -62731,6 +62888,11 @@ async function computeStackDiff(currentState, template, region, stackName, state
62731
62888
  const intrinsicResolver = new IntrinsicFunctionResolver(region, { cfnFallback: cfnFallback ?? true });
62732
62889
  let mergedParameters = parameters;
62733
62890
  let parametersBound = false;
62891
+ const tokenParameterNames = new Set(Object.entries(parameters ?? {}).filter(([, value]) => carriesDynamicReference(value)).map(([name]) => name));
62892
+ for (const name of tokenParameterNames) {
62893
+ const declaredType = (template.Parameters?.[name])?.Type;
62894
+ if (typeof declaredType === "string" && parameterTypeMayLoseSecretIdentity(declaredType)) logger.warn(`Stack ${stackName}: parameter '${name}' is declared 'Type: ${declaredType}' and is fed a secret dynamic reference. 'cdkd deploy' refuses this — declare it 'Type: String' — and this diff keeps the unresolved reference rather than coercing it.`);
62895
+ }
62734
62896
  try {
62735
62897
  const userParameters = {};
62736
62898
  for (const [name, value] of Object.entries(parameters ?? {})) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") userParameters[name] = String(value);
@@ -62739,13 +62901,14 @@ async function computeStackDiff(currentState, template, region, stackName, state
62739
62901
  ...parameters,
62740
62902
  ...templateParameters
62741
62903
  };
62904
+ for (const name of tokenParameterNames) mergedParameters[name] = (parameters ?? {})[name];
62742
62905
  parametersBound = true;
62743
62906
  } catch (error) {
62744
62907
  logger.debug(`Diff parameter binding for stack ${stackName} is partial: ${error instanceof Error ? error.message : String(error)}`);
62745
62908
  }
62746
62909
  let effectiveTemplate = template;
62747
62910
  let conditions;
62748
- if (parametersBound) try {
62911
+ if (parametersBound && !conditionsDependOnTokenParameters(template, tokenParameterNames)) try {
62749
62912
  conditions = await intrinsicResolver.evaluateConditions({
62750
62913
  template,
62751
62914
  resources: currentState.resources,
@@ -62818,7 +62981,8 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
62818
62981
  stateBackend,
62819
62982
  stackName: parentStackName,
62820
62983
  bestEffort: true,
62821
- ...parentParameters && { parameters: parentParameters }
62984
+ ...parentParameters && { parameters: parentParameters },
62985
+ skipDynamicReferences: true
62822
62986
  });
62823
62987
  } catch {}
62824
62988
  return resolved;
@@ -68750,6 +68914,7 @@ async function destroyCommand(stackArgs, options) {
68750
68914
  let totalSkipped = 0;
68751
68915
  let interrupted = false;
68752
68916
  const runInterrupted = () => interrupted || interruptWatch.interrupted();
68917
+ let stoppedEarly = false;
68753
68918
  let stackNames;
68754
68919
  if (options.all) stackNames = candidateStacks.map((s) => s.stackName);
68755
68920
  else if (stackPatterns.length > 0) stackNames = matchStacks(candidateStacks, stackPatterns).map((s) => s.stackName);
@@ -68790,7 +68955,7 @@ async function destroyCommand(stackArgs, options) {
68790
68955
  arr.push(ref);
68791
68956
  stateRefsByName.set(ref.stackName, arr);
68792
68957
  }
68793
- for (const stackName of stackNames) {
68958
+ for (const [stackIndex, stackName] of stackNames.entries()) {
68794
68959
  logger.info(`\nPreparing to destroy stack: ${stackName}`);
68795
68960
  const refs = stateRefsByName.get(stackName) ?? [];
68796
68961
  const synthStack = appStacks.find((s) => s.stackName === stackName);
@@ -68826,7 +68991,10 @@ async function destroyCommand(stackArgs, options) {
68826
68991
  totalErrors++;
68827
68992
  continue;
68828
68993
  }
68829
- if (runInterrupted()) break;
68994
+ if (runInterrupted()) {
68995
+ stoppedEarly = true;
68996
+ break;
68997
+ }
68830
68998
  const eventRecorder = startRunRecorder({
68831
68999
  backend: stateBackend,
68832
69000
  stackName,
@@ -68834,6 +69002,8 @@ async function destroyCommand(stackArgs, options) {
68834
69002
  command: "destroy"
68835
69003
  });
68836
69004
  let destroyRunResult = "SUCCEEDED";
69005
+ let stackInterrupted = false;
69006
+ let stackCancelled = false;
68837
69007
  try {
68838
69008
  const result = await interruptWatch.runStack(() => withNestedStackContext({
68839
69009
  stateBackend,
@@ -68877,6 +69047,8 @@ async function destroyCommand(stackArgs, options) {
68877
69047
  })));
68878
69048
  totalErrors += result.errorCount;
68879
69049
  totalSkipped += result.skippedCount;
69050
+ stackInterrupted = result.interrupted;
69051
+ stackCancelled = result.cancelled === true;
68880
69052
  if (result.interrupted) interrupted = true;
68881
69053
  destroyRunResult = result.errorCount > 0 || result.skippedCount > 0 ? "FAILED" : "SUCCEEDED";
68882
69054
  eventRecorder.record({
@@ -68901,12 +69073,15 @@ async function destroyCommand(stackArgs, options) {
68901
69073
  await purgeEventsAfterDestroy(new DeploymentEventsReader(stateBackend), stackName, stackTargetRegion, {
68902
69074
  purgeEvents: options.purgeEvents,
68903
69075
  runResult: destroyRunResult,
68904
- interrupted: runInterrupted()
69076
+ interrupted: stackInterrupted || stackCancelled
68905
69077
  }, logger);
68906
- if (runInterrupted()) break;
69078
+ if (runInterrupted()) {
69079
+ if (stackIndex < stackNames.length - 1) stoppedEarly = true;
69080
+ break;
69081
+ }
68907
69082
  }
68908
69083
  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.`);
68909
- if (runInterrupted()) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd destroy' to finish.`);
69084
+ if (interrupted || interruptWatch.interrupted() && stoppedEarly) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd destroy' to finish.`);
68910
69085
  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>'.`);
68911
69086
  } finally {
68912
69087
  unforwardSigterm();
@@ -73505,12 +73680,24 @@ async function stateDestroyCommand(stackArgs, options) {
73505
73680
  process.stdout.write(`\nWARNING: This destroys ${stackNames.length} stack(s) and removes their state records:\n`);
73506
73681
  for (const name of stackNames) process.stdout.write(` - ${name}\n`);
73507
73682
  process.stdout.write("\n");
73683
+ if (process.stdin.isTTY !== true) throw new CdkdError("The state destroy --all confirmation prompt cannot run in a non-interactive environment. Pass --yes / -y to confirm the batch, or run the command from a real terminal.", "NON_INTERACTIVE_CONFIRM");
73508
73684
  const rl = readline$1.createInterface({
73509
73685
  input: process.stdin,
73510
73686
  output: process.stdout
73511
73687
  });
73512
- const answer = await rl.question(`Destroy all ${stackNames.length} stack(s)? (y/N): `);
73513
- rl.close();
73688
+ let answer;
73689
+ try {
73690
+ answer = await rl.question(`Destroy all ${stackNames.length} stack(s)? (y/N): `, { signal: interruptWatch.signal });
73691
+ } catch (error) {
73692
+ if (interruptWatch.interrupted() && isPromptAbortError(error)) {
73693
+ process.stderr.write("\nDestroy cancelled — nothing was destroyed.\n");
73694
+ rl.close();
73695
+ process.exit(130);
73696
+ }
73697
+ throw error;
73698
+ } finally {
73699
+ rl.close();
73700
+ }
73514
73701
  const trimmed = answer.trim().toLowerCase();
73515
73702
  if (trimmed !== "y" && trimmed !== "yes") {
73516
73703
  logger.info("Destroy cancelled");
@@ -73522,7 +73709,31 @@ async function stateDestroyCommand(stackArgs, options) {
73522
73709
  let totalSkipped = 0;
73523
73710
  let interrupted = false;
73524
73711
  const runInterrupted = () => interrupted || interruptWatch.interrupted();
73525
- for (const stackName of stackNames) {
73712
+ let stoppedEarly = false;
73713
+ /**
73714
+ * Would any stack AFTER `stackIndex` actually have been destroyed?
73715
+ *
73716
+ * `stackIndex < stackNames.length - 1` answers "does an INDEX remain",
73717
+ * which is not the same question and over-claims: with
73718
+ * `--stack-region us-east-1`, a later name whose only state record lives in
73719
+ * eu-west-1 is warn-and-skipped by the `targets.length === 0` branch below
73720
+ * — it was never going to be destroyed, so a signal before it leaves
73721
+ * nothing undone and the run must still exit 0. Measured on
73722
+ * `state destroy --all --yes --stack-region us-east-1` with StackA in
73723
+ * us-east-1 and StackB only in eu-west-1: the index form exited 2 having
73724
+ * dispatched StackA, i.e. every target it had.
73725
+ *
73726
+ * The predicate mirrors that branch's own filter over `stateRefs`, the
73727
+ * same list the loop selects from. `destroy.ts` needs no counterpart: its
73728
+ * `candidateStacks` is already pre-filtered by the set of stack names that
73729
+ * have state, so an index there IS a target.
73730
+ */
73731
+ const targetRemainsAfter = (stackIndex) => stackNames.slice(stackIndex + 1).some((name) => {
73732
+ const laterRefs = stateRefs.filter((r) => r.stackName === name);
73733
+ if (!options.stackRegion) return laterRefs.length > 0;
73734
+ return laterRefs.some((r) => r.region === options.stackRegion || !r.region);
73735
+ });
73736
+ for (const [stackIndex, stackName] of stackNames.entries()) {
73526
73737
  const refs = stateRefs.filter((r) => r.stackName === stackName);
73527
73738
  let targets;
73528
73739
  if (options.stackRegion) {
@@ -73536,14 +73747,17 @@ async function stateDestroyCommand(stackArgs, options) {
73536
73747
  const regions = refs.map((r) => r.region ?? "(legacy)").join(", ");
73537
73748
  throw new Error(`Stack '${stackName}' has state in multiple regions: ${regions}. Use --stack-region <region> to pick one.`);
73538
73749
  }
73539
- for (const ref of targets) {
73750
+ for (const [refIndex, ref] of targets.entries()) {
73540
73751
  logger.info(`\nPreparing to destroy stack: ${stackName}${ref.region ? ` (${ref.region})` : ""}`);
73541
73752
  const stateResult = await setup.stateBackend.getState(stackName, ref.region ?? setup.region);
73542
73753
  if (!stateResult) {
73543
73754
  logger.warn(`No state found for stack ${stackName}${ref.region ? ` in ${ref.region}` : ""}, skipping`);
73544
73755
  continue;
73545
73756
  }
73546
- if (runInterrupted()) break;
73757
+ if (runInterrupted()) {
73758
+ stoppedEarly = true;
73759
+ break;
73760
+ }
73547
73761
  const result = await interruptWatch.runStack(() => withNestedStackContext({
73548
73762
  stateBackend: setup.stateBackend,
73549
73763
  lockManager: setup.lockManager,
@@ -73586,12 +73800,18 @@ async function stateDestroyCommand(stackArgs, options) {
73586
73800
  totalErrors += result.errorCount;
73587
73801
  totalSkipped += result.skippedCount;
73588
73802
  if (result.interrupted) interrupted = true;
73589
- if (runInterrupted()) break;
73803
+ if (runInterrupted()) {
73804
+ if (refIndex < targets.length - 1) stoppedEarly = true;
73805
+ break;
73806
+ }
73807
+ }
73808
+ if (runInterrupted()) {
73809
+ if (targetRemainsAfter(stackIndex)) stoppedEarly = true;
73810
+ break;
73590
73811
  }
73591
- if (runInterrupted()) break;
73592
73812
  }
73593
73813
  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.`);
73594
- if (runInterrupted()) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd state destroy' to finish.`);
73814
+ if (interrupted || interruptWatch.interrupted() && stoppedEarly) throw new PartialFailureError(`Destroy interrupted by Ctrl-C. State preserved — re-run 'cdkd state destroy' to finish.`);
73595
73815
  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>'.`);
73596
73816
  } finally {
73597
73817
  unforwardSigterm();
@@ -76213,6 +76433,7 @@ var AttributeFetcher = class {
76213
76433
  };
76214
76434
  }
76215
76435
  this.logger.warn(`--force: live fetch failed for '${orphanLogicalId}.${attribute}' (${reason}); falling back to cached value from state.attributes.`);
76436
+ if (carriesDynamicReference(cached)) this.logger.warn(`--force: the cached value for '${orphanLogicalId}.${attribute}' is an UNRESOLVED dynamic reference (a nested stack's redacted output, issue #2055). It is being written into the referring resource's state VERBATIM — cdkd cannot re-resolve it from here. Re-run without --force once the live attribute is readable, or fix the referring property by hand.`);
76216
76437
  const cacheKey = `${orphanLogicalId}\0${attribute}`;
76217
76438
  this.cache.set(cacheKey, cached);
76218
76439
  return {
@@ -87873,4 +88094,4 @@ function buildProgram() {
87873
88094
 
87874
88095
  //#endregion
87875
88096
  export { buildProgram };
87876
- //# sourceMappingURL=program-CrJMVg4e.js.map
88097
+ //# sourceMappingURL=program-alOnKpLV.js.map