@go-to-k/cdkd 0.285.7 → 0.285.8

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,8 +1,8 @@
1
- import { $ as DEFAULT_STATE_PREFIX, $n as CFN_TEMPLATE_BODY_LIMIT, $t as maskSecretsInError, A as bold, An as describeAwsFailure, Ar as NestedStackChildDirectDestroyError, At as requireConfigObject, B as secretBearingStateKeyWarning, Bn as Synthesizer, Bt as DiffCalculator, C as unsupportedFinalSnapshotError, Cn as getBootstrapMarkerKey, Cr as DeployCancelledError, Ct as assertRegionMatch, D as isStatefulRecreateTargetSync, Dn as validateAssetBucketName, Dr as LocalStartServiceError, Dt as readConfigString, E as MULTI_REGION_RECREATE_BLOCKED_TYPES, En as readBootstrapMarkerBody, Er as LocalMigrateError, Et as configStringRefusal, F as yellow, Fn as partitionSensitiveEnv, Fr as StackHasActiveImportsError, Ft as s3BucketDomainName, G as clearOnUpdateRemoval, Gn as resolveAutoAssetStorage, Gr as isThrottlingError, Gt as TemplateParser, H as getCurrentResourceSecrets, Hn as getDefaultStateBucketName, Hr as withErrorHandling, Ht as describeTypeWithThrottleRetry, I as collectDeclaredOutputNames, In as runDockerForeground, Ir as StackTerminationProtectionError, It as s3BucketDualStackDomainName, J as findSilentDropProperties, Jn as resolveStateBucketWithDefault, Jr as markRedactedCause, Jt as TEMPLATE_SOURCED_RULES, K as ProviderRegistry, Kn as resolveCaptureObservedState, Kr as isTransientServerError, Kt as STATE_SOURCED_CROSS_GENERATION_RULES, L as collectPublishedOutputNames, Ln as runDockerStreaming, Lt as s3BucketRegionalDomainName, M as gray, Mn as dockerSpawnEnvWithSensitive, Mr as ProvisioningError, Mt as classifyReplaySecretRegion, N as green, Nn as formatDockerLoginError, Nr as ResourceTimeoutError, Nt as producerRegionsFromState, O as renderStatefulReason, On as validateContainerRepoName, Ot as replayWarn, P as red, Pn as getDockerCmd, Pr as ResourceUpdateNotSupportedError, Pt as s3BucketArn, Q as CUSTOM_RESOURCE_RESPONSE_PREFIX, Qn as warnDeprecatedNoPrefixCliFlag, Qt as isSingleDynamicReferenceToken, R as exportAliasCollisionScrubWarning, Rn as AssetManifestLoader, Rt as s3BucketWebsiteUrl, S as refusesFinalSnapshot, Sn as ensureAssetStorage, St as resolveExplicitPhysicalId, T as extractDeploymentEventError, Tn as parseBootstrapMarker, Tr as LocalInvokeBuildError$1, Tt as configBooleanRefusal, U as IAMRoleProvider, Un as getLegacyStateBucketName, Ur as isMarkedNonRetryable, Ut as withRetry, V as stateKeySecretExposure, Vn as synthesisStatusMessage, Vr as normalizeAwsError, Vt as INTRINSIC_KEYS, W as collectInlinePolicyNamesManagedBySiblings, Wn as resolveApp, Wr as isRetryableTransientError, Wt as DagBuilder, X as maskDeep, Xn as resolveUseCdkBootstrapAssets, Xt as dynamicReferenceTokens, Y as createMaskedRetryLogger, Yn as resolveStateBucketWithDefaultAndSource, Yr as retryClassificationText, Yt as createSecretMasker, Z as maskerOrIdentity, Zn as stateBucketExistenceConfirmed, Zt as errorCauseChain, _ as PRE_DELETE_SNAPSHOT_TYPES, _n as escapeRegExp, _r as setAwsClients, _t as isUnboundTemplateParameter, a as DeploymentEventsStore, an as rebuildClientForBucketRegion, ar as displaySafe, at as CloudControlProvider, b as createPreDeleteFinalSnapshot, bn as BOOTSTRAP_MARKER_PREFIX, bt as WAFv2WebACLProvider, c as replayFailedOperations, cn as importableOutputs, cr as canonicalizeRegion, ct as deleteIndeterminateGuards, d as updatePartialReason, dn as stringifyValue, dr as processStackMessages, dt as isTerminationProtectionPropagationError, en as maskSecretsInText, er as CFN_TEMPLATE_URL_LIMIT, et as beginCommandInterruptScope, f as withResourceDeadline, fn as WorkGraph, ft as IntrinsicFunctionResolver, g as ATOMIC_FINAL_SNAPSHOT_TYPES, gn as rewriteTemplateAssetReferences, gr as resetAwsClients, gt as getAccountInfo, h as computeImplicitDeleteEdges, hn as loadPublishableAssetManifest, hr as getAwsClients, ht as coerceParameterTypedValue, i as DeploymentEventsReader, in as S3StateBackend, ir as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, it as startInterruptWatch, j as cyan, jn as buildDockerImage, jr as PartialFailureError, jt as requireConfigString, k as formatResourceLine, kn as buildDenyExternalAccessPolicy, kr as MissingCdkCliError, kt as requireConfigArray, l as replayRollback, ln as shouldRetainResource, lr as derivePartitionAndUrlSuffix, lt as deleteSkipReason, m as IMPLICIT_DELETE_DEPENDENCIES, mn as createAssetRedirectResolver, mr as AwsClients, mt as cfnRefValueFromPhysicalId, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as scrubResourceRecord, nr as findLargeInlineResources, nt as interruptWatchListenerCount, o as planFailedOps, on as exportNamesCarriedFrom, or as expectedOwnerParam, ot as slowCcOperationTimeoutMs, p as maskingRetryLogger, pn as buildAssetRedirectMap, pr as resolveBucketRegion, pt as carriesDynamicReference, q as findActionableSilentDrops, qn as resolveSkipPrefix, qr as markNonRetryable, qt as STATE_SOURCED_READBACK_RULES, r as DeployEngine, rn as LockManager, rr as uploadCfnTemplate, rt as isInterruptedWaitError, s as planRollback, sn as importableOutputKeys, sr as PARTITION_TABLE, st as UNSPECIFIED_SKIP_REASON, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as redactSecretsForState, tr as MIGRATE_TMP_PREFIX, tt as endCommandInterruptScope, u as updatePartialMessage, un as AssetPublisher, ur as AssemblyReader, ut as disableInstanceApiTermination, v as buildFinalSnapshotIdentifier, vn as stripControlChars, vt as parameterTypeMayLoseSecretIdentity, w as makeCanonicalizePropertiesFn, wn as isCrossRegionRedirect, wr as DynamicReferenceRegionAmbiguousError, wt as coerceCfnBoolean, x as isFinalSnapshotError, xn as assertAssetBucketRegion, xr as CrossAccountSecretRefusalError, xt as normalizeAwsTagsToCfn, y as ccRoutedFinalSnapshotError, yn as AssetModeResolver, yr as CdkdError, yt as refStateLookupFromResource, z as isExportAliasCollision, zn as getDockerImageBySourceHash, zt as applyRoleArnIfSet } from "./deploy-engine-DmWPwiyQ.js";
2
- import { _ as withStackName, c as PATTERN_B_NAME_OPTIONS, f as generateResourceName, g as withSkipPrefix, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as getCurrentStackName, n as getLogger, o as runStackBuffered, p as generateResourceNameWithFallback, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES } from "./logger-DyTk5GeO.js";
1
+ import { $ as DEFAULT_STATE_PREFIX, $n as CFN_TEMPLATE_BODY_LIMIT, $t as maskSecretsInError, A as bold, An as describeAwsFailure, Ar as NestedStackChildDirectDestroyError, At as requireConfigObject, B as secretBearingStateKeyWarning, Bn as Synthesizer, Bt as DiffCalculator, C as unsupportedFinalSnapshotError, Cn as getBootstrapMarkerKey, Cr as DeployCancelledError, Ct as assertRegionMatch, D as isStatefulRecreateTargetSync, Dn as validateAssetBucketName, Dr as LocalStartServiceError, Dt as readConfigString, E as MULTI_REGION_RECREATE_BLOCKED_TYPES, En as readBootstrapMarkerBody, Er as LocalMigrateError, Et as configStringRefusal, F as yellow, Fn as partitionSensitiveEnv, Fr as StackHasActiveImportsError, Ft as s3BucketDomainName, G as clearOnUpdateRemoval, Gn as resolveAutoAssetStorage, Gr as isThrottlingError, Gt as TemplateParser, H as getCurrentResourceSecrets, Hn as getDefaultStateBucketName, Hr as withErrorHandling, Ht as describeTypeWithThrottleRetry, I as collectDeclaredOutputNames, In as runDockerForeground, Ir as StackTerminationProtectionError, It as s3BucketDualStackDomainName, J as findSilentDropProperties, Jn as resolveStateBucketWithDefault, Jr as markRedactedCause, Jt as TEMPLATE_SOURCED_RULES, K as ProviderRegistry, Kn as resolveCaptureObservedState, Kr as isTransientServerError, Kt as STATE_SOURCED_CROSS_GENERATION_RULES, L as collectPublishedOutputNames, Ln as runDockerStreaming, Lt as s3BucketRegionalDomainName, M as gray, Mn as dockerSpawnEnvWithSensitive, Mr as ProvisioningError, Mt as classifyReplaySecretRegion, N as green, Nn as formatDockerLoginError, Nr as ResourceTimeoutError, Nt as producerRegionsFromState, O as renderStatefulReason, On as validateContainerRepoName, Ot as replayWarn, P as red, Pn as getDockerCmd, Pr as ResourceUpdateNotSupportedError, Pt as s3BucketArn, Q as CUSTOM_RESOURCE_RESPONSE_PREFIX, Qn as warnDeprecatedNoPrefixCliFlag, Qt as isSingleDynamicReferenceToken, R as exportAliasCollisionScrubWarning, Rn as AssetManifestLoader, Rt as s3BucketWebsiteUrl, S as refusesFinalSnapshot, Sn as ensureAssetStorage, St as resolveExplicitPhysicalId, T as extractDeploymentEventError, Tn as parseBootstrapMarker, Tr as LocalInvokeBuildError$1, Tt as configBooleanRefusal, U as IAMRoleProvider, Un as getLegacyStateBucketName, Ur as isMarkedNonRetryable, Ut as withRetry, V as stateKeySecretExposure, Vn as synthesisStatusMessage, Vr as normalizeAwsError, Vt as INTRINSIC_KEYS, W as collectInlinePolicyNamesManagedBySiblings, Wn as resolveApp, Wr as isRetryableTransientError, Wt as DagBuilder, X as maskDeep, Xn as resolveUseCdkBootstrapAssets, Xt as dynamicReferenceTokens, Y as createMaskedRetryLogger, Yn as resolveStateBucketWithDefaultAndSource, Yr as retryClassificationText, Yt as createSecretMasker, Z as maskerOrIdentity, Zn as stateBucketExistenceConfirmed, Zt as errorCauseChain, _ as PRE_DELETE_SNAPSHOT_TYPES, _n as escapeRegExp, _r as setAwsClients, _t as isUnboundTemplateParameter, a as DeploymentEventsStore, an as rebuildClientForBucketRegion, ar as displaySafe, at as CloudControlProvider, b as createPreDeleteFinalSnapshot, bn as BOOTSTRAP_MARKER_PREFIX, bt as WAFv2WebACLProvider, c as replayFailedOperations, cn as importableOutputs, cr as canonicalizeRegion, ct as deleteIndeterminateGuards, d as updatePartialReason, dn as stringifyValue, dr as processStackMessages, dt as isTerminationProtectionPropagationError, en as maskSecretsInText, er as CFN_TEMPLATE_URL_LIMIT, et as beginCommandInterruptScope, f as withResourceDeadline, fn as WorkGraph, ft as IntrinsicFunctionResolver, g as ATOMIC_FINAL_SNAPSHOT_TYPES, gn as rewriteTemplateAssetReferences, gr as resetAwsClients, gt as getAccountInfo, h as computeImplicitDeleteEdges, hn as loadPublishableAssetManifest, hr as getAwsClients, ht as coerceParameterTypedValue, i as DeploymentEventsReader, in as S3StateBackend, ir as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, it as startInterruptWatch, j as cyan, jn as buildDockerImage, jr as PartialFailureError, jt as requireConfigString, k as formatResourceLine, kn as buildDenyExternalAccessPolicy, kr as MissingCdkCliError, kt as requireConfigArray, l as replayRollback, ln as shouldRetainResource, lr as derivePartitionAndUrlSuffix, lt as deleteSkipReason, m as IMPLICIT_DELETE_DEPENDENCIES, mn as createAssetRedirectResolver, mr as AwsClients, mt as cfnRefValueFromPhysicalId, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as scrubResourceRecord, nr as findLargeInlineResources, nt as interruptWatchListenerCount, o as planFailedOps, on as exportNamesCarriedFrom, or as expectedOwnerParam, ot as slowCcOperationTimeoutMs, p as maskingRetryLogger, pn as buildAssetRedirectMap, pr as resolveBucketRegion, pt as carriesDynamicReference, q as findActionableSilentDrops, qn as resolveSkipPrefix, qr as markNonRetryable, qt as STATE_SOURCED_READBACK_RULES, r as DeployEngine, rn as LockManager, rr as uploadCfnTemplate, rt as isInterruptedWaitError, s as planRollback, sn as importableOutputKeys, sr as PARTITION_TABLE, st as UNSPECIFIED_SKIP_REASON, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as redactSecretsForState, tr as MIGRATE_TMP_PREFIX, tt as endCommandInterruptScope, u as updatePartialMessage, un as AssetPublisher, ur as AssemblyReader, ut as disableInstanceApiTermination, v as buildFinalSnapshotIdentifier, vn as stripControlChars, vt as parameterTypeMayLoseSecretIdentity, w as makeCanonicalizePropertiesFn, wn as isCrossRegionRedirect, wr as DynamicReferenceRegionAmbiguousError, wt as coerceCfnBoolean, x as isFinalSnapshotError, xn as assertAssetBucketRegion, xr as CrossAccountSecretRefusalError, xt as normalizeAwsTagsToCfn, y as ccRoutedFinalSnapshotError, yn as AssetModeResolver, yr as CdkdError, yt as refStateLookupFromResource, z as isExportAliasCollision, zn as getDockerImageBySourceHash, zt as applyRoleArnIfSet } from "./deploy-engine-D4QA_B1V.js";
2
+ import { _ as withStackName, c as PATTERN_B_NAME_OPTIONS, f as generateResourceName, g as withSkipPrefix, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as getCurrentStackName, n as getLogger, o as runStackBuffered, p as generateResourceNameWithFallback, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES } from "./logger-Dw-3y48G.js";
3
3
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
4
- import { t as getCdkdVersion } from "./version-DlUEyyGa.js";
5
- import { t as ASGProvider } from "./asg-provider-B1EEjygi.js";
4
+ import { t as getCdkdVersion } from "./version-DFdq83MF.js";
5
+ import { t as ASGProvider } from "./asg-provider-CvYAGRMV.js";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
7
7
  import { createHash, randomBytes, randomUUID } from "node:crypto";
