@go-to-k/cdkd 0.290.24 → 0.290.25
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/{asg-provider-CHeJf5zw.js → asg-provider-DL62n7uG.js} +2 -2
- package/dist/{asg-provider-CHeJf5zw.js.map → asg-provider-DL62n7uG.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-BcNBidfy.js → deploy-engine-DtFiFMxh.js} +468 -220
- package/dist/deploy-engine-DtFiFMxh.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-DaoJ7P4H.js → program-babtsurS.js} +395 -84
- package/dist/{program-DaoJ7P4H.js.map → program-babtsurS.js.map} +1 -1
- package/dist/{version-PsB8OjhM.js → version-CduXA6UL.js} +2 -2
- package/dist/{version-PsB8OjhM.js.map → version-CduXA6UL.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-BcNBidfy.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as secretBearing, $n as
|
|
1
|
+
import { $ as secretBearing, $i as normalizeAwsError, $n as redactSecretsForState, $r as synthesisStatusMessage, $t as s3BucketArn, A as extractDeploymentEventError, An as repairMalformedResourcePropertiesForReadOnly, Ar as getBootstrapMarkerKey, At as isImpossibleEmptyStoredAttribute, B as toCfnInteger, Bn as STATE_SOURCED_READBACK_RULES, Br as describeDockerFailure, Bt as coerceCfnBoolean, C as buildFinalSnapshotIdentifier, Ci as processStackMessages, Cn as refuseMalformedNestedChildOutputs, Cr as createAssetRedirectResolver, Ct as isTerminationProtectionPropagationError, D as refusesFinalSnapshot, Di as getAwsClients, Dn as refuseMalformedResourcesForDestroy, Dr as BOOTSTRAP_MARKER_PREFIX, Dt as cfnRefValueFromPhysicalId, E as isFinalSnapshotError, Ei as AwsClients, En as refuseMalformedResourcePropertiesForOrphan, Er as AssetModeResolver, Et as carriesFetchableDynamicReference, F as isStatefulRecreateTargetSync, Fi as DeployCancelledError, Fn as withRetry, Fr as validateContainerRepoName, Ft as isSettledInstanceState, G as gray, Gi as ResourceTimeoutError, Gn as errorCauseChain, Gr as redactDockerArgvValues, Gt as readConfigString, H as formatResourceLine, Hi as NestedStackChildDirectDestroyError, Hn as carriesSecretMask, Hr as formatDockerLoginError, Ht as configBooleanRefusal, I as renderStatefulReason, Ii as DynamicReferenceRegionAmbiguousError, In as DagBuilder, Ir as buildDenyExternalAccessPolicy, It as WAFv2WebACLProvider, J as yellow, Ji as StackTerminationProtectionError, Jn as markSameGenerationBag, Jr as escapeRegExp, Jt as requireConfigObject, K as green, Ki as ResourceUpdateNotSupportedError, Kn as identityKeyFor, Kr as runDockerForeground, Kt as replayWarn, L as WARM_THROUGHPUT_MEMBERS, Li as IntrinsicResolutionRefusalError, Ln as TemplateParser, Lr as buildDockerImage, Lt as normalizeAwsTagsToCfn, M as planOrphanAdoption, Mn as findActionableSilentDrops, Mr as parseBootstrapMarker, Mt as isUnboundTemplateParameter, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Ni as CrossAccountSecretRefusalError, Nn as findSilentDropProperties, Nr as readBootstrapMarkerBody, Nt as parameterTypeMayLoseSecretIdentity, O as unsupportedFinalSnapshotError, Oi as resetAwsClients, On as refuseMalformedState, Or as assertAssetBucketRegion, Ot as coerceParameterTypedValue, P as isStatefulRecreateTargetForReplace, Pn as describeTypeWithThrottleRetry, Pr as validateAssetBucketName, Pt as refStateLookupFromResource, Q as isExportAliasCollision, Qn as recoverMaskedOutput, Qr as Synthesizer, Qt as withSharedDrainBudget, R as coerceWarmThroughput$1, Ri as LocalInvokeBuildError$1, Rn as STATE_SOURCED_BASELINE_RULES, Rr as describeDockerCapturedOutput, Rt as resolveExplicitPhysicalId, S as PRE_DELETE_SNAPSHOT_TYPES, Si as AssemblyReader, Sn as refuseDivergentRecordRegionForDestroy, Sr as buildAssetRedirectMap, St as disableInstanceApiTermination, T as createPreDeleteFinalSnapshot, Ti as resolveBucketRegion, Tn as refuseMalformedOutputsForDestroy, Tr as rewriteTemplateAssetReferences, Tt as carriesDynamicReference, U as bold, Ui as PartialFailureError, Un as createSecretMasker, Ur as getDockerCmd, Ut as configIntegerRefusal, V as toFiniteNumber, Vi as MalformedProducerRecordRefusalError, Vn as TEMPLATE_SOURCED_RULES, Vr as dockerSpawnEnvWithSensitive, Vt as coerceCfnInteger, W as cyan, Wi as ProvisioningError, Wn as dynamicReferenceTokens, Wr as partitionSensitiveEnv, Wt as configStringRefusal, X as collectPublishedOutputNames, Xn as maskSecretsInText, Xr as AssetManifestLoader, Xt as classifyReplaySecretRegion, Y as collectDeclaredOutputNames, Yn as maskSecretsInError, Yr as stripControlChars, Yt as requireConfigString, Z as exportAliasCollisionScrubWarning, Zn as recordMaskOnlyValue, Zr as getDockerImageBySourceHash, Zt as producerRegionsFromState, _ as isSecretBearingReferenceString, _i as decodeListingKey, _n as malformedRenderedContainersWarning, _r as orphansCarriedFrom, _t as isWaitAbandonedError, a as DeploymentEventsStore, aa as markNonRetryable, ai as resolveSkipPrefix, an as applyCallerIdentityCredentials, ar as buildLockContentionMessage, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bi as canonicalizeRegion, bn as malformedStateRefusalMessage, br as stringifyValue, bt as deleteIndeterminateGuards, c as replayFailedOperations, ca as describeAwsFailure, ci as resolveUseCdkBootstrapAssets, cn as INTRINSIC_KEYS, cr as shellQuote, ct as createMaskedRetryLogger, d as updatePartialReason, di as CFN_TEMPLATE_BODY_LIMIT, dn as hasReadableResources, dr as exportNamesCarriedFrom, dt as beginCommandInterruptScope, ea as withErrorHandling, ei as getDefaultStateBucketName, en as s3BucketDomainName, er as scrubResourceRecord, et as secretBearingStateKeyWarning, f as withResourceDeadline, fi as CFN_TEMPLATE_URL_LIMIT, fn as malformedExportNamesWarning, fr as hasReadableExportSet, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, gn as malformedOutputsWarning, gr as orphansAfterRollback, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as uploadCfnTemplate, hn as malformedOutputsRefusalMessage, hr as isReadableBag, ht as startInterruptWatch, i as DeploymentEventsReader, ia as isTransientServerError, ii as resolveCaptureObservedState, in as applyRoleArnIfSet, ir as buildForceUnlockCommand, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, ji as CdkdError, jn as repairMalformedResourcesForReadOnly, jr as isCrossRegionRedirect, jt as isNamelessDynamicReferenceError, k as makeCanonicalizePropertiesFn, ki as setAwsClients, kn as repairMalformedOutputsForReadOnly, kr as ensureAssetStorage, kt as getAccountInfo, l as replayRollback, la as safeStringify, li as stateBucketExistenceConfirmed, ln as STATE_RESOURCES_MALFORMED, lr as CUSTOM_RESOURCE_RESPONSE_PREFIX, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as findLargeInlineResources, mn as malformedLocalOutputsWarning, mr as importableOutputs, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, na as isRetryableTransientError, ni as resolveApp, nn as s3BucketRegionalDomainName, nr as S3StateBackend, nt as getCurrentResourceSecrets, o as planFailedOps, oa as markRedactedCause, oi as resolveStateBucketWithDefault, on as callerEnvCredentials, or as forceQuitRecoveryClause, ot as ProviderRegistry, p as maskingRetryLogger, pi as MIGRATE_TMP_PREFIX, pn as malformedExportSourceWarning, pr as importableOutputKeys, pt as interruptWatchListenerCount, q as red, qi as StackHasActiveImportsError, qn as isSingleDynamicReferenceToken, qr as runDockerStreaming, qt as requireConfigArray, r as DeployEngine, ra as isThrottlingError, ri as resolveAutoAssetStorage, rn as s3BucketWebsiteUrl, rr as rebuildClientForBucketRegion, rt as IAMRoleProvider, s as planRollback, sa as retryClassificationText, si as resolveStateBucketWithDefaultAndSource, sn as DiffCalculator, sr as formatLockExpiry, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ta as isMarkedNonRetryable, ti as getLegacyStateBucketName, tn as s3BucketDualStackDomainName, tr as LockManager, tt as secretSafeKeyDisplay, u as updatePartialMessage, ui as warnDeprecatedNoPrefixCliFlag, un as hasReadableOutputs, ur as DEFAULT_STATE_PREFIX, ut as maskerOrIdentity, v as keptWholeReasonText, vi as expectedOwnerParam, vn as malformedResourcePropertiesWarning, vr as shouldRetainResource, vt as slowCcOperationTimeoutMs, w as ccRoutedFinalSnapshotError, wn as refuseMalformedOutputs, wr as loadPublishableAssetManifest, wt as IntrinsicFunctionResolver, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xi as derivePartitionAndUrlSuffix, xn as producerRecordKey, xr as WorkGraph, xt as deleteSkipReason, y as mergeNoChangeOutputs, yi as PARTITION_TABLE, yn as malformedResourcesWarning, yr as AssetPublisher, yt as UNSPECIFIED_SKIP_REASON, z as isWarmThroughputDecrease, zi as LocalStartServiceError, zn as STATE_SOURCED_CROSS_GENERATION_RULES, zr as describeDockerExecFailure, zt as assertRegionMatch } from "./deploy-engine-DtFiFMxh.js";
|
|
2
2
|
import { C as displaySafe, S as displayIdent, _ as withSkipPrefix, b as STACK_REF_MAX_CODE_POINTS, c as PATTERN_B_NAME_OPTIONS, f as explicitNamePropertyFor, h as getCurrentStackName, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as generateResourceNameWithFallback, n as getLogger, o as runStackBuffered, p as generateResourceName, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES, v as withStackName, w as truncateCodePoints, x as UNRENDERABLE } from "./logger-CkHVgzLx.js";
|
|
3
3
|
import { n as awsClientDefaults } from "./aws-client-defaults-BoIxyCir.js";
|
|
4
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
-
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as getCdkdVersion } from "./version-CduXA6UL.js";
|
|
5
|
+
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-DL62n7uG.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, NotFound, 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";
|
|
@@ -3132,7 +3132,11 @@ async function probeStatefulRecreateTargetsAsync(targets, clients, logger = getL
|
|
|
3132
3132
|
} catch (e) {
|
|
3133
3133
|
if (e instanceof ResourceNotFoundException$5) {
|
|
3134
3134
|
let regionVerified = true;
|
|
3135
|
-
|
|
3135
|
+
if (clients.expectedRegionDiverged) {
|
|
3136
|
+
regionVerified = false;
|
|
3137
|
+
logger.warn(`--recreate-via-cc-api / --recreate-via-sdk-provider: CloudWatch Logs reported ${target.logicalId} (log group ${target.physicalId}) missing, but this stack's state record names a region that is not the one its S3 key holds, so cdkd cannot attribute that not-found to any region — run 'cdkd state show' with --verbose to see what the record claims, and repair it. Re-run with --force-stateful-recreation if the log group really is disposable (that flag clears the data guard for every target in the run). Until then it is treated as NOT provably empty.`);
|
|
3138
|
+
}
|
|
3139
|
+
const recordedRegion = clients.expectedRegionDiverged ? void 0 : foldRegion(clients.expectedRegion);
|
|
3136
3140
|
if (recordedRegion) {
|
|
3137
3141
|
let clientRegion;
|
|
3138
3142
|
try {
|
|
@@ -17831,6 +17835,20 @@ function hasUsableDeclaredCapacity(entry) {
|
|
|
17831
17835
|
*/
|
|
17832
17836
|
const PROVISIONED_CAPACITY_MEMBERS = ["ReadCapacityUnits", "WriteCapacityUnits"];
|
|
17833
17837
|
/**
|
|
17838
|
+
* The two `OnDemandThroughput` members, named once for the same reason
|
|
17839
|
+
* {@link PROVISIONED_CAPACITY_MEMBERS} is (issue
|
|
17840
|
+
* [#3265](https://github.com/go-to-k/cdkd/issues/3265)). CloudFormation's
|
|
17841
|
+
* `OnDemandThroughput` block carries the same two members at the TABLE level
|
|
17842
|
+
* and inside every `GlobalSecondaryIndexes[]` entry, and all FOUR send sites
|
|
17843
|
+
* read them through
|
|
17844
|
+
* {@link DynamoDBTableProvider.coerceOnDemandCeilingsForSend}.
|
|
17845
|
+
*
|
|
17846
|
+
* Both are `Long` in the SDK model and BOTH ARE OPTIONAL — the property that
|
|
17847
|
+
* makes this block's drop decision differ from its `ProvisionedThroughput`
|
|
17848
|
+
* sibling's. See {@link DynamoDBTableProvider.warnUnusableOnDemandCeiling}.
|
|
17849
|
+
*/
|
|
17850
|
+
const ON_DEMAND_CEILING_MEMBERS = ["MaxReadRequestUnits", "MaxWriteRequestUnits"];
|
|
17851
|
+
/**
|
|
17834
17852
|
* The capacity both TABLE-level forwarders substitute for a member the
|
|
17835
17853
|
* template does not declare at all — `create()`'s `CreateTable` and the
|
|
17836
17854
|
* BillingMode flip's `UpdateTable`. It is the pre-issue-#3147 `?? 5`, kept
|
|
@@ -18290,14 +18308,20 @@ function reverseMapSecondaryIndex(live, desired, bagInformative) {
|
|
|
18290
18308
|
* in PR review round 6; the carve-out is gone because the divergence is.
|
|
18291
18309
|
* - `ProvisionedThroughput` / `OnDemandThroughput` keep TRUTHINESS, which is
|
|
18292
18310
|
* still what their write path does: `applyGsiUpdates` gates on
|
|
18293
|
-
* `gsi.ProvisionedThroughput`
|
|
18294
|
-
*
|
|
18295
|
-
* [#3255](https://github.com/go-to-k/cdkd/issues/3255)
|
|
18311
|
+
* `gsi.ProvisionedThroughput` / `gsi.OnDemandThroughput` alone, and NEITHER
|
|
18312
|
+
* block's Integer coercion — `ProvisionedThroughput`'s from issue
|
|
18313
|
+
* [#3255](https://github.com/go-to-k/cdkd/issues/3255), across ALL FOUR
|
|
18296
18314
|
* per-index send sites (`create()`'s mapper plus `applyGsiUpdates`'
|
|
18297
|
-
* adopted-repair, `Create` and same-name `Update` actions)
|
|
18298
|
-
*
|
|
18299
|
-
*
|
|
18300
|
-
*
|
|
18315
|
+
* adopted-repair, `Create` and same-name `Update` actions);
|
|
18316
|
+
* `OnDemandThroughput`'s from issue
|
|
18317
|
+
* [#3265](https://github.com/go-to-k/cdkd/issues/3265), across its own four
|
|
18318
|
+
* (the two TABLE-level forwards plus `create()`'s mapper and
|
|
18319
|
+
* `applyGsiUpdates`' `Create` action) — ever SUPPRESSES the block: each
|
|
18320
|
+
* rewrites members inside a block that is sent either way, so "would cdkd
|
|
18321
|
+
* send this block" is unchanged by them, even where both members were
|
|
18322
|
+
* dropped and an empty block goes out. That is the reason
|
|
18323
|
+
* {@link DynamoDBTableProvider.coerceOnDemandCeilingsForSend} drops a
|
|
18324
|
+
* MEMBER and never the block. Widening
|
|
18301
18325
|
* `isSendableWarmThroughput` over them would be wrong twice over — it reads
|
|
18302
18326
|
* members those blocks do not have (`ReadUnitsPerSecond` vs
|
|
18303
18327
|
* `ReadCapacityUnits`), so every declared capacity would read as undeclared.
|
|
@@ -18495,7 +18519,7 @@ var DynamoDBTableProvider = class {
|
|
|
18495
18519
|
WriteCapacityUnits: tableCapacityForSend(pt, "WriteCapacityUnits", onUnusable)
|
|
18496
18520
|
};
|
|
18497
18521
|
}
|
|
18498
|
-
if (properties["OnDemandThroughput"]) createParams.OnDemandThroughput = properties["OnDemandThroughput"];
|
|
18522
|
+
if (properties["OnDemandThroughput"]) createParams.OnDemandThroughput = this.coerceOnDemandCeilingsForSend(`AWS::DynamoDB::Table ${logicalId}`, properties["OnDemandThroughput"], maskSecrets);
|
|
18499
18523
|
const createWarmThroughput = this.coerceWarmThroughputForSend(`AWS::DynamoDB::Table ${logicalId}`, properties["WarmThroughput"], maskSecrets);
|
|
18500
18524
|
if (createWarmThroughput) createParams.WarmThroughput = createWarmThroughput;
|
|
18501
18525
|
else this.warnRefusedWarmThroughput(`AWS::DynamoDB::Table ${logicalId}`, properties["WarmThroughput"], maskSecrets);
|
|
@@ -18664,7 +18688,7 @@ var DynamoDBTableProvider = class {
|
|
|
18664
18688
|
if (properties["OnDemandThroughput"]) {
|
|
18665
18689
|
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
18666
18690
|
TableName: physicalId,
|
|
18667
|
-
OnDemandThroughput: properties["OnDemandThroughput"]
|
|
18691
|
+
OnDemandThroughput: this.coerceOnDemandCeilingsForSend(`AWS::DynamoDB::Table ${logicalId}`, properties["OnDemandThroughput"], maskSecrets)
|
|
18668
18692
|
}));
|
|
18669
18693
|
await this.waitForTableActiveAfterUpdate(physicalId);
|
|
18670
18694
|
this.logger.debug(`Updated OnDemandThroughput on DynamoDB table ${physicalId}`);
|
|
@@ -19334,7 +19358,7 @@ var DynamoDBTableProvider = class {
|
|
|
19334
19358
|
KeySchema: gsi.KeySchema,
|
|
19335
19359
|
Projection: gsi.Projection,
|
|
19336
19360
|
...gsi.ProvisionedThroughput ? { ProvisionedThroughput: this.indexCapacityForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.ProvisionedThroughput, maskSecrets) } : {},
|
|
19337
|
-
...gsi.OnDemandThroughput ? { OnDemandThroughput: gsi.OnDemandThroughput } : {},
|
|
19361
|
+
...gsi.OnDemandThroughput ? { OnDemandThroughput: this.coerceOnDemandCeilingsForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.OnDemandThroughput, maskSecrets) } : {},
|
|
19338
19362
|
...createWarm ? { WarmThroughput: createWarm } : {}
|
|
19339
19363
|
} });
|
|
19340
19364
|
} else {
|
|
@@ -19443,16 +19467,20 @@ var DynamoDBTableProvider = class {
|
|
|
19443
19467
|
return this.coerceIndexCapacityBlock(scope, declared, maskSecrets) ?? declared;
|
|
19444
19468
|
}
|
|
19445
19469
|
/**
|
|
19446
|
-
* One `CreateTable` index entry with
|
|
19447
|
-
* `WarmThroughput` (PR review round 6)
|
|
19448
|
-
* [#3255](https://github.com/go-to-k/cdkd/issues/3255))
|
|
19470
|
+
* One `CreateTable` index entry with ALL THREE of its throughput blocks
|
|
19471
|
+
* coerced — `WarmThroughput` (PR review round 6), `ProvisionedThroughput`
|
|
19472
|
+
* (issue [#3255](https://github.com/go-to-k/cdkd/issues/3255)) and
|
|
19473
|
+
* `OnDemandThroughput` (issue
|
|
19474
|
+
* [#3265](https://github.com/go-to-k/cdkd/issues/3265)).
|
|
19449
19475
|
*
|
|
19450
19476
|
* `create()` forwards the declared `GlobalSecondaryIndexes` array to
|
|
19451
19477
|
* `CreateTable`, so it is a SEND SITE for the same per-index values the
|
|
19452
19478
|
* update path coerces. `WarmThroughput` was the one the round-5 sweep
|
|
19453
19479
|
* missed; `ProvisionedThroughput` was missed by round 6 AND by issue #3147's
|
|
19454
|
-
* own enumeration
|
|
19455
|
-
*
|
|
19480
|
+
* own enumeration; `OnDemandThroughput` was missed a THIRD time, by #3265's
|
|
19481
|
+
* own three-site grep — all for one reason, that this forwarder is a CAST
|
|
19482
|
+
* rather than a call, so no sweep's grep could see it. The
|
|
19483
|
+
* BillingMode flip's per-index reader
|
|
19456
19484
|
* ({@link readCapacityNumber}) has been on the grammar since #3147, so until
|
|
19457
19485
|
* now create and flip disagreed about the same property: a CFn-legal
|
|
19458
19486
|
* `ReadCapacityUnits: '7'` reached `CreateTable` as a STRING in a `number`
|
|
@@ -19464,18 +19492,20 @@ var DynamoDBTableProvider = class {
|
|
|
19464
19492
|
* and a provider that edits it in place would change what state reports cdkd
|
|
19465
19493
|
* sent.
|
|
19466
19494
|
*
|
|
19467
|
-
* An entry that is not a plain object, and one declaring
|
|
19468
|
-
*
|
|
19469
|
-
* declare one is rebuilt whenever that block's coercion changes
|
|
19470
|
-
* the `WarmThroughput` half rebuilds even for an already-numeric
|
|
19471
|
-
* which is pre-existing behaviour and costs one spread per declared
|
|
19495
|
+
* An entry that is not a plain object, and one declaring NONE of the three
|
|
19496
|
+
* throughput blocks, is returned UNCHANGED — identity included. An entry that
|
|
19497
|
+
* DOES declare one is rebuilt whenever that block's coercion changes
|
|
19498
|
+
* anything; the `WarmThroughput` half rebuilds even for an already-numeric
|
|
19499
|
+
* value, which is pre-existing behaviour and costs one spread per declared
|
|
19500
|
+
* block.
|
|
19472
19501
|
*/
|
|
19473
19502
|
coerceIndexThroughputForCreate(logicalId, entry, maskSecrets = (text) => text) {
|
|
19474
19503
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return entry;
|
|
19475
19504
|
const bag = entry;
|
|
19476
19505
|
const declaredWarm = bag["WarmThroughput"];
|
|
19477
19506
|
const declaredCapacity = bag["ProvisionedThroughput"];
|
|
19478
|
-
|
|
19507
|
+
const declaredOnDemand = bag["OnDemandThroughput"];
|
|
19508
|
+
if (declaredWarm === void 0 && !isPlainCapacityBlock(declaredCapacity) && !isPlainCapacityBlock(declaredOnDemand)) return entry;
|
|
19479
19509
|
const scope = this.indexScope(entry.IndexName, logicalId, maskSecrets);
|
|
19480
19510
|
let out = entry;
|
|
19481
19511
|
if (declaredWarm !== void 0) {
|
|
@@ -19494,6 +19524,13 @@ var DynamoDBTableProvider = class {
|
|
|
19494
19524
|
...out,
|
|
19495
19525
|
ProvisionedThroughput: capacity
|
|
19496
19526
|
};
|
|
19527
|
+
if (isPlainCapacityBlock(declaredOnDemand)) {
|
|
19528
|
+
const onDemand = this.coerceOnDemandCeilingsForSend(scope, declaredOnDemand, maskSecrets);
|
|
19529
|
+
if (onDemand !== declaredOnDemand) out = {
|
|
19530
|
+
...out,
|
|
19531
|
+
OnDemandThroughput: onDemand
|
|
19532
|
+
};
|
|
19533
|
+
}
|
|
19497
19534
|
return out;
|
|
19498
19535
|
}
|
|
19499
19536
|
/**
|
|
@@ -19535,9 +19572,14 @@ var DynamoDBTableProvider = class {
|
|
|
19535
19572
|
* the template had declared it, with no later call to correct it. Only
|
|
19536
19573
|
* the TABLE level defaults.
|
|
19537
19574
|
* - **A block that is not a plain object is forwarded VERBATIM**, the
|
|
19538
|
-
* fail-OPEN direction this file takes everywhere:
|
|
19539
|
-
*
|
|
19540
|
-
*
|
|
19575
|
+
* fail-OPEN direction this file takes everywhere: a STRING, a NUMBER or an
|
|
19576
|
+
* ARRAY block is AWS's to reject by name, and that is also the pre-existing
|
|
19577
|
+
* behaviour of this forwarder. An unresolved intrinsic (`{Ref: 'X'}`) is
|
|
19578
|
+
* NOT an example of it — that IS a plain object, so it takes the member
|
|
19579
|
+
* loop and survives through the preserve-unknown-member rule below. Same
|
|
19580
|
+
* destination, different route; corrected by the review of issue
|
|
19581
|
+
* [#3265](https://github.com/go-to-k/cdkd/issues/3265), which found the
|
|
19582
|
+
* wrong example here and at {@link coerceOnDemandCeilingsForSend}.
|
|
19541
19583
|
*
|
|
19542
19584
|
* A member name the grammar does not know is preserved untouched, for the
|
|
19543
19585
|
* same fail-open reason — a misspelled `ReadCapacityUnit` reaches AWS and is
|
|
@@ -19560,6 +19602,178 @@ var DynamoDBTableProvider = class {
|
|
|
19560
19602
|
return rewritten;
|
|
19561
19603
|
}
|
|
19562
19604
|
/**
|
|
19605
|
+
* One `OnDemandThroughput` block for the wire, with each DECLARED member read
|
|
19606
|
+
* through CloudFormation's DynamoDB Integer grammar (issue
|
|
19607
|
+
* [#3265](https://github.com/go-to-k/cdkd/issues/3265)).
|
|
19608
|
+
*
|
|
19609
|
+
* Called from ALL FOUR send sites, which is the whole point — the block was
|
|
19610
|
+
* forwarded VERBATIM at every one of them, so a CFn-legal
|
|
19611
|
+
* `MaxReadRequestUnits: '100'` reached AWS as the STRING `"100"` in a `Long`
|
|
19612
|
+
* field and the request failed, while a `' 100 '` / `'0x64'` / `'1e2'`
|
|
19613
|
+
* CloudFormation refuses the template for was forwarded unchanged. The sites:
|
|
19614
|
+
*
|
|
19615
|
+
* 1. `create()`'s TABLE-level `CreateTable.OnDemandThroughput`;
|
|
19616
|
+
* 2. `create()`'s PER-INDEX `CreateTable.GlobalSecondaryIndexes[]` forward,
|
|
19617
|
+
* through {@link coerceIndexThroughputForCreate} — a CAST of the whole
|
|
19618
|
+
* entry array rather than a named `OnDemandThroughput` expression, which
|
|
19619
|
+
* is why issue #3265's own grep enumerated three sites and not four (the
|
|
19620
|
+
* same blind spot that hid this forwarder from issue #3147's sweep and
|
|
19621
|
+
* from #3255's round 6, one property over);
|
|
19622
|
+
* 3. `update()`'s TABLE-level `UpdateTable.OnDemandThroughput`;
|
|
19623
|
+
* 4. `applyGsiUpdates`' per-index `Create` action.
|
|
19624
|
+
*
|
|
19625
|
+
* FOUR and not six: `UpdateGlobalSecondaryIndexAction` declares
|
|
19626
|
+
* `OnDemandThroughput` too, and neither the adopted-index repair nor the
|
|
19627
|
+
* same-name `Update` arm ever sets it — so a ceiling EDIT on a live index is
|
|
19628
|
+
* silently discarded today. That is a MISSING SEND rather than a grammar
|
|
19629
|
+
* defect, so it is not routed here; issue
|
|
19630
|
+
* [#3287](https://github.com/go-to-k/cdkd/issues/3287) holds it, and it will
|
|
19631
|
+
* read through this function when it lands.
|
|
19632
|
+
*
|
|
19633
|
+
* THREE decisions, each with a plausible alternative:
|
|
19634
|
+
*
|
|
19635
|
+
* - **A rejected spelling drops the MEMBER, never the BLOCK.** The same
|
|
19636
|
+
* DECISION {@link coerceIndexCapacityBlock} and {@link tableCapacityForSend}
|
|
19637
|
+
* already take one property over, so this file's capacity and ceiling
|
|
19638
|
+
* families answer identically about one template value; it keeps a sibling
|
|
19639
|
+
* member the template spelled LEGALLY; and it leaves `indexDeclares`'
|
|
19640
|
+
* TRUTHINESS gate ("would cdkd SEND this block") true without
|
|
19641
|
+
* qualification, so the #1767 drift-side predicate needs no new arm. An
|
|
19642
|
+
* all-rejected block therefore goes out EMPTY, as the capacity sibling's
|
|
19643
|
+
* does. ("The same decision", NOT the same SIGNATURE — the capacity
|
|
19644
|
+
* sibling answers `undefined` for "nothing to rewrite" while this one
|
|
19645
|
+
* returns the declared block by IDENTITY. The review of this issue caught
|
|
19646
|
+
* an earlier "byte for byte the shape" here, which was false and is the
|
|
19647
|
+
* kind of claim that invites a caller to swap one for the other.)
|
|
19648
|
+
* - **What a rejected spelling COSTS differs from the capacity sibling's,
|
|
19649
|
+
* and it is stated at {@link warnUnusableOnDemandCeiling} rather than
|
|
19650
|
+
* assumed.** Both `OnDemandThroughput` members are SDK-optional, so
|
|
19651
|
+
* DynamoDB does not necessarily reject a request that omits one — which is
|
|
19652
|
+
* why the announcement cannot reuse the capacity wording, which promises
|
|
19653
|
+
* AWS will name the member. **It is NOT a promise of success either**, and
|
|
19654
|
+
* an earlier revision made it one: the outcome is per PATH (create leaves
|
|
19655
|
+
* no maximum, update KEEPS the live one since only `-1` removes it) and an
|
|
19656
|
+
* ALL-rejected block is documented invalid at every one of the four send
|
|
19657
|
+
* positions ("you must specify `MaxReadRequestUnits`,
|
|
19658
|
+
* `MaxWriteRequestUnits`, or both"), so that request should FAIL. cdkd has
|
|
19659
|
+
* not measured the empty-block send against live DynamoDB; the message
|
|
19660
|
+
* under-claims accordingly.
|
|
19661
|
+
* - **A block that is not a plain object is forwarded VERBATIM**, the
|
|
19662
|
+
* fail-OPEN direction this file takes everywhere: a STRING, a NUMBER or an
|
|
19663
|
+
* ARRAY `OnDemandThroughput` is AWS's to reject by name, and that is the
|
|
19664
|
+
* pre-existing behaviour of every one of these forwarders. Note what this
|
|
19665
|
+
* arm does NOT cover: an unresolved intrinsic (`{Ref: 'X'}`) IS a plain
|
|
19666
|
+
* object, so it takes the member loop instead and survives through the
|
|
19667
|
+
* preserve-unknown-member rule below — same destination, different route.
|
|
19668
|
+
* {@link isPlainCapacityBlock}'s own doc is the authority; the review of
|
|
19669
|
+
* this issue found that example wrong here and at
|
|
19670
|
+
* {@link coerceIndexCapacityBlock}.
|
|
19671
|
+
*
|
|
19672
|
+
* A member name the grammar does not know is preserved untouched, for the
|
|
19673
|
+
* same fail-open reason. Returns the DECLARED object by IDENTITY when nothing
|
|
19674
|
+
* needs rewriting, so a numeric template rebuilds nothing and
|
|
19675
|
+
* {@link coerceIndexThroughputForCreate} can tell "unchanged" from "rewritten"
|
|
19676
|
+
* without a structural compare.
|
|
19677
|
+
*
|
|
19678
|
+
* **Known residual, deliberately not closed here.** Wherever the call
|
|
19679
|
+
* SUCCEEDS, state records the spelling the TEMPLATE declared while AWS holds
|
|
19680
|
+
* a different ceiling (none on a create, the previous one on an update) — the
|
|
19681
|
+
* permanent phantom-drift class
|
|
19682
|
+
* `.claude/rules/provider-property-fidelity.md` covers, which
|
|
19683
|
+
* {@link coerceIndexCapacityBlock} escapes only because ITS drop makes AWS
|
|
19684
|
+
* fail. Closing it needs `effectiveProperties` AND its
|
|
19685
|
+
* `canonicalizeDesiredProperties` twin (the rule requires the pair; shipping
|
|
19686
|
+
* the first alone re-issues a no-op `UpdateTable` on every later deploy), and
|
|
19687
|
+
* this provider implements neither today. Tracked as issue
|
|
19688
|
+
* [#3286](https://github.com/go-to-k/cdkd/issues/3286); the warning names the
|
|
19689
|
+
* residual so a user is not left to discover it from a `cdkd drift` line. The
|
|
19690
|
+
* `AWS::DynamoDB::GlobalTable` sibling answers the SAME way for the same
|
|
19691
|
+
* spelling — `collectTableOnDemandCeilings` coerces through
|
|
19692
|
+
* `coerceCfnInteger` and a rejected ceiling is reported and LEFT UNSENT, with
|
|
19693
|
+
* AWS untouched and no substituted value (its 5/5 FALLBACK is its PROVISIONED
|
|
19694
|
+
* capacity answer, not its ceiling one) — so the two types do not diverge.
|
|
19695
|
+
*/
|
|
19696
|
+
coerceOnDemandCeilingsForSend(scope, declared, maskSecrets) {
|
|
19697
|
+
if (!isPlainCapacityBlock(declared)) return declared;
|
|
19698
|
+
const block = declared;
|
|
19699
|
+
let rewritten;
|
|
19700
|
+
for (const member of ON_DEMAND_CEILING_MEMBERS) {
|
|
19701
|
+
if (!(member in block)) continue;
|
|
19702
|
+
const coerced = coerceCfnInteger(block[member]);
|
|
19703
|
+
if (coerced === block[member]) continue;
|
|
19704
|
+
rewritten ??= { ...block };
|
|
19705
|
+
if (coerced === void 0) {
|
|
19706
|
+
this.warnUnusableOnDemandCeiling(scope, member, block[member], maskSecrets);
|
|
19707
|
+
delete rewritten[member];
|
|
19708
|
+
} else rewritten[member] = coerced;
|
|
19709
|
+
}
|
|
19710
|
+
return rewritten ?? block;
|
|
19711
|
+
}
|
|
19712
|
+
/**
|
|
19713
|
+
* Announce an `OnDemandThroughput` member a forwarder DROPPED (issue
|
|
19714
|
+
* [#3265](https://github.com/go-to-k/cdkd/issues/3265)).
|
|
19715
|
+
*
|
|
19716
|
+
* A SEPARATE sentence from {@link warnUnusableProvisionedCapacity}, and the
|
|
19717
|
+
* difference is the load-bearing part rather than a wording preference: that
|
|
19718
|
+
* one can promise "DynamoDB will reject the request naming this member",
|
|
19719
|
+
* because both `ProvisionedThroughput` members are REQUIRED wherever the
|
|
19720
|
+
* block is declared. Both `OnDemandThroughput` members are OPTIONAL, so the
|
|
19721
|
+
* same promise would send the user looking for a failure that may never come.
|
|
19722
|
+
*
|
|
19723
|
+
* What it says instead is PER PATH, because the outcome genuinely differs and
|
|
19724
|
+
* an earlier revision of this message asserted ONE outcome for all of them —
|
|
19725
|
+
* wrongly in both directions (the review of this issue):
|
|
19726
|
+
*
|
|
19727
|
+
* - on a CREATE the table comes up with no maximum for the dropped half;
|
|
19728
|
+
* - on an UPDATE DynamoDB KEEPS whatever maximum the table already carries.
|
|
19729
|
+
* Omitting a member is NOT "no maximum" there — `-1` is AWS's documented
|
|
19730
|
+
* way to REMOVE one (`OnDemandThroughput`'s own SDK model doc), so a
|
|
19731
|
+
* dropped member silently leaves the live ceiling in force;
|
|
19732
|
+
* - when NO member survived, the block {@link coerceOnDemandCeilingsForSend}
|
|
19733
|
+
* sends is EMPTY. AWS's model says at all four of this provider's send
|
|
19734
|
+
* positions that "if you use this parameter, you must specify
|
|
19735
|
+
* `MaxReadRequestUnits`, `MaxWriteRequestUnits`, or both", which reads
|
|
19736
|
+
* like a LOUD failure — and it is NOT enforced.
|
|
19737
|
+
*
|
|
19738
|
+
* MEASURED us-east-1 2026-09-17, on the go-to-k/cdkd#3291 review's
|
|
19739
|
+
* request: `CreateTable` with `OnDemandThroughput: {}` and
|
|
19740
|
+
* `BillingMode: PAY_PER_REQUEST` is ACCEPTED, the table reaches ACTIVE,
|
|
19741
|
+
* and `DescribeTable` reports NO `OnDemandThroughput` at all. So the
|
|
19742
|
+
* all-rejected case is the SILENT one, not the loud one: the deploy goes
|
|
19743
|
+
* green with no ceiling applied.
|
|
19744
|
+
*
|
|
19745
|
+
* That matters because it is a REGRESSION IN LOUDNESS introduced by this
|
|
19746
|
+
* very change. Before it, a `' 100 '` reached AWS as a string in a Long
|
|
19747
|
+
* field and the request FAILED, certainly. After it, the value is dropped
|
|
19748
|
+
* and the deploy succeeds. The warning is therefore the ONLY signal a user
|
|
19749
|
+
* gets for the all-rejected case, which is why it is emitted per member
|
|
19750
|
+
* and why its text was corrected to say so rather than to promise a
|
|
19751
|
+
* failure that does not come.
|
|
19752
|
+
*
|
|
19753
|
+
* The caller cannot tell the three cases apart from inside the member loop,
|
|
19754
|
+
* so the message names all three rather than asserting the one it cannot
|
|
19755
|
+
* know.
|
|
19756
|
+
*
|
|
19757
|
+
* And it names the residual: where the request does succeed, state keeps the
|
|
19758
|
+
* spelling the template declared and `cdkd drift` reports it until the
|
|
19759
|
+
* template is corrected. `-1` is called out because a user who meant the
|
|
19760
|
+
* removal must not read "spell it as a decimal integer" as excluding it.
|
|
19761
|
+
*
|
|
19762
|
+
* The CloudFormation clause is HEDGED ("is expected to"), and deliberately.
|
|
19763
|
+
* The live A/B that settled this grammar was run on
|
|
19764
|
+
* `ProvisionedThroughput.ReadCapacityUnits`, not on `MaxReadRequestUnits`.
|
|
19765
|
+
* Both are Integer members of the same type validated at the same properties
|
|
19766
|
+
* layer, so the extrapolation is sound and is what issue #3265 reasons from --
|
|
19767
|
+
* but it IS an extrapolation, and a user-facing string asserting it flatly
|
|
19768
|
+
* about an unmeasured property would be the kind of claim nothing downstream
|
|
19769
|
+
* re-checks. Measure it and the hedge can go. Raised by the
|
|
19770
|
+
* go-to-k/cdkd#3291 spec review.
|
|
19771
|
+
*/
|
|
19772
|
+
warnUnusableOnDemandCeiling(scope, member, raw, maskSecrets) {
|
|
19773
|
+
const warn = (message) => this.logger.warn(maskSecrets(message));
|
|
19774
|
+
warn(`${scope}: OnDemandThroughput.${member} ${JSON.stringify(maskLeafValue$1(raw, maskSecrets))} is not an Integer CloudFormation accepts (an optional sign and decimal digits only — no surrounding whitespace, hex, exponent or decimal point), so it was NOT sent and NO ceiling was substituted for it. Both OnDemandThroughput members are OPTIONAL to DynamoDB, so — unlike a ProvisionedThroughput member — AWS does not necessarily reject the request, and what happens next depends on the path: a CREATE gets no maximum for this half; an UPDATE KEEPS whatever maximum the table already carries, since only an explicit -1 removes one; and if NEITHER member was usable the block is sent EMPTY, which AWS ACCEPTS (measured us-east-1 2026-09-17) and stores as no maximum at all, so the deploy SUCCEEDS with no ceiling applied. cdkd's state keeps the value the template declared and \`cdkd drift\` reports the difference until the template is corrected; CloudFormation is expected to refuse the same template at properties validation. Check for an unresolved intrinsic, or spell the value as a decimal integer (\`-1\` removes an existing maximum).`);
|
|
19775
|
+
}
|
|
19776
|
+
/**
|
|
19563
19777
|
* Announce a `ProvisionedThroughput` member a forwarder DROPPED (issue
|
|
19564
19778
|
* [#3147](https://github.com/go-to-k/cdkd/issues/3147); widened to the
|
|
19565
19779
|
* per-index create forwarder by issue
|
|
@@ -62206,6 +62420,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62206
62420
|
const guardIndeterminateTargets = /* @__PURE__ */ new Set();
|
|
62207
62421
|
const regionForState = state.region ?? ctx.baseRegion;
|
|
62208
62422
|
refuseMalformedResourcesForDestroy(state, stackName, regionForState);
|
|
62423
|
+
refuseDivergentRecordRegionForDestroy(state, stackName, regionForState, ctx.divergentBodyRegion);
|
|
62209
62424
|
const resourceCount = Object.keys(state.resources).length;
|
|
62210
62425
|
const orphanCount = (state.orphans ?? []).length;
|
|
62211
62426
|
refuseMalformedOutputsForDestroy(state, stackName, regionForState);
|
|
@@ -62265,7 +62480,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62265
62480
|
try {
|
|
62266
62481
|
await ctx.lockManager.releaseLock(stackName, regionForState);
|
|
62267
62482
|
} catch (releaseErr) {
|
|
62268
|
-
logger.warn(`Failed to release lock after empty-state cleanup: ${
|
|
62483
|
+
logger.warn(`Failed to release lock after empty-state cleanup: ${describeAwsFailure(releaseErr).detail}`);
|
|
62269
62484
|
} finally {
|
|
62270
62485
|
process.removeListener("SIGINT", emptySigintHandler);
|
|
62271
62486
|
}
|
|
@@ -62324,7 +62539,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62324
62539
|
try {
|
|
62325
62540
|
switchedClients?.destroy();
|
|
62326
62541
|
} catch (destroyError) {
|
|
62327
|
-
logger.debug(`Failed to destroy region-scoped AWS clients: ${
|
|
62542
|
+
logger.debug(`Failed to destroy region-scoped AWS clients: ${describeAwsFailure(destroyError).detail}`);
|
|
62328
62543
|
}
|
|
62329
62544
|
};
|
|
62330
62545
|
if (stackRegion && stackRegion !== ctx.baseRegion) {
|
|
@@ -62393,7 +62608,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62393
62608
|
try {
|
|
62394
62609
|
await ctx.lockManager.releaseLock(stackName, regionForState);
|
|
62395
62610
|
} catch (releaseErr) {
|
|
62396
|
-
logger.warn(`Failed to release lock after strong-ref refusal/failure: ${
|
|
62611
|
+
logger.warn(`Failed to release lock after strong-ref refusal/failure: ${describeAwsFailure(releaseErr).detail}`);
|
|
62397
62612
|
}
|
|
62398
62613
|
restoreBaseRegionAndClients();
|
|
62399
62614
|
process.removeListener("SIGINT", sigintHandler);
|
|
@@ -62433,7 +62648,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62433
62648
|
await ctx.stateBackend.saveState(stackName, regionForState, buildDestroySnapshot());
|
|
62434
62649
|
logger.debug(`State persisted after deleting ${logicalId}`);
|
|
62435
62650
|
} catch (error) {
|
|
62436
|
-
logger.warn(`Failed to persist state after deleting ${logicalId} (continuing): ${
|
|
62651
|
+
logger.warn(`Failed to persist state after deleting ${logicalId} (continuing): ${describeAwsFailure(error).detail}`);
|
|
62437
62652
|
}
|
|
62438
62653
|
});
|
|
62439
62654
|
};
|
|
@@ -62629,7 +62844,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62629
62844
|
persistStateAfterDelete(logicalId);
|
|
62630
62845
|
} catch (error) {
|
|
62631
62846
|
renderer.removeTask(logicalId);
|
|
62632
|
-
const msg =
|
|
62847
|
+
const msg = describeAwsFailure(error).detail;
|
|
62633
62848
|
if (!isInterruptedWaitError(error) && !isFinalSnapshotError(error) && !isMarkedNonRetryable(error) && !isWaitAbandonedError(error) && (msg.includes("does not exist") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException"))) {
|
|
62634
62849
|
logger.debug(` ${logicalId} already deleted, removing from state`);
|
|
62635
62850
|
result.deletedCount++;
|
|
@@ -62661,7 +62876,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62661
62876
|
error: extractDeploymentEventError(wrapped)
|
|
62662
62877
|
});
|
|
62663
62878
|
} else {
|
|
62664
|
-
logger.error(` ✗ Failed to delete ${logicalId}:`,
|
|
62879
|
+
logger.error(` ✗ Failed to delete ${logicalId}:`, safeStringify(error));
|
|
62665
62880
|
result.errorCount++;
|
|
62666
62881
|
failedStateTargets.add(stateTargetFor(logicalId, resource.resourceType));
|
|
62667
62882
|
ctx.eventRecorder?.record({
|
|
@@ -62693,7 +62908,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62693
62908
|
try {
|
|
62694
62909
|
await ctx.stateBackend.saveState(stackName, regionForState, buildDestroySnapshot());
|
|
62695
62910
|
} catch (error) {
|
|
62696
|
-
logger.warn(`Failed to persist remaining state after partial destroy: ${
|
|
62911
|
+
logger.warn(`Failed to persist remaining state after partial destroy: ${describeAwsFailure(error).detail}. The state file may still list already-deleted resources; a re-run resolves them idempotently.`);
|
|
62697
62912
|
}
|
|
62698
62913
|
if (result.interrupted) logger.warn(`Destroy interrupted — ${Object.keys(remainingResources).length} resource(s) not deleted. State preserved.`);
|
|
62699
62914
|
else if (result.errorCount > 0) logger.warn(`${result.errorCount} resource(s) failed to delete. State preserved.`);
|
|
@@ -62724,14 +62939,14 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62724
62939
|
try {
|
|
62725
62940
|
renderer.stop();
|
|
62726
62941
|
} catch (rendererError) {
|
|
62727
|
-
logger.debug(`Live renderer teardown failed (continuing to release the lock): ${
|
|
62942
|
+
logger.debug(`Live renderer teardown failed (continuing to release the lock): ${describeAwsFailure(rendererError).detail}`);
|
|
62728
62943
|
}
|
|
62729
62944
|
await saveChain;
|
|
62730
62945
|
logger.debug("Releasing lock...");
|
|
62731
62946
|
try {
|
|
62732
62947
|
await ctx.lockManager.releaseLock(stackName, regionForState);
|
|
62733
62948
|
} catch (releaseErr) {
|
|
62734
|
-
logger.warn(`Failed to release lock for stack '${stackName}': ${
|
|
62949
|
+
logger.warn(`Failed to release lock for stack '${stackName}': ${describeAwsFailure(releaseErr).detail}`);
|
|
62735
62950
|
}
|
|
62736
62951
|
} finally {
|
|
62737
62952
|
process.removeListener("SIGINT", sigintHandler);
|
|
@@ -63004,6 +63219,7 @@ var NestedStackProvider = class {
|
|
|
63004
63219
|
providerRegistry: ctx.providerRegistry,
|
|
63005
63220
|
baseAwsClients: ctx.awsClients,
|
|
63006
63221
|
baseRegion: childRegion,
|
|
63222
|
+
...childStateData.divergentBodyRegion !== void 0 && { divergentBodyRegion: childStateData.divergentBodyRegion },
|
|
63007
63223
|
stateBucket: ctx.stateBucket,
|
|
63008
63224
|
skipConfirmation: true,
|
|
63009
63225
|
...ctx.exportIndexStore && { exportIndexStore: ctx.exportIndexStore },
|
|
@@ -64378,7 +64594,8 @@ async function deployCommand(stacks, options) {
|
|
|
64378
64594
|
clients: {
|
|
64379
64595
|
s3: stackAwsClients.s3,
|
|
64380
64596
|
cloudWatchLogs: stackAwsClients.cloudWatchLogs,
|
|
64381
|
-
expectedRegion: stateForRecreateCheck?.state.region
|
|
64597
|
+
expectedRegion: stateForRecreateCheck?.state.region,
|
|
64598
|
+
...stateForRecreateCheck?.divergentBodyRegion !== void 0 && { expectedRegionDiverged: true }
|
|
64382
64599
|
},
|
|
64383
64600
|
forceStatefulRecreation: options.forceStatefulRecreation ?? false
|
|
64384
64601
|
});
|
|
@@ -74363,6 +74580,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
74363
74580
|
providerRegistry,
|
|
74364
74581
|
baseAwsClients: awsClients,
|
|
74365
74582
|
baseRegion: region,
|
|
74583
|
+
...stateResult.divergentBodyRegion !== void 0 && { divergentBodyRegion: stateResult.divergentBodyRegion },
|
|
74366
74584
|
...options.profile && { profile: options.profile },
|
|
74367
74585
|
stateBucket,
|
|
74368
74586
|
statePrefix: options.statePrefix,
|
|
@@ -76117,10 +76335,10 @@ async function exportCommand(stackArg, options) {
|
|
|
76117
76335
|
}
|
|
76118
76336
|
const cfnStackName = options.cfnStackName ?? resolvedStackName;
|
|
76119
76337
|
const targetRegion = await pickStackRegion$1(stateBackend, resolvedStackName, synthedRegion, options.stackRegion);
|
|
76120
|
-
logger.info(`Migrating cdkd stack '${resolvedStackName}' (${targetRegion}) → CloudFormation stack '${cfnStackName}'`);
|
|
76338
|
+
logger.info(`Migrating cdkd stack '${resolvedStackName}' (${safeSegment(targetRegion)}) → CloudFormation stack '${cfnStackName}'`);
|
|
76121
76339
|
await assertCfnStackAbsent(awsClients.cloudFormation, cfnStackName);
|
|
76122
76340
|
const stateData = await stateBackend.getState(resolvedStackName, targetRegion);
|
|
76123
|
-
if (!stateData) throw new Error(`No cdkd state found for stack '${resolvedStackName}' (${targetRegion}). Nothing to migrate.`);
|
|
76341
|
+
if (!stateData) throw new Error(`No cdkd state found for stack '${resolvedStackName}' (${safeSegment(targetRegion)}). Nothing to migrate.`);
|
|
76124
76342
|
const { state, etag, migrationPending } = stateData;
|
|
76125
76343
|
const exportJournal = await stateBackend.loadRollbackJournal(resolvedStackName, targetRegion);
|
|
76126
76344
|
if (exportJournal && exportJournal.segments.length > 0) {
|
|
@@ -76237,7 +76455,7 @@ async function exportCommand(stackArg, options) {
|
|
|
76237
76455
|
const phase2Note = phase2Creates.length > 0 ? ` Phase 2 will then CREATE ${phase2Creates.length} non-importable resource(s) (invoking each Custom Resource's onCreate handler).` : "";
|
|
76238
76456
|
const recreateNote = recreateBeforePhase2.length > 0 ? ` cdkd will also DELETE ${recreateBeforePhase2.length} AWS resource(s) between phases so CFn can re-CREATE them in phase 2 (brief unavailability window — see plan above for the affected resources).` : "";
|
|
76239
76457
|
const unchangedClaim = recreateBeforePhase2.length > 0 ? ` All other AWS resources are unchanged on import.` : ` AWS resources are unchanged on import.`;
|
|
76240
|
-
if (!await confirmPrompt$3(`Create CloudFormation stack '${cfnStackName}' by importing ${phase1Imports.length} resource(s) from cdkd state '${resolvedStackName}' (${targetRegion})?` + phase2Note + recreateNote + unchangedClaim + ` cdkd state for '${resolvedStackName}' will be deleted on success.`)) {
|
|
76458
|
+
if (!await confirmPrompt$3(`Create CloudFormation stack '${cfnStackName}' by importing ${phase1Imports.length} resource(s) from cdkd state '${resolvedStackName}' (${safeSegment(targetRegion)})?` + phase2Note + recreateNote + unchangedClaim + ` cdkd state for '${resolvedStackName}' will be deleted on success.`)) {
|
|
76241
76459
|
logger.info("Migration cancelled. cdkd state and CloudFormation are unchanged.");
|
|
76242
76460
|
return;
|
|
76243
76461
|
}
|
|
@@ -76272,7 +76490,7 @@ async function exportCommand(stackArg, options) {
|
|
|
76272
76490
|
logger.info(` ✓ deleted ${entry.physicalId}`);
|
|
76273
76491
|
} catch (err) {
|
|
76274
76492
|
const msg = err instanceof Error ? err.message : String(err);
|
|
76275
|
-
throw new Error(`Phase 1 (IMPORT) succeeded; pre-delete of ${entry.logicalId} (${entry.resourceType}, physicalId: ${entry.physicalId}) failed: ${msg}\n\nThe CloudFormation stack '${cfnStackName}' contains the phase-1 imports but the IMPORT-unsupported resources (${recreateBeforePhase2.length} total) still exist in AWS unmanaged. cdkd state is UNCHANGED. Re-running \`cdkd export\` does NOT work — the existing-stack check rejects it. To recover manually:\n 1. Fix the failure cause (typically IAM permissions for the underlying AWS API — e.g. apigatewayv2:DeleteStage for AWS::ApiGatewayV2::Stage).\n 2. Delete the remaining AWS-side IMPORT-unsupported resources by hand:\n aws apigatewayv2 delete-stage --api-id <ApiId> --stage-name <StageName>\n (one per entry in the pre-delete list logged above).\n 3. Run the phase-2 UPDATE manually with the full synth template:\n aws cloudformation create-change-set --stack-name ${cfnStackName} \\\n --change-set-name cdkd-phase2-retry --change-set-type UPDATE \\\n --template-body file://<full-template.json>\n 4. Once phase 2 succeeds, run:
|
|
76493
|
+
throw new Error(`Phase 1 (IMPORT) succeeded; pre-delete of ${entry.logicalId} (${entry.resourceType}, physicalId: ${entry.physicalId}) failed: ${msg}\n\nThe CloudFormation stack '${cfnStackName}' contains the phase-1 imports but the IMPORT-unsupported resources (${recreateBeforePhase2.length} total) still exist in AWS unmanaged. cdkd state is UNCHANGED. Re-running \`cdkd export\` does NOT work — the existing-stack check rejects it. To recover manually:\n 1. Fix the failure cause (typically IAM permissions for the underlying AWS API — e.g. apigatewayv2:DeleteStage for AWS::ApiGatewayV2::Stage).\n 2. Delete the remaining AWS-side IMPORT-unsupported resources by hand:\n aws apigatewayv2 delete-stage --api-id <ApiId> --stage-name <StageName>\n (one per entry in the pre-delete list logged above).\n 3. Run the phase-2 UPDATE manually with the full synth template:\n aws cloudformation create-change-set --stack-name ${cfnStackName} \\\n --change-set-name cdkd-phase2-retry --change-set-type UPDATE \\\n --template-body file://<full-template.json>\n 4. Once phase 2 succeeds, run: ${orphanCommandFor(resolvedStackName, targetRegion)}\n to clean up cdkd's stale state record.`);
|
|
76276
76494
|
}
|
|
76277
76495
|
}
|
|
76278
76496
|
const phase2Count = phase2Creates.length + recreateBeforePhase2.length;
|
|
@@ -76291,10 +76509,10 @@ async function exportCommand(stackArg, options) {
|
|
|
76291
76509
|
const recreateNote = recreateBeforePhase2.length > 0 ? ` - cdkd deleted ${recreateBeforePhase2.length} AWS resource(s) before phase 2 (Stage etc.). They are gone in AWS but absent from the CFn stack. Running phase 2 UPDATE manually will CFn-CREATE them fresh.\n` : "";
|
|
76292
76510
|
throw new Error(`Phase 1 (IMPORT) succeeded; phase 2 (UPDATE) failed: ${msg}\n\nThe CloudFormation stack '${cfnStackName}' now contains the imported resources but is missing ${phase2Count} resource(s) (${phase2Creates.length} Custom Resource(s) + ${recreateBeforePhase2.length} IMPORT-unsupported). cdkd state is UNCHANGED so you can inspect what's in it, but DO NOT run \`cdkd deploy\` against this stack (the imported resources are now CFn-managed). To recover:\n` + recreateNote + ` 1. Fix the failure cause (typically an onCreate Lambda error).
|
|
76293
76511
|
2. Re-run the phase 2 UPDATE manually with the full synth template:
|
|
76294
|
-
aws cloudformation create-change-set --stack-name ${cfnStackName} \\\n --change-set-name cdkd-phase2-retry --change-set-type UPDATE \\\n --template-body file://<full-template.json>\n 3. Once phase 2 succeeds, run:
|
|
76512
|
+
aws cloudformation create-change-set --stack-name ${cfnStackName} \\\n --change-set-name cdkd-phase2-retry --change-set-type UPDATE \\\n --template-body file://<full-template.json>\n 3. Once phase 2 succeeds, run: ${orphanCommandFor(resolvedStackName, targetRegion)}\n to clean up cdkd's stale state record.`);
|
|
76295
76513
|
}
|
|
76296
76514
|
await stateBackend.deleteState(resolvedStackName, targetRegion);
|
|
76297
|
-
logger.info(`cdkd state for '${resolvedStackName}' (${targetRegion}) removed. Manage the stack with 'cdk deploy' or 'aws cloudformation' from here on.`);
|
|
76515
|
+
logger.info(`cdkd state for '${resolvedStackName}' (${safeSegment(targetRegion)}) removed. Manage the stack with 'cdk deploy' or 'aws cloudformation' from here on.`);
|
|
76298
76516
|
printNextSteps({
|
|
76299
76517
|
cfnStackName,
|
|
76300
76518
|
cdkStackName: resolvedStackName,
|
|
@@ -76322,7 +76540,7 @@ async function pickStackRegion$1(stateBackend, stackName, synthRegion, flag) {
|
|
|
76322
76540
|
}
|
|
76323
76541
|
if (flag) {
|
|
76324
76542
|
if (!refs.find((r) => r.region === flag)) {
|
|
76325
|
-
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
76543
|
+
const seen = refs.map((r) => safeSegment(r.region ?? "(legacy)")).join(", ");
|
|
76326
76544
|
throw new Error(`No state found for stack '${stackName}' in region '${flag}'. Available regions: ${seen}.`);
|
|
76327
76545
|
}
|
|
76328
76546
|
return flag;
|
|
@@ -76331,7 +76549,7 @@ async function pickStackRegion$1(stateBackend, stackName, synthRegion, flag) {
|
|
|
76331
76549
|
if (refs.find((r) => r.region === synthRegion)) return synthRegion;
|
|
76332
76550
|
}
|
|
76333
76551
|
if (refs.length === 1) return refs[0].region ?? synthRegion ?? "";
|
|
76334
|
-
const regions = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
76552
|
+
const regions = refs.map((r) => safeSegment(r.region ?? "(legacy)")).join(", ");
|
|
76335
76553
|
throw new Error(`Stack '${stackName}' has state in multiple regions: ${regions}. Re-run with --stack-region <region> to disambiguate.`);
|
|
76336
76554
|
}
|
|
76337
76555
|
function parseTemplateFile(path) {
|
|
@@ -76395,9 +76613,81 @@ function isPhase2CreatableType(resourceType) {
|
|
|
76395
76613
|
* Module-scoped rather than local to the walker: the first cut of this change
|
|
76396
76614
|
* guarded the walker's two refusals and left the root one, which is the shape
|
|
76397
76615
|
* the issue is about.
|
|
76616
|
+
*
|
|
76617
|
+
* The parameter is `unknown`, not `string | undefined`, because a record-body
|
|
76618
|
+
* value is only TYPED as a string: the region-mismatch refusal below has
|
|
76619
|
+
* rendered `{"toString": null}` since issue #2947, and `getState`'s
|
|
76620
|
+
* `divergentBodyRegion` (#3328) is declared honestly as `unknown`.
|
|
76621
|
+
* `displaySafe` has always accepted `unknown` and coerces totally, so this
|
|
76622
|
+
* widens the signature to what the function already did.
|
|
76623
|
+
*
|
|
76624
|
+
* CAPPED as well as sanitized, since go-to-k/cdkd#3328 review round 2. Every
|
|
76625
|
+
* value reaching here is a state-record field or an S3 key segment, so a
|
|
76626
|
+
* planted multi-kilobyte one — or an ARRAY, which `String` comma-joins with no
|
|
76627
|
+
* bound — pushes the rest of the refusal off the reader's screen. The bound is
|
|
76628
|
+
* the STATE-RECORD grammar rather than `IDENT_MAX_CODE_POINTS` because a
|
|
76629
|
+
* legitimate multi-level nested-child stack name runs past 255 code points,
|
|
76630
|
+
* and this function renders those; `truncateCodePoints` so a cut never lands
|
|
76631
|
+
* inside a surrogate pair.
|
|
76398
76632
|
*/
|
|
76399
76633
|
function safeSegment(value) {
|
|
76400
|
-
|
|
76634
|
+
const safe = displaySafe(value, { asciiOnly: true });
|
|
76635
|
+
if (!safe) return UNRENDERABLE;
|
|
76636
|
+
const { text, truncated } = truncateCodePoints(safe, STACK_REF_MAX_CODE_POINTS);
|
|
76637
|
+
return truncated ? `${text} [cut: ${safe.length - text.length} more characters withheld]` : text;
|
|
76638
|
+
}
|
|
76639
|
+
/**
|
|
76640
|
+
* The free-form counterpart of {@link safeSegment}: an AWS error message, not
|
|
76641
|
+
* an identifier.
|
|
76642
|
+
*
|
|
76643
|
+
* **Its BEHAVIOUR is identical today, and the separation is about the CALLER's
|
|
76644
|
+
* contract rather than about the transformation** — stated plainly because an
|
|
76645
|
+
* earlier revision of this comment claimed a "tighter cap" for identifiers that
|
|
76646
|
+
* does not exist, and a false distinction is worse than none. What differs is
|
|
76647
|
+
* what a caller may DO with the result: `safeSegment`'s callers compare it
|
|
76648
|
+
* against the raw value to decide whether an identity renders EXACTLY
|
|
76649
|
+
* ({@link orphanCommandFor}), which is meaningless for a sentence; and
|
|
76650
|
+
* `display-safe.ts` says its identifier helpers are not for free-form text, so
|
|
76651
|
+
* routing a message through the one named `Segment` invites the next reader to
|
|
76652
|
+
* give it the identifier grammar's cap. Both sanitize with the ASCII allowlist
|
|
76653
|
+
* and bound the length, because both are untrusted text bound for a terminal.
|
|
76654
|
+
*/
|
|
76655
|
+
function safeDetail(value) {
|
|
76656
|
+
const safe = displaySafe(value instanceof Error ? value.message : value, { asciiOnly: true });
|
|
76657
|
+
if (!safe) return UNRENDERABLE;
|
|
76658
|
+
const { text, truncated } = truncateCodePoints(safe, STACK_REF_MAX_CODE_POINTS);
|
|
76659
|
+
return truncated ? `${text} [cut: ${safe.length - text.length} more characters withheld]` : text;
|
|
76660
|
+
}
|
|
76661
|
+
/**
|
|
76662
|
+
* A `cdkd state orphan` line for a record-derived stack name — PASTEABLE only
|
|
76663
|
+
* when that name renders EXACTLY, and a template otherwise (issue
|
|
76664
|
+
* [#3328](https://github.com/go-to-k/cdkd/issues/3328), review round 3).
|
|
76665
|
+
*
|
|
76666
|
+
* Three messages in this file suggested that command with a name minted from
|
|
76667
|
+
* `Object.keys(state.resources)` (`walkCdkdStateStackTree` builds
|
|
76668
|
+
* `` `${parent}~${logicalId}` ``), i.e. body content anyone able to write one
|
|
76669
|
+
* state key chooses. Two rendered it RAW, so a newline or a CSI sequence in a
|
|
76670
|
+
* logical id forged whole lines around a DELETE command; the third sanitized
|
|
76671
|
+
* but did not shell-quote, so `'`, `;`, `|` and `` ` `` survived into a line an
|
|
76672
|
+
* operator pastes into a shell.
|
|
76673
|
+
*
|
|
76674
|
+
* Both halves of `malformed-resources-bag.ts`'s rule, and for its reasons:
|
|
76675
|
+
* sanitize THEN shell-quote, and gate the substituted form on the identity
|
|
76676
|
+
* rendering exactly — `displaySafe` TRIMS, so a name ending in a space renders
|
|
76677
|
+
* byte-identically to a healthy sibling, and `state orphan` DELETES a record.
|
|
76678
|
+
* Every caller emits the result LAST and unwrapped so the quoting composes; that
|
|
76679
|
+
* is an instruction to the CALLER and must not leak into the message it builds.
|
|
76680
|
+
*
|
|
76681
|
+
* `region` is REQUIRED even though the flag is optional on the command: omitting
|
|
76682
|
+
* `--stack-region` makes `state orphan` drop the record for that name in EVERY
|
|
76683
|
+
* region, which is wider than any of these messages describes. A helper whose
|
|
76684
|
+
* job is to make a destructive suggestion safe should not have an arm that
|
|
76685
|
+
* widens one.
|
|
76686
|
+
*/
|
|
76687
|
+
function orphanCommandFor(stackName, region) {
|
|
76688
|
+
const shownStack = safeSegment(stackName);
|
|
76689
|
+
if (!(typeof stackName === "string" && shownStack === stackName && typeof region === "string" && safeSegment(region) === region)) return "cdkd state orphan <stack> --stack-region <region> — spelled out because this record's name or region does NOT render exactly, so another record may render identically; list them as stored with 'cdkd state list --long' and act on the one whose key matches";
|
|
76690
|
+
return `cdkd state orphan ${shellQuote(shownStack)} --stack-region ${shellQuote(safeSegment(region))}`;
|
|
76401
76691
|
}
|
|
76402
76692
|
/**
|
|
76403
76693
|
* Recursively load the cdkd-state tree rooted at `(rootStackName, region)`.
|
|
@@ -76452,8 +76742,8 @@ async function walkCdkdStateStackTree(stackName, region, state, stateBackend) {
|
|
|
76452
76742
|
if (entry?.resourceType !== "AWS::CloudFormation::Stack") continue;
|
|
76453
76743
|
const childStackName = `${stackName}~${logicalId}`;
|
|
76454
76744
|
const childResult = await stateBackend.getState(childStackName, region);
|
|
76455
|
-
if (!childResult) throw new Error(`cdkd state is missing nested-child '${safeSegment(childStackName)}' (${safeSegment(region)}). Parent stack '${safeSegment(stackName)}' lists '${safeSegment(logicalId)}' as an ${NESTED_STACK_RESOURCE_TYPE} row but no child state file exists at 'cdkd/${safeSegment(childStackName)}/${safeSegment(region)}/state.json'. The cdkd state tree is inconsistent — re-deploy the parent stack to refresh, or
|
|
76456
|
-
if (childResult.
|
|
76745
|
+
if (!childResult) throw new Error(`cdkd state is missing nested-child '${safeSegment(childStackName)}' (${safeSegment(region)}). Parent stack '${safeSegment(stackName)}' lists '${safeSegment(logicalId)}' as an ${NESTED_STACK_RESOURCE_TYPE} row but no child state file exists at 'cdkd/${safeSegment(childStackName)}/${safeSegment(region)}/state.json'. The cdkd state tree is inconsistent — re-deploy the parent stack to refresh, or drop the parent's record and re-import with: ${orphanCommandFor(stackName, region)}`);
|
|
76746
|
+
if (childResult.divergentBodyRegion !== void 0) throw new Error(`cdkd state region mismatch: nested-child '${safeSegment(childStackName)}' has state.region='${safeSegment(childResult.divergentBodyRegion)}' but its parent '${safeSegment(stackName)}' is being walked against region='${safeSegment(region)}'. AWS does not support cross-region nested stacks; the state tree appears corrupt — re-deploy the parent stack to refresh.`);
|
|
76457
76747
|
nestedChildren.set(logicalId, await walkCdkdStateStackTree(childStackName, region, childResult.state, stateBackend));
|
|
76458
76748
|
}
|
|
76459
76749
|
return {
|
|
@@ -76534,7 +76824,7 @@ function flattenCdkdStateTreeRootFirst(tree) {
|
|
|
76534
76824
|
*/
|
|
76535
76825
|
function cdkd2cfnStackName(cdkdStackName) {
|
|
76536
76826
|
const cfnName = cdkdStackName.replace(/~/g, "-");
|
|
76537
|
-
if (!/^[a-zA-Z][-a-zA-Z0-9]*$/.test(cfnName)) throw new Error(`cdkd stack name '${cdkdStackName}' maps to CFn stack name '${cfnName}', which violates the CloudFormation stack-name constraint [a-zA-Z][-a-zA-Z0-9]* (must start with a letter; no '_', '.', '/', or other special characters). Supply an explicit name via --cfn-stack-name (root) or --cfn-child-stack-name '<cdkdName>=<cfnName>' (nested child).`);
|
|
76827
|
+
if (!/^[a-zA-Z][-a-zA-Z0-9]*$/.test(cfnName)) throw new Error(`cdkd stack name '${safeSegment(cdkdStackName)}' maps to CFn stack name '${safeSegment(cfnName)}', which violates the CloudFormation stack-name constraint [a-zA-Z][-a-zA-Z0-9]* (must start with a letter; no '_', '.', '/', or other special characters). Supply an explicit name via --cfn-stack-name (root) or --cfn-child-stack-name '<cdkdName>=<cfnName>' (nested child).`);
|
|
76538
76828
|
return cfnName;
|
|
76539
76829
|
}
|
|
76540
76830
|
/**
|
|
@@ -76560,7 +76850,7 @@ function parseCfnChildStackNameOverrides(values) {
|
|
|
76560
76850
|
if (!cdkdName) throw new Error(`--cfn-child-stack-name '${raw}' has an empty cdkd stack name.`);
|
|
76561
76851
|
if (!cfnName) throw new Error(`--cfn-child-stack-name '${raw}' has an empty CFn stack name.`);
|
|
76562
76852
|
if (!/^[a-zA-Z][-a-zA-Z0-9]*$/.test(cfnName)) throw new Error(`--cfn-child-stack-name '${raw}': CFn stack name '${cfnName}' must match [a-zA-Z][-a-zA-Z0-9]* (no '~', no '/', no '_', no '.').`);
|
|
76563
|
-
if (out.has(cdkdName)) throw new Error(`--cfn-child-stack-name: duplicate override for cdkd stack '${cdkdName}'. Pass it once with the final CFn name.`);
|
|
76853
|
+
if (out.has(cdkdName)) throw new Error(`--cfn-child-stack-name: duplicate override for cdkd stack '${safeSegment(cdkdName)}'. Pass it once with the final CFn name.`);
|
|
76564
76854
|
out.set(cdkdName, cfnName);
|
|
76565
76855
|
}
|
|
76566
76856
|
return out;
|
|
@@ -76646,7 +76936,7 @@ function indexNestedTemplatePaths(template, templateDir) {
|
|
|
76646
76936
|
* orchestrator.
|
|
76647
76937
|
*/
|
|
76648
76938
|
function buildPerStackImportNodes(rootStackName, rootTemplate, rootNestedTemplatePaths, rootTemplateFormat, tree) {
|
|
76649
|
-
if (tree.stackName !== rootStackName) throw new Error(`buildPerStackImportNodes: tree root '${tree.stackName}' does not match expected root stack name '${rootStackName}'.`);
|
|
76939
|
+
if (tree.stackName !== rootStackName) throw new Error(`buildPerStackImportNodes: tree root '${safeSegment(tree.stackName)}' does not match expected root stack name '${safeSegment(rootStackName)}'.`);
|
|
76650
76940
|
const out = /* @__PURE__ */ new Map();
|
|
76651
76941
|
walk(tree, rootTemplate, rootNestedTemplatePaths, rootTemplateFormat);
|
|
76652
76942
|
return out;
|
|
@@ -76660,7 +76950,7 @@ function buildPerStackImportNodes(rootStackName, rootTemplate, rootNestedTemplat
|
|
|
76660
76950
|
});
|
|
76661
76951
|
for (const [childLogicalId, childNode] of node.nestedChildren) {
|
|
76662
76952
|
const childTemplatePath = nodeNestedTemplatePaths[childLogicalId];
|
|
76663
|
-
if (!childTemplatePath) throw new Error(`cdkd export: nested-stack child '${childLogicalId}' under parent '${node.stackName}' has cdkd state but no Metadata['aws:asset:path'] in the parent template's '${childLogicalId}' row. The synth output and the cdkd state tree are out of sync — re-deploy the parent stack to refresh, or remove the cdkd state for the orphaned child
|
|
76953
|
+
if (!childTemplatePath) throw new Error(`cdkd export: nested-stack child '${safeSegment(childLogicalId)}' under parent '${safeSegment(node.stackName)}' has cdkd state but no Metadata['aws:asset:path'] in the parent template's '${safeSegment(childLogicalId)}' row. The synth output and the cdkd state tree are out of sync — re-deploy the parent stack to refresh, or remove the cdkd state for the orphaned child with ${orphanCommandFor(childNode.stackName, childNode.region)}`);
|
|
76664
76954
|
const { template: childTemplate, format: childFormat } = readNestedChildTemplateFile(childTemplatePath, childLogicalId);
|
|
76665
76955
|
walk(childNode, childTemplate, indexNestedTemplatePaths(childTemplate, path.dirname(childTemplatePath)), childFormat);
|
|
76666
76956
|
}
|
|
@@ -76712,7 +77002,7 @@ async function buildImportPlan(state, template, cfnClient, parentStackName, opti
|
|
|
76712
77002
|
blocked.push({
|
|
76713
77003
|
logicalId,
|
|
76714
77004
|
resourceType,
|
|
76715
|
-
reason: `template has AWS::CloudFormation::Stack row '${logicalId}' but cdkd state has no matching nested-stack entry on parent '${parentStackName}'. Re-deploy or re-import the parent stack to refresh state.`
|
|
77005
|
+
reason: `template has AWS::CloudFormation::Stack row '${logicalId}' but cdkd state has no matching nested-stack entry on parent '${safeSegment(parentStackName)}'. Re-deploy or re-import the parent stack to refresh state.`
|
|
76716
77006
|
});
|
|
76717
77007
|
continue;
|
|
76718
77008
|
}
|
|
@@ -77405,13 +77695,13 @@ function reportDriftBaselineGaps(state, logger) {
|
|
|
77405
77695
|
const missing = entries.filter(([, r]) => r.observedProperties === void 0);
|
|
77406
77696
|
if (missing.length === 0) return;
|
|
77407
77697
|
if (state.version !== void 0 && state.version < 3) {
|
|
77408
|
-
logger.warn(`cdkd state schema is v${state.version} (pre-observedProperties). cdkd drift cannot reliably compare against AWS for this stack; the next \`cdk deploy\` after migration may surface spurious changes if AWS has drifted from the template. Run \`cdkd state refresh-observed ${state.stackName}\` (or any redeploy) before export to capture an AWS-current baseline.`);
|
|
77698
|
+
logger.warn(`cdkd state schema is v${state.version} (pre-observedProperties). cdkd drift cannot reliably compare against AWS for this stack; the next \`cdk deploy\` after migration may surface spurious changes if AWS has drifted from the template. Run \`cdkd state refresh-observed ${safeSegment(state.stackName)}\` (or any redeploy) before export to capture an AWS-current baseline.`);
|
|
77409
77699
|
return;
|
|
77410
77700
|
}
|
|
77411
77701
|
const refused = missing.filter(([, r]) => r.observedBaselineRefused === true);
|
|
77412
77702
|
const refreshable = missing.filter(([, r]) => r.observedBaselineRefused !== true);
|
|
77413
77703
|
if (refreshable.length > 0) {
|
|
77414
|
-
logger.warn(`${refreshable.length} of ${entries.length} resource(s) in cdkd state lack an AWS-current baseline (observedProperties). cdkd drift may produce false positives for them; the next \`cdk deploy\` after migration may surface unexpected changes. Run \`cdkd state refresh-observed ${state.stackName}\` to capture a baseline before export, then \`cdkd drift\` to verify the stack matches AWS.`);
|
|
77704
|
+
logger.warn(`${refreshable.length} of ${entries.length} resource(s) in cdkd state lack an AWS-current baseline (observedProperties). cdkd drift may produce false positives for them; the next \`cdk deploy\` after migration may surface unexpected changes. Run \`cdkd state refresh-observed ${safeSegment(state.stackName)}\` to capture a baseline before export, then \`cdkd drift\` to verify the stack matches AWS.`);
|
|
77415
77705
|
for (const [logicalId] of refreshable.slice(0, 10)) logger.warn(` ${logicalId}`);
|
|
77416
77706
|
if (refreshable.length > 10) logger.warn(` ... and ${refreshable.length - 10} more`);
|
|
77417
77707
|
}
|
|
@@ -77954,12 +78244,12 @@ async function buildResolvedParametersPerStack(args) {
|
|
|
77954
78244
|
for (const ref of flattenCdkdStateTreeRootFirst(args.tree)) {
|
|
77955
78245
|
if (ref.stackName === args.rootStackName) continue;
|
|
77956
78246
|
const node = nodeByName.get(ref.stackName);
|
|
77957
|
-
if (!node) throw new Error(`buildResolvedParametersPerStack: tree node '${ref.stackName}' has no per-stack template/state entry. This is a cdkd bug.`);
|
|
78247
|
+
if (!node) throw new Error(`buildResolvedParametersPerStack: tree node '${safeSegment(ref.stackName)}' has no per-stack template/state entry. This is a cdkd bug.`);
|
|
77958
78248
|
const parentLogicalId = node.state.parentLogicalId;
|
|
77959
78249
|
const parentStackName = node.state.parentStack;
|
|
77960
|
-
if (!parentLogicalId || !parentStackName) throw new Error(`buildResolvedParametersPerStack: child '${node.cdkdName}' state is missing parentLogicalId / parentStack (v6 fields). Re-deploy or re-import the parent stack.`);
|
|
78250
|
+
if (!parentLogicalId || !parentStackName) throw new Error(`buildResolvedParametersPerStack: child '${safeSegment(node.cdkdName)}' state is missing parentLogicalId / parentStack (v6 fields). Re-deploy or re-import the parent stack.`);
|
|
77961
78251
|
const parentNode = nodeByName.get(parentStackName);
|
|
77962
|
-
if (!parentNode) throw new Error(`buildResolvedParametersPerStack: child '${node.cdkdName}' references parent '${parentStackName}' which is not in the per-stack node list — tree shape is inconsistent.`);
|
|
78252
|
+
if (!parentNode) throw new Error(`buildResolvedParametersPerStack: child '${safeSegment(node.cdkdName)}' references parent '${safeSegment(parentStackName)}' which is not in the per-stack node list — tree shape is inconsistent.`);
|
|
77963
78253
|
const parentParamValues = {};
|
|
77964
78254
|
for (const p of paramsByCdkdName.get(parentStackName) ?? []) if (p.ParameterKey !== void 0) parentParamValues[p.ParameterKey] = p.ParameterValue;
|
|
77965
78255
|
const parentResolverContext = {
|
|
@@ -78044,7 +78334,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78044
78334
|
};
|
|
78045
78335
|
const nodesByCdkdName = buildPerStackImportNodes(rootStackName, rootTemplate, rootStackInfoNestedTemplates, rootTemplateFormat, tree);
|
|
78046
78336
|
const leafFirst = flattenCdkdStateTreeLeafFirst(tree);
|
|
78047
|
-
for (const n of leafFirst) if (!nodesByCdkdName.has(n.stackName)) throw new Error(`runPerStackImportLoop: missing per-stack template for '${n.stackName}' — tree node has cdkd state but no synth template was loaded. This is a cdkd bug.`);
|
|
78337
|
+
for (const n of leafFirst) if (!nodesByCdkdName.has(n.stackName)) throw new Error(`runPerStackImportLoop: missing per-stack template for '${safeSegment(n.stackName)}' — tree node has cdkd state but no synth template was loaded. This is a cdkd bug.`);
|
|
78048
78338
|
const perStackPlans = [];
|
|
78049
78339
|
for (const node of leafFirst) {
|
|
78050
78340
|
const meta = nodesByCdkdName.get(node.stackName);
|
|
@@ -78055,7 +78345,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78055
78345
|
});
|
|
78056
78346
|
if (plan.blocked.length > 0) {
|
|
78057
78347
|
const lines = groupBlockedReasons(plan.blocked);
|
|
78058
|
-
throw new Error(`Stack '${meta.cdkdStackName}' has ${new Set(plan.blocked.map((b) => b.logicalId)).size} resource(s) that block migration:\n${lines.join("\n")}`);
|
|
78348
|
+
throw new Error(`Stack '${safeSegment(meta.cdkdStackName)}' has ${new Set(plan.blocked.map((b) => b.logicalId)).size} resource(s) that block migration:\n${lines.join("\n")}`);
|
|
78059
78349
|
}
|
|
78060
78350
|
perStackPlans.push({
|
|
78061
78351
|
cdkdName: meta.cdkdStackName,
|
|
@@ -78074,7 +78364,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78074
78364
|
const totalRecreate = perStackPlans.reduce((acc, p) => acc + p.recreateBeforePhase2.length, 0);
|
|
78075
78365
|
logger.info("");
|
|
78076
78366
|
logger.info(`Migrating cdkd nested-stack tree rooted at '${rootStackName}' → CloudFormation (${perStackPlans.length} stack(s), leaf-first):`);
|
|
78077
|
-
for (const plan of perStackPlans) logger.info(` [${plan.cdkdName}] → CFn stack '${plan.cfnName}': ${plan.phase1Imports.length} leaf import(s)` + (plan.nestedStackRows.length > 0 ? `, ${plan.nestedStackRows.length} nested-child adoption(s)` : "") + (plan.phase2Creates.length > 0 ? `, ${plan.phase2Creates.length} phase-2 CREATE(s)` : "") + (plan.recreateBeforePhase2.length > 0 ? `, ${plan.recreateBeforePhase2.length} pre-delete + re-CREATE` : ""));
|
|
78367
|
+
for (const plan of perStackPlans) logger.info(` [${safeSegment(plan.cdkdName)}] → CFn stack '${safeSegment(plan.cfnName)}': ${plan.phase1Imports.length} leaf import(s)` + (plan.nestedStackRows.length > 0 ? `, ${plan.nestedStackRows.length} nested-child adoption(s)` : "") + (plan.phase2Creates.length > 0 ? `, ${plan.phase2Creates.length} phase-2 CREATE(s)` : "") + (plan.recreateBeforePhase2.length > 0 ? `, ${plan.recreateBeforePhase2.length} pre-delete + re-CREATE` : ""));
|
|
78078
78368
|
logger.info("");
|
|
78079
78369
|
if (totalPhase2Creates > 0 && !options.includeNonImportable) {
|
|
78080
78370
|
if (options.dryRun) {
|
|
@@ -78098,7 +78388,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78098
78388
|
if (!options.yes) {
|
|
78099
78389
|
const phase2Note = totalPhase2Creates > 0 ? ` Phase 2 will CREATE ${totalPhase2Creates} non-importable resource(s) across the tree.` : "";
|
|
78100
78390
|
const recreateNote = totalRecreate > 0 ? ` cdkd will also pre-DELETE + re-CREATE ${totalRecreate} IMPORT-unsupported resource(s) (brief unavailability window per resource).` : "";
|
|
78101
|
-
if (!await confirmPrompt$3(`Create ${perStackPlans.length} CloudFormation stack(s) by importing the cdkd nested-stack tree rooted at '${rootStackName}' (${rootRegion}) — leaf-first, per-stack IMPORT loop (#464 design §4.3).` + phase2Note + recreateNote + " AWS resources are unchanged. cdkd state for every adopted stack will be deleted on success.")) {
|
|
78391
|
+
if (!await confirmPrompt$3(`Create ${perStackPlans.length} CloudFormation stack(s) by importing the cdkd nested-stack tree rooted at '${rootStackName}' (${safeSegment(rootRegion)}) — leaf-first, per-stack IMPORT loop (#464 design §4.3).` + phase2Note + recreateNote + " AWS resources are unchanged. cdkd state for every adopted stack will be deleted on success.")) {
|
|
78102
78392
|
logger.info("Migration cancelled. cdkd state and CloudFormation are unchanged.");
|
|
78103
78393
|
return {
|
|
78104
78394
|
outcome: "cancelled",
|
|
@@ -78144,11 +78434,11 @@ async function runPerStackImportLoop(args) {
|
|
|
78144
78434
|
resolver: paramResolver,
|
|
78145
78435
|
stateBackend: deps.stateBackend
|
|
78146
78436
|
}));
|
|
78147
|
-
for (const [cdkdName, skipped] of sessionIntrinsicSkipped) logger.warn(` Child '${cdkdName}': could not resolve intrinsic-valued Parameter(s) ${skipped.join(", ")} at IMPORT time. The child template's Parameter Default values must cover these — otherwise CFn will reject the IMPORT.`);
|
|
78437
|
+
for (const [cdkdName, skipped] of sessionIntrinsicSkipped) logger.warn(` Child '${safeSegment(cdkdName)}': could not resolve intrinsic-valued Parameter(s) ${skipped.join(", ")} at IMPORT time. The child template's Parameter Default values must cover these — otherwise CFn will reject the IMPORT.`);
|
|
78148
78438
|
try {
|
|
78149
78439
|
for (let i = 0; i < perStackPlans.length; i++) {
|
|
78150
78440
|
const plan = perStackPlans[i];
|
|
78151
|
-
logger.info(`[${i + 1}/${perStackPlans.length}] Importing cdkd stack '${plan.cdkdName}' → CFn stack '${plan.cfnName}' (${plan.phase1Imports.length} leaf, ${plan.nestedStackRows.length} nested-child adoption)`);
|
|
78441
|
+
logger.info(`[${i + 1}/${perStackPlans.length}] Importing cdkd stack '${safeSegment(plan.cdkdName)}' → CFn stack '${plan.cfnName}' (${plan.phase1Imports.length} leaf, ${plan.nestedStackRows.length} nested-child adoption)`);
|
|
78152
78442
|
const stackParameters = paramsByCdkdName.get(plan.cdkdName) ?? [];
|
|
78153
78443
|
const phase1ATemplate = filterTemplateForImport(plan.template, plan.phase1Imports);
|
|
78154
78444
|
const phase1AResources = plan.phase1Imports.map((entry) => ({
|
|
@@ -78157,13 +78447,13 @@ async function runPerStackImportLoop(args) {
|
|
|
78157
78447
|
ResourceIdentifier: entry.resourceIdentifier
|
|
78158
78448
|
}));
|
|
78159
78449
|
const injectedDeletion = injectDeletionPolicyForImport(phase1ATemplate);
|
|
78160
|
-
if (injectedDeletion > 0) logger.info(` Injected DeletionPolicy: Delete on ${injectedDeletion} resource(s) in '${plan.cdkdName}' (required by CFn IMPORT; matches CDK/CFn default).`);
|
|
78450
|
+
if (injectedDeletion > 0) logger.info(` Injected DeletionPolicy: Delete on ${injectedDeletion} resource(s) in '${safeSegment(plan.cdkdName)}' (required by CFn IMPORT; matches CDK/CFn default).`);
|
|
78161
78451
|
try {
|
|
78162
78452
|
await submitImportChangeSet(deps.cfnClient, plan.cfnName, phase1ATemplate, phase1AResources, stackParameters, plan.templateFormat, deps.uploadOpts);
|
|
78163
78453
|
} catch (err) {
|
|
78164
|
-
const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${s.cdkdStackName} → ${s.cfnStackName}`).join(", ") : "(none)";
|
|
78165
|
-
const remainingSummary = perStackPlans.slice(i).map((p) => p.cdkdName).join(", ");
|
|
78166
|
-
throw new Error(`Phase 1A IMPORT changeset failed for cdkd stack '${plan.cdkdName}' (CFn name '${plan.cfnName}'). Stacks imported successfully so far: ${importedSummary}. Stacks not yet imported (cdkd state preserved): ${remainingSummary}. After resolving the underlying cause, re-run 'cdkd export ${rootStackName}' — already-imported children will be adopted as nested references on retry. Cause: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
|
|
78454
|
+
const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${safeSegment(s.cdkdStackName)} → ${safeSegment(s.cfnStackName)}`).join(", ") : "(none)";
|
|
78455
|
+
const remainingSummary = perStackPlans.slice(i).map((p) => safeSegment(p.cdkdName)).join(", ");
|
|
78456
|
+
throw new Error(`Phase 1A IMPORT changeset failed for cdkd stack '${safeSegment(plan.cdkdName)}' (CFn name '${plan.cfnName}'). Stacks imported successfully so far: ${importedSummary}. Stacks not yet imported (cdkd state preserved): ${remainingSummary}. After resolving the underlying cause, re-run 'cdkd export ${rootStackName}' — already-imported children will be adopted as nested references on retry. Cause: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
|
|
78167
78457
|
}
|
|
78168
78458
|
const cfnArn = (await deps.cfnClient.send(new DescribeStacksCommand({ StackName: plan.cfnName }))).Stacks?.[0]?.StackId;
|
|
78169
78459
|
if (!cfnArn) throw new Error(`runPerStackImportLoop: DescribeStacks returned no StackId for '${plan.cfnName}' immediately after Phase 1A IMPORT — AWS may be in an unexpected state.`);
|
|
@@ -78185,7 +78475,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78185
78475
|
const phase1BResources = [];
|
|
78186
78476
|
for (const row of plan.nestedStackRows) {
|
|
78187
78477
|
const childArn = cfnArnByCdkdName.get(row.childStackName);
|
|
78188
|
-
if (!childArn) throw new Error(`runPerStackImportLoop: nested-stack child '${row.childStackName}' has no recorded CFn ARN when processing parent '${plan.cdkdName}'. Leaf-first iteration order should have IMPORTed the child first — this is a cdkd bug.`);
|
|
78478
|
+
if (!childArn) throw new Error(`runPerStackImportLoop: nested-stack child '${safeSegment(row.childStackName)}' has no recorded CFn ARN when processing parent '${safeSegment(plan.cdkdName)}'. Leaf-first iteration order should have IMPORTed the child first — this is a cdkd bug.`);
|
|
78189
78479
|
const childCfnName = cfnStackNameOf(row.childStackName);
|
|
78190
78480
|
const childTemplateBody = await fetchCfnStackTemplate(deps.cfnClient, childCfnName);
|
|
78191
78481
|
const uploaded = await uploadCfnTemplate({
|
|
@@ -78198,7 +78488,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78198
78488
|
uploadCleanups.push(uploaded.cleanup);
|
|
78199
78489
|
const childActualTags = (await deps.cfnClient.send(new DescribeStacksCommand({ StackName: childCfnName }))).Stacks?.[0]?.Tags ?? [];
|
|
78200
78490
|
const originalRow = getResourceFromTemplate(plan.template, row.logicalId);
|
|
78201
|
-
if (!originalRow) throw new Error(`runPerStackImportLoop: parent template for '${plan.cdkdName}' has no resource '${row.logicalId}'. State and template are out of sync.`);
|
|
78491
|
+
if (!originalRow) throw new Error(`runPerStackImportLoop: parent template for '${safeSegment(plan.cdkdName)}' has no resource '${row.logicalId}'. State and template are out of sync.`);
|
|
78202
78492
|
const rewrittenRow = injectRetainAndRewriteTemplateUrl(originalRow, uploaded.url, childActualTags);
|
|
78203
78493
|
phase1BTemplate["Resources"][row.logicalId] = rewrittenRow;
|
|
78204
78494
|
rewrittenNestedRows.set(row.logicalId, rewrittenRow);
|
|
@@ -78211,8 +78501,8 @@ async function runPerStackImportLoop(args) {
|
|
|
78211
78501
|
try {
|
|
78212
78502
|
await submitImportChangeSet(deps.cfnClient, plan.cfnName, phase1BTemplate, phase1BResources, stackParameters, plan.templateFormat, deps.uploadOpts);
|
|
78213
78503
|
} catch (err) {
|
|
78214
|
-
const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${s.cdkdStackName} → ${s.cfnStackName}`).join(", ") : "(none)";
|
|
78215
|
-
throw new Error(`Phase 1B (nested-child adoption) IMPORT changeset failed for parent '${plan.cdkdName}' (CFn name '${plan.cfnName}'). The parent CFn stack exists with its ${plan.phase1Imports.length} leaf resource(s); ${plan.nestedStackRows.length} nested-child adoption(s) did NOT complete. Stacks IMPORTed so far (each is a standalone CFn stack): ${importedSummary}. cdkd state for every stack in the tree is preserved. To recover: (1) fix the underlying cause (typically a template-match validation error per AWS-docs "Nested stack import validation"); (2) clear cdkd state for the migrated stacks via 'cdkd state orphan <stack>' (do NOT 'cdkd destroy' — that would tear down the live AWS resources); (3) re-attempt the parent-side nested adoption manually via the AWS console or CLI per the AWS docs procedure. Cause: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
|
|
78504
|
+
const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${safeSegment(s.cdkdStackName)} → ${safeSegment(s.cfnStackName)}`).join(", ") : "(none)";
|
|
78505
|
+
throw new Error(`Phase 1B (nested-child adoption) IMPORT changeset failed for parent '${safeSegment(plan.cdkdName)}' (CFn name '${safeSegment(plan.cfnName)}'). The parent CFn stack exists with its ${plan.phase1Imports.length} leaf resource(s); ${plan.nestedStackRows.length} nested-child adoption(s) did NOT complete. Stacks IMPORTed so far (each is a standalone CFn stack): ${importedSummary}. cdkd state for every stack in the tree is preserved. To recover: (1) fix the underlying cause (typically a template-match validation error per AWS-docs "Nested stack import validation"); (2) clear cdkd state for the migrated stacks via 'cdkd state orphan <stack>' (do NOT 'cdkd destroy' — that would tear down the live AWS resources); (3) re-attempt the parent-side nested adoption manually via the AWS console or CLI per the AWS docs procedure. Cause: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
|
|
78216
78506
|
}
|
|
78217
78507
|
logger.info(` ✓ Phase 1B: parent '${plan.cfnName}' adopted ${plan.nestedStackRows.length} nested child(ren) via UPDATE-IMPORT.`);
|
|
78218
78508
|
if (plan.cdkdName !== rootStackName) {
|
|
@@ -78222,7 +78512,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78222
78512
|
}
|
|
78223
78513
|
if (plan.recreateBeforePhase2.length > 0) for (const entry of plan.recreateBeforePhase2) {
|
|
78224
78514
|
const handler = PRE_DELETE_HANDLERS[entry.resourceType];
|
|
78225
|
-
if (!handler) throw new Error(`No pre-delete handler registered for ${entry.resourceType} (${entry.logicalId}) in stack '${plan.cdkdName}'. This is a cdkd bug — the resource is in IMPORT_UNSUPPORTED_RECREATABLE_TYPES but lacks a PRE_DELETE_HANDLERS entry.`);
|
|
78515
|
+
if (!handler) throw new Error(`No pre-delete handler registered for ${entry.resourceType} (${entry.logicalId}) in stack '${safeSegment(plan.cdkdName)}'. This is a cdkd bug — the resource is in IMPORT_UNSUPPORTED_RECREATABLE_TYPES but lacks a PRE_DELETE_HANDLERS entry.`);
|
|
78226
78516
|
logger.info(` Pre-deleting AWS resource for ${entry.logicalId} (${entry.resourceType}) so CFn can re-CREATE in phase 2...`);
|
|
78227
78517
|
await handler(entry);
|
|
78228
78518
|
logger.info(` ✓ deleted ${entry.physicalId}`);
|
|
@@ -78241,21 +78531,21 @@ async function runPerStackImportLoop(args) {
|
|
|
78241
78531
|
const stateDeletionFailures = [];
|
|
78242
78532
|
for (const node of leafFirst) try {
|
|
78243
78533
|
await deps.stateBackend.deleteState(node.stackName, node.region);
|
|
78244
|
-
logger.info(`cdkd state for '${node.stackName}' (${node.region}) removed.`);
|
|
78534
|
+
logger.info(`cdkd state for '${safeSegment(node.stackName)}' (${safeSegment(node.region)}) removed.`);
|
|
78245
78535
|
} catch (err) {
|
|
78246
78536
|
stateDeletionFailures.push({
|
|
78247
78537
|
stackName: node.stackName,
|
|
78248
78538
|
region: node.region,
|
|
78249
78539
|
reason: err instanceof Error ? err.message : String(err)
|
|
78250
78540
|
});
|
|
78251
|
-
logger.warn(`Failed to delete cdkd state for '${node.stackName}' (${node.region}): ${
|
|
78541
|
+
logger.warn(`Failed to delete cdkd state for '${safeSegment(node.stackName)}' (${safeSegment(node.region)}): ${safeDetail(err)}. The stack IS CFn-managed; clean up with: ${orphanCommandFor(node.stackName, node.region)}`);
|
|
78252
78542
|
}
|
|
78253
78543
|
if (stateDeletionFailures.length > 0) {
|
|
78254
|
-
const lines = stateDeletionFailures.map((f) => ` - cdkd/${f.stackName}/${f.region}/state.json: ${f.reason}`).join("\n");
|
|
78255
|
-
throw new Error(`${stateDeletionFailures.length} cdkd state record(s) could not be deleted after a successful per-stack IMPORT loop. Every stack in the tree IS CFn-managed (the migration succeeded AWS-side); orphan state remains under:\n${lines}\nRecover with 'cdkd state orphan <stack>' per record.`);
|
|
78544
|
+
const lines = stateDeletionFailures.map((f) => ` - cdkd/${safeSegment(f.stackName)}/${safeSegment(f.region)}/state.json: ${safeDetail(f.reason)}`).join("\n");
|
|
78545
|
+
throw new Error(`${stateDeletionFailures.length} cdkd state record(s) could not be deleted after a successful per-stack IMPORT loop. Every stack in the tree IS CFn-managed (the migration succeeded AWS-side); orphan state remains under:\n${lines}\nRecover with 'cdkd state orphan <stack> --stack-region <region>' per record.`);
|
|
78256
78546
|
}
|
|
78257
78547
|
if (sessionIntrinsicSkipped.size > 0) {
|
|
78258
|
-
const detail = [...sessionIntrinsicSkipped.entries()].map(([cdkdName, params]) => `${cdkdName} (${params.join(", ")})`).join("; ");
|
|
78548
|
+
const detail = [...sessionIntrinsicSkipped.entries()].map(([cdkdName, params]) => `${safeSegment(cdkdName)} (${params.join(", ")})`).join("; ");
|
|
78259
78549
|
logger.warn(`${sessionIntrinsicSkipped.size} stack(s) had intrinsic-valued Parameter(s) that cdkd could not resolve at IMPORT time: ${detail}. Verify each child template's Parameter Default values cover these before the first 'cdk deploy'.`);
|
|
78260
78550
|
}
|
|
78261
78551
|
return {
|
|
@@ -78269,7 +78559,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78269
78559
|
for (let i = acquiredChildLocks.length - 1; i >= 0; i--) {
|
|
78270
78560
|
const lock = acquiredChildLocks[i];
|
|
78271
78561
|
await deps.lockManager.releaseLock(lock.stackName, lock.region).catch((err) => {
|
|
78272
|
-
logger.warn(`Failed to release lock for '${lock.stackName}' (${lock.region}): ${
|
|
78562
|
+
logger.warn(`Failed to release lock for '${safeSegment(lock.stackName)}' (${safeSegment(lock.region)}): ${safeDetail(err)}`);
|
|
78273
78563
|
});
|
|
78274
78564
|
}
|
|
78275
78565
|
}
|
|
@@ -79679,9 +79969,29 @@ function createStateOrphanCommand() {
|
|
|
79679
79969
|
* state record. AWS resources are left intact.
|
|
79680
79970
|
*
|
|
79681
79971
|
* Region scoping: when a stack name has multiple state records spread across
|
|
79682
|
-
* regions (PR 1 territory), `--region` selects one
|
|
79683
|
-
*
|
|
79684
|
-
*
|
|
79972
|
+
* regions (PR 1 territory), `--stack-region` selects one — it picks WHICH
|
|
79973
|
+
* record is loaded, because the region is part of the S3 key.
|
|
79974
|
+
*
|
|
79975
|
+
* What it does with a record whose BODY names a different region than that key
|
|
79976
|
+
* is two things, neither of which this comment described until issue
|
|
79977
|
+
* [#3328](https://github.com/go-to-k/cdkd/issues/3328) — it claimed a refusal
|
|
79978
|
+
* on `state.region` that had never existed anywhere in the command, while the
|
|
79979
|
+
* measured behaviour was the opposite: a planted record at
|
|
79980
|
+
* `.../us-east-1/state.json` carrying `"region": "eu-west-1"` locked, saved
|
|
79981
|
+
* and deleted against `eu-west-1` and reported `✓ State deleted` while the
|
|
79982
|
+
* us-east-1 key survived.
|
|
79983
|
+
*
|
|
79984
|
+
* 1. `S3StateBackend.getState` normalizes the loaded record's `region` to the
|
|
79985
|
+
* KEY's and WARNS, so this command — which hands `stateResult.state`
|
|
79986
|
+
* straight to `runDestroyForStack` — acts on the region it was pointed at.
|
|
79987
|
+
* The read itself refuses NOTHING; `adoptKeyRegion` carries why, and it is
|
|
79988
|
+
* what keeps `state show` / `state list` / `state orphan` working on such a
|
|
79989
|
+
* record.
|
|
79990
|
+
* 2. The DESTROY refuses it when the record still lists resources
|
|
79991
|
+
* (`refuseDivergentRecordRegionForDestroy`, reached through
|
|
79992
|
+
* `runDestroyForStack`), because which region those resources are in is
|
|
79993
|
+
* exactly what the divergence makes undecidable. A resource-less record is
|
|
79994
|
+
* not refused, so cleaning one up still works.
|
|
79685
79995
|
*/
|
|
79686
79996
|
async function stateDestroyCommand(stackArgs, options) {
|
|
79687
79997
|
const logger = getLogger();
|
|
@@ -79828,6 +80138,7 @@ async function stateDestroyCommand(stackArgs, options) {
|
|
|
79828
80138
|
providerRegistry,
|
|
79829
80139
|
baseAwsClients: setup.awsClients,
|
|
79830
80140
|
baseRegion: setup.region,
|
|
80141
|
+
...stateResult.divergentBodyRegion !== void 0 && { divergentBodyRegion: stateResult.divergentBodyRegion },
|
|
79831
80142
|
...options.profile && { profile: options.profile },
|
|
79832
80143
|
stateBucket: setup.bucket,
|
|
79833
80144
|
statePrefix: options.statePrefix,
|
|
@@ -94395,4 +94706,4 @@ function buildProgram() {
|
|
|
94395
94706
|
|
|
94396
94707
|
//#endregion
|
|
94397
94708
|
export { buildProgram };
|
|
94398
|
-
//# sourceMappingURL=program-
|
|
94709
|
+
//# sourceMappingURL=program-babtsurS.js.map
|