@go-to-k/cdkd 0.291.2 → 0.291.4
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-DPGr3o8N.js → asg-provider-_vdcb9QG.js} +4 -4
- package/dist/asg-provider-_vdcb9QG.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-C1w5fjW1.js → deploy-engine-CpMT9Bmm.js} +1980 -132
- package/dist/deploy-engine-CpMT9Bmm.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-DnmYd7BO.js → program-C5r-kmIQ.js} +556 -177
- package/dist/{program-DnmYd7BO.js.map → program-C5r-kmIQ.js.map} +1 -1
- package/dist/{version-5OUR3VB2.js → version-ChuDKy_E.js} +2 -2
- package/dist/{version-5OUR3VB2.js.map → version-ChuDKy_E.js.map} +1 -1
- package/package.json +2 -2
- package/dist/asg-provider-DPGr3o8N.js.map +0 -1
- package/dist/deploy-engine-C1w5fjW1.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as yellow, $i as
|
|
1
|
+
import { $ as yellow, $a as describeAwsFailure, $i as expectedOwnerParam, $n as repairMalformedResourcePropertiesForReadOnly, $r as rewriteTemplateAssetReferences, $t as requireConfigObject, A as createPreDeleteFinalSnapshot, Aa as LocalStartServiceError, Ai as getDefaultStateBucketName, An as malformedOutputsRefusalMessage, Ar as buildForceUnlockCommand, At as carriesDynamicReference, B as isStatefulRecreateTargetSync, Bi as warnDeprecatedNoPrefixCliFlag, Bn as refuseMalformedOrphansForDestroy, Br as hasUnverifiableParameterRefusal, Bt as isSettledInstanceState, C as namesAnyDeclaredParameter, Ci as runDockerStreaming, Cn as isReadableResourceEntry, Cr as recoverMaskedOutput, Ct as slowCcOperationTimeoutMs, D as PRE_DELETE_SNAPSHOT_TYPES, Da as DynamicReferenceRegionAmbiguousError, Di as getDockerImageBySourceHash, Dn as malformedOrphanRecordsWarning, Dr as S3StateBackend, Dt as disableInstanceApiTermination, E as ATOMIC_FINAL_SNAPSHOT_TYPES, Ea as DeployCancelledError, Ei as AssetManifestLoader, En as malformedLocalOutputsWarning, Er as LockManager, Et as deleteSkipReason, F as extractDeploymentEventError, Fa as ProvisioningError, Fi as resolveSkipPrefix, Fn as malformedResourcesWarning, Fr as CUSTOM_RESOURCE_RESPONSE_PREFIX, Ft as isImpossibleEmptyStoredAttribute, G as toCfnInteger, Ga as withErrorHandling, Gi as findLargeInlineResources, Gn as refuseMalformedResourceEntries, Gr as orphansCarriedFrom, Gt as coerceCfnBoolean, H as WARM_THROUGHPUT_MEMBERS, Hi as CFN_TEMPLATE_BODY_LIMIT, Hn as refuseMalformedOutputs, Hr as importableOutputs, Ht as normalizeAwsTagsToCfn, I as makeSiblingClaimReader, Ia as ResourceTimeoutError, Ii as resolveStateBucketWithDefault, In as malformedStateRefusalMessage, Ir as DEFAULT_STATE_PREFIX, It as isNamelessDynamicReferenceError, J as bold, Ja as isThrottlingError, Ji as injectiveKey, Jn as refuseMalformedResourcesForDestroy, Jr as stringifyValue, Jt as configIntegerRefusal, K as toFiniteNumber, Ka as isMarkedNonRetryable, Ki as uploadCfnTemplate, Kn as refuseMalformedResourceEntriesForOrphan, Kr as shouldRetainResource, Kt as coerceCfnInteger, L as planOrphanAdoption, La as ResourceUpdateNotSupportedError, Li as resolveStateBucketWithDefaultAndSource, Ln as refuseDivergentRecordRegionForDestroy, Lr as exportNamesCarriedFrom, Lt as isUnboundTemplateParameter, M as refusesFinalSnapshot, Ma as MalformedProducerRecordRefusalError, Mi as resolveApp, Mn as malformedRenderedContainersWarning, Mr as forceQuitRecoveryClause, Mt as cfnRefValueFromPhysicalId, N as unsupportedFinalSnapshotError, Na as NestedStackChildDirectDestroyError, Ni as resolveAutoAssetStorage, Nn as malformedResourceEntriesWarning, Nr as formatLockExpiry, Nt as coerceParameterTypedValue, O as buildFinalSnapshotIdentifier, Oa as IntrinsicResolutionRefusalError, Oi as Synthesizer, On as malformedOrphansRefusalMessage, Or as rebuildClientForBucketRegion, Ot as isTerminationProtectionPropagationError, P as makeCanonicalizePropertiesFn, Pa as PartialFailureError, Pi as resolveCaptureObservedState, Pn as malformedResourcePropertiesWarning, Pr as shellQuote, Pt as getAccountInfo, Q as red, Qa as retryClassificationText, Qi as decodeListingKey, Qn as repairMalformedResourceEntriesForReadOnly, Qr as loadPublishableAssetManifest, Qt as requireConfigArray, R as MULTI_REGION_RECREATE_BLOCKED_TYPES, Ra as StackHasActiveImportsError, Ri as resolveUseCdkBootstrapAssets, Rn as refuseMalformedNestedChildOutputs, Rr as hasReadableExportSet, Rt as parameterTypeMayLoseSecretIdentity, S as computeParameterTaint, Sa as CdkdError, Si as runDockerForeground, Sn as hasReadableResources, Sr as recordMaskOnlyValue, St as isWaitAbandonedError, T as resourcesNamingDeclaredParameter, Ti as stripControlChars, Tn as malformedExportSourceWarning, Tr as scrubResourceRecord, Tt as deleteIndeterminateGuards, U as coerceWarmThroughput$1, Ui as CFN_TEMPLATE_URL_LIMIT, Un as refuseMalformedOutputsForDestroy, Ur as isReadableBag, Ut as resolveExplicitPhysicalId, V as renderStatefulReason, Va as SynthesisError, Vi as ssmResolvedValueType, Vn as refuseMalformedOrphansForOrphan, Vr as importableOutputKeys, Vt as WAFv2WebACLProvider, W as isWarmThroughputDecrease, Wa as normalizeAwsError, Wi as MIGRATE_TMP_PREFIX, Wn as refuseMalformedResourceAttributesForOrphan, Wr as orphansAfterRollback, Wt as assertRegionMatch, X as gray, Xa as markNonRetryable, Xi as producerCoordinateKey, Xn as repairMalformedOrphansForReadOnly, Xr as buildAssetRedirectMap, Xt as readConfigString, Y as cyan, Ya as isTransientServerError, Yi as injectiveKeyPrefix, Yn as refuseMalformedState, Yr as WorkGraph, Yt as configStringRefusal, Z as green, Za as markRedactedCause, Zi as producerRecordKey, Zn as repairMalformedOutputsForReadOnly, Zr as createAssetRedirectResolver, Zt as replayWarn, _ as bagHoldsSecretExpression, _a as AwsClients, _i as dockerSpawnEnvWithSensitive, _n as UNREADABLE_ORPHANS_CONTAINER_ROW, _r as identityKeyFor, _t as endCommandInterruptScope, a as DeploymentEventsStore, aa as globMatches, ai as isCrossRegionRedirect, an as s3BucketDomainName, ar as describeTypeWithThrottleRetry, at as secretBearingStateKeyWarning, b as mergeNoChangeOutputs, ba as setAwsClients, bi as partitionSensitiveEnv, bn as hasReadableOrphans, br as maskSecretsInError, bt as startInterruptWatch, c as replayFailedOperations, ca as hasOwnKey, ci as validateAssetBucketName, cn as s3BucketWebsiteUrl, cr as TemplateParser, ct as IAMRoleProvider, d as updatePartialMessage, da as ownValue, di as buildDockerImage, dn as applyCallerIdentityCredentials, dr as STATE_SOURCED_READBACK_RULES, dt as ProviderRegistry, ea as PARTITION_TABLE, ei as AssetModeResolver, en as requireConfigString, eo as safeStringify, er as repairMalformedResourcesForReadOnly, et as collectDeclaredOutputNames, f as updatePartialReason, fa as absoluteAssemblyPathEscape, fi as resolveDockerContextDirectory, fn as callerEnvCredentials, fr as TEMPLATE_SOURCED_RULES, ft as wouldReturnToSdkProvider, g as computeImplicitDeleteEdges, ga as resolveBucketRegion, gi as describeDockerFailure, gn as STATE_RESOURCES_MALFORMED, gr as errorCauseChain, gt as beginCommandInterruptScope, h as IMPLICIT_DELETE_DEPENDENCIES, hi as describeDockerExecFailure, hn as INTRINSIC_KEYS, hr as dynamicReferenceTokens, ht as maskerOrIdentity, i as DeploymentEventsReader, ia as failedStageNote, ii as getBootstrapMarkerKey, in as s3BucketArn, ir as findSilentDropProperties, it as secretBearing, j as isFinalSnapshotError, ji as getLegacyStateBucketName, jn as malformedOutputsWarning, jr as buildLockContentionMessage, jt as carriesFetchableDynamicReference, k as ccRoutedFinalSnapshotError, ka as LocalInvokeBuildError$1, ki as synthesisStatusMessage, kn as malformedOrphansWarning, kr as UNREPRODUCIBLE_LOCK_CLAUSE, kt as IntrinsicFunctionResolver, l as replayRollback, la as hasPlainPrototype, li as validateContainerRepoName, ln as applyRoleArnIfSet, lr as STATE_SOURCED_BASELINE_RULES, lt as collectInlinePolicyNamesManagedBySiblings, m as maskingRetryLogger, ma as resolveAssemblyPath, mi as describeDockerCapturedOutput, mn as DiffCalculator, mr as createSecretMasker, mt as maskDeep, n as DEFAULT_RESOURCE_WARN_AFTER_MS, na as derivePartitionAndUrlSuffix, ni as assertAssetBucketRegion, nn as producerRegionsFromState, nr as safeStackName, nt as exportAliasCollisionScrubWarning, o as planFailedOps, oa as processStackMessages, oi as parseBootstrapMarker, on as s3BucketDualStackDomainName, or as withRetry, ot as secretSafeKeyDisplay, p as withResourceDeadline, pa as renderAssemblyPathEscape, pi as warnManifestExecutable, pn as envCredentialsAreAssumedRole, pr as carriesSecretMask, pt as createMaskedRetryLogger, q as formatResourceLine, qa as isRetryableTransientError, qi as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, qn as refuseMalformedResourcePropertiesForOrphan, qr as AssetPublisher, qt as configBooleanRefusal, r as DeployEngine, ra as AssemblyReader, ri as ensureAssetStorage, rn as withSharedDrainBudget, rr as findActionableSilentDrops, rt as isExportAliasCollision, s as planRollback, sa as defineOwnKey, si as readBootstrapMarkerBody, sn as s3BucketRegionalDomainName, sr as DagBuilder, st as getCurrentResourceSecrets, t as DEFAULT_RESOURCE_TIMEOUT_MS, ta as canonicalizeRegion, ti as BOOTSTRAP_MARKER_PREFIX, tn as classifyReplaySecretRegion, tr as safeRegion, tt as collectPublishedOutputNames, u as resolveReplacementOldType, ua as nullPrototypeRecord, ui as buildDenyExternalAccessPolicy, un as AWS_CREDENTIAL_ENV_KEYS, ur as STATE_SOURCED_CROSS_GENERATION_RULES, ut as clearOnUpdateRemoval, v as isSecretBearingReferenceString, va as getAwsClients, vi as formatDockerLoginError, vn as UNREADABLE_RESOURCES_MAP_ROW, vr as isSingleDynamicReferenceToken, vt as interruptWatchListenerCount, w as reachableDivergentParameters, wa as CrossAccountSecretRefusalError, wi as escapeRegExp, wn as malformedExportNamesWarning, wr as redactSecretsForState, wt as UNSPECIFIED_SKIP_REASON, x as bindingSkippedOutputs, xi as redactDockerArgvValues, xn as hasReadableOutputs, xr as maskSecretsInText, xt as CloudControlProvider, y as keptWholeReasonText, ya as resetAwsClients, yi as getDockerCmd, yn as displayLogicalId, yr as markSameGenerationBag, yt as isInterruptedWaitError, z as isStatefulRecreateTargetForReplace, za as StackTerminationProtectionError, zi as stateBucketExistenceConfirmed, zn as refuseMalformedOrphans, zr as hasReasonlessBaselineRefusal, zt as refStateLookupFromResource } from "./deploy-engine-CpMT9Bmm.js";
|
|
2
2
|
import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, _ as withSkipPrefix, b as SECRET_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 displayIdent, x as STACK_REF_MAX_CODE_POINTS } from "./logger-GASr9bAF.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-ChuDKy_E.js";
|
|
5
|
+
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-_vdcb9QG.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";
|
|
@@ -290,7 +290,7 @@ const STATE_KEY_MAX_CODE_POINTS = STACK_REF_MAX_CODE_POINTS + 64;
|
|
|
290
290
|
* Parse context key=value pairs from CLI arguments into a Record
|
|
291
291
|
*/
|
|
292
292
|
function parseContextOptions(contextArgs) {
|
|
293
|
-
const context =
|
|
293
|
+
const context = nullPrototypeRecord();
|
|
294
294
|
if (contextArgs) for (const arg of contextArgs) {
|
|
295
295
|
const eqIndex = arg.indexOf("=");
|
|
296
296
|
if (eqIndex > 0) context[arg.substring(0, eqIndex)] = arg.substring(eqIndex + 1);
|
|
@@ -2046,6 +2046,104 @@ function createBootstrapCommand() {
|
|
|
2046
2046
|
return cmd;
|
|
2047
2047
|
}
|
|
2048
2048
|
|
|
2049
|
+
//#endregion
|
|
2050
|
+
//#region src/cli/stack-matcher.ts
|
|
2051
|
+
/**
|
|
2052
|
+
* Match stacks against user-supplied name patterns.
|
|
2053
|
+
*
|
|
2054
|
+
* Patterns are evaluated against two fields:
|
|
2055
|
+
*
|
|
2056
|
+
* - `stackName` — the physical CloudFormation stack name (e.g. `MyStage-MyStack`)
|
|
2057
|
+
* - `displayName` — the hierarchical CDK path (e.g. `MyStage/MyStack`); falls
|
|
2058
|
+
* back to `stackName` when the assembly does not carry one
|
|
2059
|
+
*
|
|
2060
|
+
* Routing is decided by whether the pattern contains `/`:
|
|
2061
|
+
*
|
|
2062
|
+
* - Pattern contains `/` → matched only against `displayName` (a `/` cannot
|
|
2063
|
+
* appear in a CloudFormation stack name, so this is unambiguous)
|
|
2064
|
+
* - Pattern contains no `/` → matched only against `stackName`
|
|
2065
|
+
*
|
|
2066
|
+
* Wildcards (`*`) are supported in either case; every other character is
|
|
2067
|
+
* literal. Results are de-duplicated by
|
|
2068
|
+
* `stackName`, so a pattern that incidentally matches the same stack via both
|
|
2069
|
+
* fields is returned only once.
|
|
2070
|
+
*/
|
|
2071
|
+
function matchStacks(stacks, patterns) {
|
|
2072
|
+
if (patterns.length === 0) return [];
|
|
2073
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2074
|
+
const result = [];
|
|
2075
|
+
for (const stack of stacks) if (patterns.some((pattern) => stackMatchesPattern(stack, pattern)) && !seen.has(stack.stackName)) {
|
|
2076
|
+
seen.add(stack.stackName);
|
|
2077
|
+
result.push(stack);
|
|
2078
|
+
}
|
|
2079
|
+
return result;
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Render a stack for diagnostic messages. When `displayName` differs from the
|
|
2083
|
+
* physical name, both are shown so the user can see which forms are valid as
|
|
2084
|
+
* patterns (e.g. `MyStage-Api (MyStage/Api)`).
|
|
2085
|
+
*
|
|
2086
|
+
* Both names come from the Cloud Assembly, and every one of this helper's
|
|
2087
|
+
* render sites interpolates the result into PROSE cdkd authors — `Available:
|
|
2088
|
+
* ...`, `Multiple stacks found: ...`, `Publishing assets for stack: ...`, the
|
|
2089
|
+
* last of which prints on a normal run rather than only on an error. So they
|
|
2090
|
+
* go through `displayIdent` ([#3277](https://github.com/go-to-k/cdkd/issues/3277)):
|
|
2091
|
+
* a `stackName` carrying `ESC [ 2 K` plus a carriage return ERASES cdkd's own
|
|
2092
|
+
* line and a newline forges a second one, and a value carrying quotes and
|
|
2093
|
+
* periods writes a cdkd-sounding clause.
|
|
2094
|
+
*
|
|
2095
|
+
* It is the identity on every legitimate `stackName` — a CloudFormation stack
|
|
2096
|
+
* name is `[A-Za-z0-9-]`, inside `PLAIN_IDENT`. It is NOT the identity on
|
|
2097
|
+
* every legitimate `displayName`: CDK sets that to the construct path, and
|
|
2098
|
+
* `constructs` rewrites only `/` in an id, so `new Stack(app, 'My Stack')` is
|
|
2099
|
+
* legal and prints `MyStack ("My Stack")`. Those quotes are not part of any
|
|
2100
|
+
* pattern, in a clause whose job is to say which forms ARE valid as patterns —
|
|
2101
|
+
* accepted, because the alternative is `displaySafe`, which passes the quotes
|
|
2102
|
+
* and spaces a crafted value needs, and the pattern still matches the RAW
|
|
2103
|
+
* name either way (`matchStacks` never sees this rendering).
|
|
2104
|
+
*
|
|
2105
|
+
* Sanitizing HERE rather than at the call sites is what keeps the rule whole:
|
|
2106
|
+
* the sites include `scrub.ts` and `destroy.ts`, which this change does not
|
|
2107
|
+
* touch, and a rule widened by hand is how the class survived being closed
|
|
2108
|
+
* twice already.
|
|
2109
|
+
*/
|
|
2110
|
+
function describeStack(stack) {
|
|
2111
|
+
const name = displayIdent(stack.stackName, { maxCodePoints: STACK_REF_MAX_CODE_POINTS });
|
|
2112
|
+
if (stack.displayName && stack.displayName !== stack.stackName) return `${name} (${displayIdent(stack.displayName, { maxCodePoints: STACK_REF_MAX_CODE_POINTS })})`;
|
|
2113
|
+
return name;
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
2116
|
+
* The message a command raises when SELECTION came back empty — shared by
|
|
2117
|
+
* `deploy`, `diff`, `list` and `publish-assets`, which all built the identical
|
|
2118
|
+
* string by hand.
|
|
2119
|
+
*
|
|
2120
|
+
* `assembly` is required AND so is its `failedStages` member, which is the
|
|
2121
|
+
* point: a Stage that failed to load dropped every stack under it from
|
|
2122
|
+
* `available`, so "no stacks matching" names the wrong problem unless the
|
|
2123
|
+
* failure is reported with it (issue
|
|
2124
|
+
* [#3482](https://github.com/go-to-k/cdkd/issues/3482)). A REQUIRED member,
|
|
2125
|
+
* not an optional one — `{ failedStages?: ... }` accepts `{}`, so it fences
|
|
2126
|
+
* the argument while leaving the content unfenced, which is the wiring hole
|
|
2127
|
+
* this signature exists to close. `SynthesisResult.failedStages` is required
|
|
2128
|
+
* for the same reason, so passing the result satisfies it and an ad-hoc `{}`
|
|
2129
|
+
* does not.
|
|
2130
|
+
*
|
|
2131
|
+
* What it does NOT fence: a caller passing a STALE or empty list. That is
|
|
2132
|
+
* behaviour, not shape, and it is covered by a wiring test per command.
|
|
2133
|
+
*/
|
|
2134
|
+
function renderNoStackMatch(patterns, available, assembly) {
|
|
2135
|
+
return (patterns.length > 0 ? `No stacks matching ${patterns.join(", ")} found in assembly. ` + (available.length > 0 ? `Available: ${available.map(describeStack).join(", ")}` : "The assembly has no stacks") : "No stacks found in assembly") + failedStageNote(patterns, assembly.failedStages);
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* `*` matches any run of characters and every other character is literal —
|
|
2139
|
+
* `globMatches` owns that rule for this matcher and for the failed-Stage
|
|
2140
|
+
* attribution alike ([#3508](https://github.com/go-to-k/cdkd/issues/3508)).
|
|
2141
|
+
*/
|
|
2142
|
+
function stackMatchesPattern(stack, pattern) {
|
|
2143
|
+
const target = pattern.includes("/") ? stack.displayName ?? stack.stackName : stack.stackName;
|
|
2144
|
+
return globMatches(pattern, target);
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2049
2147
|
//#endregion
|
|
2050
2148
|
//#region src/utils/yaml.ts
|
|
2051
2149
|
/**
|
|
@@ -2293,7 +2391,7 @@ function resolveVerboseTemplatePath(outputDir, stackName) {
|
|
|
2293
2391
|
* (go-to-k/cdkd#3489): the guard has its own cases, but reverting the CALL
|
|
2294
2392
|
* SITE left the suite green.
|
|
2295
2393
|
*/
|
|
2296
|
-
async function synthCommand(options) {
|
|
2394
|
+
async function synthCommand(stackPatterns, options) {
|
|
2297
2395
|
const logger = getLogger();
|
|
2298
2396
|
if (options.verbose) logger.setLevel("debug");
|
|
2299
2397
|
reserveStdoutForPayload();
|
|
@@ -2319,7 +2417,11 @@ async function synthCommand(options) {
|
|
|
2319
2417
|
...options.profile && { profile: options.profile },
|
|
2320
2418
|
...Object.keys(context).length > 0 && { context }
|
|
2321
2419
|
};
|
|
2322
|
-
const
|
|
2420
|
+
const result = await synthesizer.synthesize(synthOptions);
|
|
2421
|
+
const { stacks: allStacks, assemblyDir } = result;
|
|
2422
|
+
const selected = stackPatterns.length > 0 ? matchStacks(allStacks, stackPatterns) : allStacks;
|
|
2423
|
+
if (selected.length === 0) throw new Error(renderNoStackMatch(stackPatterns, allStacks, result));
|
|
2424
|
+
const stacks = selected;
|
|
2323
2425
|
processStackMessages(stacks, logger, {
|
|
2324
2426
|
strict: options.strict === true,
|
|
2325
2427
|
ignoreErrors: options.ignoreErrors === true
|
|
@@ -2327,8 +2429,9 @@ async function synthCommand(options) {
|
|
|
2327
2429
|
if (stacks.length === 1) {
|
|
2328
2430
|
const template = stacks[0].template;
|
|
2329
2431
|
process.stdout.write(toYaml(template));
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2432
|
+
} else logger.info(`Supply a stack id (${stacks.map(describeStack).join(", ")}) to display its template.`);
|
|
2433
|
+
const found = stacks.length === allStacks.length ? `Found ${stacks.length} stack(s):` : `Found ${stacks.length} of ${allStacks.length} stack(s):`;
|
|
2434
|
+
logger.info(`\n${green("✓")} ${bold("Synthesis complete!")} ${found}`);
|
|
2332
2435
|
for (const stack of stacks) {
|
|
2333
2436
|
const resourceCount = countDeployableResources(stack.template);
|
|
2334
2437
|
const outputCount = Object.keys(stack.template.Outputs ?? {}).length;
|
|
@@ -2348,7 +2451,7 @@ async function synthCommand(options) {
|
|
|
2348
2451
|
* Create synth command
|
|
2349
2452
|
*/
|
|
2350
2453
|
function createSynthCommand() {
|
|
2351
|
-
const cmd = new Command("synth").description("Synthesize CDK app to CloudFormation template").action(withErrorHandling(synthCommand));
|
|
2454
|
+
const cmd = new Command("synth").description("Synthesize CDK app to CloudFormation template").argument("[stacks...]", "Stack name(s) whose template to print. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*'). Every stack is synthesized either way -- the selection decides what reaches stdout.").action(withErrorHandling(synthCommand));
|
|
2352
2455
|
[
|
|
2353
2456
|
...commonOptions,
|
|
2354
2457
|
...appOptions,
|
|
@@ -2359,99 +2462,6 @@ function createSynthCommand() {
|
|
|
2359
2462
|
return cmd;
|
|
2360
2463
|
}
|
|
2361
2464
|
|
|
2362
|
-
//#endregion
|
|
2363
|
-
//#region src/cli/stack-matcher.ts
|
|
2364
|
-
/**
|
|
2365
|
-
* Match stacks against user-supplied name patterns.
|
|
2366
|
-
*
|
|
2367
|
-
* Patterns are evaluated against two fields:
|
|
2368
|
-
*
|
|
2369
|
-
* - `stackName` — the physical CloudFormation stack name (e.g. `MyStage-MyStack`)
|
|
2370
|
-
* - `displayName` — the hierarchical CDK path (e.g. `MyStage/MyStack`); falls
|
|
2371
|
-
* back to `stackName` when the assembly does not carry one
|
|
2372
|
-
*
|
|
2373
|
-
* Routing is decided by whether the pattern contains `/`:
|
|
2374
|
-
*
|
|
2375
|
-
* - Pattern contains `/` → matched only against `displayName` (a `/` cannot
|
|
2376
|
-
* appear in a CloudFormation stack name, so this is unambiguous)
|
|
2377
|
-
* - Pattern contains no `/` → matched only against `stackName`
|
|
2378
|
-
*
|
|
2379
|
-
* Wildcards (`*`) are supported in either case. Results are de-duplicated by
|
|
2380
|
-
* `stackName`, so a pattern that incidentally matches the same stack via both
|
|
2381
|
-
* fields is returned only once.
|
|
2382
|
-
*/
|
|
2383
|
-
function matchStacks(stacks, patterns) {
|
|
2384
|
-
if (patterns.length === 0) return [];
|
|
2385
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2386
|
-
const result = [];
|
|
2387
|
-
for (const stack of stacks) if (patterns.some((pattern) => stackMatchesPattern(stack, pattern)) && !seen.has(stack.stackName)) {
|
|
2388
|
-
seen.add(stack.stackName);
|
|
2389
|
-
result.push(stack);
|
|
2390
|
-
}
|
|
2391
|
-
return result;
|
|
2392
|
-
}
|
|
2393
|
-
/**
|
|
2394
|
-
* Render a stack for diagnostic messages. When `displayName` differs from the
|
|
2395
|
-
* physical name, both are shown so the user can see which forms are valid as
|
|
2396
|
-
* patterns (e.g. `MyStage-Api (MyStage/Api)`).
|
|
2397
|
-
*
|
|
2398
|
-
* Both names come from the Cloud Assembly, and every one of this helper's
|
|
2399
|
-
* render sites interpolates the result into PROSE cdkd authors — `Available:
|
|
2400
|
-
* ...`, `Multiple stacks found: ...`, `Publishing assets for stack: ...`, the
|
|
2401
|
-
* last of which prints on a normal run rather than only on an error. So they
|
|
2402
|
-
* go through `displayIdent` ([#3277](https://github.com/go-to-k/cdkd/issues/3277)):
|
|
2403
|
-
* a `stackName` carrying `ESC [ 2 K` plus a carriage return ERASES cdkd's own
|
|
2404
|
-
* line and a newline forges a second one, and a value carrying quotes and
|
|
2405
|
-
* periods writes a cdkd-sounding clause.
|
|
2406
|
-
*
|
|
2407
|
-
* It is the identity on every legitimate `stackName` — a CloudFormation stack
|
|
2408
|
-
* name is `[A-Za-z0-9-]`, inside `PLAIN_IDENT`. It is NOT the identity on
|
|
2409
|
-
* every legitimate `displayName`: CDK sets that to the construct path, and
|
|
2410
|
-
* `constructs` rewrites only `/` in an id, so `new Stack(app, 'My Stack')` is
|
|
2411
|
-
* legal and prints `MyStack ("My Stack")`. Those quotes are not part of any
|
|
2412
|
-
* pattern, in a clause whose job is to say which forms ARE valid as patterns —
|
|
2413
|
-
* accepted, because the alternative is `displaySafe`, which passes the quotes
|
|
2414
|
-
* and spaces a crafted value needs, and the pattern still matches the RAW
|
|
2415
|
-
* name either way (`matchStacks` never sees this rendering).
|
|
2416
|
-
*
|
|
2417
|
-
* Sanitizing HERE rather than at the call sites is what keeps the rule whole:
|
|
2418
|
-
* the sites include `scrub.ts` and `destroy.ts`, which this change does not
|
|
2419
|
-
* touch, and a rule widened by hand is how the class survived being closed
|
|
2420
|
-
* twice already.
|
|
2421
|
-
*/
|
|
2422
|
-
function describeStack(stack) {
|
|
2423
|
-
const name = displayIdent(stack.stackName, { maxCodePoints: STACK_REF_MAX_CODE_POINTS });
|
|
2424
|
-
if (stack.displayName && stack.displayName !== stack.stackName) return `${name} (${displayIdent(stack.displayName, { maxCodePoints: STACK_REF_MAX_CODE_POINTS })})`;
|
|
2425
|
-
return name;
|
|
2426
|
-
}
|
|
2427
|
-
/**
|
|
2428
|
-
* The message a command raises when SELECTION came back empty — shared by
|
|
2429
|
-
* `deploy`, `diff`, `list` and `publish-assets`, which all built the identical
|
|
2430
|
-
* string by hand.
|
|
2431
|
-
*
|
|
2432
|
-
* `assembly` is required AND so is its `failedStages` member, which is the
|
|
2433
|
-
* point: a Stage that failed to load dropped every stack under it from
|
|
2434
|
-
* `available`, so "no stacks matching" names the wrong problem unless the
|
|
2435
|
-
* failure is reported with it (issue
|
|
2436
|
-
* [#3482](https://github.com/go-to-k/cdkd/issues/3482)). A REQUIRED member,
|
|
2437
|
-
* not an optional one — `{ failedStages?: ... }` accepts `{}`, so it fences
|
|
2438
|
-
* the argument while leaving the content unfenced, which is the wiring hole
|
|
2439
|
-
* this signature exists to close. `SynthesisResult.failedStages` is required
|
|
2440
|
-
* for the same reason, so passing the result satisfies it and an ad-hoc `{}`
|
|
2441
|
-
* does not.
|
|
2442
|
-
*
|
|
2443
|
-
* What it does NOT fence: a caller passing a STALE or empty list. That is
|
|
2444
|
-
* behaviour, not shape, and it is covered by a wiring test per command.
|
|
2445
|
-
*/
|
|
2446
|
-
function renderNoStackMatch(patterns, available, assembly) {
|
|
2447
|
-
return (patterns.length > 0 ? `No stacks matching ${patterns.join(", ")} found in assembly. ` + (available.length > 0 ? `Available: ${available.map(describeStack).join(", ")}` : "The assembly has no stacks") : "No stacks found in assembly") + failedStageNote(patterns, assembly.failedStages);
|
|
2448
|
-
}
|
|
2449
|
-
function stackMatchesPattern(stack, pattern) {
|
|
2450
|
-
const target = pattern.includes("/") ? stack.displayName ?? stack.stackName : stack.stackName;
|
|
2451
|
-
if (pattern.includes("*")) return new RegExp("^" + pattern.replace(/\*/g, ".*") + "$").test(target);
|
|
2452
|
-
return target === pattern;
|
|
2453
|
-
}
|
|
2454
|
-
|
|
2455
2465
|
//#endregion
|
|
2456
2466
|
//#region src/cli/commands/list.ts
|
|
2457
2467
|
/**
|
|
@@ -22349,17 +22359,21 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
22349
22359
|
} catch (error) {
|
|
22350
22360
|
throw new ProvisioningError(error instanceof Error ? error.message : String(error), resourceType, logicalId, void 0, error instanceof Error ? error : void 0);
|
|
22351
22361
|
}
|
|
22362
|
+
let wiringProperties = properties;
|
|
22352
22363
|
if (indexesOmitted) {
|
|
22353
22364
|
effectiveProperties = { ...effectiveProperties ?? properties };
|
|
22354
22365
|
delete effectiveProperties["GlobalSecondaryIndexes"];
|
|
22355
|
-
const
|
|
22356
|
-
if (
|
|
22357
|
-
|
|
22358
|
-
|
|
22359
|
-
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22366
|
+
const recorded = withdrawReplicaIndexBlocks(effectiveProperties["Replicas"]);
|
|
22367
|
+
if (recorded.changed) effectiveProperties["Replicas"] = recorded.replicas;
|
|
22368
|
+
const wired = withdrawReplicaIndexBlocks(properties["Replicas"]);
|
|
22369
|
+
if (wired.changed) {
|
|
22370
|
+
wiringProperties = {
|
|
22371
|
+
...properties,
|
|
22372
|
+
Replicas: wired.replicas
|
|
22373
|
+
};
|
|
22374
|
+
const crossRegion = wired.withdrawnRegions.filter((region) => region !== currentRegion);
|
|
22375
|
+
if (crossRegion.length > 0) warn(`DynamoDB GlobalTable ${logicalId}: omitting the GlobalSecondaryIndexes overrides of replica(s) ${crossRegion.join(", ")}: they name indexes this create does not build, so there is nothing for them to apply to`);
|
|
22376
|
+
}
|
|
22363
22377
|
const keptKeyAttributes = collectDesiredKeyAttributeNames(properties, { indexListKeys: ["LocalSecondaryIndexes"] });
|
|
22364
22378
|
if (keptKeyAttributes.size > 0 && Array.isArray(attributeDefinitions)) {
|
|
22365
22379
|
const prunedAttributeDefinitions = attributeDefinitions.filter((definition) => {
|
|
@@ -22392,7 +22406,8 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
22392
22406
|
if (billingMode !== "PROVISIONED" && Object.keys(createOnDemand).length > 0) createParams.OnDemandThroughput = createOnDemand;
|
|
22393
22407
|
const localReplicaTags = replicas.find((r) => r["Region"] === currentRegion)?.["Tags"];
|
|
22394
22408
|
if (localReplicaTags && localReplicaTags.length > 0) createParams.Tags = localReplicaTags;
|
|
22395
|
-
|
|
22409
|
+
const wiringReplicas = wiringProperties["Replicas"] ?? [];
|
|
22410
|
+
for (const replica of wiringReplicas) {
|
|
22396
22411
|
const replicaRegion = replica["Region"];
|
|
22397
22412
|
if (!replicaRegion || replicaRegion === currentRegion) continue;
|
|
22398
22413
|
toSdkReplicaGlobalSecondaryIndexes(replica["GlobalSecondaryIndexes"], billingMode, diagnostics, maskSecrets);
|
|
@@ -22408,7 +22423,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
22408
22423
|
}
|
|
22409
22424
|
try {
|
|
22410
22425
|
const tableInfo = await this.waitForTableActive(tableName, logicalId);
|
|
22411
|
-
for (const replica of
|
|
22426
|
+
for (const replica of wiringReplicas) {
|
|
22412
22427
|
const region = replica["Region"];
|
|
22413
22428
|
if (!region || region === currentRegion) continue;
|
|
22414
22429
|
await this.addReplica(tableName, replica, region, logicalId, billingMode, void 0, maskSecrets);
|
|
@@ -22433,7 +22448,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
22433
22448
|
}));
|
|
22434
22449
|
}
|
|
22435
22450
|
if (billingMode === "PROVISIONED") try {
|
|
22436
|
-
await this.reconcileAutoScalingTargets(tableName, [], collectAutoScalingTargets(
|
|
22451
|
+
await this.reconcileAutoScalingTargets(tableName, [], collectAutoScalingTargets(wiringProperties, currentRegion), currentRegion, void 0, maskSecrets);
|
|
22437
22452
|
} catch (autoScalingErr) {
|
|
22438
22453
|
warn(`Auto-scaling registration failed for ${tableName}: ${describeAwsFailure(autoScalingErr).detail}. The table was created successfully; re-run the deploy to register the scaling targets.`);
|
|
22439
22454
|
}
|
|
@@ -22555,13 +22570,55 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
22555
22570
|
if (tableOverrides.OnDemandThroughputOverride) create.OnDemandThroughputOverride = tableOverrides.OnDemandThroughputOverride;
|
|
22556
22571
|
if (replica["TableClassOverride"]) create.TableClassOverride = replica["TableClassOverride"];
|
|
22557
22572
|
const replicaUpdates = [{ Create: create }];
|
|
22558
|
-
|
|
22559
|
-
|
|
22560
|
-
|
|
22561
|
-
|
|
22573
|
+
for (let attempt = 1;; attempt++) try {
|
|
22574
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
22575
|
+
TableName: tableName,
|
|
22576
|
+
ReplicaUpdates: replicaUpdates
|
|
22577
|
+
}));
|
|
22578
|
+
break;
|
|
22579
|
+
} catch (error) {
|
|
22580
|
+
if (attempt >= 3 || !isReplicaCopyExistsRefusal(error)) throw error;
|
|
22581
|
+
if (!await this.waitForRegionalCopyReleased(tableName, region, maskSecrets)) throw error;
|
|
22582
|
+
await new Promise((resolve) => setTimeout(resolve, REPLICA_COPY_RELEASE_POLL_MS));
|
|
22583
|
+
}
|
|
22562
22584
|
await this.waitForReplicaActive(tableName, region, logicalId);
|
|
22563
22585
|
}
|
|
22564
22586
|
/**
|
|
22587
|
+
* Poll the replica REGION for its same-named table until it is gone,
|
|
22588
|
+
* answering `true` once it is (so the replica-add may be retried) and
|
|
22589
|
+
* `false` for anything that is not a copy on its way out: a table in any
|
|
22590
|
+
* status but `DELETING`, a probe error, or the cap running out. Only ever
|
|
22591
|
+
* READS the region — the caller re-throws AWS's own refusal on `false`.
|
|
22592
|
+
* Interruptible, like every other bounded wait on this provider's paths.
|
|
22593
|
+
*/
|
|
22594
|
+
async waitForRegionalCopyReleased(tableName, region, maskSecrets) {
|
|
22595
|
+
const client = this.getRegionalClient(region);
|
|
22596
|
+
const watch = startInterruptWatch(maskSecrets(`DynamoDB ${region} copy of ${tableName} to finish deleting`));
|
|
22597
|
+
let announced = false;
|
|
22598
|
+
try {
|
|
22599
|
+
for (let poll = 0; poll < 180; poll++) {
|
|
22600
|
+
if (watch.isInterrupted()) throw watch.onInterrupted();
|
|
22601
|
+
let status;
|
|
22602
|
+
try {
|
|
22603
|
+
status = (await client.send(new DescribeTableCommand({ TableName: tableName }))).Table?.TableStatus;
|
|
22604
|
+
} catch (probeError) {
|
|
22605
|
+
if (probeError instanceof ResourceNotFoundException$1) return true;
|
|
22606
|
+
this.logger.debug(maskSecrets(`Could not read the ${region} copy of ${tableName}: ${describeAwsFailure(probeError).detail}`));
|
|
22607
|
+
return false;
|
|
22608
|
+
}
|
|
22609
|
+
if (status !== "DELETING") return false;
|
|
22610
|
+
if (!announced) {
|
|
22611
|
+
this.logger.info(maskSecrets(`The ${region} copy of ${tableName} is still being deleted; waiting for it before adding the replica`));
|
|
22612
|
+
announced = true;
|
|
22613
|
+
}
|
|
22614
|
+
await new Promise((resolve) => setTimeout(resolve, REPLICA_COPY_RELEASE_POLL_MS));
|
|
22615
|
+
}
|
|
22616
|
+
return false;
|
|
22617
|
+
} finally {
|
|
22618
|
+
watch.dispose();
|
|
22619
|
+
}
|
|
22620
|
+
}
|
|
22621
|
+
/**
|
|
22565
22622
|
* Update a DynamoDB Global Table in place.
|
|
22566
22623
|
*
|
|
22567
22624
|
* AWS-side state-machine constraint: `UpdateTable` does not accept
|
|
@@ -23173,7 +23230,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
23173
23230
|
const oldEnabled = oldSettings !== void 0 && oldSettings !== null;
|
|
23174
23231
|
const newEnabled = newSettings !== void 0 && newSettings !== null;
|
|
23175
23232
|
if (!oldEnabled && !newEnabled) return;
|
|
23176
|
-
const watch = startInterruptWatch(`DynamoDB auto-scaling for ${tableName}`);
|
|
23233
|
+
const watch = startInterruptWatch(maskSecrets(`DynamoDB auto-scaling for ${tableName}`));
|
|
23177
23234
|
try {
|
|
23178
23235
|
if (newEnabled) {
|
|
23179
23236
|
const minCapacity = coerceCfnInteger(newSettings["MinCapacity"] ?? 0);
|
|
@@ -24526,6 +24583,65 @@ function collectAutoScalingTargets(properties, localRegion) {
|
|
|
24526
24583
|
return specs;
|
|
24527
24584
|
}
|
|
24528
24585
|
/**
|
|
24586
|
+
* How long `addReplica` waits out a same-named regional copy that is still
|
|
24587
|
+
* DELETING (issue #3569): polls x interval, ~15 minutes. A regional copy has
|
|
24588
|
+
* been measured to linger for minutes after it left the source table's
|
|
24589
|
+
* `Replicas[]`.
|
|
24590
|
+
*/
|
|
24591
|
+
const REPLICA_COPY_RELEASE_POLLS = 180;
|
|
24592
|
+
const REPLICA_COPY_RELEASE_POLL_MS = 5e3;
|
|
24593
|
+
/** Replica-add attempts in total, the first included. */
|
|
24594
|
+
const REPLICA_COPY_RELEASE_ATTEMPTS = 3;
|
|
24595
|
+
/**
|
|
24596
|
+
* AWS's refusal of a `ReplicaUpdates[].Create` whose region already holds a
|
|
24597
|
+
* table of that name. Keyed on the MESSAGE: the error class is a generic
|
|
24598
|
+
* `ValidationException`, shared with every other malformed replica request.
|
|
24599
|
+
*/
|
|
24600
|
+
function isReplicaCopyExistsRefusal(error) {
|
|
24601
|
+
return error instanceof Error && /one or more replicas already existed as tables/i.test(error.message);
|
|
24602
|
+
}
|
|
24603
|
+
/**
|
|
24604
|
+
* `Replicas` with every entry's `GlobalSecondaryIndexes` key REMOVED — the
|
|
24605
|
+
* replay-CREATE GSI omit arm's prune of the per-replica index blocks (issue
|
|
24606
|
+
* #1741, second instance), which name indexes that create does not build.
|
|
24607
|
+
*
|
|
24608
|
+
* Copy-on-write: an entry that carries the key is copied without it, every
|
|
24609
|
+
* other entry (a non-object included — an unresolved intrinsic must reach AWS
|
|
24610
|
+
* as-is, not be rewritten) is passed through by reference, and the caller's
|
|
24611
|
+
* array is never mutated. The caller's bag IS `previousState.properties` on
|
|
24612
|
+
* this path. A non-array container is returned untouched with `changed: false`.
|
|
24613
|
+
*
|
|
24614
|
+
* `withdrawnRegions` lists the `Region` of each entry whose block was a
|
|
24615
|
+
* non-empty ARRAY, i.e. one `toSdkReplicaGlobalSecondaryIndexes` would have
|
|
24616
|
+
* turned into a real override on the wire; a malformed or empty block was
|
|
24617
|
+
* never sendable, so withdrawing it is not a loss worth announcing.
|
|
24618
|
+
*/
|
|
24619
|
+
function withdrawReplicaIndexBlocks(replicas) {
|
|
24620
|
+
if (!Array.isArray(replicas)) return {
|
|
24621
|
+
replicas,
|
|
24622
|
+
changed: false,
|
|
24623
|
+
withdrawnRegions: []
|
|
24624
|
+
};
|
|
24625
|
+
let changed = false;
|
|
24626
|
+
const withdrawnRegions = [];
|
|
24627
|
+
const out = replicas.map((entry) => {
|
|
24628
|
+
const replica = asRecord$1(entry);
|
|
24629
|
+
if (!replica || !Object.prototype.hasOwnProperty.call(replica, "GlobalSecondaryIndexes")) return entry;
|
|
24630
|
+
changed = true;
|
|
24631
|
+
const block = replica["GlobalSecondaryIndexes"];
|
|
24632
|
+
const region = replica["Region"];
|
|
24633
|
+
if (Array.isArray(block) && block.length > 0 && typeof region === "string") withdrawnRegions.push(region);
|
|
24634
|
+
const copy = { ...replica };
|
|
24635
|
+
delete copy["GlobalSecondaryIndexes"];
|
|
24636
|
+
return copy;
|
|
24637
|
+
});
|
|
24638
|
+
return {
|
|
24639
|
+
replicas: changed ? out : replicas,
|
|
24640
|
+
changed,
|
|
24641
|
+
withdrawnRegions
|
|
24642
|
+
};
|
|
24643
|
+
}
|
|
24644
|
+
/**
|
|
24529
24645
|
* Every attribute name some KEY SCHEMA in the desired template references:
|
|
24530
24646
|
* the table's own `KeySchema`, each GSI's, and each LSI's. This is the set
|
|
24531
24647
|
* the `AttributeDefinitions`-removal guard in `update()` tests membership
|
|
@@ -46196,11 +46312,29 @@ function isEmptyObjectPlaceholder(value) {
|
|
|
46196
46312
|
* - `EMAIL_OTP` -> `EmailMfaConfiguration` (carries the email-OTP
|
|
46197
46313
|
* message/subject template — i.e. CFn's
|
|
46198
46314
|
* `EmailAuthenticationMessage` / `Subject`)
|
|
46315
|
+
*
|
|
46316
|
+
* **The email-OTP model, stated once (issue #1924): the `EmailMfaConfiguration`
|
|
46317
|
+
* block's PRESENCE is the factor.** `EmailMfaConfigType` is exactly
|
|
46318
|
+
* `{Message?, Subject?}` -- unlike `SoftwareTokenMfaConfigType` it has no
|
|
46319
|
+
* `Enabled` member, so the API has no way to carry a message template without
|
|
46320
|
+
* enabling email OTP, nor to report one. That is read off the API's TYPE, not
|
|
46321
|
+
* measured: a live probe needs a DEVELOPER email sender (a COGNITO_DEFAULT pool
|
|
46322
|
+
* refuses the block outright), which the maintainer's account does not have.
|
|
46323
|
+
* Both sides honor that: `buildMfaConfigRequest` sends the block for `EMAIL_OTP` OR a bare
|
|
46324
|
+
* message/subject (announcing the second, since the template did not list the
|
|
46325
|
+
* factor), and `readCurrentState` reads a present block back as `EMAIL_OTP`.
|
|
46199
46326
|
*/
|
|
46200
46327
|
const MFA_FACTOR_SMS = "SMS_MFA";
|
|
46201
46328
|
const MFA_FACTOR_SOFTWARE_TOKEN = "SOFTWARE_TOKEN_MFA";
|
|
46202
46329
|
const MFA_FACTOR_EMAIL_OTP = "EMAIL_OTP";
|
|
46203
46330
|
/**
|
|
46331
|
+
* The one `WebAuthnConfiguration.FactorConfiguration` value AWS accepts beside
|
|
46332
|
+
* `MfaConfiguration: ON` when `WEB_AUTHN` is an allowed first auth factor
|
|
46333
|
+
* (issue #2064). Typed against the SDK enum so a rename upstream is a compile
|
|
46334
|
+
* error rather than a silently never-matching comparison.
|
|
46335
|
+
*/
|
|
46336
|
+
const WEB_AUTHN_MULTI_FACTOR = "MULTI_FACTOR_WITH_USER_VERIFICATION";
|
|
46337
|
+
/**
|
|
46204
46338
|
* CFn path every `MfaConfiguration` shape refusal in this provider reports.
|
|
46205
46339
|
* One constant so the three sites that READ the property -- `create`,
|
|
46206
46340
|
* `update`, and `narrowMfaConfiguration` (the diff / effectiveProperties
|
|
@@ -46290,7 +46424,7 @@ function readEnabledMfas(properties) {
|
|
|
46290
46424
|
* mirrors how CloudFormation/CDK sequence the two calls.
|
|
46291
46425
|
*/
|
|
46292
46426
|
function hasMfaConfigProps(properties) {
|
|
46293
|
-
return readEnabledMfas(properties).declaresFactor || !!properties["EmailAuthenticationMessage"] || !!properties["EmailAuthenticationSubject"] || !!properties["WebAuthnRelyingPartyID"] || !!properties["WebAuthnUserVerification"];
|
|
46427
|
+
return readEnabledMfas(properties).declaresFactor || !!properties["EmailAuthenticationMessage"] || !!properties["EmailAuthenticationSubject"] || !!properties["WebAuthnRelyingPartyID"] || !!properties["WebAuthnUserVerification"] || !!properties["WebAuthnFactorConfiguration"];
|
|
46294
46428
|
}
|
|
46295
46429
|
/**
|
|
46296
46430
|
* Build the `SetUserPoolMfaConfig` request from the CFn-level MFA properties,
|
|
@@ -46356,6 +46490,7 @@ function buildMfaConfigRequest(physicalId, properties, logger, declaredMfaConfig
|
|
|
46356
46490
|
const emailSubject = properties["EmailAuthenticationSubject"] || void 0;
|
|
46357
46491
|
const webAuthnRpId = properties["WebAuthnRelyingPartyID"] || void 0;
|
|
46358
46492
|
const webAuthnUserVerification = properties["WebAuthnUserVerification"] || void 0;
|
|
46493
|
+
const webAuthnFactorConfiguration = properties["WebAuthnFactorConfiguration"] || void 0;
|
|
46359
46494
|
if (!hasMfaConfigProps(properties)) return void 0;
|
|
46360
46495
|
const request = { UserPoolId: physicalId };
|
|
46361
46496
|
const factors = new Set(enabledMfas ?? []);
|
|
@@ -46366,13 +46501,15 @@ function buildMfaConfigRequest(physicalId, properties, logger, declaredMfaConfig
|
|
|
46366
46501
|
...emailMessage !== void 0 ? { Message: emailMessage } : {},
|
|
46367
46502
|
...emailSubject !== void 0 ? { Subject: emailSubject } : {}
|
|
46368
46503
|
};
|
|
46369
|
-
if (webAuthnRpId !== void 0 || webAuthnUserVerification !== void 0) request.WebAuthnConfiguration = {
|
|
46504
|
+
if (webAuthnRpId !== void 0 || webAuthnUserVerification !== void 0 || webAuthnFactorConfiguration !== void 0) request.WebAuthnConfiguration = {
|
|
46370
46505
|
...webAuthnRpId !== void 0 ? { RelyingPartyId: webAuthnRpId } : {},
|
|
46371
|
-
...webAuthnUserVerification !== void 0 ? { UserVerification: webAuthnUserVerification } : {}
|
|
46506
|
+
...webAuthnUserVerification !== void 0 ? { UserVerification: webAuthnUserVerification } : {},
|
|
46507
|
+
...webAuthnFactorConfiguration !== void 0 ? { FactorConfiguration: webAuthnFactorConfiguration } : {}
|
|
46372
46508
|
};
|
|
46373
46509
|
const enablesMfaFactor = declaresFactor || request.EmailMfaConfiguration !== void 0;
|
|
46374
46510
|
request.MfaConfiguration = declaredMfaConfiguration || (enablesMfaFactor ? "OPTIONAL" : "OFF");
|
|
46375
46511
|
if (enabledMfasBlank) warn(`UserPool ${physicalId}: EnabledMfas is an empty string, which declares no MFA factor and is treated as absent — a mangled Fn::Join or an empty String parameter collapses to this shape. Nothing is enabled from it and MfaConfiguration is ${request.MfaConfiguration}; declare the factor names as a list (e.g. ["${MFA_FACTOR_SOFTWARE_TOKEN}"]) if a factor was intended.`);
|
|
46512
|
+
if (request.EmailMfaConfiguration !== void 0 && !factors.has(MFA_FACTOR_EMAIL_OTP) && !enabledMfasMalformed) warn(`UserPool ${physicalId}: EmailAuthenticationMessage / EmailAuthenticationSubject is sent in SetUserPoolMfaConfig's EmailMfaConfiguration block, which has no enable switch of its own -- sending it ENABLES the ${MFA_FACTOR_EMAIL_OTP} MFA factor although EnabledMfas does not list it, and a read-back reports EnabledMfas with ${MFA_FACTOR_EMAIL_OTP}. Add ${MFA_FACTOR_EMAIL_OTP} to EnabledMfas to declare it, or remove the message and subject if email OTP is not wanted.`);
|
|
46376
46513
|
const anyFactorBlock = hasAnyMfaFactorBlock(request);
|
|
46377
46514
|
const maskLeaves = (v) => maskDeep(v, maskSecrets);
|
|
46378
46515
|
const dropped = unrecognizedFactors.map((f) => JSON.stringify(maskLeaves(f)) ?? String(f));
|
|
@@ -46546,7 +46683,7 @@ const POLICIES_SUB_KEY_ANNOUNCEMENT = {
|
|
|
46546
46683
|
* The PRE-FLIGHT: the reason this user-pool configuration cannot be applied at
|
|
46547
46684
|
* all, or `undefined` when nothing is known to refuse it.
|
|
46548
46685
|
*
|
|
46549
|
-
*
|
|
46686
|
+
* Three rules, all for combinations AWS rejects 100% of the time, and all
|
|
46550
46687
|
* raised BEFORE the first AWS call on BOTH the create and the update path. The
|
|
46551
46688
|
* ordering is what makes them worth refusing rather than warning about: on the
|
|
46552
46689
|
* UPDATE path `UpdateUserPool` lands FIRST and `SetUserPoolMfaConfig` is
|
|
@@ -46601,20 +46738,33 @@ const POLICIES_SUB_KEY_ANNOUNCEMENT = {
|
|
|
46601
46738
|
*
|
|
46602
46739
|
* -- with NO WebAuthn block at all, with `UserVerification: preferred`, and with
|
|
46603
46740
|
* `required`. The only accepted shape is
|
|
46604
|
-
* `WebAuthnConfiguration.FactorConfiguration = MULTI_FACTOR_WITH_USER_VERIFICATION
|
|
46605
|
-
*
|
|
46606
|
-
*
|
|
46607
|
-
*
|
|
46608
|
-
*
|
|
46609
|
-
*
|
|
46610
|
-
*
|
|
46611
|
-
*
|
|
46612
|
-
*
|
|
46613
|
-
*
|
|
46614
|
-
*
|
|
46615
|
-
*
|
|
46616
|
-
*
|
|
46617
|
-
*
|
|
46741
|
+
* `WebAuthnConfiguration.FactorConfiguration = MULTI_FACTOR_WITH_USER_VERIFICATION`
|
|
46742
|
+
* (the CFn `WebAuthnFactorConfiguration`), so switching to `WEB_AUTHN` is a
|
|
46743
|
+
* two-property edit with its own rule -- rule 3 -- and the remedy names the
|
|
46744
|
+
* one-property fix instead.
|
|
46745
|
+
*
|
|
46746
|
+
* Rule 3 (issue #2064) -- the request resolves `MfaConfiguration` to ON while
|
|
46747
|
+
* the allowed first auth factors include `WEB_AUTHN` and the request does not
|
|
46748
|
+
* carry `FactorConfiguration: MULTI_FACTOR_WITH_USER_VERIFICATION`. It cannot
|
|
46749
|
+
* be a deny-list entry: the SAME factor is legal or illegal by that one field.
|
|
46750
|
+
* MEASURED us-east-1 2026-09-23, on a pool allowing `[PASSWORD, WEB_AUTHN]`:
|
|
46751
|
+
*
|
|
46752
|
+
* {ON, SoftwareToken, FactorConfiguration: MULTI} -> ACCEPTED
|
|
46753
|
+
* {ON, SoftwareToken, FactorConfiguration: MULTI, UV: preferred} -> ACCEPTED
|
|
46754
|
+
* {OFF, FactorConfiguration: MULTI} -> ACCEPTED
|
|
46755
|
+
* {ON, SoftwareToken, FactorConfiguration: SINGLE_FACTOR} -> rejected
|
|
46756
|
+
* {ON, SoftwareToken, no WebAuthn block}, live value MULTI -> rejected
|
|
46757
|
+
*
|
|
46758
|
+
* all rejections reading "Cannot set WebAuthn factor configuration to
|
|
46759
|
+
* SINGLE_FACTOR if MFA is required and WebAuthn is an allowed first auth
|
|
46760
|
+
* factor". The last line is what licenses reading the REQUEST rather than the
|
|
46761
|
+
* pool: `SetUserPoolMfaConfig` is a full replace here too, so an omitted block
|
|
46762
|
+
* is SINGLE_FACTOR whatever the pool held. It is the #1975 partial apply
|
|
46763
|
+
* exactly -- measured from both an OFF and an OPTIONAL pool, `UpdateUserPool`
|
|
46764
|
+
* adding `WEB_AUTHN` LANDED and the `SetUserPoolMfaConfig(ON)` after it was
|
|
46765
|
+
* rejected. (From a pool already at ON, `UpdateUserPool` itself refuses to add
|
|
46766
|
+
* `WEB_AUTHN` -- "WEB_AUTHN cannot be used as an auth factor when MFA is
|
|
46767
|
+
* enabled if not configured for MFA" -- atomically.)
|
|
46618
46768
|
*
|
|
46619
46769
|
* SCOPE LIMIT, deliberate and load-bearing: rule 2 fires only when a
|
|
46620
46770
|
* `SetUserPoolMfaConfig` request is actually BUILT (`hasMfaConfigProps`), i.e.
|
|
@@ -46651,13 +46801,27 @@ const POLICIES_SUB_KEY_ANNOUNCEMENT = {
|
|
|
46651
46801
|
* NO logger, so the pre-flight is silent and `applyMfaConfig`'s own warnings
|
|
46652
46802
|
* still fire exactly once.
|
|
46653
46803
|
*
|
|
46654
|
-
*
|
|
46655
|
-
*
|
|
46656
|
-
*
|
|
46657
|
-
*
|
|
46658
|
-
*
|
|
46659
|
-
*
|
|
46660
|
-
*
|
|
46804
|
+
* WHICH FACTOR LIST rules 2 and 3 read (issue #2051): the one the pool will
|
|
46805
|
+
* hold when `SetUserPoolMfaConfig` runs. When the template SENDS
|
|
46806
|
+
* `Policies.SignInPolicy.AllowedFirstAuthFactors` as a list that is the
|
|
46807
|
+
* template's list; when it does not, it is the LIVE list, because an omitted
|
|
46808
|
+
* sub-key -- or a sent `SignInPolicy` without the member -- is PRESERVED by
|
|
46809
|
+
* `UpdateUserPool` (the ledger at `readLiveMfaConfiguration`;
|
|
46810
|
+
* `sendsFirstAuthFactorList`). `update()` supplies that live
|
|
46811
|
+
* list as `liveFirstAuthFactors`, read only when a rule could fire on it; on
|
|
46812
|
+
* create, and when the read fails, it is absent and only the template is
|
|
46813
|
+
* judged -- AWS's own default `[PASSWORD]` cannot trip either rule. MEASURED
|
|
46814
|
+
* us-east-1 2026-09-23: a pool at `[PASSWORD, EMAIL_OTP]` + OPTIONAL took an
|
|
46815
|
+
* `UpdateUserPool` omitting `Policies` (its `AutoVerifiedAttributes` canary
|
|
46816
|
+
* landed, the factor list survived), then `SetUserPoolMfaConfig(ON)` was
|
|
46817
|
+
* rejected -- the partial apply of DELETING `SignInPolicy` while setting ON.
|
|
46818
|
+
*
|
|
46819
|
+
* The OTHER #2051 path -- omitting `MfaConfiguration` while adding `EMAIL_OTP`
|
|
46820
|
+
* to a pool whose live MFA is ON -- is NOT a partial apply and is not guarded:
|
|
46821
|
+
* MEASURED the same day, `UpdateUserPool` itself rejects the added factor
|
|
46822
|
+
* ("Only PASSWORD and WEB_AUTHN (if configured) can be enabled as an auth
|
|
46823
|
+
* factor if MFA is enabled") and its co-sent `AutoVerifiedAttributes` did NOT
|
|
46824
|
+
* land, so AWS answers it atomically before any second call.
|
|
46661
46825
|
*
|
|
46662
46826
|
* Deliberately UNCONDITIONAL -- no `CreateContext.replayingState` downgrade.
|
|
46663
46827
|
* `.claude/rules/providers.md` says a create-path pre-flight refusal MUST
|
|
@@ -46665,16 +46829,23 @@ const POLICIES_SUB_KEY_ANNOUNCEMENT = {
|
|
|
46665
46829
|
* oversight, and the rule's own reasoning is what licenses it: the downgrade
|
|
46666
46830
|
* exists because a refusal against a STATE record leaves a resource
|
|
46667
46831
|
* un-rollbackable with no template-side remedy. That presupposes the replay
|
|
46668
|
-
* could otherwise SUCCEED. Here it cannot --
|
|
46832
|
+
* could otherwise SUCCEED. Here it cannot -- every combination is rejected by
|
|
46669
46833
|
* AWS 100% of the time (measured above, and CloudFormation rolls back on the
|
|
46670
46834
|
* same templates), so downgrading would trade a clear cdkd-worded refusal for
|
|
46671
46835
|
* the identical failure arriving later from AWS, on the create path with a
|
|
46672
|
-
* pool to roll back and on the update path with a partial apply.
|
|
46673
|
-
*
|
|
46674
|
-
*
|
|
46675
|
-
*
|
|
46676
|
-
|
|
46677
|
-
|
|
46836
|
+
* pool to roll back and on the update path with a partial apply. Revisit this
|
|
46837
|
+
* if AWS ever starts accepting one of them.
|
|
46838
|
+
*
|
|
46839
|
+
* That argument is about the REPLAY failing either way, not about state being
|
|
46840
|
+
* unable to hold the combination -- the live-list arm can meet a perfectly
|
|
46841
|
+
* legitimate record. Example: a record at `ON` with no `SignInPolicy`, then a
|
|
46842
|
+
* failed deploy that lowered MFA and added `EMAIL_OTP`; the rollback replays
|
|
46843
|
+
* the `ON` record against a live `EMAIL_OTP` and is refused. It would have been
|
|
46844
|
+
* rejected by AWS all the same, and in both cases the pool keeps the MFA value
|
|
46845
|
+
* the failed deploy left -- a rollback cannot restore `ON` until the live
|
|
46846
|
+
* factor list allows it, which the refusal text names.
|
|
46847
|
+
*/
|
|
46848
|
+
function describeUnsupportedMfaCombination(properties, declaredMfaConfiguration, liveFirstAuthFactors) {
|
|
46678
46849
|
const request = buildMfaConfigRequest("", properties, void 0, declaredMfaConfiguration);
|
|
46679
46850
|
if (request && request.MfaConfiguration === "OFF" && hasAnyMfaFactorBlock(request)) {
|
|
46680
46851
|
const blocks = [
|
|
@@ -46684,11 +46855,74 @@ function describeUnsupportedMfaCombination(properties, declaredMfaConfiguration)
|
|
|
46684
46855
|
];
|
|
46685
46856
|
return `${MFA_CONFIGURATION_PATH} is OFF while an MFA factor is configured (SetUserPoolMfaConfig would carry ${blocks.join(", ")}, built from EnabledMfas / EmailAuthenticationMessage / EmailAuthenticationSubject). AWS rejects that combination ("Invalid MFA configuration given, can't turn off MFA and configure an MFA together"), so cdkd refuses it before sending anything rather than leaving a partly-applied update behind. Set MfaConfiguration to ON or OPTIONAL (or remove it) to enable the declared factor. (If MFA is genuinely not wanted, remove EnabledMfas / EmailAuthenticationMessage / EmailAuthenticationSubject instead.)`;
|
|
46686
46857
|
}
|
|
46687
|
-
if (request
|
|
46688
|
-
|
|
46689
|
-
|
|
46690
|
-
|
|
46691
|
-
|
|
46858
|
+
if (!request || request.MfaConfiguration !== "ON") return void 0;
|
|
46859
|
+
const fromTemplate = sendsFirstAuthFactorList(properties);
|
|
46860
|
+
const factors = fromTemplate ? readAllowedFirstAuthFactors(properties) : liveFirstAuthFactors ?? [];
|
|
46861
|
+
const where = fromTemplate ? `Policies.SignInPolicy.AllowedFirstAuthFactors allows` : `the pool's LIVE Policies.SignInPolicy.AllowedFirstAuthFactors allows`;
|
|
46862
|
+
const liveNote = fromTemplate ? "" : " (this template does not declare Policies.SignInPolicy.AllowedFirstAuthFactors, and UpdateUserPool PRESERVES an omitted sub-key, so the live list is what SetUserPoolMfaConfig meets)";
|
|
46863
|
+
const partialApply = "and on an update the rest of the change has already been applied by the time it does, so cdkd refuses it before sending anything rather than leaving a partly-applied update behind.";
|
|
46864
|
+
const denied = MFA_INCOMPATIBLE_FIRST_AUTH_FACTORS;
|
|
46865
|
+
const offending = factors.filter((factor) => denied.includes(factor));
|
|
46866
|
+
if (offending.length > 0) return fromTemplate ? `${MFA_CONFIGURATION_PATH} is ON while Policies.SignInPolicy.AllowedFirstAuthFactors allows ${offending.join(", ")}. AWS rejects that combination ("Only PASSWORD and WEB_AUTHN (if configured) can be enabled as an auth factor if MFA is enabled"), and on an update the sign-in policy has already been applied by the time it does, so cdkd refuses it before sending anything rather than leaving a partly-applied update behind. Remove ${offending.join(", ")} from Policies.SignInPolicy.AllowedFirstAuthFactors, leaving PASSWORD. (Setting MfaConfiguration to OPTIONAL or OFF also clears the conflict, but weakens MFA.)` : `${MFA_CONFIGURATION_PATH} is ON while ${where} ${offending.join(", ")}${liveNote}. AWS rejects that combination ("Only PASSWORD and WEB_AUTHN (if configured) can be enabled as an auth factor if MFA is enabled"), ${partialApply} Declare Policies.SignInPolicy with AllowedFirstAuthFactors that leave out ${offending.join(", ")} (for example [PASSWORD]) -- removing the block cannot remove a factor. (Setting MfaConfiguration to OPTIONAL or OFF also clears the conflict, but weakens MFA.)`;
|
|
46867
|
+
if (factors.includes("WEB_AUTHN") && request.WebAuthnConfiguration?.FactorConfiguration !== WEB_AUTHN_MULTI_FACTOR) return `${MFA_CONFIGURATION_PATH} is ON while ${where} WEB_AUTHN${liveNote} and WebAuthnFactorConfiguration is not ${WEB_AUTHN_MULTI_FACTOR}. AWS rejects that combination ("Cannot set WebAuthn factor configuration to SINGLE_FACTOR if MFA is required and WebAuthn is an allowed first auth factor"), ${partialApply} Set WebAuthnFactorConfiguration to ${WEB_AUTHN_MULTI_FACTOR}, which is the only value AWS accepts beside MFA ON, or ${fromTemplate ? "remove WEB_AUTHN from Policies.SignInPolicy.AllowedFirstAuthFactors" : "declare Policies.SignInPolicy with AllowedFirstAuthFactors that leave out WEB_AUTHN"}. (Setting MfaConfiguration to OPTIONAL also clears the conflict, but weakens MFA.)`;
|
|
46868
|
+
}
|
|
46869
|
+
/**
|
|
46870
|
+
* Does this bag put `Policies.SignInPolicy.AllowedFirstAuthFactors` on the
|
|
46871
|
+
* wire as a LIST? Only then does the pool's list change. MEASURED us-east-1
|
|
46872
|
+
* 2026-09-23: an `UpdateUserPool` carrying `SignInPolicy: {}` landed (its
|
|
46873
|
+
* `AutoVerifiedAttributes` canary applied) and left a live
|
|
46874
|
+
* `[PASSWORD, EMAIL_OTP]` intact -- so a SENT sub-key without the member is,
|
|
46875
|
+
* for the factor list, the same as an omitted one. The sub-key gate is the
|
|
46876
|
+
* wire's own (`sendsPoliciesSubKey`); the member gate mirrors
|
|
46877
|
+
* `readAllowedFirstAuthFactors`, which reads a non-list as nothing. A declared
|
|
46878
|
+
* non-list takes the live arm, the side that can refuse (what AWS does with
|
|
46879
|
+
* that shape is unmeasured). An EMPTY list counts as sent and judges nothing, which is safe:
|
|
46880
|
+
* MEASURED the same day, `UpdateUserPool` rejects `AllowedFirstAuthFactors: []`
|
|
46881
|
+
* ("Member must have length greater than or equal to 1") atomically -- its
|
|
46882
|
+
* co-sent `AutoVerifiedAttributes` did not land.
|
|
46883
|
+
*/
|
|
46884
|
+
function sendsFirstAuthFactorList(properties) {
|
|
46885
|
+
if (!sendsPoliciesSubKey(properties, "SignInPolicy")) return false;
|
|
46886
|
+
const signInPolicy = properties["Policies"]["SignInPolicy"];
|
|
46887
|
+
return Array.isArray(signInPolicy["AllowedFirstAuthFactors"]);
|
|
46888
|
+
}
|
|
46889
|
+
/**
|
|
46890
|
+
* Does the pre-flight need the pool's LIVE first-auth-factor list (issue
|
|
46891
|
+
* #2051)? Only when rules 2 / 3 can evaluate -- a `SetUserPoolMfaConfig`
|
|
46892
|
+
* request resolving to ON -- AND the template does not send
|
|
46893
|
+
* `Policies.SignInPolicy`, so the live list is what that call will meet. Every
|
|
46894
|
+
* other update spends no extra read.
|
|
46895
|
+
*/
|
|
46896
|
+
function mfaPreflightNeedsLiveSignInPolicy(properties, declaredMfaConfiguration) {
|
|
46897
|
+
return buildMfaConfigRequest("", properties, void 0, declaredMfaConfiguration)?.MfaConfiguration === "ON" && !sendsFirstAuthFactorList(properties);
|
|
46898
|
+
}
|
|
46899
|
+
/**
|
|
46900
|
+
* Could this update need `SetUserPoolMfaConfig` BEFORE `UpdateUserPool`
|
|
46901
|
+
* (issue #3562)? True only for the shape that FAILS in cdkd's usual order and
|
|
46902
|
+
* DEPLOYS under CloudFormation: the request keeps MFA `ON`, carries
|
|
46903
|
+
* `FactorConfiguration: MULTI_FACTOR_WITH_USER_VERIFICATION`, and the
|
|
46904
|
+
* template's factor list includes `WEB_AUTHN`. Whether the order actually
|
|
46905
|
+
* flips is then decided by the pool's LIVE MFA state
|
|
46906
|
+
* (`readMfaFirstPrior`) -- this only decides whether that read is worth making.
|
|
46907
|
+
*
|
|
46908
|
+
* MEASURED us-east-1 2026-09-23, from a pool at `ON` + SINGLE_FACTOR +
|
|
46909
|
+
* `[PASSWORD]`:
|
|
46910
|
+
*
|
|
46911
|
+
* UpdateUserPool adding WEB_AUTHN first -> REJECTED ("WEB_AUTHN cannot be
|
|
46912
|
+
* used as an auth factor when MFA is enabled if not configured for MFA")
|
|
46913
|
+
* SetUserPoolMfaConfig(ON, MULTI) first, then UpdateUserPool -> both ACCEPTED
|
|
46914
|
+
* CloudFormation, the same template edit -> UPDATE_COMPLETE
|
|
46915
|
+
*
|
|
46916
|
+
* The OTHER order-dependent edit the issue measured -- lowering MFA from `ON`
|
|
46917
|
+
* while ADDING `EMAIL_OTP` / `SMS_OTP` -- deliberately keeps cdkd's order:
|
|
46918
|
+
* CloudFormation ROLLS BACK that edit (same day, UPDATE_ROLLBACK_COMPLETE with
|
|
46919
|
+
* the same AWS message), so succeeding there would hide from a cdkd user a
|
|
46920
|
+
* failure their production CloudFormation deploy will hit. cdkd's own
|
|
46921
|
+
* rejection is atomic (`UpdateUserPool` refuses before anything lands).
|
|
46922
|
+
*/
|
|
46923
|
+
function mfaMayNeedToPrecedeUpdateUserPool(properties, declaredMfaConfiguration) {
|
|
46924
|
+
const request = buildMfaConfigRequest("", properties, void 0, declaredMfaConfiguration);
|
|
46925
|
+
return request?.MfaConfiguration === "ON" && request.WebAuthnConfiguration?.FactorConfiguration === WEB_AUTHN_MULTI_FACTOR && readAllowedFirstAuthFactors(properties).includes("WEB_AUTHN");
|
|
46692
46926
|
}
|
|
46693
46927
|
/**
|
|
46694
46928
|
* Replace a DECLARED-but-unusable `MfaConfiguration` with the value cdkd
|
|
@@ -46772,9 +47006,10 @@ var CognitoUserPoolProvider = class {
|
|
|
46772
47006
|
"EmailAuthenticationMessage",
|
|
46773
47007
|
"EmailAuthenticationSubject",
|
|
46774
47008
|
"WebAuthnRelyingPartyID",
|
|
46775
|
-
"WebAuthnUserVerification"
|
|
47009
|
+
"WebAuthnUserVerification",
|
|
47010
|
+
"WebAuthnFactorConfiguration"
|
|
46776
47011
|
])]]);
|
|
46777
|
-
unhandledByDesign = /* @__PURE__ */ new Map(
|
|
47012
|
+
unhandledByDesign = /* @__PURE__ */ new Map();
|
|
46778
47013
|
/**
|
|
46779
47014
|
* Warn when `MfaConfiguration` is declared as a BLANK string (issue #1925
|
|
46780
47015
|
* item 2, review round 2).
|
|
@@ -46817,8 +47052,8 @@ var CognitoUserPoolProvider = class {
|
|
|
46817
47052
|
* physical id on update, matching what each path passes to its other
|
|
46818
47053
|
* `ProvisioningError`s.
|
|
46819
47054
|
*/
|
|
46820
|
-
assertMfaCombinationApplicable(logicalId, resourceType, target, properties, declaredMfaConfiguration) {
|
|
46821
|
-
const reason = describeUnsupportedMfaCombination(properties, declaredMfaConfiguration);
|
|
47055
|
+
assertMfaCombinationApplicable(logicalId, resourceType, target, properties, declaredMfaConfiguration, liveFirstAuthFactors) {
|
|
47056
|
+
const reason = describeUnsupportedMfaCombination(properties, declaredMfaConfiguration, liveFirstAuthFactors);
|
|
46822
47057
|
if (reason === void 0) return;
|
|
46823
47058
|
throw new ProvisioningError(reason, resourceType, logicalId, target);
|
|
46824
47059
|
}
|
|
@@ -47100,6 +47335,14 @@ var CognitoUserPoolProvider = class {
|
|
|
47100
47335
|
*
|
|
47101
47336
|
* - `AutoVerifiedAttributes` — DOES reset when omitted (2026-08-18).
|
|
47102
47337
|
* - `MfaConfiguration` — does NOT reset when omitted (2026-08-18, above).
|
|
47338
|
+
* - `DeletionProtection` — does NOT reset when omitted (2026-09-23, issue
|
|
47339
|
+
* #2675): a pool created `ACTIVE` stayed `ACTIVE` through an
|
|
47340
|
+
* `UpdateUserPool` omitting it, whose co-sent `AutoVerifiedAttributes`
|
|
47341
|
+
* change landed (the call ran), and an explicit `INACTIVE` then applied
|
|
47342
|
+
* (the field is writable on this call rather than ignored).
|
|
47343
|
+
* - `WebAuthnConfiguration.FactorConfiguration` (a `SetUserPoolMfaConfig`
|
|
47344
|
+
* field) — does NOT reset through an `UpdateUserPool` that omits every MFA
|
|
47345
|
+
* member (2026-09-23, issue #2064).
|
|
47103
47346
|
* - `Policies` — neither sub-key resets, measured in BOTH directions
|
|
47104
47347
|
* (2026-08-19, issue #1968; details at `toSdkUserPoolPolicies`): a
|
|
47105
47348
|
* container sent without `SignInPolicy` left `SignInPolicy` intact, a
|
|
@@ -47120,6 +47363,9 @@ var CognitoUserPoolProvider = class {
|
|
|
47120
47363
|
* rejected the same way (2026-08-19, issue #1968; the transcript lives at
|
|
47121
47364
|
* `describeUnsupportedMfaCombination`, which since issue #1977 REFUSES that
|
|
47122
47365
|
* combination before any call rather than warning about it).
|
|
47366
|
+
* - `WebAuthnConfiguration.FactorConfiguration` — an omitted block is read as
|
|
47367
|
+
* SINGLE_FACTOR even on a pool holding MULTI_FACTOR_WITH_USER_VERIFICATION
|
|
47368
|
+
* (2026-09-23, issue #2064; transcript at `describeUnsupportedMfaCombination`).
|
|
47123
47369
|
*
|
|
47124
47370
|
* Nothing here licenses a claim about a field on neither list, or about one
|
|
47125
47371
|
* API from the other's section. Measure it.
|
|
@@ -47158,6 +47404,79 @@ var CognitoUserPoolProvider = class {
|
|
|
47158
47404
|
}
|
|
47159
47405
|
}
|
|
47160
47406
|
/**
|
|
47407
|
+
* The pool's live `Policies.SignInPolicy.AllowedFirstAuthFactors`, for the
|
|
47408
|
+
* pre-flight (issue #2051), or `undefined` when it cannot be read.
|
|
47409
|
+
*
|
|
47410
|
+
* FAILS OPEN: an unreadable pool is judged on the template alone, which is
|
|
47411
|
+
* the pre-#2051 behavior -- AWS then answers with its own rejection. Refusing
|
|
47412
|
+
* on a failed read would block deploys over a question cdkd never answered.
|
|
47413
|
+
* `update()` already needs `cognito-idp:DescribeUserPool` (its post-update
|
|
47414
|
+
* attribute read), so this adds no permission. An ABSENT list (a pool with
|
|
47415
|
+
* no sign-in policy) is AWS's default `[PASSWORD]`, which trips no rule, so
|
|
47416
|
+
* it reads as empty.
|
|
47417
|
+
*/
|
|
47418
|
+
async readLiveAllowedFirstAuthFactors(physicalId) {
|
|
47419
|
+
try {
|
|
47420
|
+
const factors = (await this.retryOnTransientControlPlane(() => this.getClient().send(new DescribeUserPoolCommand({ UserPoolId: physicalId })), `DescribeUserPool(${physicalId}) before the MFA pre-flight`)).UserPool?.Policies?.SignInPolicy?.AllowedFirstAuthFactors;
|
|
47421
|
+
return Array.isArray(factors) ? factors : [];
|
|
47422
|
+
} catch (error) {
|
|
47423
|
+
this.logger.debug(`DescribeUserPool failed for UserPool ${physicalId} before the MFA pre-flight (${error instanceof Error ? error.name : typeof error}): ${describeAwsFailure(error).detail}`);
|
|
47424
|
+
return;
|
|
47425
|
+
}
|
|
47426
|
+
}
|
|
47427
|
+
/**
|
|
47428
|
+
* The pool's FULL live MFA configuration when `SetUserPoolMfaConfig` has to
|
|
47429
|
+
* go FIRST (issue #3562), or `undefined` to keep the usual order.
|
|
47430
|
+
*
|
|
47431
|
+
* MFA-first is needed exactly when the live pool is at `ON` without
|
|
47432
|
+
* `MULTI_FACTOR_WITH_USER_VERIFICATION`: `UpdateUserPool` then refuses to add
|
|
47433
|
+
* `WEB_AUTHN` until the factor configuration has changed. Anything else --
|
|
47434
|
+
* live `OFF` / `OPTIONAL`, live `MULTI` already, or a read that FAILED --
|
|
47435
|
+
* keeps `UpdateUserPool` first, which is the pre-#3562 behaviour and at worst
|
|
47436
|
+
* the atomic rejection it always was. The returned response is also what
|
|
47437
|
+
* `restoreMfaConfig` replays if `UpdateUserPool` fails after the MFA call
|
|
47438
|
+
* landed.
|
|
47439
|
+
*/
|
|
47440
|
+
async readMfaFirstPrior(physicalId) {
|
|
47441
|
+
try {
|
|
47442
|
+
const live = await this.retryOnTransientControlPlane(() => this.getClient().send(new GetUserPoolMfaConfigCommand({ UserPoolId: physicalId })), `GetUserPoolMfaConfig(${physicalId}) for the call order`);
|
|
47443
|
+
return live.MfaConfiguration === "ON" && live.WebAuthnConfiguration?.FactorConfiguration !== WEB_AUTHN_MULTI_FACTOR ? live : void 0;
|
|
47444
|
+
} catch (error) {
|
|
47445
|
+
this.logger.debug(`GetUserPoolMfaConfig failed for UserPool ${physicalId} while choosing the call order (${error instanceof Error ? error.name : typeof error}): ${describeAwsFailure(error).detail}`);
|
|
47446
|
+
return;
|
|
47447
|
+
}
|
|
47448
|
+
}
|
|
47449
|
+
/**
|
|
47450
|
+
* Put back the MFA configuration `readMfaFirstPrior` read, after
|
|
47451
|
+
* `UpdateUserPool` failed behind an MFA-first `SetUserPoolMfaConfig` (issue
|
|
47452
|
+
* #3562). Without this the reversed order leaves the #1975 partial apply
|
|
47453
|
+
* mirrored: the MFA change landed, the rest did not. MEASURED us-east-1
|
|
47454
|
+
* 2026-09-23: replaying the earlier `GetUserPoolMfaConfig` members through
|
|
47455
|
+
* `SetUserPoolMfaConfig` restored the pool exactly, after a MULTI change had
|
|
47456
|
+
* landed and the `UpdateUserPool` behind it was rejected.
|
|
47457
|
+
*
|
|
47458
|
+
* Best effort: the caller re-throws the ORIGINAL error either way, and a
|
|
47459
|
+
* failed restore is announced (error CLASS at warn, detail at debug) rather
|
|
47460
|
+
* than replacing it.
|
|
47461
|
+
*/
|
|
47462
|
+
async restoreMfaConfig(physicalId, prior) {
|
|
47463
|
+
const request = {
|
|
47464
|
+
UserPoolId: physicalId,
|
|
47465
|
+
...prior.MfaConfiguration !== void 0 && { MfaConfiguration: prior.MfaConfiguration },
|
|
47466
|
+
...prior.SmsMfaConfiguration !== void 0 && { SmsMfaConfiguration: prior.SmsMfaConfiguration },
|
|
47467
|
+
...prior.SoftwareTokenMfaConfiguration !== void 0 && { SoftwareTokenMfaConfiguration: prior.SoftwareTokenMfaConfiguration },
|
|
47468
|
+
...prior.EmailMfaConfiguration !== void 0 && { EmailMfaConfiguration: prior.EmailMfaConfiguration },
|
|
47469
|
+
...prior.WebAuthnConfiguration !== void 0 && { WebAuthnConfiguration: prior.WebAuthnConfiguration }
|
|
47470
|
+
};
|
|
47471
|
+
try {
|
|
47472
|
+
await this.retryOnTransientControlPlane(() => this.getClient().send(new SetUserPoolMfaConfigCommand(request)), `SetUserPoolMfaConfig(${physicalId}) restore`);
|
|
47473
|
+
this.logger.debug(`Restored the previous MFA configuration on UserPool ${physicalId}`);
|
|
47474
|
+
} catch (restoreError) {
|
|
47475
|
+
this.logger.warn(`UserPool ${physicalId}: UpdateUserPool failed after SetUserPoolMfaConfig had already applied the new MFA configuration, and restoring the previous one also failed (${restoreError instanceof Error ? restoreError.name : typeof restoreError}). The pool may now carry the NEW MFA configuration with its OLD sign-in policy; re-run the deploy, or check it with aws cognito-idp get-user-pool-mfa-config --user-pool-id ${physicalId}.`);
|
|
47476
|
+
this.logger.debug(`MFA restore failure detail for UserPool ${physicalId}: ${describeAwsFailure(restoreError).detail}`);
|
|
47477
|
+
}
|
|
47478
|
+
}
|
|
47479
|
+
/**
|
|
47161
47480
|
* Retry a Cognito control-plane call on transient "settling" errors. A
|
|
47162
47481
|
* SetUserPoolMfaConfig issued immediately after CreateUserPool (or another
|
|
47163
47482
|
* control-plane write) can briefly hit `ConcurrentModificationException` /
|
|
@@ -47271,9 +47590,11 @@ var CognitoUserPoolProvider = class {
|
|
|
47271
47590
|
this.logger.debug(`Updating Cognito User Pool ${logicalId}: ${physicalId}`);
|
|
47272
47591
|
const mfaConfiguration = readDeclaredMfaConfiguration(properties["MfaConfiguration"], { onUnusable: (message) => this.logger.warn(message) });
|
|
47273
47592
|
this.warnOnBlankMfaConfiguration(properties["MfaConfiguration"]);
|
|
47274
|
-
|
|
47593
|
+
const liveFirstAuthFactors = mfaPreflightNeedsLiveSignInPolicy(properties, mfaConfiguration) ? await this.readLiveAllowedFirstAuthFactors(physicalId) : void 0;
|
|
47594
|
+
this.assertMfaCombinationApplicable(logicalId, resourceType, physicalId, properties, mfaConfiguration, liveFirstAuthFactors);
|
|
47275
47595
|
const liveMfa = mfaConfiguration === "" && hasMfaConfigProps(properties) ? await this.readLiveMfaConfiguration(physicalId) : void 0;
|
|
47276
47596
|
this.warnOnUnremovablePoliciesSubKeys(physicalId, properties, previousProperties);
|
|
47597
|
+
const mfaFirstPrior = mfaMayNeedToPrecedeUpdateUserPool(properties, mfaConfiguration) ? await this.readMfaFirstPrior(physicalId) : void 0;
|
|
47277
47598
|
try {
|
|
47278
47599
|
const updateParams = { UserPoolId: physicalId };
|
|
47279
47600
|
if (properties["Policies"]) {
|
|
@@ -47298,14 +47619,23 @@ var CognitoUserPoolProvider = class {
|
|
|
47298
47619
|
if (properties["SmsAuthenticationMessage"] !== void 0) updateParams.SmsAuthenticationMessage = properties["SmsAuthenticationMessage"];
|
|
47299
47620
|
if (properties["SmsVerificationMessage"] !== void 0) updateParams.SmsVerificationMessage = properties["SmsVerificationMessage"];
|
|
47300
47621
|
if (properties["UserPoolTier"]) updateParams.UserPoolTier = properties["UserPoolTier"];
|
|
47301
|
-
|
|
47302
|
-
await this.reconcileSchemaCustomAttributes(logicalId, physicalId, resourceType, properties["Schema"], previousProperties["Schema"], (properties["DeletionProtection"] ? properties["DeletionProtection"] : previousProperties["DeletionProtection"]) === "ACTIVE");
|
|
47303
|
-
await this.applyMfaConfig(physicalId, properties, mfaConfiguration, {
|
|
47622
|
+
const mfaOptions = {
|
|
47304
47623
|
...liveMfa?.value !== void 0 ? { liveMfaConfiguration: liveMfa.value } : {},
|
|
47305
47624
|
...liveMfa?.failed === true ? { liveReadFailed: true } : {},
|
|
47306
47625
|
declaredKind: classifyDeclaredMfaConfiguration(properties["MfaConfiguration"]),
|
|
47307
47626
|
...context?.maskSecrets && { maskSecrets: context.maskSecrets }
|
|
47308
|
-
}
|
|
47627
|
+
};
|
|
47628
|
+
if (mfaFirstPrior) {
|
|
47629
|
+
await this.applyMfaConfig(physicalId, properties, mfaConfiguration, mfaOptions);
|
|
47630
|
+
try {
|
|
47631
|
+
await this.getClient().send(new UpdateUserPoolCommand(updateParams));
|
|
47632
|
+
} catch (updateError) {
|
|
47633
|
+
await this.restoreMfaConfig(physicalId, mfaFirstPrior);
|
|
47634
|
+
throw updateError;
|
|
47635
|
+
}
|
|
47636
|
+
} else await this.getClient().send(new UpdateUserPoolCommand(updateParams));
|
|
47637
|
+
await this.reconcileSchemaCustomAttributes(logicalId, physicalId, resourceType, properties["Schema"], previousProperties["Schema"], (properties["DeletionProtection"] ? properties["DeletionProtection"] : previousProperties["DeletionProtection"]) === "ACTIVE");
|
|
47638
|
+
if (!mfaFirstPrior) await this.applyMfaConfig(physicalId, properties, mfaConfiguration, mfaOptions);
|
|
47309
47639
|
this.logger.debug(`Successfully updated Cognito User Pool ${logicalId}`);
|
|
47310
47640
|
const userPool = (await this.getClient().send(new DescribeUserPoolCommand({ UserPoolId: physicalId }))).UserPool;
|
|
47311
47641
|
const region = await this.getClient().config.region();
|
|
@@ -47375,7 +47705,7 @@ var CognitoUserPoolProvider = class {
|
|
|
47375
47705
|
before: "aws cognito-idp update-user-pool --user-pool-id",
|
|
47376
47706
|
identifier: physicalId,
|
|
47377
47707
|
after: "--deletion-protection INACTIVE",
|
|
47378
|
-
caveat: "Note UpdateUserPool
|
|
47708
|
+
caveat: "Note UpdateUserPool resets some members a call omits (AutoVerifiedAttributes among them), so send your complete pool configuration alongside that flag rather than the flag alone."
|
|
47379
47709
|
}
|
|
47380
47710
|
}) : `AWS::Cognito::UserPool is a stateful type, so re-run with ${replaceFlags} to recreate it (this deletes all users in the pool).`;
|
|
47381
47711
|
throw new ResourceUpdateNotSupportedError(resourceType, logicalId, `the Schema change (${immutableChanges.join("; ")}) is immutable on AWS — AWS can only ADD custom attributes in place, so removing or modifying an attribute requires recreating the pool. ${remedy}`);
|
|
@@ -47515,6 +47845,7 @@ var CognitoUserPoolProvider = class {
|
|
|
47515
47845
|
result["EmailAuthenticationSubject"] = mfa.EmailMfaConfiguration?.Subject ?? "";
|
|
47516
47846
|
result["WebAuthnRelyingPartyID"] = mfa.WebAuthnConfiguration?.RelyingPartyId ?? "";
|
|
47517
47847
|
result["WebAuthnUserVerification"] = mfa.WebAuthnConfiguration?.UserVerification ?? "";
|
|
47848
|
+
result["WebAuthnFactorConfiguration"] = mfa.WebAuthnConfiguration?.FactorConfiguration ?? "";
|
|
47518
47849
|
} catch (mfaErr) {
|
|
47519
47850
|
this.logger.debug(`GetUserPoolMfaConfig failed for ${physicalId}, skipping MFA-derived drift keys: ${describeAwsFailure(mfaErr).detail}`);
|
|
47520
47851
|
}
|
|
@@ -85040,6 +85371,36 @@ function resolveCdkPathToLogicalIds(input, index) {
|
|
|
85040
85371
|
//#endregion
|
|
85041
85372
|
//#region src/analyzer/orphan-rewriter.ts
|
|
85042
85373
|
/**
|
|
85374
|
+
* Why a reference to an orphaned record that is NOT a readable resource record
|
|
85375
|
+
* is left in place rather than resolved (go-to-k/cdkd#3350).
|
|
85376
|
+
*
|
|
85377
|
+
* `cdkd orphan` may drop such a record — refusing to would close the way out of
|
|
85378
|
+
* it — but it must not resolve THROUGH one. Measured before this guard: a
|
|
85379
|
+
* string or number record made a sibling's `{"Ref": O}` resolve to `undefined`,
|
|
85380
|
+
* which the save then dropped from the JSON, and its `Fn::Sub` `${O}` became
|
|
85381
|
+
* the literal `x-undefined`. Reported as unresolvable instead, so a plain run
|
|
85382
|
+
* aborts naming the site and `--force` leaves the intrinsic untouched — there
|
|
85383
|
+
* is no cached value to fall back to either, since the cache lives inside the
|
|
85384
|
+
* record that cannot be read.
|
|
85385
|
+
*/
|
|
85386
|
+
const UNREADABLE_ORPHAN_RECORD_REASON = "the orphaned resource's state record is not a readable resource record (not an object, no resource type, or no physical id), so nothing in it can be substituted";
|
|
85387
|
+
/**
|
|
85388
|
+
* Whether the fetcher may read an orphaned record at all: the ENTRY class's
|
|
85389
|
+
* predicate PLUS a non-empty string `physicalId`. The entry predicate stops at
|
|
85390
|
+
* `resourceType` on purpose (its own note), but every LIVE value this fetcher
|
|
85391
|
+
* produces is derived from the physical id — `{"resourceType": "T"}` alone
|
|
85392
|
+
* made `{"Ref": O}` resolve to `undefined` and `Fn::Sub` to `x-undefined`
|
|
85393
|
+
* exactly as a string record did, and a type whose `Ref` recovery reads the id
|
|
85394
|
+
* threw a bare `TypeError` (review of go-to-k/cdkd#3568). The `--force` cache
|
|
85395
|
+
* is skipped for such a record too, deliberately: a record with no identity is
|
|
85396
|
+
* not one whose cached values can be trusted to describe a live resource.
|
|
85397
|
+
*/
|
|
85398
|
+
function isResolvableOrphanRecord(entry) {
|
|
85399
|
+
if (!isReadableResourceEntry(entry)) return false;
|
|
85400
|
+
const physicalId = entry.physicalId;
|
|
85401
|
+
return typeof physicalId === "string" && physicalId !== "";
|
|
85402
|
+
}
|
|
85403
|
+
/**
|
|
85043
85404
|
* Live-fetch helper. Wraps `provider.getAttribute(...)` for one orphan
|
|
85044
85405
|
* resource and memoizes results so multiple references to the same
|
|
85045
85406
|
* `(orphan, attr)` pair only hit AWS once.
|
|
@@ -85110,8 +85471,12 @@ var AttributeFetcher = class {
|
|
|
85110
85471
|
* already does with a masked cached attribute; the two arms now agree.
|
|
85111
85472
|
*/
|
|
85112
85473
|
ref(orphanLogicalId) {
|
|
85474
|
+
if (!Object.hasOwn(this.orphans, orphanLogicalId)) throw new Error(`Internal: Ref to '${orphanLogicalId}' has no orphan entry — should have been filtered out`);
|
|
85113
85475
|
const o = this.orphans[orphanLogicalId];
|
|
85114
|
-
if (!o)
|
|
85476
|
+
if (!isResolvableOrphanRecord(o)) return {
|
|
85477
|
+
ok: false,
|
|
85478
|
+
reason: UNREADABLE_ORPHAN_RECORD_REASON
|
|
85479
|
+
};
|
|
85115
85480
|
let maskedKey;
|
|
85116
85481
|
const value = cfnRefValueFromPhysicalId(o.resourceType, o.physicalId, refStateLookupFromResource(o, (key) => {
|
|
85117
85482
|
maskedKey = key;
|
|
@@ -85150,11 +85515,15 @@ var AttributeFetcher = class {
|
|
|
85150
85515
|
ok: true,
|
|
85151
85516
|
value: this.cache.get(cacheKey)
|
|
85152
85517
|
};
|
|
85153
|
-
|
|
85154
|
-
if (!orphan) return {
|
|
85518
|
+
if (!Object.hasOwn(this.orphans, orphanLogicalId)) return {
|
|
85155
85519
|
ok: false,
|
|
85156
85520
|
reason: `Internal: GetAtt to '${orphanLogicalId}' has no orphan entry`
|
|
85157
85521
|
};
|
|
85522
|
+
const orphan = this.orphans[orphanLogicalId];
|
|
85523
|
+
if (!isResolvableOrphanRecord(orphan)) return {
|
|
85524
|
+
ok: false,
|
|
85525
|
+
reason: UNREADABLE_ORPHAN_RECORD_REASON
|
|
85526
|
+
};
|
|
85158
85527
|
let provider;
|
|
85159
85528
|
try {
|
|
85160
85529
|
provider = this.providerRegistry.getProviderFor({
|
|
@@ -85191,7 +85560,15 @@ var AttributeFetcher = class {
|
|
|
85191
85560
|
reason
|
|
85192
85561
|
};
|
|
85193
85562
|
const orphan = this.orphans[orphanLogicalId];
|
|
85194
|
-
const
|
|
85563
|
+
const bag = orphan.attributes;
|
|
85564
|
+
if (bag !== void 0 && !isReadableBag(bag)) {
|
|
85565
|
+
this.logger.warn(`--force: state.attributes of '${displaySafe(orphanLogicalId, { asciiOnly: true })}' is not a readable map, so it is not consulted for '${displaySafe(attribute, { asciiOnly: true })}'; leaving the original intrinsic in place.`);
|
|
85566
|
+
return {
|
|
85567
|
+
ok: false,
|
|
85568
|
+
reason: `${reason}; the state.attributes cache is not a readable map`
|
|
85569
|
+
};
|
|
85570
|
+
}
|
|
85571
|
+
const stored = bag !== void 0 && Object.hasOwn(bag, attribute) ? bag[attribute] : void 0;
|
|
85195
85572
|
const cached = isImpossibleEmptyStoredAttribute(orphan.resourceType, attribute, stored) ? void 0 : stored;
|
|
85196
85573
|
if (cached === void 0) {
|
|
85197
85574
|
this.logger.warn(`--force: state.attributes also lacks '${orphanLogicalId}.${attribute}'; leaving the original intrinsic in place.`);
|
|
@@ -85251,11 +85628,10 @@ var AttributeFetcher = class {
|
|
|
85251
85628
|
*/
|
|
85252
85629
|
async function rewriteResourceReferences(state, orphanLogicalIds, providerRegistry, options = {}) {
|
|
85253
85630
|
const orphanSet = new Set(orphanLogicalIds);
|
|
85254
|
-
const orphans =
|
|
85631
|
+
const orphans = Object.create(null);
|
|
85255
85632
|
for (const id of orphanLogicalIds) {
|
|
85256
|
-
|
|
85257
|
-
|
|
85258
|
-
orphans[id] = r;
|
|
85633
|
+
if (!Object.hasOwn(state.resources, id)) throw new Error(`rewriteResourceReferences: orphan '${id}' not found in state.resources`);
|
|
85634
|
+
orphans[id] = state.resources[id];
|
|
85259
85635
|
}
|
|
85260
85636
|
const fetcher = new AttributeFetcher(orphans, providerRegistry, options);
|
|
85261
85637
|
const rewrites = [];
|
|
@@ -85699,10 +86075,13 @@ async function orphanCommand(pathArgs, options) {
|
|
|
85699
86075
|
const stateData = await stateBackend.getState(stackInfo.stackName, targetRegion);
|
|
85700
86076
|
if (!stateData) throw new Error(`No state found for stack '${displaySafe(stackInfo.stackName)}' (${displaySafe(targetRegion, { asciiOnly: true })}). Nothing to orphan. (Did the stack get deployed?)`);
|
|
85701
86077
|
const { state, etag, migrationPending } = stateData;
|
|
85702
|
-
refuseMalformedState(state, stackInfo.stackName,
|
|
85703
|
-
refuseMalformedOutputs(state, stackInfo.stackName,
|
|
86078
|
+
refuseMalformedState(state, stackInfo.stackName, recordRegion, recovery);
|
|
86079
|
+
refuseMalformedOutputs(state, stackInfo.stackName, recordRegion, recovery);
|
|
86080
|
+
refuseMalformedResourceEntriesForOrphan(state, orphanLogicalIds, stackInfo.stackName, recordRegion, recovery);
|
|
85704
86081
|
refuseMalformedResourcePropertiesForOrphan(state, orphanLogicalIds, stackInfo.stackName, recordRegion, recovery);
|
|
85705
|
-
|
|
86082
|
+
refuseMalformedResourceAttributesForOrphan(state, orphanLogicalIds, stackInfo.stackName, recordRegion, recovery);
|
|
86083
|
+
refuseMalformedOrphansForOrphan(state, stackInfo.stackName, recordRegion, recovery);
|
|
86084
|
+
const missing = orphanLogicalIds.filter((id) => !Object.hasOwn(state.resources, id));
|
|
85706
86085
|
if (missing.length > 0) {
|
|
85707
86086
|
const have = displayIdentList(Object.keys(state.resources), ", ");
|
|
85708
86087
|
throw new Error(`Resource(s) not in state for stack '${displaySafe(stackInfo.stackName)}' (${displaySafe(targetRegion, { asciiOnly: true })}): ${displayIdentList(missing, ", ")}.\nAvailable logical IDs: ${have}`);
|
|
@@ -97764,4 +98143,4 @@ function buildProgram() {
|
|
|
97764
98143
|
|
|
97765
98144
|
//#endregion
|
|
97766
98145
|
export { buildProgram };
|
|
97767
|
-
//# sourceMappingURL=program-
|
|
98146
|
+
//# sourceMappingURL=program-C5r-kmIQ.js.map
|