@go-to-k/cdkd 0.2.0 → 0.3.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.
Binary file
package/dist/index.js CHANGED
@@ -3139,6 +3139,62 @@ var TemplateParser = class {
3139
3139
 
3140
3140
  // src/analyzer/dag-builder.ts
3141
3141
  import graphlib from "graphlib";
3142
+
3143
+ // src/analyzer/lambda-vpc-deps.ts
3144
+ function extractLambdaVpcDeleteDeps(resources) {
3145
+ const edges = [];
3146
+ const seen = /* @__PURE__ */ new Set();
3147
+ for (const [lambdaId, resource] of Object.entries(resources)) {
3148
+ if (resource.Type !== "AWS::Lambda::Function")
3149
+ continue;
3150
+ const vpcConfig = (resource.Properties ?? {})["VpcConfig"];
3151
+ if (!isObject(vpcConfig))
3152
+ continue;
3153
+ const targets = /* @__PURE__ */ new Set();
3154
+ collectRefIds(vpcConfig["SubnetIds"], targets);
3155
+ collectRefIds(vpcConfig["SecurityGroupIds"], targets);
3156
+ for (const targetId of targets) {
3157
+ if (targetId === lambdaId)
3158
+ continue;
3159
+ if (!(targetId in resources))
3160
+ continue;
3161
+ const key = `${lambdaId}\0${targetId}`;
3162
+ if (seen.has(key))
3163
+ continue;
3164
+ seen.add(key);
3165
+ edges.push({ before: lambdaId, after: targetId });
3166
+ }
3167
+ }
3168
+ return edges;
3169
+ }
3170
+ function isObject(v) {
3171
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3172
+ }
3173
+ function collectRefIds(value, out) {
3174
+ if (value === null || value === void 0)
3175
+ return;
3176
+ if (Array.isArray(value)) {
3177
+ for (const item of value)
3178
+ collectRefIds(item, out);
3179
+ return;
3180
+ }
3181
+ if (!isObject(value))
3182
+ return;
3183
+ if (typeof value["Ref"] === "string") {
3184
+ const ref = value["Ref"];
3185
+ if (!ref.startsWith("AWS::"))
3186
+ out.add(ref);
3187
+ return;
3188
+ }
3189
+ if (Array.isArray(value["Fn::GetAtt"])) {
3190
+ const arr = value["Fn::GetAtt"];
3191
+ if (typeof arr[0] === "string")
3192
+ out.add(arr[0]);
3193
+ return;
3194
+ }
3195
+ }
3196
+
3197
+ // src/analyzer/dag-builder.ts
3142
3198
  var { Graph, alg } = graphlib;
3143
3199
  var IAM_ROLE_POLICY_TYPES = /* @__PURE__ */ new Set([
3144
3200
  "AWS::IAM::Policy",
@@ -3186,6 +3242,7 @@ var DagBuilder = class {
3186
3242
  }
3187
3243
  this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);
3188
3244
  edgeCount += this.addCustomResourcePolicyEdges(graph, template);
3245
+ edgeCount += this.addLambdaVpcEdges(graph, template);
3189
3246
  if (!alg.isAcyclic(graph)) {
3190
3247
  const cycles = this.findCycles(graph);
3191
3248
  throw new DependencyError(
@@ -3378,6 +3435,41 @@ var DagBuilder = class {
3378
3435
  }
3379
3436
  return added;
3380
3437
  }
3438
+ /**
3439
+ * Add edges from Subnets / SecurityGroups referenced by an
3440
+ * AWS::Lambda::Function VpcConfig to the Lambda itself.
3441
+ *
3442
+ * Same direction as a normal `Ref`-derived edge (Subnet -> Lambda), so for
3443
+ * deploy this just duplicates what extractDependencies already produced.
3444
+ * The point is robustness: if a future template massages the VpcConfig
3445
+ * shape in a way the recursive extractor doesn't anticipate, this pass
3446
+ * still ties the Lambda to its networking resources so that the
3447
+ * deletion-time reverse traversal continues to delete Lambda before
3448
+ * Subnet / SecurityGroup.
3449
+ *
3450
+ * Returns the number of NEW edges added (existing edges are skipped).
3451
+ */
3452
+ addLambdaVpcEdges(graph, template) {
3453
+ const edges = extractLambdaVpcDeleteDeps(template.Resources);
3454
+ if (edges.length === 0)
3455
+ return 0;
3456
+ let added = 0;
3457
+ for (const edge of edges) {
3458
+ const depId = edge.after;
3459
+ const dependentId = edge.before;
3460
+ if (!graph.hasNode(depId) || !graph.hasNode(dependentId))
3461
+ continue;
3462
+ if (graph.hasEdge(depId, dependentId))
3463
+ continue;
3464
+ graph.setEdge(depId, dependentId);
3465
+ added++;
3466
+ this.logger.debug(`Added implicit edge (lambda vpc): ${depId} -> ${dependentId}`);
3467
+ }
3468
+ if (added > 0) {
3469
+ this.logger.debug(`Added ${added} implicit edges for Lambda VpcConfig`);
3470
+ }
3471
+ return added;
3472
+ }
3381
3473
  isCustomResourceType(type) {
3382
3474
  return type === "AWS::CloudFormation::CustomResource" || type.startsWith("Custom::");
3383
3475
  }
@@ -7082,6 +7174,44 @@ var DagExecutor = class {
7082
7174
  }
7083
7175
  };
7084
7176
 
7177
+ // src/analyzer/implicit-delete-deps.ts
7178
+ var IMPLICIT_DELETE_DEPENDENCIES = {
7179
+ // IGW must be deleted AFTER VPCGatewayAttachment
7180
+ "AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
7181
+ // EventBus must be deleted AFTER Rules on that bus
7182
+ "AWS::Events::EventBus": ["AWS::Events::Rule"],
7183
+ // Athena workgroup must be deleted AFTER its named queries
7184
+ "AWS::Athena::WorkGroup": ["AWS::Athena::NamedQuery"],
7185
+ // CloudFront managed-policy-style resources must be deleted AFTER
7186
+ // any Distribution that references them
7187
+ "AWS::CloudFront::ResponseHeadersPolicy": ["AWS::CloudFront::Distribution"],
7188
+ "AWS::CloudFront::CachePolicy": ["AWS::CloudFront::Distribution"],
7189
+ "AWS::CloudFront::OriginAccessControl": ["AWS::CloudFront::Distribution"],
7190
+ // VPC must be deleted AFTER all VPC-dependent resources
7191
+ "AWS::EC2::VPC": [
7192
+ "AWS::EC2::Subnet",
7193
+ "AWS::EC2::SecurityGroup",
7194
+ "AWS::EC2::InternetGateway",
7195
+ "AWS::EC2::EgressOnlyInternetGateway",
7196
+ "AWS::EC2::VPCGatewayAttachment",
7197
+ "AWS::EC2::RouteTable"
7198
+ ],
7199
+ // Subnet must be deleted AFTER any Lambda that may still hold an ENI
7200
+ // in it. Lambda DELETE returns immediately but the ENI is detached
7201
+ // asynchronously by AWS, so deleting the Subnet first races the detach
7202
+ // and yields "DependencyViolation".
7203
+ "AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation", "AWS::Lambda::Function"],
7204
+ // RouteTable must be deleted AFTER Route and Association
7205
+ "AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
7206
+ // SecurityGroup must be deleted AFTER any Lambda whose ENI is bound
7207
+ // to it (same ENI-detach race as Subnet above).
7208
+ "AWS::EC2::SecurityGroup": [
7209
+ "AWS::EC2::SecurityGroupIngress",
7210
+ "AWS::EC2::SecurityGroupEgress",
7211
+ "AWS::Lambda::Function"
7212
+ ]
7213
+ };
7214
+
7085
7215
  // src/deployment/deploy-engine.ts
7086
7216
  var InterruptedError = class extends Error {
7087
7217
  constructor() {
@@ -7089,7 +7219,7 @@ var InterruptedError = class extends Error {
7089
7219
  this.name = "InterruptedError";
7090
7220
  }
7091
7221
  };
7092
- var DeployEngine = class _DeployEngine {
7222
+ var DeployEngine = class {
7093
7223
  constructor(stateBackend, lockManager, dagBuilder, diffCalculator, providerRegistry, options = {}, stackRegion) {
7094
7224
  this.stateBackend = stateBackend;
7095
7225
  this.lockManager = lockManager;
@@ -7926,36 +8056,9 @@ var DeployEngine = class _DeployEngine {
7926
8056
  const deps = parser.extractDependencies(resource);
7927
8057
  return deps.size > 0 ? [...deps] : void 0;
7928
8058
  }
7929
- /**
7930
- * Implicit dependency map for correct deletion order.
7931
- *
7932
- * Key = resource type that must be deleted AFTER all value types are deleted.
7933
- * Value = resource types that must be deleted BEFORE the key type.
7934
- *
7935
- * Example: InternetGateway depends on VPCGatewayAttachment being deleted first,
7936
- * because AWS won't let you delete an IGW while it's still attached to a VPC.
7937
- */
7938
- static IMPLICIT_DELETE_DEPENDENCIES = {
7939
- // IGW must be deleted AFTER VPCGatewayAttachment
7940
- "AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
7941
- // EventBus must be deleted AFTER Rules on that bus
7942
- "AWS::Events::EventBus": ["AWS::Events::Rule"],
7943
- // VPC must be deleted AFTER all VPC-dependent resources
7944
- "AWS::EC2::VPC": [
7945
- "AWS::EC2::Subnet",
7946
- "AWS::EC2::SecurityGroup",
7947
- "AWS::EC2::InternetGateway",
7948
- "AWS::EC2::EgressOnlyInternetGateway",
7949
- "AWS::EC2::VPCGatewayAttachment",
7950
- "AWS::EC2::RouteTable"
7951
- ],
7952
- // Subnet must be deleted AFTER RouteTableAssociation
7953
- "AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation"],
7954
- // RouteTable must be deleted AFTER Route and Association
7955
- "AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
7956
- // SecurityGroup must be deleted AFTER SecurityGroupIngress/Egress
7957
- "AWS::EC2::SecurityGroup": ["AWS::EC2::SecurityGroupIngress", "AWS::EC2::SecurityGroupEgress"]
7958
- };
8059
+ // Type-based implicit deletion ordering rules are defined in
8060
+ // src/analyzer/implicit-delete-deps.ts so the deploy DELETE phase and the
8061
+ // standalone destroy command apply the same rules.
7959
8062
  /**
7960
8063
  * Build a per-resource map of "must be deleted before me" dependencies for
7961
8064
  * the DELETE phase, derived from state-recorded dependencies plus implicit
@@ -8021,7 +8124,7 @@ var DeployEngine = class _DeployEngine {
8021
8124
  const resource = state.resources[id];
8022
8125
  if (!resource)
8023
8126
  continue;
8024
- const mustDeleteAfter = _DeployEngine.IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];
8127
+ const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];
8025
8128
  if (!mustDeleteAfter)
8026
8129
  continue;
8027
8130
  for (const depType of mustDeleteAfter) {