@go-to-k/cdkd 0.284.38 → 0.284.39

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 startInterruptWatch, $t as buildAssetRedirectMap, A as renderStatefulReason, An as resolveStateBucketWithDefaultAndSource, At as redactSecretsForState, B as exportAliasCollisionScrubWarning, Bn as PARTITION_TABLE, Bt as DiffCalculator, C as isFinalSnapshotError, Cn as getDefaultStateBucketName, Cr as isRetryableTransientError, Ct as TEMPLATE_SOURCED_RULES, D as extractDeploymentEventError, Dn as resolveCaptureObservedState, Dt as isSingleDynamicReferenceToken, E as makeCanonicalizePropertiesFn, En as resolveAutoAssetStorage, Et as errorCauseChain, F as green, Fn as CFN_TEMPLATE_URL_LIMIT, Ft as s3BucketDomainName, G as collectInlinePolicyNamesManagedBySiblings, Gt as TemplateParser, H as secretBearingStateKeyWarning, Hn as derivePartitionAndUrlSuffix, Ht as describeTypeWithThrottleRetry, I as red, In as MIGRATE_TMP_PREFIX, It as s3BucketDualStackDomainName, J as findActionableSilentDrops, Jn as getAwsClients, Jt as rebuildClientForBucketRegion, K as clearOnUpdateRemoval, Kn as resolveBucketRegion, Kt as LockManager, L as yellow, Ln as findLargeInlineResources, Lt as s3BucketRegionalDomainName, M as bold, Mn as stateBucketExistenceConfirmed, Mt as classifyReplaySecretRegion, N as cyan, Nn as warnDeprecatedNoPrefixCliFlag, Nt as producerRegionsFromState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as resolveSkipPrefix, Ot as maskSecretsInError, P as gray, Pn as CFN_TEMPLATE_BODY_LIMIT, Pt as s3BucketArn, Q as isInterruptedWaitError, Qn as CdkdError, Qt as WorkGraph, R as collectDeclaredOutputNames, Rn as uploadCfnTemplate, Rt as s3BucketWebsiteUrl, S as createPreDeleteFinalSnapshot, Sn as synthesisStatusMessage, Sr as isMarkedNonRetryable, St as STATE_SOURCED_READBACK_RULES, T as unsupportedFinalSnapshotError, Tn as resolveApp, Tr as markNonRetryable, Tt as dynamicReferenceTokens, U as stateKeySecretExposure, Un as AssemblyReader, Ut as withRetry, V as isExportAliasCollision, Vn as canonicalizeRegion, Vt as INTRINSIC_KEYS, W as IAMRoleProvider, Wn as processStackMessages, Wt as DagBuilder, X as beginCommandInterruptScope, Xn as setAwsClients, Xt as AssetPublisher, Y as findSilentDropProperties, Yn as resetAwsClients, Yt as shouldRetainResource, Z as endCommandInterruptScope, Zt as stringifyValue, _ as computeImplicitDeleteEdges, _n as runDockerForeground, _t as replayWarn, a as DeploymentEventsStore, an as AssetModeResolver, ar as LocalMigrateError, at as carriesDynamicReference, b as buildFinalSnapshotIdentifier, bn as getDockerImageBySourceHash, br as normalizeAwsError, bt as requireConfigString, c as replayFailedOperations, cn as getBootstrapMarkerKey, cr as MissingCdkCliError, ct as refStateLookupFromResource, d as updatePartialReason, dn as validateAssetBucketName, dr as ProvisioningError, dt as resolveExplicitPhysicalId, en as createAssetRedirectResolver, er as CrossAccountSecretRefusalError, et as CloudControlProvider, f as UNSPECIFIED_SKIP_REASON, fn as validateContainerRepoName, fr as ResourceTimeoutError, ft as assertRegionMatch, g as IMPLICIT_DELETE_DEPENDENCIES, gn as getDockerCmd, gt as readConfigString, h as maskingRetryLogger, hn as formatDockerLoginError, hr as StackTerminationProtectionError, ht as configStringRefusal, i as DeploymentEventsReader, in as stripControlChars, ir as LocalInvokeBuildError$1, it as IntrinsicFunctionResolver, j as formatResourceLine, jn as resolveUseCdkBootstrapAssets, jt as scrubResourceRecord, k as isStatefulRecreateTargetSync, kn as resolveStateBucketWithDefault, kt as maskSecretsInText, l as replayRollback, ln as parseBootstrapMarker, lr as NestedStackChildDirectDestroyError, lt as WAFv2WebACLProvider, m as withResourceDeadline, mn as buildDockerImage, mr as StackHasActiveImportsError, mt as configBooleanRefusal, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as rewriteTemplateAssetReferences, nr as DeployCancelledError, nt as disableInstanceApiTermination, o as planFailedOps, on as BOOTSTRAP_MARKER_PREFIX, or as LocalStartServiceError, ot as cfnRefValueFromPhysicalId, p as deleteSkipReason, pn as buildDenyExternalAccessPolicy, pr as ResourceUpdateNotSupportedError, pt as coerceCfnBoolean, q as ProviderRegistry, qn as AwsClients, qt as S3StateBackend, r as DeployEngine, rn as escapeRegExp, rr as DynamicReferenceRegionAmbiguousError, rt as isTerminationProtectionPropagationError, s as planRollback, sn as ensureAssetStorage, st as getAccountInfo, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as loadPublishableAssetManifest, tt as slowCcOperationTimeoutMs, u as updatePartialMessage, un as readBootstrapMarkerBody, ur as PartialFailureError, ut as normalizeAwsTagsToCfn, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as runDockerStreaming, vt as requireConfigArray, w as refusesFinalSnapshot, wn as getLegacyStateBucketName, wr as isThrottlingError, wt as createSecretMasker, x as ccRoutedFinalSnapshotError, xn as Synthesizer, xr as withErrorHandling, xt as STATE_SOURCED_CROSS_GENERATION_RULES, y as PRE_DELETE_SNAPSHOT_TYPES, yn as AssetManifestLoader, yt as requireConfigObject, z as collectPublishedOutputNames, zn as expectedOwnerParam, zt as applyRoleArnIfSet } from "./deploy-engine-DyxASW-O.js";
2
+ import { $ as startInterruptWatch, $n as CdkdError, $t as WorkGraph, A as renderStatefulReason, An as resolveStateBucketWithDefault, At as redactSecretsForState, B as exportAliasCollisionScrubWarning, Bn as expectedOwnerParam, Bt as DiffCalculator, C as isFinalSnapshotError, Cn as synthesisStatusMessage, Cr as isMarkedNonRetryable, Ct as TEMPLATE_SOURCED_RULES, D as extractDeploymentEventError, Dn as resolveAutoAssetStorage, Dt as isSingleDynamicReferenceToken, E as makeCanonicalizePropertiesFn, En as resolveApp, Er as markNonRetryable, Et as errorCauseChain, F as green, Fn as CFN_TEMPLATE_BODY_LIMIT, Ft as s3BucketDomainName, G as collectInlinePolicyNamesManagedBySiblings, Gn as processStackMessages, Gt as TemplateParser, H as secretBearingStateKeyWarning, Hn as canonicalizeRegion, Ht as describeTypeWithThrottleRetry, I as red, In as CFN_TEMPLATE_URL_LIMIT, It as s3BucketDualStackDomainName, J as findActionableSilentDrops, Jn as AwsClients, Jt as S3StateBackend, K as clearOnUpdateRemoval, Kt as LockManager, L as yellow, Ln as MIGRATE_TMP_PREFIX, Lt as s3BucketRegionalDomainName, M as bold, Mn as resolveUseCdkBootstrapAssets, Mt as classifyReplaySecretRegion, N as cyan, Nn as stateBucketExistenceConfirmed, Nt as producerRegionsFromState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as resolveCaptureObservedState, Ot as maskSecretsInError, P as gray, Pn as warnDeprecatedNoPrefixCliFlag, Pt as s3BucketArn, Q as isInterruptedWaitError, Qt as stringifyValue, R as collectDeclaredOutputNames, Rn as findLargeInlineResources, Rt as s3BucketWebsiteUrl, S as createPreDeleteFinalSnapshot, Sn as Synthesizer, Sr as withErrorHandling, St as STATE_SOURCED_READBACK_RULES, T as unsupportedFinalSnapshotError, Tn as getLegacyStateBucketName, Tr as isThrottlingError, Tt as dynamicReferenceTokens, U as stateKeySecretExposure, Un as derivePartitionAndUrlSuffix, Ut as withRetry, V as isExportAliasCollision, Vn as PARTITION_TABLE, Vt as INTRINSIC_KEYS, W as IAMRoleProvider, Wn as AssemblyReader, Wt as DagBuilder, X as beginCommandInterruptScope, Xn as resetAwsClients, Xt as shouldRetainResource, Y as findSilentDropProperties, Yn as getAwsClients, Yt as rebuildClientForBucketRegion, Z as endCommandInterruptScope, Zn as setAwsClients, Zt as AssetPublisher, _ as computeImplicitDeleteEdges, _n as getDockerCmd, _t as replayWarn, a as DeploymentEventsStore, an as stripControlChars, ar as LocalInvokeBuildError$1, at as carriesDynamicReference, b as buildFinalSnapshotIdentifier, bn as AssetManifestLoader, bt as requireConfigString, c as replayFailedOperations, cn as ensureAssetStorage, ct as refStateLookupFromResource, d as updatePartialReason, dn as readBootstrapMarkerBody, dr as PartialFailureError, dt as resolveExplicitPhysicalId, en as buildAssetRedirectMap, et as CloudControlProvider, f as UNSPECIFIED_SKIP_REASON, fn as validateAssetBucketName, fr as ProvisioningError, ft as assertRegionMatch, g as IMPLICIT_DELETE_DEPENDENCIES, gn as formatDockerLoginError, gr as StackTerminationProtectionError, gt as readConfigString, h as maskingRetryLogger, hn as buildDockerImage, hr as StackHasActiveImportsError, ht as configStringRefusal, i as DeploymentEventsReader, in as escapeRegExp, ir as DynamicReferenceRegionAmbiguousError, it as IntrinsicFunctionResolver, j as formatResourceLine, jn as resolveStateBucketWithDefaultAndSource, jt as scrubResourceRecord, k as isStatefulRecreateTargetSync, kn as resolveSkipPrefix, kt as maskSecretsInText, l as replayRollback, ln as getBootstrapMarkerKey, lr as MissingCdkCliError, lt as WAFv2WebACLProvider, m as withResourceDeadline, mn as buildDenyExternalAccessPolicy, mr as ResourceUpdateNotSupportedError, mt as configBooleanRefusal, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as loadPublishableAssetManifest, nt as disableInstanceApiTermination, o as planFailedOps, on as AssetModeResolver, or as LocalMigrateError, ot as cfnRefValueFromPhysicalId, p as deleteSkipReason, pn as validateContainerRepoName, pr as ResourceTimeoutError, pt as coerceCfnBoolean, q as ProviderRegistry, qn as resolveBucketRegion, qt as displaySafe, r as DeployEngine, rn as rewriteTemplateAssetReferences, rr as DeployCancelledError, rt as isTerminationProtectionPropagationError, s as planRollback, sn as BOOTSTRAP_MARKER_PREFIX, sr as LocalStartServiceError, st as getAccountInfo, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as createAssetRedirectResolver, tr as CrossAccountSecretRefusalError, tt as slowCcOperationTimeoutMs, u as updatePartialMessage, un as parseBootstrapMarker, ur as NestedStackChildDirectDestroyError, ut as normalizeAwsTagsToCfn, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as runDockerForeground, vt as requireConfigArray, w as refusesFinalSnapshot, wn as getDefaultStateBucketName, wr as isRetryableTransientError, wt as createSecretMasker, x as ccRoutedFinalSnapshotError, xn as getDockerImageBySourceHash, xr as normalizeAwsError, xt as STATE_SOURCED_CROSS_GENERATION_RULES, y as PRE_DELETE_SNAPSHOT_TYPES, yn as runDockerStreaming, yt as requireConfigObject, z as collectPublishedOutputNames, zn as uploadCfnTemplate, zt as applyRoleArnIfSet } from "./deploy-engine-rKBFVifY.js";
3
3
  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";
