@go-to-k/cdkd 0.230.16 → 0.230.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +11 -6
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-BqUZUty9.js → deploy-engine-CXgQSFYL.js} +146 -6
- package/dist/deploy-engine-CXgQSFYL.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-BqUZUty9.js.map +0 -1
|
@@ -1992,6 +1992,21 @@ async function bucketExists(client, bucketName) {
|
|
|
1992
1992
|
//#endregion
|
|
1993
1993
|
//#region src/synthesis/synthesizer.ts
|
|
1994
1994
|
/**
|
|
1995
|
+
* CDK CLI compatibility: a `--app` value pointing at an existing directory is
|
|
1996
|
+
* treated as a pre-synthesized cloud assembly — synthesis (the subprocess
|
|
1997
|
+
* execution) is skipped and the manifest is read directly. A `--app` value that
|
|
1998
|
+
* is a command (e.g. "node app.ts") or any path that is not an existing
|
|
1999
|
+
* directory is synthesized normally.
|
|
2000
|
+
*
|
|
2001
|
+
* Exported so callers can pick an accurate status message
|
|
2002
|
+
* ("Reading cloud assembly..." vs "Synthesizing CDK app...") BEFORE invoking
|
|
2003
|
+
* {@link Synthesizer.synthesize}, which is the single place that branches on it.
|
|
2004
|
+
*/
|
|
2005
|
+
function isPreSynthesizedAssembly(app) {
|
|
2006
|
+
const appPath = resolve(app);
|
|
2007
|
+
return existsSync(appPath) && statSync(appPath).isDirectory();
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
1995
2010
|
* CDK app synthesizer
|
|
1996
2011
|
*
|
|
1997
2012
|
* Replaces @aws-cdk/toolkit-lib with self-implemented:
|
|
@@ -2016,7 +2031,7 @@ var Synthesizer = class {
|
|
|
2016
2031
|
*/
|
|
2017
2032
|
async synthesize(options) {
|
|
2018
2033
|
const appPath = resolve(options.app);
|
|
2019
|
-
if (
|
|
2034
|
+
if (isPreSynthesizedAssembly(options.app)) {
|
|
2020
2035
|
this.logger.debug(`Using pre-synthesized cloud assembly at ${appPath}`);
|
|
2021
2036
|
const manifest = this.assemblyReader.readManifest(appPath);
|
|
2022
2037
|
const stacks = this.assemblyReader.getAllStacks(appPath, manifest);
|
|
@@ -4867,6 +4882,24 @@ var ReplacementRulesRegistry = class {
|
|
|
4867
4882
|
return false;
|
|
4868
4883
|
}
|
|
4869
4884
|
/**
|
|
4885
|
+
* Whether the registry has an EXPLICIT opinion about a property's
|
|
4886
|
+
* replacement behavior — i.e. the type has a rule AND the property is listed
|
|
4887
|
+
* in its `replacementProperties`, `updateableProperties`, OR
|
|
4888
|
+
* `conditionalReplacements`. Returns false for an unknown type, or for a
|
|
4889
|
+
* known type whose rule does not mention the property (the "default to
|
|
4890
|
+
* updateable" fall-through inside {@link requiresReplacement}).
|
|
4891
|
+
*
|
|
4892
|
+
* The diff calculator uses this to decide whether the CFn-schema
|
|
4893
|
+
* `createOnlyProperties` fallback may apply: the schema only fills the GAP
|
|
4894
|
+
* where the registry has no opinion, so a deliberate `updateableProperties`
|
|
4895
|
+
* (or conditional) classification is never overridden by the fallback.
|
|
4896
|
+
*/
|
|
4897
|
+
isClassified(resourceType, propertyPath) {
|
|
4898
|
+
const rule = this.rules.get(resourceType);
|
|
4899
|
+
if (!rule) return false;
|
|
4900
|
+
return rule.replacementProperties.has(propertyPath) || (rule.updateableProperties?.has(propertyPath) ?? false) || (rule.conditionalReplacements?.has(propertyPath) ?? false);
|
|
4901
|
+
}
|
|
4902
|
+
/**
|
|
4870
4903
|
* Initialize replacement rules for common AWS resource types
|
|
4871
4904
|
*/
|
|
4872
4905
|
initializeRules() {
|
|
@@ -5117,6 +5150,98 @@ var ReplacementRulesRegistry = class {
|
|
|
5117
5150
|
}
|
|
5118
5151
|
};
|
|
5119
5152
|
|
|
5153
|
+
//#endregion
|
|
5154
|
+
//#region src/provisioning/create-only-properties.ts
|
|
5155
|
+
/**
|
|
5156
|
+
* Create-only (immutable) property resolution for replacement detection.
|
|
5157
|
+
*
|
|
5158
|
+
* CloudFormation marks some properties "Update requires: Replacement" — the
|
|
5159
|
+
* resource type's registry schema lists them under `createOnlyProperties`.
|
|
5160
|
+
* Changing such a property cannot be an in-place UPDATE; CloudFormation
|
|
5161
|
+
* transparently DELETE+CREATEs the resource. cdkd's diff classifier
|
|
5162
|
+
* (`ReplacementRulesRegistry`) only knows the ~25 types with a hand-authored
|
|
5163
|
+
* rule, so for every other type an immutable-property change was previously
|
|
5164
|
+
* mis-classified as an in-place UPDATE (the provider's `update()` then either
|
|
5165
|
+
* rejected it with a typed error — at best — or silently dropped it).
|
|
5166
|
+
*
|
|
5167
|
+
* This module resolves each resource type's `createOnlyProperties` from the
|
|
5168
|
+
* CloudFormation registry schema via `cloudformation:DescribeType`, reduced to
|
|
5169
|
+
* the TOP-LEVEL containing property names (a nested path like
|
|
5170
|
+
* `/properties/Foo/Bar` strips to `Foo`) so the diff's top-level property keys
|
|
5171
|
+
* match. The diff calculator consults it as a fallback for any property the
|
|
5172
|
+
* registry does not explicitly classify, so a createOnly change on ANY type now
|
|
5173
|
+
* correctly drives a replacement.
|
|
5174
|
+
*
|
|
5175
|
+
* Caching / failure semantics are identical to {@link
|
|
5176
|
+
* ./write-only-properties.ts} (the sibling DescribeType-backed resolver): only
|
|
5177
|
+
* SUCCESSFUL lookups are cached per resource type for the process (deploy)
|
|
5178
|
+
* lifetime — `cloudformation:DescribeType` is throttled per-account and the
|
|
5179
|
+
* schema cannot change mid-deploy. A FAILED lookup (missing IAM permission,
|
|
5180
|
+
* transient throttle / 5xx) is logged as a warning and resolves to an empty set
|
|
5181
|
+
* WITHOUT poisoning the cache, so the caller gracefully falls back to the
|
|
5182
|
+
* registry-only classification for THIS resource while a later resource of the
|
|
5183
|
+
* same type retries. A caller permanently without `cloudformation:DescribeType`
|
|
5184
|
+
* simply keeps the pre-existing registry-only behavior — no regression.
|
|
5185
|
+
*/
|
|
5186
|
+
/**
|
|
5187
|
+
* Per-type cache of SUCCESSFUL lookups only. The value is the in-flight (or
|
|
5188
|
+
* settled) promise so concurrent diffs of the same resource type share a
|
|
5189
|
+
* single DescribeType call. A failed lookup removes its own entry so a later
|
|
5190
|
+
* call retries instead of being permanently poisoned by a transient throttle.
|
|
5191
|
+
*/
|
|
5192
|
+
const createOnlyPropertiesCache = /* @__PURE__ */ new Map();
|
|
5193
|
+
/**
|
|
5194
|
+
* Resolve the TOP-LEVEL create-only (immutable) property names for a resource
|
|
5195
|
+
* type.
|
|
5196
|
+
*
|
|
5197
|
+
* Never throws: a DescribeType failure logs a warning and resolves to an empty
|
|
5198
|
+
* set (graceful fallback to the registry-only classification). Only SUCCESSFUL
|
|
5199
|
+
* lookups are cached per resource type for the process lifetime; a failed
|
|
5200
|
+
* lookup is NOT cached, so a later call for the same type retries DescribeType
|
|
5201
|
+
* (a transient throttle must not poison the deploy's replacement detection).
|
|
5202
|
+
*/
|
|
5203
|
+
function getTopLevelCreateOnlyProperties(resourceType) {
|
|
5204
|
+
const cached = createOnlyPropertiesCache.get(resourceType);
|
|
5205
|
+
if (cached) return cached;
|
|
5206
|
+
const entry = fetchTopLevelCreateOnlyProperties(resourceType).catch((error) => {
|
|
5207
|
+
createOnlyPropertiesCache.delete(resourceType);
|
|
5208
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5209
|
+
getLogger().child("CreateOnlyProperties").warn(`Failed to resolve create-only properties for ${resourceType} via cloudformation:DescribeType (${message}). Falling back to the registry-only replacement classification for this resource — an immutable-property change may be mis-classified as an in-place update. Grant cloudformation:DescribeType to enable schema-driven replacement detection.`);
|
|
5210
|
+
return /* @__PURE__ */ new Set();
|
|
5211
|
+
});
|
|
5212
|
+
createOnlyPropertiesCache.set(resourceType, entry);
|
|
5213
|
+
return entry;
|
|
5214
|
+
}
|
|
5215
|
+
/**
|
|
5216
|
+
* Fetch + parse the type's create-only properties. THROWS on a DescribeType
|
|
5217
|
+
* failure — the caller ({@link getTopLevelCreateOnlyProperties}) catches,
|
|
5218
|
+
* warns, and declines to cache so the lookup can be retried later.
|
|
5219
|
+
*/
|
|
5220
|
+
async function fetchTopLevelCreateOnlyProperties(resourceType) {
|
|
5221
|
+
const logger = getLogger().child("CreateOnlyProperties");
|
|
5222
|
+
const response = await getAwsClients().cloudFormation.send(new DescribeTypeCommand({
|
|
5223
|
+
Type: "RESOURCE",
|
|
5224
|
+
TypeName: resourceType
|
|
5225
|
+
}));
|
|
5226
|
+
const result = /* @__PURE__ */ new Set();
|
|
5227
|
+
if (response.Schema) {
|
|
5228
|
+
const createOnly = JSON.parse(response.Schema).createOnlyProperties;
|
|
5229
|
+
if (Array.isArray(createOnly)) for (const path of createOnly) {
|
|
5230
|
+
if (typeof path !== "string") continue;
|
|
5231
|
+
const match = /^\/properties\/([^/]+)/.exec(path);
|
|
5232
|
+
if (match?.[1]) result.add(unescapeJsonPointerSegment$1(match[1]));
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
logger.debug(`Resolved ${result.size} top-level create-only properties for ${resourceType}` + (result.size > 0 ? `: ${[...result].join(", ")}` : ""));
|
|
5236
|
+
return result;
|
|
5237
|
+
}
|
|
5238
|
+
/**
|
|
5239
|
+
* Unescape an RFC 6901 JSON Pointer segment (`~1` -> `/`, `~0` -> `~`).
|
|
5240
|
+
*/
|
|
5241
|
+
function unescapeJsonPointerSegment$1(segment) {
|
|
5242
|
+
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
5243
|
+
}
|
|
5244
|
+
|
|
5120
5245
|
//#endregion
|
|
5121
5246
|
//#region src/analyzer/diff-calculator.ts
|
|
5122
5247
|
/**
|
|
@@ -5191,7 +5316,7 @@ var DiffCalculator = class DiffCalculator {
|
|
|
5191
5316
|
} else {
|
|
5192
5317
|
const rawDesiredProps = desiredResource.Properties || {};
|
|
5193
5318
|
const desiredPropsForCompare = resolveFn ? await this.resolveBestEffort(rawDesiredProps, resolveFn) : rawDesiredProps;
|
|
5194
|
-
const propertyChanges = this.compareProperties(desiredResource.Type, currentResource.properties, desiredPropsForCompare);
|
|
5319
|
+
const propertyChanges = await this.compareProperties(desiredResource.Type, currentResource.properties, desiredPropsForCompare);
|
|
5195
5320
|
const attributeChanges = this.compareAttributes(currentResource, desiredResource);
|
|
5196
5321
|
if (propertyChanges.length > 0 || attributeChanges.length > 0) {
|
|
5197
5322
|
changes.set(logicalId, {
|
|
@@ -5497,17 +5622,25 @@ var DiffCalculator = class DiffCalculator {
|
|
|
5497
5622
|
* Uses ReplacementRulesRegistry to determine which property changes require replacement.
|
|
5498
5623
|
* Reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html
|
|
5499
5624
|
*/
|
|
5500
|
-
compareProperties(resourceType, currentProperties, desiredProperties) {
|
|
5625
|
+
async compareProperties(resourceType, currentProperties, desiredProperties) {
|
|
5501
5626
|
const changes = [];
|
|
5502
5627
|
const allKeys = new Set([...Object.keys(currentProperties), ...Object.keys(desiredProperties)]);
|
|
5503
5628
|
const ignoredProperties = /* @__PURE__ */ new Set();
|
|
5504
5629
|
if (resourceType === "AWS::CloudFormation::CustomResource" || resourceType.startsWith("Custom::")) ignoredProperties.add("Timestamp");
|
|
5630
|
+
let createOnlyProps;
|
|
5505
5631
|
for (const key of allKeys) {
|
|
5506
5632
|
if (ignoredProperties.has(key)) continue;
|
|
5507
5633
|
const oldValue = currentProperties[key];
|
|
5508
5634
|
const newValue = desiredProperties[key];
|
|
5509
5635
|
if (!this.valuesEqual(oldValue, newValue)) {
|
|
5510
|
-
|
|
5636
|
+
let requiresReplacement = this.replacementRules.requiresReplacement(resourceType, key, oldValue, newValue);
|
|
5637
|
+
if (!requiresReplacement && !this.replacementRules.isClassified(resourceType, key)) {
|
|
5638
|
+
if (createOnlyProps === void 0) createOnlyProps = await getTopLevelCreateOnlyProperties(resourceType);
|
|
5639
|
+
if (createOnlyProps.has(key)) {
|
|
5640
|
+
requiresReplacement = true;
|
|
5641
|
+
this.logger.debug(`Property ${key} of ${resourceType} is createOnly per the CFn schema — requires replacement`);
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5511
5644
|
changes.push({
|
|
5512
5645
|
path: key,
|
|
5513
5646
|
oldValue,
|
|
@@ -13676,6 +13809,13 @@ var DeployEngine = class {
|
|
|
13676
13809
|
const needsReplacement = propertyDrivenReplacement || recreateFlagged;
|
|
13677
13810
|
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
13678
13811
|
if (needsReplacement) {
|
|
13812
|
+
if (propertyDrivenReplacement && !recreateFlagged) {
|
|
13813
|
+
const statefulReason = isStatefulRecreateTargetForReplace(resourceType, currentProps);
|
|
13814
|
+
if (statefulReason && this.options.forceStatefulRecreation !== true) {
|
|
13815
|
+
const immutableProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
|
|
13816
|
+
throw new CdkdError(`${logicalId} (${resourceType}) requires replacement (immutable property changed: ${immutableProps}) but it is a stateful resource — ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the immutable-property change.`, "STATEFUL_REPLACE_BLOCKED");
|
|
13817
|
+
}
|
|
13818
|
+
}
|
|
13679
13819
|
let replacementReason;
|
|
13680
13820
|
if (recreateViaCcApi) replacementReason = "--recreate-via-cc-api flag (mid-life SDK→CC migration)";
|
|
13681
13821
|
else if (recreateViaSdkProvider) replacementReason = "--recreate-via-sdk-provider flag (mid-life CC→SDK migration)";
|
|
@@ -14035,5 +14175,5 @@ var DeployEngine = class {
|
|
|
14035
14175
|
};
|
|
14036
14176
|
|
|
14037
14177
|
//#endregion
|
|
14038
|
-
export {
|
|
14039
|
-
//# sourceMappingURL=deploy-engine-
|
|
14178
|
+
export { warnDeprecatedNoPrefixCliFlag as $, LockManager as A, runDockerForeground as B, findActionableSilentDrops as C, DiffCalculator as D, applyRoleArnIfSet as E, stringifyValue as F, isPreSynthesizedAssembly as G, AssetManifestLoader as H, WorkGraph as I, resolveApp as J, getDefaultStateBucketName as K, buildDockerImage as L, rebuildClientForBucketRegion as M, shouldRetainResource as N, DagBuilder as O, AssetPublisher as P, resolveStateBucketWithDefaultAndSource as Q, formatDockerLoginError as R, ProviderRegistry as S, IntrinsicFunctionResolver as T, getDockerImageBySourceHash as U, runDockerStreaming as V, Synthesizer as W, resolveSkipPrefix as X, resolveCaptureObservedState as Y, resolveStateBucketWithDefault as Z, green as _, withRetry as a, AssemblyReader as at, IAMRoleProvider as b, computeImplicitDeleteEdges as c, isStatefulRecreateTargetSync as d, CFN_TEMPLATE_BODY_LIMIT as et, renderStatefulReason as f, gray as g, cyan as h, withResourceDeadline as i, uploadCfnTemplate as it, S3StateBackend as j, TemplateParser as k, extractDeploymentEventError as l, bold as m, DEFAULT_RESOURCE_WARN_AFTER_MS as n, MIGRATE_TMP_PREFIX as nt, isRetryableTransientError as o, clearBucketRegionCache as ot, formatResourceLine as p, getLegacyStateBucketName as q, DeployEngine as r, findLargeInlineResources as rt, IMPLICIT_DELETE_DEPENDENCIES as s, resolveBucketRegion as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, CFN_TEMPLATE_URL_LIMIT as tt, MULTI_REGION_RECREATE_BLOCKED_TYPES as u, red as v, CloudControlProvider as w, collectInlinePolicyNamesManagedBySiblings as x, yellow as y, getDockerCmd as z };
|
|
14179
|
+
//# sourceMappingURL=deploy-engine-CXgQSFYL.js.map
|