@go-to-k/cdkd 0.226.0 → 0.227.1

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.
@@ -21,6 +21,8 @@ import { homedir, tmpdir } from "node:os";
21
21
  import { GetHostedZoneCommand, ListHostedZonesByNameCommand, Route53Client } from "@aws-sdk/client-route-53";
22
22
  import { DescribeListenersCommand, DescribeLoadBalancersCommand, ElasticLoadBalancingV2Client } from "@aws-sdk/client-elastic-load-balancing-v2";
23
23
  import { KMSClient, ListAliasesCommand } from "@aws-sdk/client-kms";
24
+ import { readFile } from "fs/promises";
25
+ import { join as join$1 } from "path";
24
26
  import { DescribeImagesCommand as DescribeImagesCommand$1, ECRClient, GetAuthorizationTokenCommand } from "@aws-sdk/client-ecr";
25
27
  import { hostname } from "os";
26
28
  import graphlib from "graphlib";
@@ -2162,6 +2164,150 @@ function setsEqual(a, b) {
2162
2164
  return true;
2163
2165
  }
2164
2166
 
2167
+ //#endregion
2168
+ //#region src/assets/asset-manifest-loader.ts
2169
+ /**
2170
+ * Whether a file asset's `source.path` is a CloudFormation template asset
2171
+ * (the stack template `<Stack>.template.json` or a nested-stack template
2172
+ * `<Stack>.<Nested>.nested.template.json`). cdkd deploys templates itself and
2173
+ * never needs them in the bootstrap bucket, so they are the ONLY file assets
2174
+ * excluded from publishing.
2175
+ *
2176
+ * `.template.json` is the reliable discriminator: a plain `.json` exclusion is
2177
+ * WRONG because a legitimate user file asset can be a `.json` file (e.g. a Step
2178
+ * Functions `DefinitionS3Location` ASL document, or an app config) whose source
2179
+ * path is `asset.<hash>.json` — those MUST be published. Shared by both file-
2180
+ * asset-selection sites ({@link AssetManifestLoader.getFileAssets} and
2181
+ * `AssetPublisher.addAssetsToGraph`) so they cannot drift.
2182
+ */
2183
+ function isCfnTemplateAssetPath(sourcePath) {
2184
+ return sourcePath.endsWith(".template.json");
2185
+ }
2186
+ /**
2187
+ * Asset manifest loader
2188
+ *
2189
+ * Loads and parses CDK asset manifests from the CDK output directory
2190
+ */
2191
+ var AssetManifestLoader = class {
2192
+ logger = getLogger().child("AssetManifestLoader");
2193
+ /**
2194
+ * Load asset manifest from CDK output directory
2195
+ *
2196
+ * @param cdkOutputDir CDK output directory (e.g., "cdk.out")
2197
+ * @param stackName Stack name
2198
+ * @returns Asset manifest or null if not found
2199
+ */
2200
+ async loadManifest(cdkOutputDir, stackName) {
2201
+ const manifestPath = join$1(cdkOutputDir, `${stackName}.assets.json`);
2202
+ try {
2203
+ this.logger.debug(`Loading asset manifest from: ${manifestPath}`);
2204
+ const content = await readFile(manifestPath, "utf-8");
2205
+ const manifest = JSON.parse(content);
2206
+ this.logger.debug(`Loaded asset manifest: ${Object.keys(manifest.files).length} file assets, ${Object.keys(manifest.dockerImages).length} docker image assets`);
2207
+ return manifest;
2208
+ } catch (error) {
2209
+ if (error.code === "ENOENT") {
2210
+ this.logger.debug(`Asset manifest not found: ${manifestPath}`);
2211
+ return null;
2212
+ }
2213
+ throw new Error(`Failed to load asset manifest from ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
2214
+ }
2215
+ }
2216
+ /**
2217
+ * Get file assets from manifest (excludes CloudFormation templates)
2218
+ *
2219
+ * @param manifest Asset manifest
2220
+ * @returns Map of asset hash to file asset
2221
+ */
2222
+ getFileAssets(manifest) {
2223
+ const fileAssets = /* @__PURE__ */ new Map();
2224
+ for (const [assetHash, asset] of Object.entries(manifest.files)) {
2225
+ if (isCfnTemplateAssetPath(asset.source.path)) {
2226
+ this.logger.debug(`Skipping CloudFormation template asset: ${asset.displayName}`);
2227
+ continue;
2228
+ }
2229
+ fileAssets.set(assetHash, asset);
2230
+ }
2231
+ this.logger.debug(`Found ${fileAssets.size} file assets (excluding templates)`);
2232
+ return fileAssets;
2233
+ }
2234
+ /**
2235
+ * Get asset source path (absolute path)
2236
+ *
2237
+ * @param cdkOutputDir CDK output directory
2238
+ * @param asset File asset
2239
+ * @returns Absolute path to asset source
2240
+ */
2241
+ getAssetSourcePath(cdkOutputDir, asset) {
2242
+ return join$1(cdkOutputDir, asset.source.path);
2243
+ }
2244
+ /**
2245
+ * Resolve asset destination values (replace ${AWS::AccountId}, ${AWS::Region}, etc.)
2246
+ *
2247
+ * @param value Value with placeholders
2248
+ * @param accountId AWS account ID
2249
+ * @param region AWS region
2250
+ * @param partition AWS partition (default: "aws")
2251
+ * @returns Resolved value
2252
+ */
2253
+ resolveAssetDestinationValue(value, accountId, region, partition = "aws") {
2254
+ return value.replace(/\$\{AWS::AccountId\}/g, accountId).replace(/\$\{AWS::Region\}/g, region).replace(/\$\{AWS::Partition\}/g, partition);
2255
+ }
2256
+ };
2257
+ /**
2258
+ * Look up the docker-image asset that backs a Lambda's `Code.ImageUri`.
2259
+ *
2260
+ * The CDK template synthesizes `Code.ImageUri` as a `Fn::Sub` whose body
2261
+ * references the bootstrap ECR repo and ends in `:<hash>` — that hash is
2262
+ * the same key used in `manifest.dockerImages[<hash>]`. cdkd extracts the
2263
+ * hash by walking known image-URI shapes; on miss, when the manifest has
2264
+ * exactly one Docker image, we fall back to it (single-asset heuristic) so
2265
+ * locally-built non-bootstrapped images still work. This is documented as
2266
+ * a v1 limitation; immutable digest pins (`@sha256:<digest>`) hit the same
2267
+ * fallback path.
2268
+ *
2269
+ * Returns the `(hash, asset)` pair when matched, or `undefined` when both
2270
+ * the regex AND the single-asset fallback miss (typically: 0 docker assets,
2271
+ * or 2+ docker assets with no hash match — the caller should treat this as
2272
+ * "fall through to the ECR-pull path").
2273
+ *
2274
+ * Exported as a free function (not a method) so the local-invoke modules
2275
+ * can reuse it without depending on the `AssetManifestLoader` instance —
2276
+ * the manifest itself is a plain JSON shape.
2277
+ */
2278
+ function getDockerImageBySourceHash(manifest, imageUri) {
2279
+ const dockerImages = manifest.dockerImages ?? {};
2280
+ const entries = Object.entries(dockerImages);
2281
+ if (entries.length === 0) return void 0;
2282
+ const hash = extractHashFromImageUri(imageUri);
2283
+ if (hash !== void 0) {
2284
+ const asset = dockerImages[hash];
2285
+ if (asset) return {
2286
+ hash,
2287
+ asset
2288
+ };
2289
+ }
2290
+ if (entries.length === 1) {
2291
+ const [singleHash, singleAsset] = entries[0];
2292
+ return {
2293
+ hash: singleHash,
2294
+ asset: singleAsset
2295
+ };
2296
+ }
2297
+ }
2298
+ /**
2299
+ * Extract the source hash from a Lambda `Code.ImageUri` string. CDK's
2300
+ * bootstrap layout ends every image URI in `:<hex-hash>`, and that hash
2301
+ * is the same key used in the asset manifest's `dockerImages` map.
2302
+ *
2303
+ * Returns `undefined` for shapes we can't parse (digest pins, missing tag,
2304
+ * etc.) — the caller falls back to the single-asset heuristic.
2305
+ */
2306
+ function extractHashFromImageUri(imageUri) {
2307
+ if (imageUri.includes("@sha256:")) return void 0;
2308
+ return /:([a-f0-9]{8,})$/.exec(imageUri)?.[1];
2309
+ }
2310
+
2165
2311
  //#endregion
2166
2312
  //#region src/assets/file-asset-publisher.ts
2167
2313
  /**
@@ -2870,7 +3016,7 @@ var AssetPublisher = class {
2870
3016
  const cdkOutputDir = manifestPath.replace(/\/[^/]+$/, "");
2871
3017
  const prefix = options.nodePrefix || "";
2872
3018
  const nodeIds = [];
2873
- const fileAssets = Object.entries(manifest.files || {}).filter(([, asset]) => !asset.source.path.endsWith(".json") && !asset.source.path.endsWith(".template.json"));
3019
+ const fileAssets = Object.entries(manifest.files || {}).filter(([, asset]) => !isCfnTemplateAssetPath(asset.source.path));
2874
3020
  for (const [hash, asset] of fileAssets) {
2875
3021
  const nodeId = `asset-publish:${prefix}file:${hash}`;
2876
3022
  graph.addNode({
@@ -4625,6 +4771,35 @@ var DagBuilder = class {
4625
4771
  * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html
4626
4772
  */
4627
4773
  /**
4774
+ * Conditional-replacement predicate for `AWS::DynamoDB::Table.AttributeDefinitions`.
4775
+ *
4776
+ * Returns true only when an attribute present in BOTH the old and new definition
4777
+ * lists changed its `AttributeType` (e.g. `S` -> `N`). Adding a brand-new attribute
4778
+ * (to back a new GSI) or removing one (when a GSI is dropped) returns false — those
4779
+ * are in-place `UpdateTable` operations, matching CloudFormation's "No interruption"
4780
+ * update behavior for this property.
4781
+ */
4782
+ function attributeTypeChangedForSharedAttribute(oldValue, newValue) {
4783
+ const toTypeMap = (value) => {
4784
+ const map = /* @__PURE__ */ new Map();
4785
+ if (Array.isArray(value)) {
4786
+ for (const def of value) if (def && typeof def === "object") {
4787
+ const name = def["AttributeName"];
4788
+ const type = def["AttributeType"];
4789
+ if (typeof name === "string" && typeof type === "string") map.set(name, type);
4790
+ }
4791
+ }
4792
+ return map;
4793
+ };
4794
+ const oldTypes = toTypeMap(oldValue);
4795
+ const newTypes = toTypeMap(newValue);
4796
+ for (const [name, oldType] of oldTypes) {
4797
+ const newType = newTypes.get(name);
4798
+ if (newType !== void 0 && newType !== oldType) return true;
4799
+ }
4800
+ return false;
4801
+ }
4802
+ /**
4628
4803
  * Replacement rules registry
4629
4804
  *
4630
4805
  * Maps resource types to their replacement rules
@@ -4697,11 +4872,7 @@ var ReplacementRulesRegistry = class {
4697
4872
  ])
4698
4873
  });
4699
4874
  this.rules.set("AWS::DynamoDB::Table", {
4700
- replacementProperties: new Set([
4701
- "TableName",
4702
- "KeySchema",
4703
- "AttributeDefinitions"
4704
- ]),
4875
+ replacementProperties: new Set(["TableName", "KeySchema"]),
4705
4876
  updateableProperties: new Set([
4706
4877
  "BillingMode",
4707
4878
  "ProvisionedThroughput",
@@ -4712,7 +4883,8 @@ var ReplacementRulesRegistry = class {
4712
4883
  "Tags",
4713
4884
  "TimeToLiveSpecification",
4714
4885
  "PointInTimeRecoverySpecification"
4715
- ])
4886
+ ]),
4887
+ conditionalReplacements: new Map([["AttributeDefinitions", attributeTypeChangedForSharedAttribute]])
4716
4888
  });
4717
4889
  this.rules.set("AWS::SQS::Queue", {
4718
4890
  replacementProperties: new Set([
@@ -8810,7 +8982,7 @@ const PROPERTY_COVERAGE_BY_TYPE = new Map([
8810
8982
  "TreatMissingData",
8811
8983
  "Unit"
8812
8984
  ]),
8813
- silentDrop: new Map([["EvaluationCriteria", "not yet implemented by cdkd"], ["EvaluationInterval", "not yet implemented by cdkd"]])
8985
+ silentDrop: new Map([["EvaluationCriteria", "Absent from both the SDK PutMetricAlarm input and the aws-cdk-lib CfnAlarm L1 (a newer CFn-schema-only property ahead of SDK/CDK support); no wire path to forward it and no CDK app can emit it."], ["EvaluationInterval", "Absent from both the SDK PutMetricAlarm input and the aws-cdk-lib CfnAlarm L1 (a newer CFn-schema-only property ahead of SDK/CDK support); no wire path to forward it and no CDK app can emit it."]])
8814
8986
  }],
8815
8987
  ["AWS::CodeBuild::Project", {
8816
8988
  handled: new Set([
@@ -10276,6 +10448,7 @@ const PROPERTY_COVERAGE_BY_TYPE = new Map([
10276
10448
  ["AWS::StepFunctions::StateMachine", {
10277
10449
  handled: new Set([
10278
10450
  "Definition",
10451
+ "DefinitionS3Location",
10279
10452
  "DefinitionString",
10280
10453
  "DefinitionSubstitutions",
10281
10454
  "EncryptionConfiguration",
@@ -10286,7 +10459,7 @@ const PROPERTY_COVERAGE_BY_TYPE = new Map([
10286
10459
  "Tags",
10287
10460
  "TracingConfiguration"
10288
10461
  ]),
10289
- silentDrop: new Map([["DefinitionS3Location", "not yet implemented by cdkd"]])
10462
+ silentDrop: /* @__PURE__ */ new Map()
10290
10463
  }],
10291
10464
  ["AWS::WAFv2::WebACL", {
10292
10465
  handled: new Set([
@@ -13237,5 +13410,5 @@ var DeployEngine = class {
13237
13410
  };
13238
13411
 
13239
13412
  //#endregion
13240
- export { AssemblyReader as $, shouldRetainResource as A, getDefaultStateBucketName as B, applyRoleArnIfSet as C, LockManager as D, TemplateParser as E, formatDockerLoginError as F, resolveStateBucketWithDefault as G, resolveApp as H, getDockerCmd as I, CFN_TEMPLATE_BODY_LIMIT as J, resolveStateBucketWithDefaultAndSource as K, runDockerForeground as L, stringifyValue as M, WorkGraph as N, S3StateBackend as O, buildDockerImage as P, uploadCfnTemplate as Q, runDockerStreaming as R, IntrinsicFunctionResolver as S, DagBuilder as T, resolveCaptureObservedState as U, getLegacyStateBucketName as V, resolveSkipPrefix as W, MIGRATE_TMP_PREFIX as X, CFN_TEMPLATE_URL_LIMIT as Y, findLargeInlineResources as Z, IAMRoleProvider as _, withRetry as a, findActionableSilentDrops as b, computeImplicitDeleteEdges as c, bold as d, clearBucketRegionCache as et, cyan as f, yellow as g, red as h, withResourceDeadline as i, AssetPublisher as j, rebuildClientForBucketRegion as k, extractDeploymentEventError as l, green as m, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isRetryableTransientError as o, gray as p, warnDeprecatedNoPrefixCliFlag as q, DeployEngine as r, IMPLICIT_DELETE_DEPENDENCIES as s, DEFAULT_RESOURCE_TIMEOUT_MS as t, resolveBucketRegion as tt, formatResourceLine as u, collectInlinePolicyNamesManagedBySiblings as v, DiffCalculator as w, CloudControlProvider as x, ProviderRegistry as y, Synthesizer as z };
13241
- //# sourceMappingURL=deploy-engine-DM557Qjg.js.map
13413
+ export { findLargeInlineResources as $, shouldRetainResource as A, getDockerImageBySourceHash as B, applyRoleArnIfSet as C, LockManager as D, TemplateParser as E, formatDockerLoginError as F, resolveCaptureObservedState as G, getDefaultStateBucketName as H, getDockerCmd as I, resolveStateBucketWithDefaultAndSource as J, resolveSkipPrefix as K, runDockerForeground as L, stringifyValue as M, WorkGraph as N, S3StateBackend as O, buildDockerImage as P, MIGRATE_TMP_PREFIX as Q, runDockerStreaming as R, IntrinsicFunctionResolver as S, DagBuilder as T, getLegacyStateBucketName as U, Synthesizer as V, resolveApp as W, CFN_TEMPLATE_BODY_LIMIT as X, warnDeprecatedNoPrefixCliFlag as Y, CFN_TEMPLATE_URL_LIMIT as Z, IAMRoleProvider as _, withRetry as a, findActionableSilentDrops as b, computeImplicitDeleteEdges as c, bold as d, uploadCfnTemplate as et, cyan as f, yellow as g, red as h, withResourceDeadline as i, AssetPublisher as j, rebuildClientForBucketRegion as k, extractDeploymentEventError as l, green as m, DEFAULT_RESOURCE_WARN_AFTER_MS as n, clearBucketRegionCache as nt, isRetryableTransientError as o, gray as p, resolveStateBucketWithDefault as q, DeployEngine as r, resolveBucketRegion as rt, IMPLICIT_DELETE_DEPENDENCIES as s, DEFAULT_RESOURCE_TIMEOUT_MS as t, AssemblyReader as tt, formatResourceLine as u, collectInlinePolicyNamesManagedBySiblings as v, DiffCalculator as w, CloudControlProvider as x, ProviderRegistry as y, AssetManifestLoader as z };
13414
+ //# sourceMappingURL=deploy-engine-Bl_Ka0hj.js.map