4
- import { t as ASGProvider } from "./asg-provider-kqBWElNc.js";
4
+ import { t as ASGProvider } from "./asg-provider-Db6lnsaA.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";
@@ -77,6 +77,23 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
77
77
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
78
78
  import { WebSocketServer } from "ws";
79
79
 
80
+ //#region src/state/state-prefix.ts
81
+ /**
82
+ * The default S3 key prefix for cdkd state.
83
+ *
84
+ * Homed in the STATE layer rather than in `src/cli/commands/state-file-keys.ts`
85
+ * (which re-exports it, so its four existing importers are unchanged) because
86
+ * `src/state/lock-contention-message.ts` needs it to decide whether a recovery
87
+ * hint should spell `--state-prefix` at all, and a `src/state/**` module
88
+ * importing from `src/cli/commands/**` inverts the layering — the CLI sits
89
+ * ABOVE the state layer in the 7-layer architecture, not below it.
90
+ *
91
+ * Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
92
+ * whole-bucket listings deliberately do not scope to it.
93
+ */
94
+ const DEFAULT_STATE_PREFIX = "cdkd";
95
+
96
+ //#endregion
80
97
  //#region src/cli/commands/state-file-keys.ts
81
98
  /**
82
99
  * Shared helpers for enumerating and describing cdkd state-bucket keys.
@@ -94,20 +111,6 @@ import { WebSocketServer } from "ws";
94
111
  */
