@go-to-k/cdkd 0.290.23 → 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-BOYln2Tw.js → asg-provider-DL62n7uG.js} +2 -2
- package/dist/{asg-provider-BOYln2Tw.js.map → asg-provider-DL62n7uG.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-CBzq85r6.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-BgbPkrcx.js → program-babtsurS.js} +481 -169
- package/dist/{program-BgbPkrcx.js.map → program-babtsurS.js.map} +1 -1
- package/dist/{version-DxUVmvM5.js → version-CduXA6UL.js} +2 -2
- package/dist/{version-DxUVmvM5.js.map → version-CduXA6UL.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-CBzq85r6.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 {
|
|
@@ -12974,6 +12978,88 @@ function mapDeliveryStatusLogging(attrs, stateCaseMap = /* @__PURE__ */ new Map(
|
|
|
12974
12978
|
return result;
|
|
12975
12979
|
}
|
|
12976
12980
|
|
|
12981
|
+
//#endregion
|
|
12982
|
+
//#region src/provisioning/attribute-map.ts
|
|
12983
|
+
/**
|
|
12984
|
+
* The ONE spelling of the attribute-map rule an SDK provider's `create()` /
|
|
12985
|
+
* `update()` / `import()` result follows (issue #3077, closing the `?? ''`
|
|
12986
|
+
* shape `docs/provider-development.md` forbids under "Never store an
|
|
12987
|
+
* empty-string placeholder"):
|
|
12988
|
+
*
|
|
12989
|
+
* an attribute cdkd could not read back is ABSENT from the map — never
|
|
12990
|
+
* recorded as the empty string, and never as a present-but-`undefined` key.
|
|
12991
|
+
*
|
|
12992
|
+
* The consumers of `ResourceState.attributes` all key off exactly that
|
|
12993
|
+
* distinction, and a sentinel puts every one of them on the wrong branch:
|
|
12994
|
+
*
|
|
12995
|
+
* - the deploy engine persists the map as-is, so `''` becomes the recorded
|
|
12996
|
+
* value of a field AWS never assigned;
|
|
12997
|
+
* - the intrinsic resolver serves a stored attribute whenever it is not
|
|
12998
|
+
* `undefined`, so `''` shadows `constructAttribute` forever — the
|
|
12999
|
+
* `AWS::EC2::Instance` IP / DNS / AZ arm re-describes the instance from
|
|
13000
|
+
* AWS only on a MISS, the `Arn` arms of the RDS family rebuild the ARN
|
|
13001
|
+
* only on a MISS;
|
|
13002
|
+
* - the Outputs export pass publishes whatever the resolver returned, so a
|
|
13003
|
+
* stored `''` is exported to every `Fn::ImportValue` consumer.
|
|
13004
|
+
*
|
|
13005
|
+
* It exists as a MODULE rather than a per-provider idiom because the rule was
|
|
13006
|
+
* prose and 96 attribute-position `?? ''` / `|| ''` / bare `''` sites across
|
|
13007
|
+
* 18 providers shipped anyway (measured 2026-09-14 with the TypeScript-compiler
|
|
13008
|
+
* walk that is now `tests/unit/provisioning/attribute-map.test.ts`). A helper
|
|
13009
|
+
* that DROPS the key is the one form a provider cannot spell wrong by habit.
|
|
13010
|
+
*
|
|
13011
|
+
* Two values are treated as "not read back":
|
|
13012
|
+
*
|
|
13013
|
+
* - `undefined`: the SDK's spelling of an unassigned optional member.
|
|
13014
|
+
* - `null`: no AWS SDK v3 read-back reports a scalar attribute as `null`, and
|
|
13015
|
+
* a stored `null` takes the resolver's "not `undefined`" hit branch exactly
|
|
13016
|
+
* like `''` does.
|
|
13017
|
+
*
|
|
13018
|
+
* Every other value is kept verbatim — INCLUDING an empty string AWS itself
|
|
13019
|
+
* reported (`false`, `0`, an empty array and an object likewise). The helper
|
|
13020
|
+
* stops cdkd MANUFACTURING `''` for a field it never read; a `''` that came
|
|
13021
|
+
* off the wire is a known value with a meaning the provider owns. The EC2
|
|
13022
|
+
* Instance provider is where that distinction is load-bearing: a `running`
|
|
13023
|
+
* instance with no public address is described with `PublicDnsName: ''` and
|
|
13024
|
+
* no `PublicIpAddress`, and CloudFormation answers `''` for both — a KNOWN
|
|
13025
|
+
* empty, recorded as such so a reference does not degrade to the instance id
|
|
13026
|
+
* on every resolution — while the same `''` on a `pending` instance means
|
|
13027
|
+
* "not yet", which that provider maps to `undefined` BEFORE calling this
|
|
13028
|
+
* helper (its `describedInstanceAttributes`). A caller that knows a `''`
|
|
13029
|
+
* means "unknown" is the one that must say so; this helper cannot tell.
|
|
13030
|
+
*
|
|
13031
|
+
* Deliberately NOT a general "compact this record" utility: it is scoped to
|
|
13032
|
+
* attribute maps. A property bag keeps its `undefined`-valued keys out by other
|
|
13033
|
+
* means (`.claude/rules/provider-property-fidelity.md`, "Remove the key rather
|
|
13034
|
+
* than setting it to `undefined`").
|
|
13035
|
+
*
|
|
13036
|
+
* A LEAF module (imports nothing) so every provider can take it without adding
|
|
13037
|
+
* an edge to the provider import ring.
|
|
13038
|
+
*/
|
|
13039
|
+
/**
|
|
13040
|
+
* Build an attribute map from read-back values, omitting every key whose value
|
|
13041
|
+
* is `undefined` or `null`.
|
|
13042
|
+
*/
|
|
13043
|
+
function definedAttributes(entries) {
|
|
13044
|
+
const out = {};
|
|
13045
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
13046
|
+
if (value === void 0 || value === null) continue;
|
|
13047
|
+
out[key] = value;
|
|
13048
|
+
}
|
|
13049
|
+
return out;
|
|
13050
|
+
}
|
|
13051
|
+
/**
|
|
13052
|
+
* `String(value)` for a numeric read-back whose CloudFormation attribute is a
|
|
13053
|
+
* string (`Endpoint.Port`), or `undefined` when the field is unassigned — so
|
|
13054
|
+
* `definedAttributes` drops the key instead of recording the literal
|
|
13055
|
+
* `'undefined'`, which is what `String(described?.Port ?? '')` yields once its
|
|
13056
|
+
* `?? ''` is merely deleted.
|
|
13057
|
+
*/
|
|
13058
|
+
function stringifyIfAssigned(value) {
|
|
13059
|
+
if (value === void 0 || value === null) return void 0;
|
|
13060
|
+
return String(value);
|
|
13061
|
+
}
|
|
13062
|
+
|
|
12977
13063
|
//#endregion
|
|
12978
13064
|
//#region src/provisioning/providers/sns-subscription-provider.ts
|
|
12979
13065
|
/**
|
|
@@ -13046,17 +13132,18 @@ var SNSSubscriptionProvider = class {
|
|
|
13046
13132
|
if (replayPolicy !== void 0) attributes["ReplayPolicy"] = typeof replayPolicy === "string" ? replayPolicy : JSON.stringify(replayPolicy);
|
|
13047
13133
|
const subscriptionRoleArn = properties["SubscriptionRoleArn"];
|
|
13048
13134
|
if (subscriptionRoleArn !== void 0) attributes["SubscriptionRoleArn"] = stringifyValue(subscriptionRoleArn);
|
|
13049
|
-
const
|
|
13135
|
+
const response = await this.snsClient.send(new SubscribeCommand({
|
|
13050
13136
|
TopicArn: topicArn,
|
|
13051
13137
|
Protocol: protocol,
|
|
13052
13138
|
Endpoint: endpoint,
|
|
13053
13139
|
ReturnSubscriptionArn: true,
|
|
13054
13140
|
...Object.keys(attributes).length > 0 && { Attributes: attributes }
|
|
13055
|
-
}))
|
|
13141
|
+
}));
|
|
13142
|
+
const subscriptionArn = response.SubscriptionArn || `${topicArn}:${logicalId}`;
|
|
13056
13143
|
this.logger.debug(`Successfully created SNS subscription ${logicalId}: ${subscriptionArn}`);
|
|
13057
13144
|
return {
|
|
13058
13145
|
physicalId: subscriptionArn,
|
|
13059
|
-
attributes: {}
|
|
13146
|
+
attributes: definedAttributes({ Arn: response.SubscriptionArn?.startsWith("arn:") ? response.SubscriptionArn : void 0 })
|
|
13060
13147
|
};
|
|
13061
13148
|
} catch (error) {
|
|
13062
13149
|
const cause = error instanceof Error ? error : void 0;
|
|
@@ -17748,6 +17835,20 @@ function hasUsableDeclaredCapacity(entry) {
|
|
|
17748
17835
|
*/
|
|
17749
17836
|
const PROVISIONED_CAPACITY_MEMBERS = ["ReadCapacityUnits", "WriteCapacityUnits"];
|
|
17750
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
|
+
/**
|
|
17751
17852
|
* The capacity both TABLE-level forwarders substitute for a member the
|
|
17752
17853
|
* template does not declare at all — `create()`'s `CreateTable` and the
|
|
17753
17854
|
* BillingMode flip's `UpdateTable`. It is the pre-issue-#3147 `?? 5`, kept
|
|
@@ -18207,14 +18308,20 @@ function reverseMapSecondaryIndex(live, desired, bagInformative) {
|
|
|
18207
18308
|
* in PR review round 6; the carve-out is gone because the divergence is.
|
|
18208
18309
|
* - `ProvisionedThroughput` / `OnDemandThroughput` keep TRUTHINESS, which is
|
|
18209
18310
|
* still what their write path does: `applyGsiUpdates` gates on
|
|
18210
|
-
* `gsi.ProvisionedThroughput`
|
|
18211
|
-
*
|
|
18212
|
-
* [#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
|
|
18213
18314
|
* per-index send sites (`create()`'s mapper plus `applyGsiUpdates`'
|
|
18214
|
-
* adopted-repair, `Create` and same-name `Update` actions)
|
|
18215
|
-
*
|
|
18216
|
-
*
|
|
18217
|
-
*
|
|
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
|
|
18218
18325
|
* `isSendableWarmThroughput` over them would be wrong twice over — it reads
|
|
18219
18326
|
* members those blocks do not have (`ReadUnitsPerSecond` vs
|
|
18220
18327
|
* `ReadCapacityUnits`), so every declared capacity would read as undeclared.
|
|
@@ -18412,7 +18519,7 @@ var DynamoDBTableProvider = class {
|
|
|
18412
18519
|
WriteCapacityUnits: tableCapacityForSend(pt, "WriteCapacityUnits", onUnusable)
|
|
18413
18520
|
};
|
|
18414
18521
|
}
|
|
18415
|
-
if (properties["OnDemandThroughput"]) createParams.OnDemandThroughput = properties["OnDemandThroughput"];
|
|
18522
|
+
if (properties["OnDemandThroughput"]) createParams.OnDemandThroughput = this.coerceOnDemandCeilingsForSend(`AWS::DynamoDB::Table ${logicalId}`, properties["OnDemandThroughput"], maskSecrets);
|
|
18416
18523
|
const createWarmThroughput = this.coerceWarmThroughputForSend(`AWS::DynamoDB::Table ${logicalId}`, properties["WarmThroughput"], maskSecrets);
|
|
18417
18524
|
if (createWarmThroughput) createParams.WarmThroughput = createWarmThroughput;
|
|
18418
18525
|
else this.warnRefusedWarmThroughput(`AWS::DynamoDB::Table ${logicalId}`, properties["WarmThroughput"], maskSecrets);
|
|
@@ -18581,7 +18688,7 @@ var DynamoDBTableProvider = class {
|
|
|
18581
18688
|
if (properties["OnDemandThroughput"]) {
|
|
18582
18689
|
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
18583
18690
|
TableName: physicalId,
|
|
18584
|
-
OnDemandThroughput: properties["OnDemandThroughput"]
|
|
18691
|
+
OnDemandThroughput: this.coerceOnDemandCeilingsForSend(`AWS::DynamoDB::Table ${logicalId}`, properties["OnDemandThroughput"], maskSecrets)
|
|
18585
18692
|
}));
|
|
18586
18693
|
await this.waitForTableActiveAfterUpdate(physicalId);
|
|
18587
18694
|
this.logger.debug(`Updated OnDemandThroughput on DynamoDB table ${physicalId}`);
|
|
@@ -19251,7 +19358,7 @@ var DynamoDBTableProvider = class {
|
|
|
19251
19358
|
KeySchema: gsi.KeySchema,
|
|
19252
19359
|
Projection: gsi.Projection,
|
|
19253
19360
|
...gsi.ProvisionedThroughput ? { ProvisionedThroughput: this.indexCapacityForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.ProvisionedThroughput, maskSecrets) } : {},
|
|
19254
|
-
...gsi.OnDemandThroughput ? { OnDemandThroughput: gsi.OnDemandThroughput } : {},
|
|
19361
|
+
...gsi.OnDemandThroughput ? { OnDemandThroughput: this.coerceOnDemandCeilingsForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.OnDemandThroughput, maskSecrets) } : {},
|
|
19255
19362
|
...createWarm ? { WarmThroughput: createWarm } : {}
|
|
19256
19363
|
} });
|
|
19257
19364
|
} else {
|
|
@@ -19360,16 +19467,20 @@ var DynamoDBTableProvider = class {
|
|
|
19360
19467
|
return this.coerceIndexCapacityBlock(scope, declared, maskSecrets) ?? declared;
|
|
19361
19468
|
}
|
|
19362
19469
|
/**
|
|
19363
|
-
* One `CreateTable` index entry with
|
|
19364
|
-
* `WarmThroughput` (PR review round 6)
|
|
19365
|
-
* [#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)).
|
|
19366
19475
|
*
|
|
19367
19476
|
* `create()` forwards the declared `GlobalSecondaryIndexes` array to
|
|
19368
19477
|
* `CreateTable`, so it is a SEND SITE for the same per-index values the
|
|
19369
19478
|
* update path coerces. `WarmThroughput` was the one the round-5 sweep
|
|
19370
19479
|
* missed; `ProvisionedThroughput` was missed by round 6 AND by issue #3147's
|
|
19371
|
-
* own enumeration
|
|
19372
|
-
*
|
|
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
|
|
19373
19484
|
* ({@link readCapacityNumber}) has been on the grammar since #3147, so until
|
|
19374
19485
|
* now create and flip disagreed about the same property: a CFn-legal
|
|
19375
19486
|
* `ReadCapacityUnits: '7'` reached `CreateTable` as a STRING in a `number`
|
|
@@ -19381,18 +19492,20 @@ var DynamoDBTableProvider = class {
|
|
|
19381
19492
|
* and a provider that edits it in place would change what state reports cdkd
|
|
19382
19493
|
* sent.
|
|
19383
19494
|
*
|
|
19384
|
-
* An entry that is not a plain object, and one declaring
|
|
19385
|
-
*
|
|
19386
|
-
* declare one is rebuilt whenever that block's coercion changes
|
|
19387
|
-
* the `WarmThroughput` half rebuilds even for an already-numeric
|
|
19388
|
-
* 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.
|
|
19389
19501
|
*/
|
|
19390
19502
|
coerceIndexThroughputForCreate(logicalId, entry, maskSecrets = (text) => text) {
|
|
19391
19503
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return entry;
|
|
19392
19504
|
const bag = entry;
|
|
19393
19505
|
const declaredWarm = bag["WarmThroughput"];
|
|
19394
19506
|
const declaredCapacity = bag["ProvisionedThroughput"];
|
|
19395
|
-
|
|
19507
|
+
const declaredOnDemand = bag["OnDemandThroughput"];
|
|
19508
|
+
if (declaredWarm === void 0 && !isPlainCapacityBlock(declaredCapacity) && !isPlainCapacityBlock(declaredOnDemand)) return entry;
|
|
19396
19509
|
const scope = this.indexScope(entry.IndexName, logicalId, maskSecrets);
|
|
19397
19510
|
let out = entry;
|
|
19398
19511
|
if (declaredWarm !== void 0) {
|
|
@@ -19411,6 +19524,13 @@ var DynamoDBTableProvider = class {
|
|
|
19411
19524
|
...out,
|
|
19412
19525
|
ProvisionedThroughput: capacity
|
|
19413
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
|
+
}
|
|
19414
19534
|
return out;
|
|
19415
19535
|
}
|
|
19416
19536
|
/**
|
|
@@ -19452,9 +19572,14 @@ var DynamoDBTableProvider = class {
|
|
|
19452
19572
|
* the template had declared it, with no later call to correct it. Only
|
|
19453
19573
|
* the TABLE level defaults.
|
|
19454
19574
|
* - **A block that is not a plain object is forwarded VERBATIM**, the
|
|
19455
|
-
* fail-OPEN direction this file takes everywhere:
|
|
19456
|
-
*
|
|
19457
|
-
*
|
|
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}.
|
|
19458
19583
|
*
|
|
19459
19584
|
* A member name the grammar does not know is preserved untouched, for the
|
|
19460
19585
|
* same fail-open reason — a misspelled `ReadCapacityUnit` reaches AWS and is
|
|
@@ -19477,6 +19602,178 @@ var DynamoDBTableProvider = class {
|
|
|
19477
19602
|
return rewritten;
|
|
19478
19603
|
}
|
|
19479
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
|
+
/**
|
|
19480
19777
|
* Announce a `ProvisionedThroughput` member a forwarder DROPPED (issue
|
|
19481
19778
|
* [#3147](https://github.com/go-to-k/cdkd/issues/3147); widened to the
|
|
19482
19779
|
* per-index create forwarder by issue
|
|
@@ -26468,88 +26765,6 @@ var EventBridgeRuleProvider = class {
|
|
|
26468
26765
|
}
|
|
26469
26766
|
};
|
|
26470
26767
|
|
|
26471
|
-
//#endregion
|
|
26472
|
-
//#region src/provisioning/attribute-map.ts
|
|
26473
|
-
/**
|
|
26474
|
-
* The ONE spelling of the attribute-map rule an SDK provider's `create()` /
|
|
26475
|
-
* `update()` / `import()` result follows (issue #3077, closing the `?? ''`
|
|
26476
|
-
* shape `docs/provider-development.md` forbids under "Never store an
|
|
26477
|
-
* empty-string placeholder"):
|
|
26478
|
-
*
|
|
26479
|
-
* an attribute cdkd could not read back is ABSENT from the map — never
|
|
26480
|
-
* recorded as the empty string, and never as a present-but-`undefined` key.
|
|
26481
|
-
*
|
|
26482
|
-
* The consumers of `ResourceState.attributes` all key off exactly that
|
|
26483
|
-
* distinction, and a sentinel puts every one of them on the wrong branch:
|
|
26484
|
-
*
|
|
26485
|
-
* - the deploy engine persists the map as-is, so `''` becomes the recorded
|
|
26486
|
-
* value of a field AWS never assigned;
|
|
26487
|
-
* - the intrinsic resolver serves a stored attribute whenever it is not
|
|
26488
|
-
* `undefined`, so `''` shadows `constructAttribute` forever — the
|
|
26489
|
-
* `AWS::EC2::Instance` IP / DNS / AZ arm re-describes the instance from
|
|
26490
|
-
* AWS only on a MISS, the `Arn` arms of the RDS family rebuild the ARN
|
|
26491
|
-
* only on a MISS;
|
|
26492
|
-
* - the Outputs export pass publishes whatever the resolver returned, so a
|
|
26493
|
-
* stored `''` is exported to every `Fn::ImportValue` consumer.
|
|
26494
|
-
*
|
|
26495
|
-
* It exists as a MODULE rather than a per-provider idiom because the rule was
|
|
26496
|
-
* prose and 96 attribute-position `?? ''` / `|| ''` / bare `''` sites across
|
|
26497
|
-
* 18 providers shipped anyway (measured 2026-09-14 with the TypeScript-compiler
|
|
26498
|
-
* walk that is now `tests/unit/provisioning/attribute-map.test.ts`). A helper
|
|
26499
|
-
* that DROPS the key is the one form a provider cannot spell wrong by habit.
|
|
26500
|
-
*
|
|
26501
|
-
* Two values are treated as "not read back":
|
|
26502
|
-
*
|
|
26503
|
-
* - `undefined`: the SDK's spelling of an unassigned optional member.
|
|
26504
|
-
* - `null`: no AWS SDK v3 read-back reports a scalar attribute as `null`, and
|
|
26505
|
-
* a stored `null` takes the resolver's "not `undefined`" hit branch exactly
|
|
26506
|
-
* like `''` does.
|
|
26507
|
-
*
|
|
26508
|
-
* Every other value is kept verbatim — INCLUDING an empty string AWS itself
|
|
26509
|
-
* reported (`false`, `0`, an empty array and an object likewise). The helper
|
|
26510
|
-
* stops cdkd MANUFACTURING `''` for a field it never read; a `''` that came
|
|
26511
|
-
* off the wire is a known value with a meaning the provider owns. The EC2
|
|
26512
|
-
* Instance provider is where that distinction is load-bearing: a `running`
|
|
26513
|
-
* instance with no public address is described with `PublicDnsName: ''` and
|
|
26514
|
-
* no `PublicIpAddress`, and CloudFormation answers `''` for both — a KNOWN
|
|
26515
|
-
* empty, recorded as such so a reference does not degrade to the instance id
|
|
26516
|
-
* on every resolution — while the same `''` on a `pending` instance means
|
|
26517
|
-
* "not yet", which that provider maps to `undefined` BEFORE calling this
|
|
26518
|
-
* helper (its `describedInstanceAttributes`). A caller that knows a `''`
|
|
26519
|
-
* means "unknown" is the one that must say so; this helper cannot tell.
|
|
26520
|
-
*
|
|
26521
|
-
* Deliberately NOT a general "compact this record" utility: it is scoped to
|
|
26522
|
-
* attribute maps. A property bag keeps its `undefined`-valued keys out by other
|
|
26523
|
-
* means (`.claude/rules/provider-property-fidelity.md`, "Remove the key rather
|
|
26524
|
-
* than setting it to `undefined`").
|
|
26525
|
-
*
|
|
26526
|
-
* A LEAF module (imports nothing) so every provider can take it without adding
|
|
26527
|
-
* an edge to the provider import ring.
|
|
26528
|
-
*/
|
|
26529
|
-
/**
|
|
26530
|
-
* Build an attribute map from read-back values, omitting every key whose value
|
|
26531
|
-
* is `undefined` or `null`.
|
|
26532
|
-
*/
|
|
26533
|
-
function definedAttributes(entries) {
|
|
26534
|
-
const out = {};
|
|
26535
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
26536
|
-
if (value === void 0 || value === null) continue;
|
|
26537
|
-
out[key] = value;
|
|
26538
|
-
}
|
|
26539
|
-
return out;
|
|
26540
|
-
}
|
|
26541
|
-
/**
|
|
26542
|
-
* `String(value)` for a numeric read-back whose CloudFormation attribute is a
|
|
26543
|
-
* string (`Endpoint.Port`), or `undefined` when the field is unassigned — so
|
|
26544
|
-
* `definedAttributes` drops the key instead of recording the literal
|
|
26545
|
-
* `'undefined'`, which is what `String(described?.Port ?? '')` yields once its
|
|
26546
|
-
* `?? ''` is merely deleted.
|
|
26547
|
-
*/
|
|
26548
|
-
function stringifyIfAssigned(value) {
|
|
26549
|
-
if (value === void 0 || value === null) return void 0;
|
|
26550
|
-
return String(value);
|
|
26551
|
-
}
|
|
26552
|
-
|
|
26553
26768
|
//#endregion
|
|
26554
26769
|
//#region src/provisioning/providers/eventbridge-bus-provider.ts
|
|
26555
26770
|
/**
|
|
@@ -62205,6 +62420,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62205
62420
|
const guardIndeterminateTargets = /* @__PURE__ */ new Set();
|
|
62206
62421
|
const regionForState = state.region ?? ctx.baseRegion;
|
|
62207
62422
|
refuseMalformedResourcesForDestroy(state, stackName, regionForState);
|
|
62423
|
+
refuseDivergentRecordRegionForDestroy(state, stackName, regionForState, ctx.divergentBodyRegion);
|
|
62208
62424
|
const resourceCount = Object.keys(state.resources).length;
|
|
62209
62425
|
const orphanCount = (state.orphans ?? []).length;
|
|
62210
62426
|
refuseMalformedOutputsForDestroy(state, stackName, regionForState);
|
|
@@ -62264,7 +62480,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62264
62480
|
try {
|
|
62265
62481
|
await ctx.lockManager.releaseLock(stackName, regionForState);
|
|
62266
62482
|
} catch (releaseErr) {
|
|
62267
|
-
logger.warn(`Failed to release lock after empty-state cleanup: ${
|
|
62483
|
+
logger.warn(`Failed to release lock after empty-state cleanup: ${describeAwsFailure(releaseErr).detail}`);
|
|
62268
62484
|
} finally {
|
|
62269
62485
|
process.removeListener("SIGINT", emptySigintHandler);
|
|
62270
62486
|
}
|
|
@@ -62323,7 +62539,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62323
62539
|
try {
|
|
62324
62540
|
switchedClients?.destroy();
|
|
62325
62541
|
} catch (destroyError) {
|
|
62326
|
-
logger.debug(`Failed to destroy region-scoped AWS clients: ${
|
|
62542
|
+
logger.debug(`Failed to destroy region-scoped AWS clients: ${describeAwsFailure(destroyError).detail}`);
|
|
62327
62543
|
}
|
|
62328
62544
|
};
|
|
62329
62545
|
if (stackRegion && stackRegion !== ctx.baseRegion) {
|
|
@@ -62392,7 +62608,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62392
62608
|
try {
|
|
62393
62609
|
await ctx.lockManager.releaseLock(stackName, regionForState);
|
|
62394
62610
|
} catch (releaseErr) {
|
|
62395
|
-
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}`);
|
|
62396
62612
|
}
|
|
62397
62613
|
restoreBaseRegionAndClients();
|
|
62398
62614
|
process.removeListener("SIGINT", sigintHandler);
|
|
@@ -62432,7 +62648,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62432
62648
|
await ctx.stateBackend.saveState(stackName, regionForState, buildDestroySnapshot());
|
|
62433
62649
|
logger.debug(`State persisted after deleting ${logicalId}`);
|
|
62434
62650
|
} catch (error) {
|
|
62435
|
-
logger.warn(`Failed to persist state after deleting ${logicalId} (continuing): ${
|
|
62651
|
+
logger.warn(`Failed to persist state after deleting ${logicalId} (continuing): ${describeAwsFailure(error).detail}`);
|
|
62436
62652
|
}
|
|
62437
62653
|
});
|
|
62438
62654
|
};
|
|
@@ -62628,7 +62844,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62628
62844
|
persistStateAfterDelete(logicalId);
|
|
62629
62845
|
} catch (error) {
|
|
62630
62846
|
renderer.removeTask(logicalId);
|
|
62631
|
-
const msg =
|
|
62847
|
+
const msg = describeAwsFailure(error).detail;
|
|
62632
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"))) {
|
|
62633
62849
|
logger.debug(` ${logicalId} already deleted, removing from state`);
|
|
62634
62850
|
result.deletedCount++;
|
|
@@ -62660,7 +62876,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62660
62876
|
error: extractDeploymentEventError(wrapped)
|
|
62661
62877
|
});
|
|
62662
62878
|
} else {
|
|
62663
|
-
logger.error(` ✗ Failed to delete ${logicalId}:`,
|
|
62879
|
+
logger.error(` ✗ Failed to delete ${logicalId}:`, safeStringify(error));
|
|
62664
62880
|
result.errorCount++;
|
|
62665
62881
|
failedStateTargets.add(stateTargetFor(logicalId, resource.resourceType));
|
|
62666
62882
|
ctx.eventRecorder?.record({
|
|
@@ -62692,7 +62908,7 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62692
62908
|
try {
|
|
62693
62909
|
await ctx.stateBackend.saveState(stackName, regionForState, buildDestroySnapshot());
|
|
62694
62910
|
} catch (error) {
|
|
62695
|
-
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.`);
|
|
62696
62912
|
}
|
|
62697
62913
|
if (result.interrupted) logger.warn(`Destroy interrupted — ${Object.keys(remainingResources).length} resource(s) not deleted. State preserved.`);
|
|
62698
62914
|
else if (result.errorCount > 0) logger.warn(`${result.errorCount} resource(s) failed to delete. State preserved.`);
|
|
@@ -62723,14 +62939,14 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
62723
62939
|
try {
|
|
62724
62940
|
renderer.stop();
|
|
62725
62941
|
} catch (rendererError) {
|
|
62726
|
-
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}`);
|
|
62727
62943
|
}
|
|
62728
62944
|
await saveChain;
|
|
62729
62945
|
logger.debug("Releasing lock...");
|
|
62730
62946
|
try {
|
|
62731
62947
|
await ctx.lockManager.releaseLock(stackName, regionForState);
|
|
62732
62948
|
} catch (releaseErr) {
|
|
62733
|
-
logger.warn(`Failed to release lock for stack '${stackName}': ${
|
|
62949
|
+
logger.warn(`Failed to release lock for stack '${stackName}': ${describeAwsFailure(releaseErr).detail}`);
|
|
62734
62950
|
}
|
|
62735
62951
|
} finally {
|
|
62736
62952
|
process.removeListener("SIGINT", sigintHandler);
|
|
@@ -63003,6 +63219,7 @@ var NestedStackProvider = class {
|
|
|
63003
63219
|
providerRegistry: ctx.providerRegistry,
|
|
63004
63220
|
baseAwsClients: ctx.awsClients,
|
|
63005
63221
|
baseRegion: childRegion,
|
|
63222
|
+
...childStateData.divergentBodyRegion !== void 0 && { divergentBodyRegion: childStateData.divergentBodyRegion },
|
|
63006
63223
|
stateBucket: ctx.stateBucket,
|
|
63007
63224
|
skipConfirmation: true,
|
|
63008
63225
|
...ctx.exportIndexStore && { exportIndexStore: ctx.exportIndexStore },
|
|
@@ -64377,7 +64594,8 @@ async function deployCommand(stacks, options) {
|
|
|
64377
64594
|
clients: {
|
|
64378
64595
|
s3: stackAwsClients.s3,
|
|
64379
64596
|
cloudWatchLogs: stackAwsClients.cloudWatchLogs,
|
|
64380
|
-
expectedRegion: stateForRecreateCheck?.state.region
|
|
64597
|
+
expectedRegion: stateForRecreateCheck?.state.region,
|
|
64598
|
+
...stateForRecreateCheck?.divergentBodyRegion !== void 0 && { expectedRegionDiverged: true }
|
|
64381
64599
|
},
|
|
64382
64600
|
forceStatefulRecreation: options.forceStatefulRecreation ?? false
|
|
64383
64601
|
});
|
|
@@ -74362,6 +74580,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
74362
74580
|
providerRegistry,
|
|
74363
74581
|
baseAwsClients: awsClients,
|
|
74364
74582
|
baseRegion: region,
|
|
74583
|
+
...stateResult.divergentBodyRegion !== void 0 && { divergentBodyRegion: stateResult.divergentBodyRegion },
|
|
74365
74584
|
...options.profile && { profile: options.profile },
|
|
74366
74585
|
stateBucket,
|
|
74367
74586
|
statePrefix: options.statePrefix,
|
|
@@ -76116,10 +76335,10 @@ async function exportCommand(stackArg, options) {
|
|
|
76116
76335
|
}
|
|
76117
76336
|
const cfnStackName = options.cfnStackName ?? resolvedStackName;
|
|
76118
76337
|
const targetRegion = await pickStackRegion$1(stateBackend, resolvedStackName, synthedRegion, options.stackRegion);
|
|
76119
|
-
logger.info(`Migrating cdkd stack '${resolvedStackName}' (${targetRegion}) → CloudFormation stack '${cfnStackName}'`);
|
|
76338
|
+
logger.info(`Migrating cdkd stack '${resolvedStackName}' (${safeSegment(targetRegion)}) → CloudFormation stack '${cfnStackName}'`);
|
|
76120
76339
|
await assertCfnStackAbsent(awsClients.cloudFormation, cfnStackName);
|
|
76121
76340
|
const stateData = await stateBackend.getState(resolvedStackName, targetRegion);
|
|
76122
|
-
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.`);
|
|
76123
76342
|
const { state, etag, migrationPending } = stateData;
|
|
76124
76343
|
const exportJournal = await stateBackend.loadRollbackJournal(resolvedStackName, targetRegion);
|
|
76125
76344
|
if (exportJournal && exportJournal.segments.length > 0) {
|
|
@@ -76236,7 +76455,7 @@ async function exportCommand(stackArg, options) {
|
|
|
76236
76455
|
const phase2Note = phase2Creates.length > 0 ? ` Phase 2 will then CREATE ${phase2Creates.length} non-importable resource(s) (invoking each Custom Resource's onCreate handler).` : "";
|
|
76237
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).` : "";
|
|
76238
76457
|
const unchangedClaim = recreateBeforePhase2.length > 0 ? ` All other AWS resources are unchanged on import.` : ` AWS resources are unchanged on import.`;
|
|
76239
|
-
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.`)) {
|
|
76240
76459
|
logger.info("Migration cancelled. cdkd state and CloudFormation are unchanged.");
|
|
76241
76460
|
return;
|
|
76242
76461
|
}
|
|
@@ -76271,7 +76490,7 @@ async function exportCommand(stackArg, options) {
|
|
|
76271
76490
|
logger.info(` ✓ deleted ${entry.physicalId}`);
|
|
76272
76491
|
} catch (err) {
|
|
76273
76492
|
const msg = err instanceof Error ? err.message : String(err);
|
|
76274
|
-
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.`);
|
|
76275
76494
|
}
|
|
76276
76495
|
}
|
|
76277
76496
|
const phase2Count = phase2Creates.length + recreateBeforePhase2.length;
|
|
@@ -76290,10 +76509,10 @@ async function exportCommand(stackArg, options) {
|
|
|
76290
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` : "";
|
|
76291
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).
|
|
76292
76511
|
2. Re-run the phase 2 UPDATE manually with the full synth template:
|
|
76293
|
-
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.`);
|
|
76294
76513
|
}
|
|
76295
76514
|
await stateBackend.deleteState(resolvedStackName, targetRegion);
|
|
76296
|
-
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.`);
|
|
76297
76516
|
printNextSteps({
|
|
76298
76517
|
cfnStackName,
|
|
76299
76518
|
cdkStackName: resolvedStackName,
|
|
@@ -76321,7 +76540,7 @@ async function pickStackRegion$1(stateBackend, stackName, synthRegion, flag) {
|
|
|
76321
76540
|
}
|
|
76322
76541
|
if (flag) {
|
|
76323
76542
|
if (!refs.find((r) => r.region === flag)) {
|
|
76324
|
-
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
76543
|
+
const seen = refs.map((r) => safeSegment(r.region ?? "(legacy)")).join(", ");
|
|
76325
76544
|
throw new Error(`No state found for stack '${stackName}' in region '${flag}'. Available regions: ${seen}.`);
|
|
76326
76545
|
}
|
|
76327
76546
|
return flag;
|
|
@@ -76330,7 +76549,7 @@ async function pickStackRegion$1(stateBackend, stackName, synthRegion, flag) {
|
|
|
76330
76549
|
if (refs.find((r) => r.region === synthRegion)) return synthRegion;
|
|
76331
76550
|
}
|
|
76332
76551
|
if (refs.length === 1) return refs[0].region ?? synthRegion ?? "";
|
|
76333
|
-
const regions = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
76552
|
+
const regions = refs.map((r) => safeSegment(r.region ?? "(legacy)")).join(", ");
|
|
76334
76553
|
throw new Error(`Stack '${stackName}' has state in multiple regions: ${regions}. Re-run with --stack-region <region> to disambiguate.`);
|
|
76335
76554
|
}
|
|
76336
76555
|
function parseTemplateFile(path) {
|
|
@@ -76394,9 +76613,81 @@ function isPhase2CreatableType(resourceType) {
|
|
|
76394
76613
|
* Module-scoped rather than local to the walker: the first cut of this change
|
|
76395
76614
|
* guarded the walker's two refusals and left the root one, which is the shape
|
|
76396
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.
|
|
76397
76632
|
*/
|
|
76398
76633
|
function safeSegment(value) {
|
|
76399
|
-
|
|
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))}`;
|
|
76400
76691
|
}
|
|
76401
76692
|
/**
|
|
76402
76693
|
* Recursively load the cdkd-state tree rooted at `(rootStackName, region)`.
|
|
@@ -76451,8 +76742,8 @@ async function walkCdkdStateStackTree(stackName, region, state, stateBackend) {
|
|
|
76451
76742
|
if (entry?.resourceType !== "AWS::CloudFormation::Stack") continue;
|
|
76452
76743
|
const childStackName = `${stackName}~${logicalId}`;
|
|
76453
76744
|
const childResult = await stateBackend.getState(childStackName, region);
|
|
76454
|
-
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
|
|
76455
|
-
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.`);
|
|
76456
76747
|
nestedChildren.set(logicalId, await walkCdkdStateStackTree(childStackName, region, childResult.state, stateBackend));
|
|
76457
76748
|
}
|
|
76458
76749
|
return {
|
|
@@ -76533,7 +76824,7 @@ function flattenCdkdStateTreeRootFirst(tree) {
|
|
|
76533
76824
|
*/
|
|
76534
76825
|
function cdkd2cfnStackName(cdkdStackName) {
|
|
76535
76826
|
const cfnName = cdkdStackName.replace(/~/g, "-");
|
|
76536
|
-
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).`);
|
|
76537
76828
|
return cfnName;
|
|
76538
76829
|
}
|
|
76539
76830
|
/**
|
|
@@ -76559,7 +76850,7 @@ function parseCfnChildStackNameOverrides(values) {
|
|
|
76559
76850
|
if (!cdkdName) throw new Error(`--cfn-child-stack-name '${raw}' has an empty cdkd stack name.`);
|
|
76560
76851
|
if (!cfnName) throw new Error(`--cfn-child-stack-name '${raw}' has an empty CFn stack name.`);
|
|
76561
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 '.').`);
|
|
76562
|
-
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.`);
|
|
76563
76854
|
out.set(cdkdName, cfnName);
|
|
76564
76855
|
}
|
|
76565
76856
|
return out;
|
|
@@ -76645,7 +76936,7 @@ function indexNestedTemplatePaths(template, templateDir) {
|
|
|
76645
76936
|
* orchestrator.
|
|
76646
76937
|
*/
|
|
76647
76938
|
function buildPerStackImportNodes(rootStackName, rootTemplate, rootNestedTemplatePaths, rootTemplateFormat, tree) {
|
|
76648
|
-
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)}'.`);
|
|
76649
76940
|
const out = /* @__PURE__ */ new Map();
|
|
76650
76941
|
walk(tree, rootTemplate, rootNestedTemplatePaths, rootTemplateFormat);
|
|
76651
76942
|
return out;
|
|
@@ -76659,7 +76950,7 @@ function buildPerStackImportNodes(rootStackName, rootTemplate, rootNestedTemplat
|
|
|
76659
76950
|
});
|
|
76660
76951
|
for (const [childLogicalId, childNode] of node.nestedChildren) {
|
|
76661
76952
|
const childTemplatePath = nodeNestedTemplatePaths[childLogicalId];
|
|
76662
|
-
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)}`);
|
|
76663
76954
|
const { template: childTemplate, format: childFormat } = readNestedChildTemplateFile(childTemplatePath, childLogicalId);
|
|
76664
76955
|
walk(childNode, childTemplate, indexNestedTemplatePaths(childTemplate, path.dirname(childTemplatePath)), childFormat);
|
|
76665
76956
|
}
|
|
@@ -76711,7 +77002,7 @@ async function buildImportPlan(state, template, cfnClient, parentStackName, opti
|
|
|
76711
77002
|
blocked.push({
|
|
76712
77003
|
logicalId,
|
|
76713
77004
|
resourceType,
|
|
76714
|
-
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.`
|
|
76715
77006
|
});
|
|
76716
77007
|
continue;
|
|
76717
77008
|
}
|
|
@@ -77404,13 +77695,13 @@ function reportDriftBaselineGaps(state, logger) {
|
|
|
77404
77695
|
const missing = entries.filter(([, r]) => r.observedProperties === void 0);
|
|
77405
77696
|
if (missing.length === 0) return;
|
|
77406
77697
|
if (state.version !== void 0 && state.version < 3) {
|
|
77407
|
-
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.`);
|
|
77408
77699
|
return;
|
|
77409
77700
|
}
|
|
77410
77701
|
const refused = missing.filter(([, r]) => r.observedBaselineRefused === true);
|
|
77411
77702
|
const refreshable = missing.filter(([, r]) => r.observedBaselineRefused !== true);
|
|
77412
77703
|
if (refreshable.length > 0) {
|
|
77413
|
-
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.`);
|
|
77414
77705
|
for (const [logicalId] of refreshable.slice(0, 10)) logger.warn(` ${logicalId}`);
|
|
77415
77706
|
if (refreshable.length > 10) logger.warn(` ... and ${refreshable.length - 10} more`);
|
|
77416
77707
|
}
|
|
@@ -77953,12 +78244,12 @@ async function buildResolvedParametersPerStack(args) {
|
|
|
77953
78244
|
for (const ref of flattenCdkdStateTreeRootFirst(args.tree)) {
|
|
77954
78245
|
if (ref.stackName === args.rootStackName) continue;
|
|
77955
78246
|
const node = nodeByName.get(ref.stackName);
|
|
77956
|
-
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.`);
|
|
77957
78248
|
const parentLogicalId = node.state.parentLogicalId;
|
|
77958
78249
|
const parentStackName = node.state.parentStack;
|
|
77959
|
-
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.`);
|
|
77960
78251
|
const parentNode = nodeByName.get(parentStackName);
|
|
77961
|
-
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.`);
|
|
77962
78253
|
const parentParamValues = {};
|
|
77963
78254
|
for (const p of paramsByCdkdName.get(parentStackName) ?? []) if (p.ParameterKey !== void 0) parentParamValues[p.ParameterKey] = p.ParameterValue;
|
|
77964
78255
|
const parentResolverContext = {
|
|
@@ -78043,7 +78334,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78043
78334
|
};
|
|
78044
78335
|
const nodesByCdkdName = buildPerStackImportNodes(rootStackName, rootTemplate, rootStackInfoNestedTemplates, rootTemplateFormat, tree);
|
|
78045
78336
|
const leafFirst = flattenCdkdStateTreeLeafFirst(tree);
|
|
78046
|
-
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.`);
|
|
78047
78338
|
const perStackPlans = [];
|
|
78048
78339
|
for (const node of leafFirst) {
|
|
78049
78340
|
const meta = nodesByCdkdName.get(node.stackName);
|
|
@@ -78054,7 +78345,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78054
78345
|
});
|
|
78055
78346
|
if (plan.blocked.length > 0) {
|
|
78056
78347
|
const lines = groupBlockedReasons(plan.blocked);
|
|
78057
|
-
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")}`);
|
|
78058
78349
|
}
|
|
78059
78350
|
perStackPlans.push({
|
|
78060
78351
|
cdkdName: meta.cdkdStackName,
|
|
@@ -78073,7 +78364,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78073
78364
|
const totalRecreate = perStackPlans.reduce((acc, p) => acc + p.recreateBeforePhase2.length, 0);
|
|
78074
78365
|
logger.info("");
|
|
78075
78366
|
logger.info(`Migrating cdkd nested-stack tree rooted at '${rootStackName}' → CloudFormation (${perStackPlans.length} stack(s), leaf-first):`);
|
|
78076
|
-
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` : ""));
|
|
78077
78368
|
logger.info("");
|
|
78078
78369
|
if (totalPhase2Creates > 0 && !options.includeNonImportable) {
|
|
78079
78370
|
if (options.dryRun) {
|
|
@@ -78097,7 +78388,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78097
78388
|
if (!options.yes) {
|
|
78098
78389
|
const phase2Note = totalPhase2Creates > 0 ? ` Phase 2 will CREATE ${totalPhase2Creates} non-importable resource(s) across the tree.` : "";
|
|
78099
78390
|
const recreateNote = totalRecreate > 0 ? ` cdkd will also pre-DELETE + re-CREATE ${totalRecreate} IMPORT-unsupported resource(s) (brief unavailability window per resource).` : "";
|
|
78100
|
-
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.")) {
|
|
78101
78392
|
logger.info("Migration cancelled. cdkd state and CloudFormation are unchanged.");
|
|
78102
78393
|
return {
|
|
78103
78394
|
outcome: "cancelled",
|
|
@@ -78143,11 +78434,11 @@ async function runPerStackImportLoop(args) {
|
|
|
78143
78434
|
resolver: paramResolver,
|
|
78144
78435
|
stateBackend: deps.stateBackend
|
|
78145
78436
|
}));
|
|
78146
|
-
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.`);
|
|
78147
78438
|
try {
|
|
78148
78439
|
for (let i = 0; i < perStackPlans.length; i++) {
|
|
78149
78440
|
const plan = perStackPlans[i];
|
|
78150
|
-
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)`);
|
|
78151
78442
|
const stackParameters = paramsByCdkdName.get(plan.cdkdName) ?? [];
|
|
78152
78443
|
const phase1ATemplate = filterTemplateForImport(plan.template, plan.phase1Imports);
|
|
78153
78444
|
const phase1AResources = plan.phase1Imports.map((entry) => ({
|
|
@@ -78156,13 +78447,13 @@ async function runPerStackImportLoop(args) {
|
|
|
78156
78447
|
ResourceIdentifier: entry.resourceIdentifier
|
|
78157
78448
|
}));
|
|
78158
78449
|
const injectedDeletion = injectDeletionPolicyForImport(phase1ATemplate);
|
|
78159
|
-
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).`);
|
|
78160
78451
|
try {
|
|
78161
78452
|
await submitImportChangeSet(deps.cfnClient, plan.cfnName, phase1ATemplate, phase1AResources, stackParameters, plan.templateFormat, deps.uploadOpts);
|
|
78162
78453
|
} catch (err) {
|
|
78163
|
-
const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${s.cdkdStackName} → ${s.cfnStackName}`).join(", ") : "(none)";
|
|
78164
|
-
const remainingSummary = perStackPlans.slice(i).map((p) => p.cdkdName).join(", ");
|
|
78165
|
-
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 });
|
|
78166
78457
|
}
|
|
78167
78458
|
const cfnArn = (await deps.cfnClient.send(new DescribeStacksCommand({ StackName: plan.cfnName }))).Stacks?.[0]?.StackId;
|
|
78168
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.`);
|
|
@@ -78184,7 +78475,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78184
78475
|
const phase1BResources = [];
|
|
78185
78476
|
for (const row of plan.nestedStackRows) {
|
|
78186
78477
|
const childArn = cfnArnByCdkdName.get(row.childStackName);
|
|
78187
|
-
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.`);
|
|
78188
78479
|
const childCfnName = cfnStackNameOf(row.childStackName);
|
|
78189
78480
|
const childTemplateBody = await fetchCfnStackTemplate(deps.cfnClient, childCfnName);
|
|
78190
78481
|
const uploaded = await uploadCfnTemplate({
|
|
@@ -78197,7 +78488,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78197
78488
|
uploadCleanups.push(uploaded.cleanup);
|
|
78198
78489
|
const childActualTags = (await deps.cfnClient.send(new DescribeStacksCommand({ StackName: childCfnName }))).Stacks?.[0]?.Tags ?? [];
|
|
78199
78490
|
const originalRow = getResourceFromTemplate(plan.template, row.logicalId);
|
|
78200
|
-
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.`);
|
|
78201
78492
|
const rewrittenRow = injectRetainAndRewriteTemplateUrl(originalRow, uploaded.url, childActualTags);
|
|
78202
78493
|
phase1BTemplate["Resources"][row.logicalId] = rewrittenRow;
|
|
78203
78494
|
rewrittenNestedRows.set(row.logicalId, rewrittenRow);
|
|
@@ -78210,8 +78501,8 @@ async function runPerStackImportLoop(args) {
|
|
|
78210
78501
|
try {
|
|
78211
78502
|
await submitImportChangeSet(deps.cfnClient, plan.cfnName, phase1BTemplate, phase1BResources, stackParameters, plan.templateFormat, deps.uploadOpts);
|
|
78212
78503
|
} catch (err) {
|
|
78213
|
-
const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${s.cdkdStackName} → ${s.cfnStackName}`).join(", ") : "(none)";
|
|
78214
|
-
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 });
|
|
78215
78506
|
}
|
|
78216
78507
|
logger.info(` ✓ Phase 1B: parent '${plan.cfnName}' adopted ${plan.nestedStackRows.length} nested child(ren) via UPDATE-IMPORT.`);
|
|
78217
78508
|
if (plan.cdkdName !== rootStackName) {
|
|
@@ -78221,7 +78512,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78221
78512
|
}
|
|
78222
78513
|
if (plan.recreateBeforePhase2.length > 0) for (const entry of plan.recreateBeforePhase2) {
|
|
78223
78514
|
const handler = PRE_DELETE_HANDLERS[entry.resourceType];
|
|
78224
|
-
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.`);
|
|
78225
78516
|
logger.info(` Pre-deleting AWS resource for ${entry.logicalId} (${entry.resourceType}) so CFn can re-CREATE in phase 2...`);
|
|
78226
78517
|
await handler(entry);
|
|
78227
78518
|
logger.info(` ✓ deleted ${entry.physicalId}`);
|
|
@@ -78240,21 +78531,21 @@ async function runPerStackImportLoop(args) {
|
|
|
78240
78531
|
const stateDeletionFailures = [];
|
|
78241
78532
|
for (const node of leafFirst) try {
|
|
78242
78533
|
await deps.stateBackend.deleteState(node.stackName, node.region);
|
|
78243
|
-
logger.info(`cdkd state for '${node.stackName}' (${node.region}) removed.`);
|
|
78534
|
+
logger.info(`cdkd state for '${safeSegment(node.stackName)}' (${safeSegment(node.region)}) removed.`);
|
|
78244
78535
|
} catch (err) {
|
|
78245
78536
|
stateDeletionFailures.push({
|
|
78246
78537
|
stackName: node.stackName,
|
|
78247
78538
|
region: node.region,
|
|
78248
78539
|
reason: err instanceof Error ? err.message : String(err)
|
|
78249
78540
|
});
|
|
78250
|
-
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)}`);
|
|
78251
78542
|
}
|
|
78252
78543
|
if (stateDeletionFailures.length > 0) {
|
|
78253
|
-
const lines = stateDeletionFailures.map((f) => ` - cdkd/${f.stackName}/${f.region}/state.json: ${f.reason}`).join("\n");
|
|
78254
|
-
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.`);
|
|
78255
78546
|
}
|
|
78256
78547
|
if (sessionIntrinsicSkipped.size > 0) {
|
|
78257
|
-
const detail = [...sessionIntrinsicSkipped.entries()].map(([cdkdName, params]) => `${cdkdName} (${params.join(", ")})`).join("; ");
|
|
78548
|
+
const detail = [...sessionIntrinsicSkipped.entries()].map(([cdkdName, params]) => `${safeSegment(cdkdName)} (${params.join(", ")})`).join("; ");
|
|
78258
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'.`);
|
|
78259
78550
|
}
|
|
78260
78551
|
return {
|
|
@@ -78268,7 +78559,7 @@ async function runPerStackImportLoop(args) {
|
|
|
78268
78559
|
for (let i = acquiredChildLocks.length - 1; i >= 0; i--) {
|
|
78269
78560
|
const lock = acquiredChildLocks[i];
|
|
78270
78561
|
await deps.lockManager.releaseLock(lock.stackName, lock.region).catch((err) => {
|
|
78271
|
-
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)}`);
|
|
78272
78563
|
});
|
|
78273
78564
|
}
|
|
78274
78565
|
}
|
|
@@ -79678,9 +79969,29 @@ function createStateOrphanCommand() {
|
|
|
79678
79969
|
* state record. AWS resources are left intact.
|
|
79679
79970
|
*
|
|
79680
79971
|
* Region scoping: when a stack name has multiple state records spread across
|
|
79681
|
-
* regions (PR 1 territory), `--region` selects one
|
|
79682
|
-
*
|
|
79683
|
-
*
|
|
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.
|
|
79684
79995
|
*/
|
|
79685
79996
|
async function stateDestroyCommand(stackArgs, options) {
|
|
79686
79997
|
const logger = getLogger();
|
|
@@ -79827,6 +80138,7 @@ async function stateDestroyCommand(stackArgs, options) {
|
|
|
79827
80138
|
providerRegistry,
|
|
79828
80139
|
baseAwsClients: setup.awsClients,
|
|
79829
80140
|
baseRegion: setup.region,
|
|
80141
|
+
...stateResult.divergentBodyRegion !== void 0 && { divergentBodyRegion: stateResult.divergentBodyRegion },
|
|
79830
80142
|
...options.profile && { profile: options.profile },
|
|
79831
80143
|
stateBucket: setup.bucket,
|
|
79832
80144
|
statePrefix: options.statePrefix,
|
|
@@ -94394,4 +94706,4 @@ function buildProgram() {
|
|
|
94394
94706
|
|
|
94395
94707
|
//#endregion
|
|
94396
94708
|
export { buildProgram };
|
|
94397
|
-
//# sourceMappingURL=program-
|
|
94709
|
+
//# sourceMappingURL=program-babtsurS.js.map
|