@friggframework/devtools 2.0.0--canary.428.b4a2c90.0 → 2.0.0--canary.428.4fa6f20.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.
@@ -60,15 +60,13 @@ const getAppEnvironmentVars = (AppDefinition) => {
60
60
 
61
61
  if (envKeys.length > 0) {
62
62
  console.log(
63
- ` Found ${
64
- envKeys.length
63
+ ` Found ${envKeys.length
65
64
  } environment variables: ${envKeys.join(', ')}`
66
65
  );
67
66
  }
68
67
  if (skippedKeys.length > 0) {
69
68
  console.log(
70
- ` ⚠️ Skipped ${
71
- skippedKeys.length
69
+ ` ⚠️ Skipped ${skippedKeys.length
72
70
  } reserved AWS Lambda variables: ${skippedKeys.join(', ')}`
73
71
  );
74
72
  }
@@ -372,7 +370,7 @@ const createVPCInfrastructure = (AppDefinition) => {
372
370
  },
373
371
  },
374
372
 
375
- // Private Route Table
373
+ // Private Route Table for Private Subnets
376
374
  FriggPrivateRouteTable: {
377
375
  Type: 'AWS::EC2::RouteTable',
378
376
  Properties: {
@@ -572,6 +570,7 @@ const createVPCInfrastructure = (AppDefinition) => {
572
570
  * @returns {Object} Complete serverless framework configuration
573
571
  */
574
572
  const composeServerlessDefinition = async (AppDefinition) => {
573
+ console.log('composeServerlessDefinition', AppDefinition);
575
574
  // Store discovered resources
576
575
  let discoveredResources = {};
577
576
 
@@ -942,10 +941,9 @@ const composeServerlessDefinition = async (AppDefinition) => {
942
941
  Resource: '*',
943
942
  Condition: {
944
943
  StringEquals: {
945
- 'kms:ViaService': `lambda.${
946
- process.env.AWS_REGION ||
944
+ 'kms:ViaService': `lambda.${process.env.AWS_REGION ||
947
945
  'us-east-1'
948
- }.amazonaws.com`,
946
+ }.amazonaws.com`,
949
947
  },
950
948
  },
951
949
  },
@@ -982,7 +980,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
982
980
  // No key found and createIfNoneFound is not enabled - error
983
981
  throw new Error(
984
982
  'KMS field-level encryption is enabled but no KMS key was found. ' +
985
- 'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
983
+ 'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
986
984
  );
987
985
  }
988
986
  }
@@ -1068,11 +1066,11 @@ const composeServerlessDefinition = async (AppDefinition) => {
1068
1066
  subnetIds:
1069
1067
  AppDefinition.vpc.subnetIds ||
1070
1068
  (discoveredResources.privateSubnetId1 &&
1071
- discoveredResources.privateSubnetId2
1069
+ discoveredResources.privateSubnetId2
1072
1070
  ? [
1073
- discoveredResources.privateSubnetId1,
1074
- discoveredResources.privateSubnetId2,
1075
- ]
1071
+ discoveredResources.privateSubnetId1,
1072
+ discoveredResources.privateSubnetId2,
1073
+ ]
1076
1074
  : []),
1077
1075
  };
1078
1076
 
@@ -1086,36 +1084,44 @@ const composeServerlessDefinition = async (AppDefinition) => {
1086
1084
  // ALWAYS manage NAT Gateway through CloudFormation for self-healing
1087
1085
  // This ensures NAT Gateway is always in the correct subnet with proper configuration
1088
1086
 
1087
+ console.log('AppDefinition.vpc.natGateway', AppDefinition.vpc.natGateway);
1088
+ const natGatewayMethod =
1089
+ AppDefinition.vpc.natGateway?.method || 'useExisting';
1090
+ console.log('natGatewayMethod', natGatewayMethod);
1089
1091
  const needsNewNatGateway =
1090
- (natGatewayMethod ===
1091
- AppDefinition.vpc.natGateway?.method) ===
1092
- 'createAndManage';
1092
+ natGatewayMethod === 'createAndManage';
1093
1093
 
1094
- if (needsNewNatGateway) {
1095
- console.log(
1096
- 'Creating CloudFormation-managed NAT Gateway resources...'
1094
+ console.log('needsNewNatGateway', needsNewNatGateway);
1095
+
1096
+ // Helper function to validate discovered public subnet
1097
+ const isValidPublicSubnet = (subnetId, discoveredResources) => {
1098
+ // Basic validation - in production, AWSDiscovery should check route tables for IGW routes
1099
+ return (
1100
+ discoveredResources.publicSubnetHasIgwRoute !== false
1097
1101
  );
1102
+ };
1103
+
1104
+ if (needsNewNatGateway) {
1105
+ // Always create new dedicated resources in create mode to avoid confusion with existing ones
1098
1106
  console.log(
1099
- 'Note: Any existing misconfigured NAT Gateways will be replaced by CloudFormation'
1107
+ 'Create mode: Creating dedicated EIP, public subnet, and NAT Gateway...'
1100
1108
  );
1101
1109
 
1102
- // Only create EIP if we don't have an existing one available
1103
- if (!discoveredResources.existingElasticIpAllocationId) {
1104
- definition.resources.Resources.FriggNATGatewayEIP = {
1105
- Type: 'AWS::EC2::EIP',
1106
- Properties: {
1107
- Domain: 'vpc',
1108
- Tags: [
1109
- {
1110
- Key: 'Name',
1111
- Value: '${self:service}-${self:provider.stage}-nat-eip',
1112
- },
1113
- ],
1114
- },
1115
- };
1116
- }
1110
+ // Create EIP (ignore any discovered)
1111
+ definition.resources.Resources.FriggNATGatewayEIP = {
1112
+ Type: 'AWS::EC2::EIP',
1113
+ Properties: {
1114
+ Domain: 'vpc',
1115
+ Tags: [
1116
+ {
1117
+ Key: 'Name',
1118
+ Value: '${self:service}-${self:provider.stage}-nat-eip',
1119
+ },
1120
+ ],
1121
+ },
1122
+ };
1117
1123
 
1118
- // If no public subnet exists, create one for NAT Gateway placement
1124
+ // Create public subnet (ignore any discovered; ensure it's in a matching AZ)
1119
1125
  if (!discoveredResources.publicSubnetId) {
1120
1126
  console.log(
1121
1127
  'No public subnet found, creating one for NAT Gateway placement...'
@@ -1124,28 +1130,28 @@ const composeServerlessDefinition = async (AppDefinition) => {
1124
1130
  // Check if Internet Gateway exists or create one
1125
1131
  if (!discoveredResources.internetGatewayId) {
1126
1132
  definition.resources.Resources.FriggInternetGateway =
1127
- {
1128
- Type: 'AWS::EC2::InternetGateway',
1129
- Properties: {
1130
- Tags: [
1131
- {
1132
- Key: 'Name',
1133
- Value: '${self:service}-${self:provider.stage}-igw',
1134
- },
1135
- ],
1136
- },
1137
- };
1133
+ {
1134
+ Type: 'AWS::EC2::InternetGateway',
1135
+ Properties: {
1136
+ Tags: [
1137
+ {
1138
+ Key: 'Name',
1139
+ Value: '${self:service}-${self:provider.stage}-igw',
1140
+ },
1141
+ ],
1142
+ },
1143
+ };
1138
1144
 
1139
1145
  definition.resources.Resources.FriggIGWAttachment =
1140
- {
1141
- Type: 'AWS::EC2::VPCGatewayAttachment',
1142
- Properties: {
1143
- VpcId: discoveredResources.defaultVpcId,
1144
- InternetGatewayId: {
1145
- Ref: 'FriggInternetGateway',
1146
- },
1146
+ {
1147
+ Type: 'AWS::EC2::VPCGatewayAttachment',
1148
+ Properties: {
1149
+ VpcId: discoveredResources.defaultVpcId,
1150
+ InternetGatewayId: {
1151
+ Ref: 'FriggInternetGateway',
1147
1152
  },
1148
- };
1153
+ },
1154
+ };
1149
1155
  }
1150
1156
 
1151
1157
  // Create a small public subnet for NAT Gateway
@@ -1155,7 +1161,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
1155
1161
  VpcId: discoveredResources.defaultVpcId,
1156
1162
  CidrBlock:
1157
1163
  AppDefinition.vpc.natGateway
1158
- ?.publicSubnetCidr || '172.31.250.0/24', // Small /24 subnet
1164
+ ?.publicSubnetCidr || '172.31.250.0/24',
1159
1165
  AvailabilityZone: {
1160
1166
  'Fn::Select': [0, { 'Fn::GetAZs': '' }],
1161
1167
  },
@@ -1205,28 +1211,27 @@ const composeServerlessDefinition = async (AppDefinition) => {
1205
1211
 
1206
1212
  // Associate public subnet with public route table
1207
1213
  definition.resources.Resources.FriggPublicSubnetRouteTableAssociation =
1208
- {
1209
- Type: 'AWS::EC2::SubnetRouteTableAssociation',
1210
- Properties: {
1211
- SubnetId: { Ref: 'FriggPublicSubnet' },
1212
- RouteTableId: {
1213
- Ref: 'FriggPublicRouteTable',
1214
- },
1214
+ {
1215
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1216
+ Properties: {
1217
+ SubnetId: { Ref: 'FriggPublicSubnet' },
1218
+ RouteTableId: {
1219
+ Ref: 'FriggPublicRouteTable',
1215
1220
  },
1216
- };
1221
+ },
1222
+ };
1217
1223
  }
1218
1224
 
1219
- // ALWAYS create NAT Gateway in CloudFormation for management and self-healing
1225
+ // Create NAT Gateway using the new resources
1220
1226
  definition.resources.Resources.FriggNATGateway = {
1221
1227
  Type: 'AWS::EC2::NatGateway',
1222
1228
  Properties: {
1223
- AllocationId:
1224
- discoveredResources.existingElasticIpAllocationId || {
1225
- 'Fn::GetAtt': [
1226
- 'FriggNATGatewayEIP',
1227
- 'AllocationId',
1228
- ],
1229
- },
1229
+ AllocationId: {
1230
+ 'Fn::GetAtt': [
1231
+ 'FriggNATGatewayEIP',
1232
+ 'AllocationId',
1233
+ ],
1234
+ },
1230
1235
  SubnetId: discoveredResources.publicSubnetId || {
1231
1236
  Ref: 'FriggPublicSubnet',
1232
1237
  },
@@ -1242,17 +1247,33 @@ const composeServerlessDefinition = async (AppDefinition) => {
1242
1247
  ],
1243
1248
  },
1244
1249
  };
1250
+ } else if (discoveredResources.existingNatGatewayId) {
1251
+ console.log('discoveredResources.existingNatGatewayId', discoveredResources.existingNatGatewayId);
1252
+ // Reuse mode: Use existing NAT, but validate first
1253
+ if (
1254
+ discoveredResources.publicSubnetId &&
1255
+ isValidPublicSubnet(
1256
+ discoveredResources.publicSubnetId,
1257
+ discoveredResources
1258
+ )
1259
+ ) {
1260
+ console.log(
1261
+ 'Reuse mode: Valid existing NAT found; adding routes...'
1262
+ );
1263
+ // No new NAT creation; just add routes referencing existingNatGatewayId
1264
+ } else {
1265
+ throw new Error(
1266
+ 'Existing NAT discovered but public subnet is invalid or missing. Set method to "createAndManage" or fix subnet configuration.'
1267
+ );
1268
+ }
1245
1269
  } else {
1246
- // We have an existing valid NAT Gateway - import it into CloudFormation management
1247
- console.log(
1248
- 'Found existing NAT Gateway - importing into CloudFormation management for self-healing...'
1270
+ // No NAT and not in create mode: Error out to prevent isolated subnets
1271
+ throw new Error(
1272
+ 'No existing NAT Gateway found and createAndManage not enabled. Update appDefinition.vpc.natGateway.method or ensure discovery finds a valid NAT.'
1249
1273
  );
1250
-
1251
- // Note: CloudFormation will detect if a NAT Gateway already exists with these properties
1252
- // and will adopt it rather than creating a duplicate
1253
1274
  }
1254
1275
 
1255
- // Create route table for Lambda subnets to use NAT Gateway
1276
+ // Always add route table and routes (referencing the NAT, whether new or existing)
1256
1277
  definition.resources.Resources.FriggLambdaRouteTable = {
1257
1278
  Type: 'AWS::EC2::RouteTable',
1258
1279
  Properties: {
@@ -1274,7 +1295,17 @@ const composeServerlessDefinition = async (AppDefinition) => {
1274
1295
  Properties: {
1275
1296
  RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1276
1297
  DestinationCidrBlock: '0.0.0.0/0',
1277
- NatGatewayId: { Ref: 'FriggNATGateway' }, // Always use CloudFormation-managed NAT Gateway
1298
+ NatGatewayId: { Ref: 'FriggNATGateway' },
1299
+ },
1300
+ };
1301
+ } else {
1302
+ definition.resources.Resources.FriggNATRoute = {
1303
+ Type: 'AWS::EC2::Route',
1304
+ Properties: {
1305
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1306
+ DestinationCidrBlock: '0.0.0.0/0',
1307
+ NatGatewayId:
1308
+ discoveredResources.existingNatGatewayId,
1278
1309
  },
1279
1310
  };
1280
1311
  }
@@ -1335,28 +1366,30 @@ const composeServerlessDefinition = async (AppDefinition) => {
1335
1366
  .VPCEndpointSecurityGroup
1336
1367
  ) {
1337
1368
  definition.resources.Resources.VPCEndpointSecurityGroup =
1338
- {
1339
- Type: 'AWS::EC2::SecurityGroup',
1340
- Properties: {
1341
- GroupDescription:
1342
- 'Security group for VPC endpoints',
1343
- VpcId: discoveredResources.defaultVpcId,
1344
- SecurityGroupIngress: [
1345
- {
1346
- IpProtocol: 'tcp',
1347
- FromPort: 443,
1348
- ToPort: 443,
1349
- CidrIp: '172.31.0.0/16', // VPC CIDR
1350
- },
1351
- ],
1352
- Tags: [
1353
- {
1354
- Key: 'Name',
1355
- Value: '${self:service}-${self:provider.stage}-vpc-endpoints-sg',
1356
- },
1357
- ],
1358
- },
1359
- };
1369
+ {
1370
+ Type: 'AWS::EC2::SecurityGroup',
1371
+ Properties: {
1372
+ GroupDescription:
1373
+ 'Security group for VPC endpoints',
1374
+ VpcId: discoveredResources.defaultVpcId,
1375
+ SecurityGroupIngress: [
1376
+ {
1377
+ IpProtocol: 'tcp',
1378
+ FromPort: 443,
1379
+ ToPort: 443,
1380
+ CidrIp:
1381
+ discoveredResources.vpcCidr ||
1382
+ '10.0.0.0/16', // Dynamic VPC CIDR
1383
+ },
1384
+ ],
1385
+ Tags: [
1386
+ {
1387
+ Key: 'Name',
1388
+ Value: '${self:service}-${self:provider.stage}-vpc-endpoints-sg',
1389
+ },
1390
+ ],
1391
+ },
1392
+ };
1360
1393
  }
1361
1394
 
1362
1395
  definition.resources.Resources.VPCEndpointKMS = {
@@ -1377,20 +1410,20 @@ const composeServerlessDefinition = async (AppDefinition) => {
1377
1410
  // Also add Secrets Manager endpoint if using Secrets Manager
1378
1411
  if (AppDefinition.secretsManager?.enable === true) {
1379
1412
  definition.resources.Resources.VPCEndpointSecretsManager =
1380
- {
1381
- Type: 'AWS::EC2::VPCEndpoint',
1382
- Properties: {
1383
- VpcId: discoveredResources.defaultVpcId,
1384
- ServiceName:
1385
- 'com.amazonaws.${self:provider.region}.secretsmanager',
1386
- VpcEndpointType: 'Interface',
1387
- SubnetIds: vpcConfig.subnetIds,
1388
- SecurityGroupIds: [
1389
- { Ref: 'VPCEndpointSecurityGroup' },
1390
- ],
1391
- PrivateDnsEnabled: true,
1392
- },
1393
- };
1413
+ {
1414
+ Type: 'AWS::EC2::VPCEndpoint',
1415
+ Properties: {
1416
+ VpcId: discoveredResources.defaultVpcId,
1417
+ ServiceName:
1418
+ 'com.amazonaws.${self:provider.region}.secretsmanager',
1419
+ VpcEndpointType: 'Interface',
1420
+ SubnetIds: vpcConfig.subnetIds,
1421
+ SecurityGroupIds: [
1422
+ { Ref: 'VPCEndpointSecurityGroup' },
1423
+ ],
1424
+ PrivateDnsEnabled: true,
1425
+ },
1426
+ };
1394
1427
  }
1395
1428
  }
1396
1429
  }
@@ -1453,10 +1486,9 @@ const composeServerlessDefinition = async (AppDefinition) => {
1453
1486
  };
1454
1487
 
1455
1488
  // Add SQS Queue for the integration
1456
- const queueReference = `${
1457
- integrationName.charAt(0).toUpperCase() +
1489
+ const queueReference = `${integrationName.charAt(0).toUpperCase() +
1458
1490
  integrationName.slice(1)
1459
- }Queue`;
1491
+ }Queue`;
1460
1492
  const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
1461
1493
  definition.resources.Resources[queueReference] = {
1462
1494
  Type: 'AWS::SQS::Queue',
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.428.b4a2c90.0",
4
+ "version": "2.0.0--canary.428.4fa6f20.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-ec2": "^3.835.0",
7
7
  "@aws-sdk/client-kms": "^3.835.0",
@@ -9,8 +9,8 @@
9
9
  "@babel/eslint-parser": "^7.18.9",
10
10
  "@babel/parser": "^7.25.3",
11
11
  "@babel/traverse": "^7.25.3",
12
- "@friggframework/schemas": "2.0.0--canary.428.b4a2c90.0",
13
- "@friggframework/test": "2.0.0--canary.428.b4a2c90.0",
12
+ "@friggframework/schemas": "2.0.0--canary.428.4fa6f20.0",
13
+ "@friggframework/test": "2.0.0--canary.428.4fa6f20.0",
14
14
  "@hapi/boom": "^10.0.1",
15
15
  "@inquirer/prompts": "^5.3.8",
16
16
  "axios": "^1.7.2",
@@ -32,8 +32,8 @@
32
32
  "serverless-http": "^2.7.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@friggframework/eslint-config": "2.0.0--canary.428.b4a2c90.0",
36
- "@friggframework/prettier-config": "2.0.0--canary.428.b4a2c90.0",
35
+ "@friggframework/eslint-config": "2.0.0--canary.428.4fa6f20.0",
36
+ "@friggframework/prettier-config": "2.0.0--canary.428.4fa6f20.0",
37
37
  "jest": "^30.1.3",
38
38
  "prettier": "^2.7.1",
39
39
  "serverless": "3.39.0",
@@ -66,5 +66,5 @@
66
66
  "publishConfig": {
67
67
  "access": "public"
68
68
  },
69
- "gitHead": "b4a2c9027b96e1009f95926205f39044560edc83"
69
+ "gitHead": "4fa6f20b9371677cb86a8035ac9736af726ffa82"
70
70
  }