95
112
  const STATE_FILE_SUFFIX = "/state.json";
96
113
  /**
97
- * The prefix `cdkd bootstrap` writes under, and the one `gc` /
98
- * `bootstrap --destroy` configure their own state backend with.
99
- *
100
- * Shared here rather than spelled per command for the same reason this module
101
- * exists at all: `bootstrap-destroy.ts` and `gc.ts` each held their own copy,
102
- * and it is now also the `knownPrefix` argument to {@link describeStateKey},
103
- * where a copy that drifted from the backend's actual prefix would silently
104
- * send every key back to the shape heuristic instead of the exact depth rule.
105
- *
106
- * Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
107
- * the whole-bucket listings deliberately do not scope to it.
108
- */
109
- const DEFAULT_STATE_PREFIX = "cdkd";
110
- /**
111
114
  * Segments left under a known prefix for the legacy `{prefix}/{stack}` layout.
112
115
  *
113
116
  * The twin of `S3StateBackend`'s `LEGACY_KEY_DEPTH`, which counts the
@@ -58162,6 +58165,104 @@ var ECRProvider = class {
58162
58165
  }
58163
58166
  };
58164
58167
 
58168
+ //#endregion
58169
+ //#region src/state/lock-contention-message.ts
58170
+ /**
58171
+ * The message every fail-fast lock-contention site raises.
58172
+ *
58173
+ * Issue #2161 made six commands throw on `acquireLock` returning `false`
58174
+ * instead of running under a foreign lock. Issue #2170 is the follow-up: the
58175
+ * message those sites raise asked the user to decide "is another process
58176
+ * active?" while printing none of the evidence that would answer it, and the
58177
+ * recovery command it suggested could resolve against the wrong AWS account.
58178
+ *
58179
+ * Both matter more than they look, because of WHICH locks reach this message.
58180
+ * `LockManager.acquireLock` reaps an EXPIRED foreign lock and retries, so a
58181
+ * `false` return means the lock is LIVE — in practice a running `cdkd deploy`.
58182
+ * The user who follows a bare `cdkd force-unlock <stack>` suggestion therefore
58183
+ * deletes a live owner's lock and reproduces issue #2161's harm by hand, which
58184
+ * is the outcome #2161 exists to prevent.
58185
+ *
58186
+ * Centralising the text here also settles the third finding: the nine sites
58187
+ * had drifted to three spellings (`for stack` / `for nested stack` /
58188
+ * `for nested-stack child`), so a user grepping CI logs for one of them found
58189
+ * two of three. `subject` now varies only the noun.
58190
+ */
58191
+ /**
58192
+ * Stand-in for a value with nothing renderable left after sanitization. Named
58193
+ * rather than inlined so the message and the command-suppression branch cannot
58194
+ * disagree about what "unrenderable" looks like.
58195
+ */
58196
+ const UNRENDERABLE = "<unrenderable>";
58197
+ /** Render `expiresIn` without implying more precision than a clock skew allows. */
58198
+ function formatRemaining(ms) {
58199
+ if (!Number.isFinite(ms)) return "at an unknown time";
58200
+ if (ms <= 0) return "already expired";
58201
+ const minutes = Math.round(ms / 6e4);
58202
+ if (minutes < 1) return "in under a minute";
58203
+ return `in ~${minutes}m`;
58204
+ }
58205
+ function shellQuote(value) {
58206
+ return /^[A-Za-z0-9._/@:+-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
58207
+ }
58208
+ /**
58209
+ * The `cdkd force-unlock ...` line, carrying every flag that decides which
58210
+ * lock object it resolves to.
58211
+ */
58212
+ function buildForceUnlockCommand(stackName, region, recovery) {
58213
+ const safeStack = displaySafe(stackName, { asciiOnly: true });
58214
+ const stackIsExact = safeStack === stackName;
58215
+ const safeRegion = region === void 0 ? void 0 : displaySafe(region, { asciiOnly: true });
58216
+ if (!safeStack || safeRegion === "" || !stackIsExact || !(region === void 0 || safeRegion === region)) return "";
58217
+ const parts = [safeRegion === void 0 ? `cdkd force-unlock ${shellQuote(safeStack)}` : `cdkd force-unlock ${shellQuote(safeStack)} --stack-region ${shellQuote(safeRegion)}`];
58218
+ if (recovery?.profile) parts.push(`--profile ${shellQuote(recovery.profile)}`);
58219
+ if (recovery?.stateBucket) parts.push(`--state-bucket ${shellQuote(recovery.stateBucket)}`);
58220
+ if (recovery?.statePrefix && recovery.statePrefix !== "cdkd") parts.push(`--state-prefix ${shellQuote(recovery.statePrefix)}`);
58221
+ return parts.join(" ");
58222
+ }
58223
+ /**
58224
+ * The force-quit banner's recovery sentence.
58225
+ *
58226
+ * Exported so the two `destroy-runner.ts` banners do not each decide what to
58227
+ * say when {@link buildForceUnlockCommand} suppresses — a banner ending in a
58228
+ * bare `run: ` is the shape the review found, and two copies of the branch is
58229
+ * how the next one drifts. Returns a leading-space clause so the caller can
58230
+ * concatenate it unconditionally.
58231
+ */
58232
+ function forceQuitRecoveryClause(stackName, region, recovery) {
58233
+ const command = buildForceUnlockCommand(stackName, region, recovery);
58234
+ return command ? ` If the next run reports a lock, run: ${command}` : " Inspect the lock object directly: the name or region recorded for this stack cannot be reproduced safely on a command line, so any command shown here would address a different lock.";
58235
+ }
58236
+ /**
58237
+ * Build the contention message, reading the holder's identity best-effort.
58238
+ *
58239
+ * The `getLockInfo` read is one GetObject and is deliberately NOT allowed to
58240
+ * fail the command: the caller is already on its way to throwing, and turning
58241
+ * a contention refusal into an S3 error would lose the reason. A failed or
58242
+ * absent read degrades to the evidence-free wording rather than to a crash.
58243
+ */
58244
+ async function buildLockContentionMessage(args) {
58245
+ const { lockManager, stackName, region, subject = "stack", recovery, heldClause, suffix } = args;
58246
+ let held = heldClause ?? "another cdkd process holds it";
58247
+ let sawHolder = false;
58248
+ try {
58249
+ const info = await lockManager.getLockInfo(stackName, region);
58250
+ if (info) {
58251
+ const operation = info.operation ? `, operation: ${displaySafe(info.operation)}` : "";
58252
+ const expires = formatRemaining(info.expiresAt - Date.now());
58253
+ const owner = displaySafe(info.owner);
58254
+ const holder = owner ? `held by ${owner}${operation}` : `held by an unnamed holder`;
58255
+ held = `${heldClause ? `${heldClause} — ` : ""}${holder}, expires ${expires}`;
58256
+ if (owner) sawHolder = true;
58257
+ }
58258
+ } catch {}
58259
+ const advice = sawHolder ? `That process is still running — wait for it to finish. Only if you are certain it is gone` : `Wait for it to finish, or if you are certain no other process is active`;
58260
+ const recoveryCommand = buildForceUnlockCommand(stackName, region, recovery);
58261
+ const head = `Could not acquire lock for ${subject} '${displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>"}' (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"}) — ${held}.` + (suffix ? ` ${suffix}` : "");
58262
+ if (!recoveryCommand) return `${head} ${advice}. No recovery command can be shown: the name or region recorded for this lock contains characters that cannot be reproduced safely on a command line, so any command shown here would address a different lock — inspect the lock object directly.`;
58263
+ return `${head} ${advice}, run: ${recoveryCommand}`;
58264
+ }
58265
+
58165
58266
  //#endregion
58166
58267
  //#region src/cli/commands/destroy-runner.ts
58167
58268
  /**
@@ -58298,9 +58399,64 @@ async function runDestroyForStack(stackName, state, ctx) {
58298
58399
  const regionForState = state.region ?? ctx.baseRegion;
58299
58400
  if (resourceCount === 0) {
58300
58401
  logger.info(`Stack ${stackName} has no resources, cleaning up state...`);
58301
- await ctx.stateBackend.deleteState(stackName, regionForState);
58302
- logger.info(`${green("✓")} State deleted`);
58402
+ let emptyLockHeld = false;
58403
+ let emptyInterrupted = false;
58404
+ const emptySigintHandler = () => {
58405
+ if (emptyInterrupted) {
58406
+ if (emptyLockHeld) {
58407
+ ctx.lockManager.releaseLock(stackName, regionForState).catch(() => {});
58408
+ process.stderr.write(`\nForce-quit: stack lock may not be released.${forceQuitRecoveryClause(stackName, regionForState, {
58409
+ profile: ctx.profile,
58410
+ stateBucket: ctx.stateBucket,
58411
+ statePrefix: ctx.statePrefix
58412
+ })}\n`);
58413
+ }
58414
+ process.exit(130);
58415
+ }
58416
+ emptyInterrupted = true;
58417
+ try {
58418
+ process.stderr.write(`\nInterrupt received - finishing the state cleanup for ${stackName} (press Ctrl-C again to force-quit)\n`);
58419
+ } catch {}
58420
+ };
58421
+ process.setMaxListeners(Math.max(process.getMaxListeners(), 100));
58422
+ process.on("SIGINT", emptySigintHandler);
58423
+ let emptyAcquired;
58424
+ try {
58425
+ emptyAcquired = await ctx.lockManager.acquireLock(stackName, regionForState, void 0, "destroy");
58426
+ } catch (acquireErr) {
58427
+ process.removeListener("SIGINT", emptySigintHandler);
58428
+ throw acquireErr;
58429
+ }
58430
+ if (!emptyAcquired) {
58431
+ process.removeListener("SIGINT", emptySigintHandler);
58432
+ throw new Error(await buildLockContentionMessage({
58433
+ lockManager: ctx.lockManager,
58434
+ stackName,
58435
+ region: regionForState,
58436
+ recovery: {
58437
+ profile: ctx.profile,
58438
+ stateBucket: ctx.stateBucket,
58439
+ statePrefix: ctx.statePrefix
58440
+ }
58441
+ }));
58442
+ }
58443
+ emptyLockHeld = true;
58444
+ try {
58445
+ const recheck = await ctx.stateBackend.getState(stackName, regionForState);
58446
+ if (!(!recheck || Object.keys(recheck.state.resources).length === 0)) throw new Error(`Stack '${stackName}' (${regionForState}) was empty when this run started but now has ${Object.keys(recheck.state.resources).length} resource(s) — another cdkd process deployed into it. Re-run the destroy to act on the current state.`);
58447
+ await ctx.stateBackend.deleteState(stackName, regionForState);
58448
+ logger.info(`${green("✓")} State deleted`);
58449
+ } finally {
58450
+ try {
58451
+ await ctx.lockManager.releaseLock(stackName, regionForState);
58452
+ } catch (releaseErr) {
58453
+ logger.warn(`Failed to release lock after empty-state cleanup: ${releaseErr instanceof Error ? releaseErr.message : String(releaseErr)}`);
58454
+ } finally {
58455
+ process.removeListener("SIGINT", emptySigintHandler);
58456
+ }
58457
+ }
58303
58458
  result.skippedEmpty = true;
58459
+ result.interrupted ||= emptyInterrupted;
58304
58460
  return result;
58305
58461
  }
58306
58462
  const needsStrongRefCheck = !!(state.outputs && Object.keys(state.outputs).length > 0);
@@ -58377,7 +58533,11 @@ async function runDestroyForStack(stackName, state, ctx) {
58377
58533
  if (draining) {
58378
58534
  if (lockHeld) {
58379
58535
  ctx.lockManager.releaseLock(stackName, regionForState).catch(() => {});
58380
- process.stderr.write(`\nForce-quit: stack lock may not be released. If the next run reports a lock, run: cdkd force-unlock ${stackName}\n`);
58536
+ process.stderr.write(`\nForce-quit: stack lock may not be released.${forceQuitRecoveryClause(stackName, regionForState, {
58537
+ profile: ctx.profile,
58538
+ stateBucket: ctx.stateBucket,
58539
+ statePrefix: ctx.statePrefix
58540
+ })}\n`);
58381
58541
  }
58382
58542
  process.exit(130);
58383
58543
  }
@@ -58390,7 +58550,16 @@ async function runDestroyForStack(stackName, state, ctx) {
58390
58550
  process.on("SIGINT", sigintHandler);
58391
58551
  try {
58392
58552
  logger.info(`\nAcquiring lock for stack ${stackName}...`);
58393
- if (!await ctx.lockManager.acquireLock(stackName, regionForState, void 0, "destroy")) throw new Error(`Could not acquire lock for stack '${stackName}' (${regionForState}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${stackName} --stack-region ${regionForState}' if you are certain no other process is active.`);
58553
+ if (!await ctx.lockManager.acquireLock(stackName, regionForState, void 0, "destroy")) throw new Error(await buildLockContentionMessage({
58554
+ lockManager: ctx.lockManager,
58555
+ stackName,
58556
+ region: regionForState,
58557
+ recovery: {
58558
+ profile: ctx.profile,
58559
+ stateBucket: ctx.stateBucket,
58560
+ statePrefix: ctx.statePrefix
58561
+ }
58562
+ }));
58394
58563
  } catch (error) {
58395
58564
  restoreBaseRegionAndClients();
58396
58565
  process.removeListener("SIGINT", sigintHandler);
@@ -58984,6 +59153,7 @@ var NestedStackProvider = class {
58984
59153
  skipConfirmation: true,
58985
59154
  ...ctx.exportIndexStore && { exportIndexStore: ctx.exportIndexStore },
58986
59155
  ...ctx.destroyOptions?.profile && { profile: ctx.destroyOptions.profile },
59156
+ ...ctx.destroyOptions?.statePrefix !== void 0 && { statePrefix: ctx.destroyOptions.statePrefix },
58987
59157
  ...deleteContext?.removeProtection === true && { removeProtection: true },
58988
59158
  ...(ctx.destroyOptions?.skipFinalSnapshot === true || ctx.options?.skipFinalSnapshot === true) && { skipFinalSnapshot: true },
58989
59159
  ...ctx.destroyOptions?.resourceWarnAfterMs !== void 0 && { resourceWarnAfterMs: ctx.destroyOptions.resourceWarnAfterMs },
@@ -59905,7 +60075,7 @@ async function deployCommand(stacks, options) {
59905
60075
  let deployInterrupted = false;
59906
60076
  const topLevelSigintHandler = () => {
59907
60077
  if (deployInterrupted) {
59908
- process.stderr.write("\nForce-quit: stack locks may not be released. If the next run reports a lock, run: cdkd force-unlock <stackName>\n");
60078
+ process.stderr.write("\nForce-quit: stack locks may not be released. If the next run reports a lock, run this for EACH stack it names (the region-qualified form — see the message that run prints): cdkd force-unlock <stackName> --stack-region <region>\n");
59909
60079
  process.exit(130);
59910
60080
  }
59911
60081
  process.stderr.write("\nInterrupted — waiting for in-progress operations to complete...\n");
@@ -60172,6 +60342,10 @@ async function deployCommand(stacks, options) {
60172
60342
  dagBuilder,
60173
60343
  diffCalculator,
60174
60344
  options: deployEngineOptions,
60345
+ destroyOptions: {
60346
+ statePrefix: options.statePrefix,
60347
+ ...options.profile && { profile: options.profile }
60348
+ },
60175
60349
  ...assetRedirect && { assetRedirect }
60176
60350
  }, () => stackDeployEngine.deploy(stackInfo.stackName, stackInfo.template));
60177
60351
  logger.info(`\n${bold("Deployment Summary:")}`);
@@ -66142,6 +66316,11 @@ function setAtPath(target, path, value) {
66142
66316
  */
66143
66317
  async function runAccept(reports, stateBackend, stateConfig, awsClients, options) {
66144
66318
  const logger = getLogger();
66319
+ const lockRecovery = {
66320
+ profile: options.profile,
66321
+ stateBucket: stateConfig.bucket,
66322
+ statePrefix: stateConfig.prefix
66323
+ };
66145
66324
  printAcceptPlan(reports);
66146
66325
  if (options.dryRun) {
66147
66326
  logger.info("--dry-run: state will NOT be written. Re-run without --dry-run to apply.");
@@ -66164,7 +66343,12 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
66164
66343
  skipped: () => []
66165
66344
  }));
