@friggframework/devtools 2.0.0--canary.490.1c28319.0 → 2.0.0--canary.490.de9ed00.0

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.
@@ -932,6 +932,8 @@ class VpcBuilder extends InfrastructureBuilder {
932
932
 
933
933
  if (endpointsInStack.length > 0) {
934
934
  console.log(` ✓ VPC Endpoints in stack: ${endpointsInStack.join(', ')}`);
935
+ // CRITICAL: Must add stack-managed endpoints back to template or CloudFormation will DELETE them!
936
+ this._addStackManagedEndpointsToTemplate(decisions, result);
935
937
  }
936
938
 
937
939
  if (externalEndpoints.length > 0) {
@@ -1112,6 +1114,101 @@ class VpcBuilder extends InfrastructureBuilder {
1112
1114
  return healingReport;
1113
1115
  }
1114
1116
 
1117
+ /**
1118
+ * Add stack-managed VPC endpoints back to template
1119
+ *
1120
+ * CRITICAL: CloudFormation will DELETE resources that exist in the previous template
1121
+ * but are missing from the new template. We must re-add discovered stack-managed
1122
+ * endpoints to prevent CloudFormation from deleting them.
1123
+ *
1124
+ * @private
1125
+ */
1126
+ _addStackManagedEndpointsToTemplate(decisions, result) {
1127
+ const vpcId = result.vpcId;
1128
+ const logicalIdMap = {
1129
+ s3: 'FriggS3VPCEndpoint',
1130
+ dynamodb: 'FriggDynamoDBVPCEndpoint',
1131
+ kms: 'FriggKMSVPCEndpoint',
1132
+ secretsManager: 'FriggSecretsManagerVPCEndpoint',
1133
+ sqs: 'FriggSQSVPCEndpoint'
1134
+ };
1135
+
1136
+ Object.entries(decisions).forEach(([type, decision]) => {
1137
+ if (decision.ownership === ResourceOwnership.STACK && decision.physicalId) {
1138
+ const logicalId = logicalIdMap[type];
1139
+
1140
+ // Determine endpoint type and properties based on service
1141
+ if (type === 's3') {
1142
+ result.resources[logicalId] = {
1143
+ Type: 'AWS::EC2::VPCEndpoint',
1144
+ Properties: {
1145
+ VpcId: vpcId,
1146
+ ServiceName: 'com.amazonaws.${self:provider.region}.s3',
1147
+ VpcEndpointType: 'Gateway',
1148
+ RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
1149
+ }
1150
+ };
1151
+ } else if (type === 'dynamodb') {
1152
+ result.resources[logicalId] = {
1153
+ Type: 'AWS::EC2::VPCEndpoint',
1154
+ Properties: {
1155
+ VpcId: vpcId,
1156
+ ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
1157
+ VpcEndpointType: 'Gateway',
1158
+ RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
1159
+ }
1160
+ };
1161
+ } else {
1162
+ // Interface endpoints (KMS, Secrets Manager, SQS)
1163
+ const serviceMap = {
1164
+ kms: 'kms',
1165
+ secretsManager: 'secretsmanager',
1166
+ sqs: 'sqs'
1167
+ };
1168
+
1169
+ result.resources[logicalId] = {
1170
+ Type: 'AWS::EC2::VPCEndpoint',
1171
+ Properties: {
1172
+ VpcId: vpcId,
1173
+ ServiceName: `com.amazonaws.\${self:provider.region}.${serviceMap[type]}`,
1174
+ VpcEndpointType: 'Interface',
1175
+ SubnetIds: result.vpcConfig.subnetIds,
1176
+ SecurityGroupIds: [{ Ref: 'FriggVPCEndpointSecurityGroup' }],
1177
+ PrivateDnsEnabled: true
1178
+ }
1179
+ };
1180
+ }
1181
+ }
1182
+ });
1183
+
1184
+ // If any interface endpoints exist, ensure security group is in template
1185
+ const hasInterfaceEndpoints = ['kms', 'secretsManager', 'sqs'].some(
1186
+ type => decisions[type]?.ownership === ResourceOwnership.STACK && decisions[type]?.physicalId
1187
+ );
1188
+
1189
+ if (hasInterfaceEndpoints && !result.resources.FriggVPCEndpointSecurityGroup) {
1190
+ result.resources.FriggVPCEndpointSecurityGroup = {
1191
+ Type: 'AWS::EC2::SecurityGroup',
1192
+ Properties: {
1193
+ GroupDescription: 'Security group for VPC Endpoints',
1194
+ VpcId: vpcId,
1195
+ SecurityGroupIngress: [
1196
+ {
1197
+ IpProtocol: 'tcp',
1198
+ FromPort: 443,
1199
+ ToPort: 443,
1200
+ SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' }
1201
+ }
1202
+ ],
1203
+ Tags: [
1204
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' },
1205
+ { Key: 'ManagedBy', Value: 'Frigg' }
1206
+ ]
1207
+ }
1208
+ };
1209
+ }
1210
+ }
1211
+
1115
1212
  /**
1116
1213
  * Build new VPC from scratch
1117
1214
  */
@@ -1721,7 +1818,7 @@ class VpcBuilder extends InfrastructureBuilder {
1721
1818
  // Stack-managed resources should be reused, not recreated
1722
1819
  const stackManagedEndpoints = {
1723
1820
  s3: discoveredResources.s3VpcEndpointId && typeof discoveredResources.s3VpcEndpointId === 'string',
1724
- dynamodb: discoveredResources.dynamoDbVpcEndpointId && typeof discoveredResources.dynamoDbVpcEndpointId === 'string',
1821
+ dynamodb: discoveredResources.dynamodbVpcEndpointId && typeof discoveredResources.dynamodbVpcEndpointId === 'string',
1725
1822
  kms: discoveredResources.kmsVpcEndpointId && typeof discoveredResources.kmsVpcEndpointId === 'string',
1726
1823
  secretsManager: discoveredResources.secretsManagerVpcEndpointId && typeof discoveredResources.secretsManagerVpcEndpointId === 'string',
1727
1824
  sqs: discoveredResources.sqsVpcEndpointId && typeof discoveredResources.sqsVpcEndpointId === 'string',
@@ -418,7 +418,7 @@ class CloudFormationDiscovery {
418
418
  // DynamoDB Endpoint (both naming patterns)
419
419
  if ((LogicalResourceId === 'FriggDynamoDBVPCEndpoint' || LogicalResourceId === 'VPCEndpointDynamoDB') &&
420
420
  ResourceType === 'AWS::EC2::VPCEndpoint') {
421
- discovered.dynamoDbVpcEndpointId = PhysicalResourceId;
421
+ discovered.dynamodbVpcEndpointId = PhysicalResourceId; // Note: all lowercase for consistency
422
422
  discovered.vpcEndpoints.dynamodb = PhysicalResourceId;
423
423
  console.log(` ✓ Found DynamoDB VPC endpoint in stack: ${PhysicalResourceId}`);
424
424
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.490.1c28319.0",
4
+ "version": "2.0.0--canary.490.de9ed00.0",
5
5
  "bin": {
6
6
  "frigg": "./frigg-cli/index.js"
7
7
  },
@@ -16,9 +16,9 @@
16
16
  "@babel/eslint-parser": "^7.18.9",
17
17
  "@babel/parser": "^7.25.3",
18
18
  "@babel/traverse": "^7.25.3",
19
- "@friggframework/core": "2.0.0--canary.490.1c28319.0",
20
- "@friggframework/schemas": "2.0.0--canary.490.1c28319.0",
21
- "@friggframework/test": "2.0.0--canary.490.1c28319.0",
19
+ "@friggframework/core": "2.0.0--canary.490.de9ed00.0",
20
+ "@friggframework/schemas": "2.0.0--canary.490.de9ed00.0",
21
+ "@friggframework/test": "2.0.0--canary.490.de9ed00.0",
22
22
  "@hapi/boom": "^10.0.1",
23
23
  "@inquirer/prompts": "^5.3.8",
24
24
  "axios": "^1.7.2",
@@ -46,8 +46,8 @@
46
46
  "validate-npm-package-name": "^5.0.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@friggframework/eslint-config": "2.0.0--canary.490.1c28319.0",
50
- "@friggframework/prettier-config": "2.0.0--canary.490.1c28319.0",
49
+ "@friggframework/eslint-config": "2.0.0--canary.490.de9ed00.0",
50
+ "@friggframework/prettier-config": "2.0.0--canary.490.de9ed00.0",
51
51
  "aws-sdk-client-mock": "^4.1.0",
52
52
  "aws-sdk-client-mock-jest": "^4.1.0",
53
53
  "jest": "^30.1.3",
@@ -79,5 +79,5 @@
79
79
  "publishConfig": {
80
80
  "access": "public"
81
81
  },
82
- "gitHead": "1c28319a3d8c7c5df38b05d62d637b2579fa573f"
82
+ "gitHead": "de9ed004fd58650d6be0a619a3fcd02cc35b7c6d"
83
83
  }