8
8
  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";
@@ -2675,14 +2675,32 @@ async function promptRecreateConfirm(input) {
2675
2675
  //#endregion
2676
2676
  //#region src/cli/commands/confirm-prompt.ts
2677
2677
  /**
2678
- * Default-YES confirmation prompt (`[Y/n]`, empty input = yes). Used by the
2679
- * issue #1007 asset-storage auto-create on interactive TTY deploys without
2680
- * `--yes`. Kept in its own module (mirrors `recreate-confirm-prompt.ts`) so
2681
- * the accept/decline parsing is unit-testable without importing the whole
2682
- * deploy command.
2678
+ * The CLI's two shared confirmation-prompt helpers.
2679
+ *
2680
+ * They have OPPOSITE defaults and opposite non-interactive behaviour, which is
2681
+ * why both live here rather than one being expressed in terms of the other:
2682
+ *
2683
+ * - {@link promptYesNo} — default-YES (`[Y/n]`, empty input accepts). Used by
2684
+ * the issue #1007 asset-storage auto-create on interactive TTY deploys
2685
+ * without `--yes`. It deliberately carries NO non-interactive guard: its
2686
+ * only caller (`deploy.ts`, at the `options.yes || !process.stdin.isTTY`
2687
+ * short-circuit) never reaches it on a non-TTY stdin, and a deploy that
2688
+ * assumes "yes" is the one place in the CLI where auto-confirming is the
2689
+ * documented choice.
2690
+ * - {@link confirmOrRefuse} — default-NO (`[y/N]`), and REFUSES outright on a
2691
+ * non-interactive stdin. This is the one every MUTATING command uses.
2692
+ *
2693
+ * Kept in its own module (mirrors `recreate-confirm-prompt.ts`) so the
2694
+ * accept/decline parsing is unit-testable without importing whole command
2695
+ * files.
2696
+ */
2697
+ /**
2698
+ * Default-YES confirmation prompt (`[Y/n]`, empty input = yes).
2699
+ *
2700
+ * See the module doc for why this one is deliberately unguarded.
2683
2701
  */
2684
2702
  async function promptYesNo(prompt) {
2685
- const rl = readline.createInterface({
2703
+ const rl = readline$1.createInterface({
2686
2704
  input: process.stdin,
2687
2705
  output: process.stdout
2688
2706
  });
@@ -2693,6 +2711,79 @@ async function promptYesNo(prompt) {
2693
2711
  rl.close();
2694
2712
  }
2695
2713
  }
2714
+ /** Suffix appended to a {@link confirmOrRefuse} prompt when none is given. */
2715
+ const DEFAULT_CONFIRM_SUFFIX = " [y/N] ";
2716
+ /**
2717
+ * Default-NO confirmation prompt that REFUSES a non-interactive run instead of
2718
+ * hanging on it (issue #2275, the class issue #2259 fixed for `cdkd destroy`).
2719
+ *
2720
+ * `rl.question` never settles when stdin is already at EOF, and EOF delivers
2721
+ * no signal, so nothing wakes it: a command that prompts without this guard
2722
+ * parks FOREVER in CI rather than failing, burning the job's whole timeout
2723
+ * budget. Measured on Node 24.15.0, the version `.node-version` pins, against
2724
+ * real `node:readline/promises`: `echo y |` resolves `"y"`, while both
2725
+ * `printf 'y' |` (a real answer with no trailing newline) and `< /dev/null`
2726
+ * stay pending indefinitely.
2727
+ *
2728
+ * REFUSE rather than auto-confirm, at every one of the nine sites this helper
2729
+ * replaced. Every one of them guards a MUTATION — a rollback replay, a state
2730
+ * record removal, an observed-property refresh, an orphan, an import, an
2731
+ * export-then-delete-state, a drift accept/revert, a CloudFormation stack
2732
+ * retirement, a state-bucket migration — so silently answering "yes" on
2733
+ * behalf of an absent operator is never the safe reading. `deploy.ts`'s
2734
+ * auto-confirm (`options.yes || !process.stdin.isTTY` -> proceed) stays the
2735
+ * deliberate exception, because a deploy that assumes "yes" is recoverable.
2736
+ *
2737
+ * A first cut of this guard (in `state.ts`, see the long comment at the
2738
+ * `state destroy --all` prompt) raced the question against readline's `close`
2739
+ * event and turned EOF into a decline. That lost three measured ways — a real
2740
+ * answer with no trailing newline was silently discarded, a delayed answer
2741
+ * lost the race, and an interactive Ctrl-C landed on the EOF arm — so do not
2742
+ * reintroduce it. A refusal has none of those.
2743
+ *
2744
+ * POSITION IS LOAD-BEARING, and this helper gets it for free: every call site
2745
+ * sits INSIDE the caller's `--yes` / `--force` short-circuit
2746
+ * (`if (!options.yes && !options.force) { ... }`), so a flagged
2747
+ * non-interactive run never reaches this function and never consults stdin at
2748
+ * all. Keep it that way — hoisting a call above its flag check would make
2749
+ * `--yes` refuse instead of proceed, which is the inverse bug.
2750
+ *
2751
+ * Throws {@link CdkdError} with the `NON_INTERACTIVE_CONFIRM` code, matching
2752
+ * `gc.ts` and `bootstrap-destroy.ts` — the only two of the five originally
2753
+ * guarded prompts that carry a code (the other three throw a bare `Error` or
2754
+ * a `LocalMigrateError`). `handleError` maps it to exit 1, so CI can branch on
2755
+ * it.
2756
+ *
2757
+ * TWO GUARDED PROMPTS DELIBERATELY DO NOT ROUTE THROUGH HERE:
2758
+ * `destroy-runner.ts`'s per-stack prompt (the issue #2259 fix) and
2759
+ * `state.ts`'s `state destroy --all` batch prompt (issue #2247). Both already
2760
+ * carry this exact guard inline, and both differ in ways a shared helper would
2761
+ * have to grow parameters for — `destroy-runner.ts` has a default-YES bare
2762
+ * form alongside a default-NO `--remove-protection` form, and `state.ts`
2763
+ * passes an abort `signal` for the issue #2117 Ctrl-C handling. Folding them
2764
+ * in would also drag `destroy-runner.ts` (in the `integ-destroy` AND
2765
+ * `integ-broad` gate scopes) into a pure refactor's blast radius, buying a
2766
+ * real-AWS run for no behaviour change. The other five pre-existing guarded
2767
+ * prompts (`gc.ts`, `bootstrap-destroy.ts`, `recreate-confirm-prompt.ts`,
2768
+ * `prefix-migration-check.ts`, `migrate-command.ts`) stay put for the same
2769
+ * reason: each guards its own flow with its own error type.
2770
+ *
2771
+ * @param prompt the question, WITHOUT its trailing `[y/N]` marker
2772
+ * @returns `true` only for `y` / `yes` (any case, trimmed)
2773
+ */
2774
+ async function confirmOrRefuse(prompt, options) {
2775
+ if (process.stdin.isTTY !== true) throw new CdkdError(options.refusal, "NON_INTERACTIVE_CONFIRM");
2776
+ const rl = readline$1.createInterface({
2777
+ input: process.stdin,
2778
+ output: options.output ?? process.stdout
2779
+ });
2780
+ try {
2781
+ const ans = await rl.question(`${prompt}${options.suffix ?? " [y/N] "}`);
2782
+ return /^y(es)?$/i.test(ans.trim());
2783
+ } finally {
2784
+ rl.close();
2785
+ }
2786
+ }
2696
2787
 
2697
2788
  //#endregion
2698
2789
  //#region src/cli/commands/deployment-events-run.ts
@@ -62830,6 +62921,36 @@ const TEMPLATE_BODY_LIMIT = CFN_TEMPLATE_BODY_LIMIT;
62830
62921
  */
62831
62922
  const TEMPLATE_URL_LIMIT = CFN_TEMPLATE_URL_LIMIT;
62832
62923
  /**
62924
+ * Best-effort reaper for the transient template bodies this flow uploads to
62925
+ * the state bucket under `cdkd-migrate-tmp/`.
62926
+ *
62927
+ * ONE implementation because {@link retireCloudFormationStack} drains from
62928
+ * four different exits — the recursive walk's `catch`, the confirmation
62929
+ * gate's decline AND its non-interactive refusal, the no-modification fast
62930
+ * path, and the post-`UpdateStack` `finally`. Each used to carry its own copy
62931
+ * of this loop, and the copies were how the refusal exit came to have none at
62932
+ * all: a `throw` from the gate passes every one of the other three.
62933
+ *
62934
+ * Best-effort by design. A leaked transient object costs pennies and lives
62935
+ * under an explicitly-named prefix, while a failed DELETE that propagated
62936
+ * would turn a successful retire into a reported failure. The retire's
62937
+ * success is governed by CloudFormation, not by S3 — so each cleanup's own
62938
+ * error becomes a `warn` naming the prefix to sweep manually.
62939
+ *
62940
+ * `what` / `when` keep the four sites' distinct wording (a caller says
62941
+ * whether these are the parent's own upload or a nested child's, and whether
62942
+ * it is unwinding); the SENTENCE they sit in is shared.
62943
+ */
62944
+ async function drainTemplateUploads(cleanups, stateBucket, labels) {
62945
+ const logger = getLogger();
62946
+ for (const cleanup of cleanups) try {
62947
+ await cleanup();
62948
+ } catch (cleanupErr) {
62949
+ const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
62950
+ logger.warn(`Failed to delete ${labels.what} from '${stateBucket}'${labels.when ? ` ${labels.when}` : ""}. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
62951
+ }
62952
+ }
62953
+ /**
62833
62954
  * Retire a CloudFormation stack whose resources have just been adopted into
62834
62955
  * cdkd state. The 4-step procedure is the one AWS recommends for handing
62835
62956
  * over resources between management tools without deleting them:
@@ -62893,35 +63014,31 @@ async function retireCloudFormationStack(options) {
62893
63014
  format = recursive.format;
62894
63015
  nestedCleanups.push(...recursive.cleanups);
62895
63016
  } catch (err) {
62896
- if (err instanceof RecursiveRetainInjectionError) for (const cleanup of err.cleanups) try {
62897
- await cleanup();
62898
- } catch (cleanupErr) {
62899
- const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
62900
- logger.warn(`Failed to delete partial nested-template upload from '${stateBucket}' during error recovery. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
62901
- }
63017
+ if (err instanceof RecursiveRetainInjectionError) await drainTemplateUploads(err.cleanups, stateBucket, {
63018
+ what: "partial nested-template upload",
63019
+ when: "during error recovery"
63020
+ });
62902
63021
  throw err;
62903
63022
  }
62904
63023
  } else ({body: newBody, modified, format} = injectRetainPolicies(tpl.TemplateBody, cfnStackName));
62905
63024
  if (!yes) {
62906
- if (!await confirmPrompt$6(`Set DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource in CloudFormation stack '${cfnStackName}', then delete the stack? AWS resources will NOT be deleted (cdkd state has been written).`)) {
63025
+ let proceed = false;
63026
+ try {
63027
+ proceed = await confirmPrompt$6(`Set DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource in CloudFormation stack '${cfnStackName}', then delete the stack? AWS resources will NOT be deleted (cdkd state has been written).`);
63028
+ } finally {
63029
+ if (!proceed) await drainTemplateUploads(nestedCleanups, stateBucket, {
63030
+ what: "temporary nested-template upload",
63031
+ when: "during cancel cleanup"
63032
+ });
63033
+ }
63034
+ if (!proceed) {
62907
63035
  logger.info("CloudFormation stack retirement cancelled. cdkd state is unaffected.");
62908
- for (const cleanup of nestedCleanups) try {
62909
- await cleanup();
62910
- } catch (cleanupErr) {
62911
- const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
62912
- logger.warn(`Failed to delete temporary nested-template upload from '${stateBucket}' during cancel cleanup. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
62913
- }
62914
63036
  return { outcome: "cancelled" };
62915
63037
  }
62916
63038
  }
62917
63039
  if (!modified) {
62918
63040
  logger.info(`[2/4] Template already has Retain on every resource — skipping UpdateStack.`);
62919
- if (nestedCleanups.length > 0) for (const cleanup of nestedCleanups) try {
62920
- await cleanup();
62921
- } catch (cleanupErr) {
62922
- const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
62923
- logger.warn(`Failed to delete temporary nested-template upload from '${stateBucket}'. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
62924
- }
63041
+ await drainTemplateUploads(nestedCleanups, stateBucket, { what: "temporary nested-template upload" });
62925
63042
  } else {
62926
63043
  logger.info(`[2/4] Injected DeletionPolicy=Retain and UpdateReplacePolicy=Retain.`);
62927
63044
  if (newBody.length > TEMPLATE_URL_LIMIT) throw new Error(`Modified template is ${newBody.length} bytes, exceeds the CloudFormation UpdateStack TemplateURL limit (${TEMPLATE_URL_LIMIT}). cdkd state has already been written; retire the stack manually with (1) shrink the template, then (2) UpdateStack with Retain policies, (3) DeleteStack — or split the stack and retry.`);
@@ -62967,13 +63084,7 @@ async function retireCloudFormationStack(options) {
62967
63084
  maxDelay: 10
62968
63085
  }, { StackName: cfnStackName });
62969
63086
  } finally {
62970
- const allCleanups = [...s3Cleanup ? [s3Cleanup] : [], ...nestedCleanups];
62971
- for (const cleanup of allCleanups) try {
62972
- await cleanup();
62973
- } catch (cleanupErr) {
62974
- const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
62975
- logger.warn(`Failed to delete temporary template upload from '${stateBucket}'. Clean up manually under prefix '${MIGRATE_TMP_PREFIX}/'. Cause: ${msg}`);
62976
- }
63087
+ await drainTemplateUploads([...s3Cleanup ? [s3Cleanup] : [], ...nestedCleanups], stateBucket, { what: "temporary template upload" });
62977
63088
  }
62978
63089
  }
62979
63090
  logger.info(`[4/4] Deleting CloudFormation stack '${cfnStackName}' (resources retained)...`);
@@ -63284,17 +63395,30 @@ async function walkCfnStackTree(stackName, physicalId, cfnClient) {
63284
63395
  nested
63285
63396
  };
63286
63397
  }
63398
+ /**
63399
+ * The CloudFormation-stack retirement confirmation prompt, reached from
63400
+ * `cdkd import --migrate-from-cloudformation` and `cdkd migrate
63401
+ * --retire-cfn-stack` (both spell the skip flag `-y` / `--yes`). Its only call
63402
+ * site is inside the `if (!yes)` block above, which is what keeps
63403
+ * `confirmOrRefuse`'s non-interactive refusal (issue #2275) from firing on a
63404
+ * `--yes` run.
63405
+ *
63406
+ * TWO commands reach it, so the refusal NAMES BOTH. Every other site names one
63407
+ * command because it has one; a refusal that named neither would leave a CI
63408
+ * user knowing a flag is missing without knowing which invocation to put it
63409
+ * on, and `confirmOrRefuse`'s own contract is that the refusal is the only
63410
+ * thing that user sees.
63411
+ *
63412
+ * It also states that cdkd state has ALREADY been written. This prompt is the
63413
+ * only one of the nine that fires AFTER its command's state write, so a
63414
+ * refusal here leaves a genuine split brain — cdkd claims the resources while
63415
+ * the CloudFormation stack is still live — and the operator has to know that
63416
+ * before deciding whether to re-run or finish by hand.
63417
+ *
63418
+ * Exported for unit testing — internal to the command flow otherwise.
63419
+ */
63287
63420
  async function confirmPrompt$6(prompt) {
63288
- const rl = readline$1.createInterface({
63289
- input: process.stdin,
63290
- output: process.stdout
63291
- });
63292
- try {
63293
- const ans = await rl.question(`${prompt} [y/N] `);
63294
- return /^y(es)?$/i.test(ans.trim());
63295
- } finally {
63296
- rl.close();
63297
- }
63421
+ return confirmOrRefuse(prompt, { refusal: "The CloudFormation stack retirement confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes on whichever command you ran — cdkd import --migrate-from-cloudformation or cdkd migrate --retire-cfn-stack — or run it from a real terminal. NOTE cdkd state has already been written: the resources are recorded in cdkd state while the CloudFormation stack is still live, so re-run with -y / --yes (or retire the stack by hand) to finish the migration." });
63298
63422
  }
63299
63423
  /**
63300
63424
  * Flat `logicalId -> physicalId` map for ONE CloudFormation stack, or `null`
@@ -69335,24 +69459,26 @@ async function runWithConcurrency(tasks, concurrency) {
69335
69459
  await Promise.all(workers);
69336
69460
  }
69337
69461
  /**
69338
- * Ask the operator to confirm a mutation.
69462
+ * `cdkd drift --accept` / `--revert`'s confirmation prompt: ask the operator to
69463
+ * confirm a mutation. Both call sites sit inside an `if (!options.yes)` block,
69464
+ * which is what keeps `confirmOrRefuse`'s non-interactive refusal (issue
69465
+ * #2275) from firing on a `--yes` run.
69466
+ *
69467
+ * Issue #2230 is why this site passes an explicit `output`: the prompt is
69468
+ * written to `out.stream`, not unconditionally to `process.stdout` — under
69469
+ * `--json` that stream carries the payload, and a bare `[y/N] ` spliced into
69470
+ * it is the same corruption as a status line, so the sink is `process.stderr`
69471
+ * there. The prompt is still SHOWN; only its stream changes. See the
69472
+ * `HumanTextSink` doc above for why the STREAM (not a closure) is what gets
69473
+ * handed to `createInterface`.
69339
69474
  *
69340
- * Issue #2230: the prompt is written to `out.stream`, not unconditionally to
69341
- * `process.stdout` — under `--json` that stream carries the payload, and a
69342
- * bare `[y/N] ` spliced into it is the same corruption as a status line.
69343
- * The prompt is still SHOWN; only its stream changes.
69475
+ * Exported for unit testing internal to the command flow otherwise.
69344
69476
  */
69345
69477
  async function confirmPrompt$5(prompt, out) {
69346
- const rl = readline$1.createInterface({
69347
- input: process.stdin,
69348
- output: out.stream
69478
+ return confirmOrRefuse(prompt, {
69479
+ output: out.stream,
69480
+ refusal: "The cdkd drift confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm, or run the command from a real terminal."
69349
69481
  });
69350
- try {
69351
- const ans = await rl.question(`${prompt} [y/N] `);
69352
- return /^y(es)?$/i.test(ans.trim());
69353
- } finally {
69354
- rl.close();
69355
- }
69356
69482
  }
69357
69483
  function writeJsonReport(reports) {
69358
69484
  const payload = reports.map((r) => {
@@ -70083,17 +70209,15 @@ async function emptyBucketAllVersions(s3, bucket) {
70083
70209
  versionIdMarker = resp.NextVersionIdMarker;
70084
70210
  } while (keyMarker || versionIdMarker);
70085
70211
  }
70212
+ /**
70213
+ * `cdkd state migrate`'s confirmation prompt. Its only call site is inside the
70214
+ * `if (!options.yes)` block above, which is what keeps `confirmOrRefuse`'s
70215
+ * non-interactive refusal (issue #2275) from firing on a `--yes` run.
70216
+ *
70217
+ * Exported for unit testing — internal to the command flow otherwise.
70218
+ */
70086
70219
  async function confirmPrompt$4(prompt) {
70087
- const rl = readline$1.createInterface({
70088
- input: process.stdin,
70089
- output: process.stdout
70090
- });
70091
- try {
70092
- const ans = await rl.question(`${prompt} [y/N] `);
70093
- return /^y(es)?$/i.test(ans.trim());
70094
- } finally {
70095
- rl.close();
70096
- }
70220
+ return confirmOrRefuse(prompt, { refusal: "The cdkd state migrate confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm the migration, or run the command from a real terminal." });
70097
70221
  }
70098
70222
  /**
70099
70223
  * Create the `cdkd state migrate` subcommand.
@@ -73705,17 +73829,18 @@ function printNextSteps(args) {
73705
73829
  }
73706
73830
  logger.info("");
73707
73831
  }
73832
+ /**
73833
+ * `cdkd export`'s confirmation prompt, shared by its THREE call sites (the
73834
+ * rollback-journal override, the single-stack migration confirm, and the
73835
+ * nested-stack tree-wide confirm in `runPerStackImportLoop`). Every one of
73836
+ * them sits inside an `if (!options.yes ...)` block, which is what keeps
73837
+ * `confirmOrRefuse`'s non-interactive refusal (issue #2275) from firing on a
73838
+ * `--yes` run.
73839
+ *
73840
+ * Exported for unit testing — internal to the export flow otherwise.
73841
+ */
73708
73842
  async function confirmPrompt$3(prompt) {
73709
- const rl = readline$1.createInterface({
73710
- input: process.stdin,
73711
- output: process.stdout
73712
- });
73713
- try {
73714
- const ans = await rl.question(`${prompt} [y/N] `);
73715
- return /^y(es)?$/i.test(ans.trim());
73716
- } finally {
73717
- rl.close();
73718
- }
73843
+ return confirmOrRefuse(prompt, { refusal: "The cdkd export confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm, or run the command from a real terminal." });
73719
73844
  }
73720
73845
  function createExportCommand() {
73721
73846
  const cmd = new Command("export").description("Hand a cdkd-managed stack over to CloudFormation via CFn IMPORT (changeset). AWS resources are unchanged; cdkd state for the stack is deleted on success. Mirror of `cdkd import` (AWS → cdkd) in the reverse direction (cdkd → CFn). Accepts JSON and YAML templates (YAML via a CFn-aware codec that preserves !Ref / !GetAtt / !Sub shorthand). Aborts if any resource is not CFn-importable.").argument("[stack]", "Stack name to export (auto-detected for single-stack apps)").option("--cfn-stack-name <name>", "Name of the destination CloudFormation stack for the root stack. Defaults to the cdkd stack name (or the cdkd2cfnStackName mapping when the name contains '~'). For per-nested-child overrides, use --cfn-child-stack-name.").option("--cfn-child-stack-name <pair...>", "Per-nested-child CFn stack-name override. Repeatable. Format: '<cdkdName>=<cfnName>' (e.g. --cfn-child-stack-name 'MyApp~Database=my-app-db'). The cdkd stack name for a nested child is '<parent>~<childLogicalId>' (v6 state-key form). Without an override the CFn name is derived by replacing '~' with '-'. Only consulted when the exported stack tree contains nested children; ignored for flat stacks.").option("--template <path>", "Path to a pre-rendered CloudFormation template (JSON or YAML — format auto-detected). Skips synth.").option("--stack-region <region>", "Region of the cdkd state record to operate on. Required when the same stack name has state in multiple regions.").option("--dry-run", "Print the import plan without creating a changeset.", false).option("--accept-transient-context", "Allow CLI -c key=value overrides at export time even though they are not persisted to cdk.json / cdk.context.json (default: refuse). When set, the user is responsible for passing the same -c flags to every future cdk deploy.", false).option("--include-non-importable", "Run a 2-phase migration when the stack contains non-importable resources (Custom::*). Phase 1 imports the importable resources; phase 2 CFn-CREATEs the non-importable ones, which re-invokes each Custom Resource's onCreate handler. Make sure onCreate is idempotent before enabling.", false).option("--parameter <key=value...>", "CFn template Parameter override, repeatable. Required when the synthesized template has Parameters without Default values; otherwise overrides the template's default value. Format: --parameter Key=Value.").option("--strict-cross-stack", "Refuse to export when sibling cdkd stacks in the same CDK app reference the exporting stack via Fn::GetStackOutput. Without the flag, cdkd warns and proceeds — the consumers keep resolving via the CloudFormation fallback (weak reference) unless they deploy with --no-cfn-fallback.", false).option("--skip-import-support-preflight", "Skip the pre-flight that refuses resource types whose CloudFormation registry schema says CFn cannot IMPORT them (no read handler AND NON_PROVISIONABLE), and let CreateChangeSet answer instead. The pre-flight is a registry HEURISTIC, not AWS's supported-for-import list, so this is the escape hatch for a type AWS has since made importable. Expect the changeset to be rejected if the heuristic was right.", false).option("--no-recreate-import-unsupported", "Block instead of auto-handling resource types AWS does NOT support in IMPORT changesets (currently only AWS::ApiGatewayV2::Stage, emitted by CDK HttpApi). Default behavior: cdkd skips these from phase 1, deletes the AWS-side resource between phases, and lets CFn re-CREATE in phase 2 (brief unavailability window). With this flag, the export aborts with a clear error instead.").action(withErrorHandling(exportCommand));
@@ -73739,6 +73864,26 @@ function formatStackRef(ref) {
73739
73864
  return ref.region ? `${ref.stackName} (${ref.region})` : ref.stackName;
73740
73865
  }
73741
73866
  /**
73867
+ * {@link formatStackRef} for a sentence a CONFIRMATION PROMPT renders.
73868
+ *
73869
+ * Both halves come from an S3 key segment (or, for a legacy record, a state
73870
+ * body), so both are attacker-influenced in exactly the way issue #2170 round
73871
+ * 4 found for the lock error sixty lines below this helper's callers: a
73872
+ * planted `\n` forged a whole line inside that sentence. A forged line in a
73873
+ * PROMPT is strictly worse than one in an error, because the sentence it
73874
+ * corrupts is the one the operator answers `y` to.
73875
+ *
73876
+ * `asciiOnly` matches the lock error's own call on these same values — a
73877
+ * stack name and an AWS region both have a known charset — and is a no-op on
73878
+ * every ordinary input, so the shipped prompt strings are byte-identical.
73879
+ * `UNRENDERABLE` stands in when sanitising leaves nothing, since an empty
73880
+ * `()` would read as "no region" rather than "a region cdkd will not print".
73881
+ */
73882
+ function formatStackRefSafe(ref) {
73883
+ const stack = displaySafe(ref.stackName, { asciiOnly: true }) || "<unrenderable>";
73884
+ return ref.region ? `${stack} (${displaySafe(ref.region, { asciiOnly: true }) || "<unrenderable>"})` : stack;
73885
+ }
73886
+ /**
73742
73887
  * Resolve a stack name + optional region flag against the `listStacks` index
73743
73888
  * built up front. When a name resolves to multiple regions and the caller
73744
73889
  * didn't pin one, surface a clear error listing the candidates so the user
@@ -73842,7 +73987,7 @@ function sortRefs(refs) {
73842
73987
  async function stateListCommand(options) {
73843
73988
  const logger = getLogger();
73844
73989
  if (options.verbose) logger.setLevel("debug");
73845
- if (options.json) reserveStdoutForPayload();
73990
+ reserveStdoutForPayload();
73846
73991
  const setup = await setupStateBackend(options);
73847
73992
  try {
73848
73993
  const refs = sortRefs(await setup.stateBackend.listStacks());
@@ -74311,16 +74456,9 @@ async function stateOrphanCommand(stackArgs, options) {
74311
74456
  }
74312
74457
  }
74313
74458
  if (!options.yes && !options.force) {
74314
- const targetList = targets.map((t) => formatStackRef(t)).join(", ");
74459
+ const targetList = targets.map((t) => formatStackRefSafe(t)).join(", ");
74315
74460
  process.stdout.write(`\nWARNING: This removes cdkd's state record for [${targetList}] only. AWS resources will NOT be deleted.\nUse 'cdkd destroy ${stackName}' if you want to delete the actual resources.\n\n`);
74316
- const rl = readline$1.createInterface({
74317
- input: process.stdin,
74318
- output: process.stdout
74319
- });
74320
- const answer = await rl.question(`Remove state for ${targetList} from s3://${setup.bucket}/${setup.prefix}/? (y/N): `);
74321
- rl.close();
74322
- const trimmed = answer.trim().toLowerCase();
74323
- if (trimmed !== "y" && trimmed !== "yes") {
74461
+ if (!await confirmStateOrphanRemoval(`Remove state for ${targetList} from s3://${setup.bucket}/${setup.prefix}/?`)) {
74324
74462
  logger.info(`Cancelled removal of state for stack: ${stackName}`);
74325
74463
  continue;
74326
74464
  }
@@ -74886,7 +75024,7 @@ async function stateRefreshObservedCommand(stackArgs, options) {
74886
75024
  }
74887
75025
  }
74888
75026
  if (!options.yes && !options.dryRun) {
74889
- const targetList = targets.map(formatStackRef).join(", ");
75027
+ const targetList = targets.map(formatStackRefSafe).join(", ");
74890
75028
  if (!await confirmRefresh(`Refresh observedProperties for ${targets.length} stack(s) (${targetList})?`)) {
74891
75029
  logger.info("Aborted.");
74892
75030
  return;
@@ -75067,17 +75205,34 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
75067
75205
  });
75068
75206
  }
75069
75207
  }
75070
- async function confirmRefresh(prompt) {
75071
- const rl = readline$1.createInterface({
75072
- input: process.stdin,
75073
- output: process.stdout
75208
+ /**
75209
+ * `cdkd state orphan`'s confirmation prompt. Its only call site is inside the
75210
+ * `if (!options.yes && !options.force)` block in `stateOrphanCommand`, which
75211
+ * is what keeps `confirmOrRefuse`'s non-interactive refusal (issue #2275)
75212
+ * from firing on a flagged run.
75213
+ *
75214
+ * The `(y/N): ` suffix is preserved verbatim from before issue #2275 folded
75215
+ * the guard in: it is user-visible output, and only this prompt and
75216
+ * `cdkd rollback` ever spelled it that way.
75217
+ *
75218
+ * Exported for unit testing — internal to the state-orphan flow otherwise.
75219
+ */
75220
+ async function confirmStateOrphanRemoval(prompt) {
75221
+ return confirmOrRefuse(prompt, {
75222
+ suffix: " (y/N): ",
75223
+ refusal: "The cdkd state orphan confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes (or -f / --force) to confirm the removal, or run the command from a real terminal."
75074
75224
  });
75075
- try {
75076
- const ans = await rl.question(`${prompt} [y/N] `);
75077
- return /^y(es)?$/i.test(ans.trim());
75078
- } finally {
75079
- rl.close();
75080
- }
75225
+ }
75226
+ /**
75227
+ * `cdkd state refresh-observed`'s confirmation prompt. Its only call site is
75228
+ * inside the `if (!options.yes && !options.dryRun)` block above, which is what
75229
+ * keeps `confirmOrRefuse`'s non-interactive refusal (issue #2275) from firing
75230
+ * on a `--yes` run.
75231
+ *
75232
+ * Exported for unit testing — internal to the refresh-observed flow otherwise.
75233
+ */
75234
+ async function confirmRefresh(prompt) {
75235
+ return confirmOrRefuse(prompt, { refusal: "The cdkd state refresh-observed confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm the refresh, or run the command from a real terminal." });
75081
75236
  }
75082
75237
  /**
75083
75238
  * Create the `state refresh-observed` subcommand.
@@ -75208,17 +75363,20 @@ function failedActionLabel(item, skipFinalSnapshot) {
75208
75363
  case "skip-failed-absent": return ` - skip ${op.logicalId} (${op.resourceType}) — no previous state available`;
75209
75364
  }
75210
75365
  }
75366
+ /**
75367
+ * `cdkd rollback`'s confirmation prompt. Exported for unit testing — internal
75368
+ * to the rollback flow otherwise, whose only call site is inside the
75369
+ * `if (!skipConfirmation)` block below.
75370
+ *
75371
+ * The `(y/N): ` suffix is preserved verbatim from before issue #2275 folded
75372
+ * the non-interactive guard into `confirmOrRefuse`: it is user-visible output,
75373
+ * and only this site and `cdkd state orphan` ever spelled it that way.
75374
+ */
75211
75375
  async function confirm(question) {
75212
- const rl = readline$1.createInterface({
75213
- input: process.stdin,
75214
- output: process.stdout
75376
+ return confirmOrRefuse(question, {
75377
+ suffix: " (y/N): ",
75378
+ refusal: "The cdkd rollback confirmation prompt cannot run in a non-interactive environment. Pass --force (or -y / --yes) to confirm the rollback, or run the command from a real terminal."
75215
75379
  });
75216
- try {
75217
- const t = (await rl.question(`${question} (y/N): `)).trim().toLowerCase();
75218
- return t === "y" || t === "yes";
75219
- } finally {
75220
- rl.close();
75221
- }
75222
75380
  }
75223
75381
  async function rollbackCommand(stackArg, options) {
75224
75382
  const logger = getLogger();
@@ -77691,17 +77849,16 @@ function stringifyForAudit(value) {
77691
77849
  if (typeof value === "string") return JSON.stringify(value);
77692
77850
  return JSON.stringify(value);
77693
77851
  }
77852
+ /**
77853
+ * `cdkd orphan`'s confirmation prompt. Its only call site is inside the
77854
+ * `if (!options.yes && !options.force)` block above, which is what keeps
77855
+ * `confirmOrRefuse`'s non-interactive refusal (issue #2275) from firing on a
77856
+ * flagged run.
77857
+ *
77858
+ * Exported for unit testing — internal to the command flow otherwise.
77859
+ */
77694
77860
  async function confirmPrompt$1(prompt) {
77695
- const rl = readline$1.createInterface({
77696
- input: process.stdin,
77697
- output: process.stdout
77698
- });
77699
- try {
77700
- const ans = await rl.question(`${prompt} [y/N] `);
77701
- return /^y(es)?$/i.test(ans.trim());
77702
- } finally {
77703
- rl.close();
77704
- }
77861
+ return confirmOrRefuse(prompt, { refusal: "The cdkd orphan confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes (or -f / --force) to confirm the orphan, or run the command from a real terminal." });
77705
77862
  }
77706
77863
  /**
77707
77864
  * Create the top-level `cdkd orphan` command.
@@ -78714,17 +78871,15 @@ function formatOutcome(outcome) {
78714
78871
  case "failed": return "✗";
78715
78872
  }
78716
78873
  }
78874
+ /**
78875
+ * `cdkd import`'s confirmation prompt. Its only call site is inside the
78876
+ * `if (!options.yes)` block above, which is what keeps `confirmOrRefuse`'s
78877
+ * non-interactive refusal (issue #2275) from firing on a `--yes` run.
78878
+ *
78879
+ * Exported for unit testing — internal to the command flow otherwise.
78880
+ */
78717
78881
  async function confirmPrompt(prompt) {
78718
- const rl = readline$1.createInterface({
78719
- input: process.stdin,
78720
- output: process.stdout
78721
- });
78722
- try {
78723
- const ans = await rl.question(`${prompt} [y/N] `);
78724
- return /^y(es)?$/i.test(ans.trim());
78725
- } finally {
78726
- rl.close();
78727
- }
78882
+ return confirmOrRefuse(prompt, { refusal: "The cdkd import confirmation prompt cannot run in a non-interactive environment. Pass -y / --yes to confirm the state write, or run the command from a real terminal." });
78728
78883
  }
78729
78884
  /**
78730
78885
  * Create the `cdkd import` top-level command.
@@ -89038,4 +89193,4 @@ function buildProgram() {
89038
89193
 
89039
89194
  //#endregion
89040
89195
  export { buildProgram };
89041
- //# sourceMappingURL=program-CjlLza5f.js.map
89196
+ //# sourceMappingURL=program-CjJoJOIE.js.map