66166
66345
  if (driftedOutcomes.length === 0) continue;
66167
- if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-accept")) throw new Error(`Could not acquire lock for stack '${report.stackName}' (${report.region}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${report.stackName} --stack-region ${report.region}' if you are certain no other process is active.`);
66346
+ if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-accept")) throw new Error(await buildLockContentionMessage({
66347
+ lockManager,
66348
+ stackName: report.stackName,
66349
+ region: report.region,
66350
+ recovery: lockRecovery
66351
+ }));
66168
66352
  try {
66169
66353
  const resources = { ...report.state.resources };
66170
66354
  for (const outcome of driftedOutcomes) {
@@ -66761,6 +66945,11 @@ function deepEqualUnordered(a, b) {
66761
66945
  */
66762
66946
  async function runRevert(reports, providerRegistry, stateBackend, stateConfig, awsClients, options) {
66763
66947
  const logger = getLogger();
66948
+ const lockRecovery = {
66949
+ profile: options.profile,
66950
+ stateBucket: stateConfig.bucket,
66951
+ statePrefix: stateConfig.prefix
66952
+ };
66764
66953
  printRevertPlan(reports);
66765
66954
  if (options.dryRun) {
66766
66955
  logger.info("--dry-run: AWS will NOT be modified. Re-run without --dry-run to apply.");
@@ -66788,11 +66977,16 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
66788
66977
  skipped: () => []
66789
66978
  }));
66790
66979
  if (driftedOutcomes.length === 0) continue;
66791
- if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-revert")) throw new Error(`Could not acquire lock for stack '${report.stackName}' (${report.region}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${report.stackName} --stack-region ${report.region}' if you are certain no other process is active.`);
66980
+ if (!await lockManager.acquireLock(report.stackName, report.region, owner, "drift-revert")) throw new Error(await buildLockContentionMessage({
66981
+ lockManager,
66982
+ stackName: report.stackName,
66983
+ region: report.region,
66984
+ recovery: lockRecovery
66985
+ }));
66792
66986
  const narrowedByLogicalId = /* @__PURE__ */ new Map();
66793
- const revertSecretResolvers = new DriftSecretResolvers(report.region);
66794
- const revertProducerRegions = producerRegionsFromState(report.state);
66795
66987
  try {
66988
+ const revertSecretResolvers = new DriftSecretResolvers(report.region);
66989
+ const revertProducerRegions = producerRegionsFromState(report.state);
66796
66990
  await runWithConcurrency(driftedOutcomes.map((outcome) => async () => {
66797
66991
  const stateResource = report.state.resources[outcome.logicalId];
66798
66992
  if (!stateResource) {
@@ -67415,6 +67609,7 @@ async function destroyCommand(stackArgs, options) {
67415
67609
  exportIndexStore,
67416
67610
  destroyOptions: {
67417
67611
  ...options.profile && { profile: options.profile },
67612
+ statePrefix: options.statePrefix,
67418
67613
  ...options.removeProtection === true && { removeProtection: true },
67419
67614
  ...options.skipFinalSnapshot === true && { skipFinalSnapshot: true },
67420
67615
  ...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
@@ -67430,6 +67625,7 @@ async function destroyCommand(stackArgs, options) {
67430
67625
  baseRegion: region,
67431
67626
  ...options.profile && { profile: options.profile },
67432
67627
  stateBucket,
67628
+ statePrefix: options.statePrefix,
67433
67629
  skipConfirmation: options.yes || options.force,
67434
67630
  removeProtection: options.removeProtection === true,
67435
67631
  skipFinalSnapshot: options.skipFinalSnapshot === true,
@@ -69003,6 +69199,11 @@ async function exportCommand(stackArg, options) {
69003
69199
  bucket: stateBucket,
69004
69200
  prefix: options.statePrefix
69005
69201
  };
69202
+ const lockRecovery = {
69203
+ profile: options.profile,
69204
+ stateBucket,
69205
+ statePrefix: options.statePrefix
69206
+ };
69006
69207
  const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
69007
69208
  ...options.region && { region: options.region },
69008
69209
  ...options.profile && { profile: options.profile }
@@ -69078,7 +69279,12 @@ async function exportCommand(stackArg, options) {
69078
69279
  }
69079
69280
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
69080
69281
  if (!options.dryRun) {
69081
- if (!await lockManager.acquireLock(resolvedStackName, targetRegion, owner, "export")) throw new Error(`Could not acquire lock for stack '${resolvedStackName}' (${targetRegion}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${resolvedStackName} --stack-region ${targetRegion}' if you are certain no other process is active.`);
69282
+ if (!await lockManager.acquireLock(resolvedStackName, targetRegion, owner, "export")) throw new Error(await buildLockContentionMessage({
69283
+ lockManager,
69284
+ stackName: resolvedStackName,
69285
+ region: targetRegion,
69286
+ recovery: lockRecovery
69287
+ }));
69082
69288
  }
69083
69289
  try {
69084
69290
  if (nestedStackRows.length > 0) {
@@ -69098,6 +69304,7 @@ async function exportCommand(stackArg, options) {
69098
69304
  if (missingNested.length > 0) throw new Error(`Template requires parameter(s) without defaults: ${missingNested.join(", ")}. Pass each one as --parameter Key=Value (or set a Default in the CDK code).`);
69099
69305
  const childOverrides = parseCfnChildStackNameOverrides(options.cfnChildStackName);
69100
69306
  if ((await runPerStackImportLoop({
69307
+ lockRecovery,
69101
69308
  rootStackName: resolvedStackName,
69102
69309
  rootRegion: targetRegion,
69103
69310
  rootStackInfoNestedTemplates: rootNestedTemplatePaths,
@@ -70994,7 +71201,15 @@ async function runPerStackImportLoop(args) {
70994
71201
  try {
70995
71202
  await deps.lockManager.acquireLockWithRetry(node.stackName, node.region, deps.lockOwner, "export", CHILD_LOCK_RETRY_MAX_ATTEMPTS, CHILD_LOCK_RETRY_DELAY_MS);
70996
71203
  } catch (err) {
70997
- throw new Error(`Could not acquire lock for nested-stack child '${node.stackName}' (${node.region}) — another cdkd process held it through the retry window. Wait for it to finish, or run 'cdkd force-unlock ${node.stackName} --stack-region ${node.region}' if you are certain no other process is active. No CloudFormation changeset has been submitted; cdkd state is unchanged.`, { cause: err instanceof Error ? err : void 0 });
71204
+ throw new Error(await buildLockContentionMessage({
71205
+ lockManager: deps.lockManager,
71206
+ stackName: node.stackName,
71207
+ region: node.region,
71208
+ subject: "nested-stack child",
71209
+ heldClause: "another cdkd process held it through the retry window",
71210
+ recovery: args.lockRecovery,
71211
+ suffix: "No CloudFormation changeset has been submitted; cdkd state is unchanged."
71212
+ }), { cause: err instanceof Error ? err : void 0 });
70998
71213
  }
70999
71214
  acquiredChildLocks.push({
71000
71215
  stackName: node.stackName,
@@ -71930,8 +72145,12 @@ async function stateOrphanCommand(stackArgs, options) {
71930
72145
  }
71931
72146
  if (!options.force) {
71932
72147
  for (const target of targets) if (await setup.lockManager.isLocked(stackName, target.region)) {
71933
- const where = target.region ?? "(legacy)";
71934
- throw new Error(`Stack '${stackName}' (${where}) is locked. Run 'cdkd force-unlock ${stackName}${target.region ? ` --stack-region ${target.region}` : ""}' first, or pass --force to remove anyway.`);
72148
+ const where = target.region ? displaySafe(target.region, { asciiOnly: true }) || "<unrenderable>" : "(legacy)";
72149
+ throw new Error(`Stack '${displaySafe(stackName, { asciiOnly: true })}' (${where}) is locked. Run: ${buildForceUnlockCommand(stackName, target.region, {
72150
+ profile: options.profile,
72151
+ stateBucket: setup.bucket,
72152
+ statePrefix: options.statePrefix
72153
+ }) || "a command cdkd cannot show safely: the recorded name or region would not survive a command line intact, so inspect the lock object directly"} first, or pass --force to remove anyway.`);
71935
72154
  }
71936
72155
  }
71937
72156
  if (!options.yes && !options.force) {
@@ -71951,9 +72170,13 @@ async function stateOrphanCommand(stackArgs, options) {
71951
72170
  }
71952
72171
  for (const target of targets) {
71953
72172
  if (target.region) {
72173
+ await warnOnLiveForeignLock(setup.lockManager, stackName, target.region, logger);
71954
72174
  await setup.stateBackend.deleteState(stackName, target.region);
71955
72175
  await setup.lockManager.forceReleaseLock(stackName, target.region);
71956
- } else await setup.lockManager.forceReleaseLock(stackName, void 0);
72176
+ } else {
72177
+ await warnOnLiveForeignLock(setup.lockManager, stackName, void 0, logger);
72178
+ await setup.lockManager.forceReleaseLock(stackName, void 0);
72179
+ }
71957
72180
  logger.info(`✓ Removed state for stack: ${formatStackRef(target)}`);
71958
72181
  }
71959
72182
  }
@@ -72085,6 +72308,7 @@ async function stateDestroyCommand(stackArgs, options) {
72085
72308
  exportIndexStore: setup.exportIndexStore,
72086
72309
  destroyOptions: {
72087
72310
  ...options.profile && { profile: options.profile },
72311
+ statePrefix: options.statePrefix,
72088
72312
  ...options.removeProtection === true && { removeProtection: true },
72089
72313
  ...options.skipFinalSnapshot === true && { skipFinalSnapshot: true },
72090
72314
  ...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
@@ -72100,6 +72324,7 @@ async function stateDestroyCommand(stackArgs, options) {
72100
72324
  baseRegion: setup.region,
72101
72325
  ...options.profile && { profile: options.profile },
72102
72326
  stateBucket: setup.bucket,
72327
+ statePrefix: options.statePrefix,
72103
72328
  skipConfirmation: options.yes || options.all === true,
72104
72329
  removeProtection: options.removeProtection === true,
72105
72330
  skipFinalSnapshot: options.skipFinalSnapshot === true,
@@ -72461,7 +72686,12 @@ async function stateRefreshObservedCommand(stackArgs, options) {
72461
72686
  if (!target.region) throw new Error(`Stack '${target.stackName}' has only a legacy state record without a region. Run 'cdkd deploy ${target.stackName}' (or any cdkd write) first to migrate it to the region-scoped layout, then re-run refresh-observed.`);
72462
72687
  const counts = await refreshObservedForStack(target.stackName, target.region, setup.stateBackend, setup.lockManager, providerRegistry, {
72463
72688
  dryRun: options.dryRun ?? false,
72464
- logger
72689
+ logger,
72690
+ lockRecovery: {
72691
+ profile: options.profile,
72692
+ stateBucket: setup.bucket,
72693
+ statePrefix: options.statePrefix
72694
+ }
72465
72695
  });
72466
72696
  totalRefreshed += counts.refreshed;
72467
72697
  totalUnsupported += counts.unsupported;
@@ -72488,6 +72718,34 @@ async function stateRefreshObservedCommand(stackArgs, options) {
72488
72718
  */
72489
72719
  const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
72490
72720
  /**
72721
+ * Warn before `cdkd state orphan` force-releases a lock that is still LIVE.
72722
+ *
72723
+ * `forceReleaseLock` is unconditional by design — a stuck lock must never make
72724
+ * a state record unremovable — but it deletes an in-flight `cdkd deploy`'s lock
72725
+ * just as readily as a stale one, and it did so silently (issue #2171). The
72726
+ * write it enables has already happened by the time anyone notices, so the
72727
+ * only useful thing is to name the owner at the moment of the release.
72728
+ *
72729
+ * Best-effort in both directions: an expired lock is not worth a line, and a
72730
+ * failed read must not block the removal the user asked for.
72731
+ *
72732
+ * (Issue #2171's own text called this command `cdkd state rm`; no such command
72733
+ * exists -- this file registers it as `orphan`.)
72734
+ */
72735
+ async function warnOnLiveForeignLock(lockManager, stackName, region, logger) {
72736
+ try {
72737
+ const info = await lockManager.getLockInfo(stackName, region);
72738
+ if (!info) return;
72739
+ const expiryKnown = Number.isFinite(info.expiresAt);
72740
+ if (expiryKnown && info.expiresAt <= Date.now()) return;
72741
+ const safeStack = displaySafe(stackName, { asciiOnly: true }) || "<unrenderable>";
72742
+ const where = region ? `${safeStack} (${displaySafe(region, { asciiOnly: true }) || "<unrenderable>"})` : `${safeStack} (legacy lock key)`;
72743
+ const owner = info.owner;
72744
+ const operation = info.operation ? `, operation: ${info.operation}` : "";
72745
+ logger.warn(`Force-releasing a LIVE lock on ${where} ${owner ? `held by ${owner}${operation}` : "held by an unnamed holder"}. ` + (owner && expiryKnown ? `That process is still running and will keep writing; its next state write ` : expiryKnown ? `The lock has not expired, so a process may still be writing; its next state write ` : `The lock records no readable expiry, so a process may still be writing; its next state write `) + `may recreate the record being removed here.`);
72746
+ } catch {}
72747
+ }
72748
+ /**
72491
72749
  * Refresh the `observedProperties` of every resource in one stack
72492
72750
  * record. Returns counts so the caller can aggregate across `--all`.
72493
72751
  *
@@ -72497,7 +72755,7 @@ const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
72497
72755
  * for the underlying SDK reads.
72498
72756
  */
72499
72757
  async function refreshObservedForStack(stackName, region, stateBackend, lockManager, providerRegistry, opts) {
72500
- const { logger } = opts;
72758
+ const { logger, lockRecovery } = opts;
72501
72759
  const result = await stateBackend.getState(stackName, region);
72502
72760
  if (!result) throw new Error(`No state found for stack '${stackName}' (${region}). Run 'cdkd state list' to see available stacks.`);
72503
72761
  const { state, etag, migrationPending } = result;
@@ -72535,7 +72793,12 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
72535
72793
  };
72536
72794
  }
72537
72795
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
72538
- if (!await lockManager.acquireLock(stackName, region, owner, "state-refresh-observed")) throw new Error(`Could not acquire lock for stack '${stackName}' (${region}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${stackName} --stack-region ${region}' if you are certain no other process is active.`);
72796
+ if (!await lockManager.acquireLock(stackName, region, owner, "state-refresh-observed")) throw new Error(await buildLockContentionMessage({
72797
+ lockManager,
72798
+ stackName,
72799
+ region,
72800
+ recovery: lockRecovery
72801
+ }));
72539
72802
  try {
72540
72803
  let refreshed = 0;
72541
72804
  let unsupported = 0;
@@ -72878,7 +73141,10 @@ async function rollbackCommand(stackArg, options) {
72878
73141
  awsClients: setup.awsClients,
72879
73142
  stateBucket: setup.bucket,
72880
73143
  exportIndexStore: setup.exportIndexStore,
72881
- destroyOptions: { ...options.profile && { profile: options.profile } }
73144
+ destroyOptions: {
73145
+ ...options.profile && { profile: options.profile },
73146
+ statePrefix: options.statePrefix
73147
+ }
72882
73148
  }, () => withStackName(stackName, async () => {
72883
73149
  let failedOpFailures = 0;
72884
73150
  let failedOpWarnings = 0;
@@ -74915,7 +75181,16 @@ async function orphanCommand(pathArgs, options) {
74915
75181
  logger.info(`Target: ${stackInfo.stackName} (${targetRegion}); orphaning ${orphanLogicalIds.length} resource(s): ${orphanLogicalIds.join(", ")}`);
74916
75182
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
74917
75183
  if (!options.dryRun) {
74918
- if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "orphan")) throw new Error(`Could not acquire lock for stack '${stackInfo.stackName}' (${targetRegion}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${stackInfo.stackName} --stack-region ${targetRegion}' if you are certain no other process is active.`);
75184
+ if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "orphan")) throw new Error(await buildLockContentionMessage({
75185
+ lockManager,
75186
+ stackName: stackInfo.stackName,
75187
+ region: targetRegion,
75188
+ recovery: {
75189
+ profile: options.profile,
75190
+ stateBucket,
75191
+ statePrefix: options.statePrefix
75192
+ }
75193
+ }));
74919
75194
  }
74920
75195
  try {
74921
75196
  const stateData = await stateBackend.getState(stackInfo.stackName, targetRegion);
@@ -75401,6 +75676,11 @@ async function importCommand(stackArg, options) {
75401
75676
  bucket: stateBucket,
75402
75677
  prefix: options.statePrefix
75403
75678
  };
75679
+ const lockRecovery = {
75680
+ profile: options.profile,
75681
+ stateBucket,
75682
+ statePrefix: options.statePrefix
75683
+ };
75404
75684
  const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
75405
75685
  ...options.region && { region: options.region },
75406
75686
  ...options.profile && { profile: options.profile }
@@ -75501,7 +75781,16 @@ async function importCommand(stackArg, options) {
75501
75781
  }
75502
75782
  for (const overrideId of overrides.keys()) if (!templateLogicalIds.has(overrideId)) throw new Error(`--resource / --resource-mapping references logical ID '${overrideId}' which is not in the synthesized template for stack '${stackInfo.stackName}'. Available IDs: ${[...templateLogicalIds].join(", ")}`);
75503
75783
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
75504
- if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "import")) throw new Error(`Could not acquire lock for stack '${stackInfo.stackName}' (${targetRegion}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${stackInfo.stackName} --stack-region ${targetRegion}' if you are certain no other process is active.`);
75784
+ if (!await lockManager.acquireLock(stackInfo.stackName, targetRegion, owner, "import")) throw new Error(await buildLockContentionMessage({
75785
+ lockManager,
75786
+ stackName: stackInfo.stackName,
75787
+ region: targetRegion,
75788
+ recovery: {
75789
+ profile: options.profile,
75790
+ stateBucket,
75791
+ statePrefix: options.statePrefix
75792
+ }
75793
+ }));
75505
75794
  try {
75506
75795
  const rows = [];
75507
75796
  for (const { logicalId, resource } of resources) {
@@ -75565,6 +75854,7 @@ async function importCommand(stackArg, options) {
75565
75854
  logger.info(`✓ State written: ${stackInfo.stackName} (${targetRegion})`);
75566
75855
  logger.info(` ${importedRows.length} resource(s) imported. Run 'cdkd diff' to see how the imported state lines up with the template.`);
75567
75856
  if (migrationCfnStackName && migrationTree && accountIdForNestedSynth) await importNestedStackChildrenRecursive({
75857
+ lockRecovery,
75568
75858
  parentStackName: stackInfo.stackName,
75569
75859
  parentRegion: targetRegion,
75570
75860
  parentNestedTemplates: stackInfo.nestedTemplates ?? {},
@@ -76230,7 +76520,13 @@ async function importNestedStackChildrenRecursive(args) {
76230
76520
  const childRewritten = rewriteTemplateAssetReferences(childTemplate, assetRedirect);
76231
76521
  if (childRewritten > 0) logger.info(`Note: ${childRewritten} asset reference(s) in nested stack ${childStackName} are recorded in state at their pre-rewrite (CDK bootstrap) values, so the next 'cdkd deploy' repoints any resource that still holds them to cdkd asset storage.`);
76232
76522
  }
76233
- if (!await lockManager.acquireLock(childStackName, childRegion, lockOwner, "import")) throw new Error(`Could not acquire lock for nested stack '${childStackName}' (${childRegion}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${childStackName} --stack-region ${childRegion}' if you are certain no other process is active.`);
76523
+ if (!await lockManager.acquireLock(childStackName, childRegion, lockOwner, "import")) throw new Error(await buildLockContentionMessage({
76524
+ lockManager,
76525
+ stackName: childStackName,
76526
+ region: childRegion,
76527
+ subject: "nested stack",
76528
+ recovery: args.lockRecovery
76529
+ }));
76234
76530
  try {
76235
76531
  const childResources = collectImportableResources(childTemplate);
76236
76532
  const childTemplateLogicalIds = new Set(childResources.map((r) => r.logicalId));
@@ -76272,6 +76568,7 @@ async function importNestedStackChildrenRecursive(args) {
76272
76568
  await stateBackend.saveState(childStackName, childRegion, childStackState);
76273
76569
  logger.info(`✓ Nested stack state written: ${childStackName} (${childRegion}) — ${rows.filter((r) => r.outcome === "imported").length} resource(s) imported.`);
76274
76570
  if (childTreeNode.nested.size > 0) await importNestedStackChildrenRecursive({
76571
+ lockRecovery: args.lockRecovery,
76275
76572
  parentStackName: childStackName,
76276
76573
  parentRegion: childRegion,
76277
76574
  parentNestedTemplates: indexGrandchildTemplatePaths(childTemplate, childTemplatePath),
@@ -86145,7 +86442,7 @@ function createMigrateCommand() {
86145
86442
  */
86146
86443
  function buildProgram() {
86147
86444
  const program = new Command();
86148
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.38");
86445
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.39");
86149
86446
  program.hook("preAction", (_thisCommand, actionCommand) => {
86150
86447
  const { profile } = actionCommand.optsWithGlobals();
86151
86448
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;