@friggframework/devtools 2.0.0--canary.397.155fecd.0 → 2.0.0--canary.398.c9e5d61.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.
@@ -1,7 +1,14 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
3
 
4
- // Function to find the actual path to node_modules
4
+ /**
5
+ * Find the actual path to node_modules directory
6
+ * Tries multiple methods to locate node_modules:
7
+ * 1. Traversing up from current directory
8
+ * 2. Using npm root command
9
+ * 3. Looking for package.json and adjacent node_modules
10
+ * @returns {string} Path to node_modules directory
11
+ */
5
12
  const findNodeModulesPath = () => {
6
13
  try {
7
14
  // Method 1: Try to find node_modules by traversing up from current directory
@@ -75,7 +82,12 @@ const findNodeModulesPath = () => {
75
82
  }
76
83
  };
77
84
 
78
- // Function to modify handler paths to point to the correct node_modules
85
+ /**
86
+ * Modify handler paths to point to the correct node_modules location
87
+ * Only modifies paths when running in offline mode
88
+ * @param {Object} functions - Serverless functions configuration object
89
+ * @returns {Object} Modified functions object with updated handler paths
90
+ */
79
91
  const modifyHandlerPaths = (functions) => {
80
92
  // Check if we're running in offline mode
81
93
  const isOffline = process.argv.includes('offline');
@@ -94,7 +106,8 @@ const modifyHandlerPaths = (functions) => {
94
106
  const functionDef = modifiedFunctions[functionName];
95
107
  if (functionDef?.handler?.includes('node_modules/')) {
96
108
  // Replace node_modules/ with the actual path to node_modules/
97
- functionDef.handler = functionDef.handler.replace('node_modules/', '../node_modules/');
109
+ const relativePath = path.relative(process.cwd(), nodeModulesPath);
110
+ functionDef.handler = functionDef.handler.replace('node_modules/', `${relativePath}/`);
98
111
  console.log(`Updated handler for ${functionName}: ${functionDef.handler}`);
99
112
  }
100
113
  }
@@ -102,6 +115,329 @@ const modifyHandlerPaths = (functions) => {
102
115
  return modifiedFunctions;
103
116
  };
104
117
 
118
+ /**
119
+ * Create VPC infrastructure resources for CloudFormation
120
+ * Creates VPC, subnets, NAT gateway, route tables, and security groups
121
+ * @param {Object} AppDefinition - Application definition object
122
+ * @param {Object} AppDefinition.vpc - VPC configuration
123
+ * @param {string} [AppDefinition.vpc.cidrBlock='10.0.0.0/16'] - CIDR block for VPC
124
+ * @returns {Object} CloudFormation resources for VPC infrastructure
125
+ */
126
+ const createVPCInfrastructure = (AppDefinition) => {
127
+ const vpcResources = {
128
+ // VPC
129
+ FriggVPC: {
130
+ Type: 'AWS::EC2::VPC',
131
+ Properties: {
132
+ CidrBlock: AppDefinition.vpc.cidrBlock || '10.0.0.0/16',
133
+ EnableDnsHostnames: true,
134
+ EnableDnsSupport: true,
135
+ Tags: [
136
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc' }
137
+ ]
138
+ }
139
+ },
140
+
141
+ // Internet Gateway
142
+ FriggInternetGateway: {
143
+ Type: 'AWS::EC2::InternetGateway',
144
+ Properties: {
145
+ Tags: [
146
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' }
147
+ ]
148
+ }
149
+ },
150
+
151
+ // Attach Internet Gateway to VPC
152
+ FriggVPCGatewayAttachment: {
153
+ Type: 'AWS::EC2::VPCGatewayAttachment',
154
+ Properties: {
155
+ VpcId: { Ref: 'FriggVPC' },
156
+ InternetGatewayId: { Ref: 'FriggInternetGateway' }
157
+ }
158
+ },
159
+
160
+ // Public Subnet for NAT Gateway
161
+ FriggPublicSubnet: {
162
+ Type: 'AWS::EC2::Subnet',
163
+ Properties: {
164
+ VpcId: { Ref: 'FriggVPC' },
165
+ CidrBlock: '10.0.1.0/24',
166
+ AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
167
+ MapPublicIpOnLaunch: true,
168
+ Tags: [
169
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-subnet' }
170
+ ]
171
+ }
172
+ },
173
+
174
+ // Private Subnet 1 for Lambda
175
+ FriggPrivateSubnet1: {
176
+ Type: 'AWS::EC2::Subnet',
177
+ Properties: {
178
+ VpcId: { Ref: 'FriggVPC' },
179
+ CidrBlock: '10.0.2.0/24',
180
+ AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
181
+ Tags: [
182
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-1' }
183
+ ]
184
+ }
185
+ },
186
+
187
+ // Private Subnet 2 for Lambda (different AZ for redundancy)
188
+ FriggPrivateSubnet2: {
189
+ Type: 'AWS::EC2::Subnet',
190
+ Properties: {
191
+ VpcId: { Ref: 'FriggVPC' },
192
+ CidrBlock: '10.0.3.0/24',
193
+ AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
194
+ Tags: [
195
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-2' }
196
+ ]
197
+ }
198
+ },
199
+
200
+ // Elastic IP for NAT Gateway
201
+ FriggNATGatewayEIP: {
202
+ Type: 'AWS::EC2::EIP',
203
+ Properties: {
204
+ Domain: 'vpc',
205
+ Tags: [
206
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
207
+ ]
208
+ },
209
+ DependsOn: 'FriggVPCGatewayAttachment'
210
+ },
211
+
212
+ // NAT Gateway for private subnet internet access
213
+ FriggNATGateway: {
214
+ Type: 'AWS::EC2::NatGateway',
215
+ Properties: {
216
+ AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
217
+ SubnetId: { Ref: 'FriggPublicSubnet' },
218
+ Tags: [
219
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
220
+ ]
221
+ }
222
+ },
223
+
224
+ // Public Route Table
225
+ FriggPublicRouteTable: {
226
+ Type: 'AWS::EC2::RouteTable',
227
+ Properties: {
228
+ VpcId: { Ref: 'FriggVPC' },
229
+ Tags: [
230
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' }
231
+ ]
232
+ }
233
+ },
234
+
235
+ // Public Route to Internet Gateway
236
+ FriggPublicRoute: {
237
+ Type: 'AWS::EC2::Route',
238
+ Properties: {
239
+ RouteTableId: { Ref: 'FriggPublicRouteTable' },
240
+ DestinationCidrBlock: '0.0.0.0/0',
241
+ GatewayId: { Ref: 'FriggInternetGateway' }
242
+ },
243
+ DependsOn: 'FriggVPCGatewayAttachment'
244
+ },
245
+
246
+ // Associate Public Subnet with Public Route Table
247
+ FriggPublicSubnetRouteTableAssociation: {
248
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
249
+ Properties: {
250
+ SubnetId: { Ref: 'FriggPublicSubnet' },
251
+ RouteTableId: { Ref: 'FriggPublicRouteTable' }
252
+ }
253
+ },
254
+
255
+ // Private Route Table
256
+ FriggPrivateRouteTable: {
257
+ Type: 'AWS::EC2::RouteTable',
258
+ Properties: {
259
+ VpcId: { Ref: 'FriggVPC' },
260
+ Tags: [
261
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-rt' }
262
+ ]
263
+ }
264
+ },
265
+
266
+ // Private Route to NAT Gateway
267
+ FriggPrivateRoute: {
268
+ Type: 'AWS::EC2::Route',
269
+ Properties: {
270
+ RouteTableId: { Ref: 'FriggPrivateRouteTable' },
271
+ DestinationCidrBlock: '0.0.0.0/0',
272
+ NatGatewayId: { Ref: 'FriggNATGateway' }
273
+ }
274
+ },
275
+
276
+ // Associate Private Subnet 1 with Private Route Table
277
+ FriggPrivateSubnet1RouteTableAssociation: {
278
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
279
+ Properties: {
280
+ SubnetId: { Ref: 'FriggPrivateSubnet1' },
281
+ RouteTableId: { Ref: 'FriggPrivateRouteTable' }
282
+ }
283
+ },
284
+
285
+ // Associate Private Subnet 2 with Private Route Table
286
+ FriggPrivateSubnet2RouteTableAssociation: {
287
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
288
+ Properties: {
289
+ SubnetId: { Ref: 'FriggPrivateSubnet2' },
290
+ RouteTableId: { Ref: 'FriggPrivateRouteTable' }
291
+ }
292
+ },
293
+
294
+ // Security Group for Lambda functions
295
+ FriggLambdaSecurityGroup: {
296
+ Type: 'AWS::EC2::SecurityGroup',
297
+ Properties: {
298
+ GroupDescription: 'Security group for Frigg Lambda functions',
299
+ VpcId: { Ref: 'FriggVPC' },
300
+ SecurityGroupEgress: [
301
+ {
302
+ IpProtocol: 'tcp',
303
+ FromPort: 443,
304
+ ToPort: 443,
305
+ CidrIp: '0.0.0.0/0',
306
+ Description: 'HTTPS outbound'
307
+ },
308
+ {
309
+ IpProtocol: 'tcp',
310
+ FromPort: 80,
311
+ ToPort: 80,
312
+ CidrIp: '0.0.0.0/0',
313
+ Description: 'HTTP outbound'
314
+ },
315
+ {
316
+ IpProtocol: 'tcp',
317
+ FromPort: 53,
318
+ ToPort: 53,
319
+ CidrIp: '0.0.0.0/0',
320
+ Description: 'DNS TCP'
321
+ },
322
+ {
323
+ IpProtocol: 'udp',
324
+ FromPort: 53,
325
+ ToPort: 53,
326
+ CidrIp: '0.0.0.0/0',
327
+ Description: 'DNS UDP'
328
+ }
329
+ ],
330
+ Tags: [
331
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-sg' }
332
+ ]
333
+ }
334
+ }
335
+ };
336
+
337
+ // Add VPC Endpoints for cost optimization
338
+ if (AppDefinition.vpc.enableVPCEndpoints !== false) {
339
+ // S3 Gateway Endpoint (free)
340
+ vpcResources.FriggS3VPCEndpoint = {
341
+ Type: 'AWS::EC2::VPCEndpoint',
342
+ Properties: {
343
+ VpcId: { Ref: 'FriggVPC' },
344
+ ServiceName: 'com.amazonaws.${self:provider.region}.s3',
345
+ VpcEndpointType: 'Gateway',
346
+ RouteTableIds: [
347
+ { Ref: 'FriggPrivateRouteTable' }
348
+ ]
349
+ }
350
+ };
351
+
352
+ // DynamoDB Gateway Endpoint (free)
353
+ vpcResources.FriggDynamoDBVPCEndpoint = {
354
+ Type: 'AWS::EC2::VPCEndpoint',
355
+ Properties: {
356
+ VpcId: { Ref: 'FriggVPC' },
357
+ ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
358
+ VpcEndpointType: 'Gateway',
359
+ RouteTableIds: [
360
+ { Ref: 'FriggPrivateRouteTable' }
361
+ ]
362
+ }
363
+ };
364
+
365
+ // KMS Interface Endpoint (paid, but useful if using KMS)
366
+ if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
367
+ vpcResources.FriggKMSVPCEndpoint = {
368
+ Type: 'AWS::EC2::VPCEndpoint',
369
+ Properties: {
370
+ VpcId: { Ref: 'FriggVPC' },
371
+ ServiceName: 'com.amazonaws.${self:provider.region}.kms',
372
+ VpcEndpointType: 'Interface',
373
+ SubnetIds: [
374
+ { Ref: 'FriggPrivateSubnet1' },
375
+ { Ref: 'FriggPrivateSubnet2' }
376
+ ],
377
+ SecurityGroupIds: [
378
+ { Ref: 'FriggVPCEndpointSecurityGroup' }
379
+ ],
380
+ PrivateDnsEnabled: true
381
+ }
382
+ };
383
+ }
384
+
385
+ // Secrets Manager Interface Endpoint (paid, but useful for secrets)
386
+ vpcResources.FriggSecretsManagerVPCEndpoint = {
387
+ Type: 'AWS::EC2::VPCEndpoint',
388
+ Properties: {
389
+ VpcId: { Ref: 'FriggVPC' },
390
+ ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
391
+ VpcEndpointType: 'Interface',
392
+ SubnetIds: [
393
+ { Ref: 'FriggPrivateSubnet1' },
394
+ { Ref: 'FriggPrivateSubnet2' }
395
+ ],
396
+ SecurityGroupIds: [
397
+ { Ref: 'FriggVPCEndpointSecurityGroup' }
398
+ ],
399
+ PrivateDnsEnabled: true
400
+ }
401
+ };
402
+
403
+ // Security Group for VPC Endpoints
404
+ vpcResources.FriggVPCEndpointSecurityGroup = {
405
+ Type: 'AWS::EC2::SecurityGroup',
406
+ Properties: {
407
+ GroupDescription: 'Security group for Frigg VPC Endpoints',
408
+ VpcId: { Ref: 'FriggVPC' },
409
+ SecurityGroupIngress: [
410
+ {
411
+ IpProtocol: 'tcp',
412
+ FromPort: 443,
413
+ ToPort: 443,
414
+ SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
415
+ Description: 'HTTPS from Lambda'
416
+ }
417
+ ],
418
+ Tags: [
419
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' }
420
+ ]
421
+ }
422
+ };
423
+ }
424
+
425
+ return vpcResources;
426
+ };
427
+
428
+ /**
429
+ * Compose a complete serverless framework configuration from app definition
430
+ * @param {Object} AppDefinition - Application definition object
431
+ * @param {string} [AppDefinition.name] - Application name
432
+ * @param {string} [AppDefinition.provider='aws'] - Cloud provider
433
+ * @param {Array} AppDefinition.integrations - Array of integration definitions
434
+ * @param {Object} [AppDefinition.vpc] - VPC configuration
435
+ * @param {Object} [AppDefinition.encryption] - KMS encryption configuration
436
+ * @param {Object} [AppDefinition.ssm] - SSM parameter store configuration
437
+ * @param {Object} [AppDefinition.websockets] - WebSocket configuration
438
+ * @param {boolean} [AppDefinition.websockets.enable=false] - Enable WebSocket support for live update streaming
439
+ * @returns {Object} Complete serverless framework configuration
440
+ */
105
441
  const composeServerlessDefinition = (AppDefinition) => {
106
442
  const definition = {
107
443
  frameworkVersion: '>=3.17.0',
@@ -180,26 +516,6 @@ const composeServerlessDefinition = (AppDefinition) => {
180
516
  },
181
517
  },
182
518
  functions: {
183
- defaultWebsocket: {
184
- handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
185
- events: [
186
- {
187
- websocket: {
188
- route: '$connect',
189
- },
190
- },
191
- {
192
- websocket: {
193
- route: '$default',
194
- },
195
- },
196
- {
197
- websocket: {
198
- route: '$disconnect',
199
- },
200
- },
201
- ],
202
- },
203
519
  auth: {
204
520
  handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
205
521
  events: [
@@ -245,7 +561,7 @@ const composeServerlessDefinition = (AppDefinition) => {
245
561
  Type: 'AWS::SQS::Queue',
246
562
  Properties: {
247
563
  QueueName:
248
- 'internal-error-queue-${self:provider.stage}',
564
+ '${self:service}-internal-error-queue-${self:provider.stage}',
249
565
  MessageRetentionPeriod: 300,
250
566
  },
251
567
  },
@@ -329,6 +645,7 @@ const composeServerlessDefinition = (AppDefinition) => {
329
645
  },
330
646
  };
331
647
 
648
+
332
649
  // KMS Configuration based on App Definition
333
650
  if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
334
651
  // Add KMS IAM permissions
@@ -347,12 +664,181 @@ const composeServerlessDefinition = (AppDefinition) => {
347
664
  // Add serverless-kms-grants plugin
348
665
  definition.plugins.push('serverless-kms-grants');
349
666
 
350
- // Configure KMS grants with default key
667
+ // Configure KMS grants with discovered default key
351
668
  definition.custom.kmsGrants = {
352
- kmsKeyId: '*'
669
+ kmsKeyId: '${env:AWS_DISCOVERY_KMS_KEY_ID}'
353
670
  };
354
671
  }
355
672
 
673
+ // VPC Configuration based on App Definition
674
+ if (AppDefinition.vpc?.enable === true) {
675
+ // Add VPC-related IAM permissions
676
+ definition.provider.iamRoleStatements.push({
677
+ Effect: 'Allow',
678
+ Action: [
679
+ 'ec2:CreateNetworkInterface',
680
+ 'ec2:DescribeNetworkInterfaces',
681
+ 'ec2:DeleteNetworkInterface',
682
+ 'ec2:AttachNetworkInterface',
683
+ 'ec2:DetachNetworkInterface'
684
+ ],
685
+ Resource: '*'
686
+ });
687
+
688
+ // Default approach: Use AWS Discovery to find existing VPC resources
689
+ if (AppDefinition.vpc.createNew === true) {
690
+ // Option 1: Create new VPC infrastructure (explicit opt-in)
691
+ const vpcConfig = {};
692
+
693
+ if (AppDefinition.vpc.securityGroupIds) {
694
+ // User provided custom security groups
695
+ vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
696
+ } else {
697
+ // Use auto-created security group
698
+ vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
699
+ }
700
+
701
+ if (AppDefinition.vpc.subnetIds) {
702
+ // User provided custom subnets
703
+ vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
704
+ } else {
705
+ // Use auto-created private subnets
706
+ vpcConfig.subnetIds = [
707
+ { Ref: 'FriggPrivateSubnet1' },
708
+ { Ref: 'FriggPrivateSubnet2' }
709
+ ];
710
+ }
711
+
712
+ // Set VPC config for Lambda functions
713
+ definition.provider.vpc = vpcConfig;
714
+
715
+ // Add VPC infrastructure resources to CloudFormation
716
+ const vpcResources = createVPCInfrastructure(AppDefinition);
717
+ Object.assign(definition.resources.Resources, vpcResources);
718
+ } else {
719
+ // Option 2: Use AWS Discovery (default behavior)
720
+ // VPC configuration using discovered or explicitly provided resources
721
+ const vpcConfig = {
722
+ securityGroupIds: AppDefinition.vpc.securityGroupIds ||
723
+ ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}'],
724
+ subnetIds: AppDefinition.vpc.subnetIds || [
725
+ '${env:AWS_DISCOVERY_SUBNET_ID_1}',
726
+ '${env:AWS_DISCOVERY_SUBNET_ID_2}'
727
+ ]
728
+ };
729
+
730
+ // Set VPC config for Lambda functions
731
+ definition.provider.vpc = vpcConfig;
732
+
733
+ // Add NAT Gateway for Lambda internet access (required for external API calls)
734
+ // Even with existing VPC, Lambdas need guaranteed internet access for integrations
735
+ definition.resources.Resources.FriggNATGatewayEIP = {
736
+ Type: 'AWS::EC2::EIP',
737
+ Properties: {
738
+ Domain: 'vpc',
739
+ Tags: [
740
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
741
+ ]
742
+ }
743
+ };
744
+
745
+ definition.resources.Resources.FriggNATGateway = {
746
+ Type: 'AWS::EC2::NatGateway',
747
+ Properties: {
748
+ AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
749
+ SubnetId: '${env:AWS_DISCOVERY_PUBLIC_SUBNET_ID}', // Discovery finds public subnet
750
+ Tags: [
751
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
752
+ ]
753
+ }
754
+ };
755
+
756
+ // Create route table for Lambda subnets to use NAT Gateway
757
+ definition.resources.Resources.FriggLambdaRouteTable = {
758
+ Type: 'AWS::EC2::RouteTable',
759
+ Properties: {
760
+ VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
761
+ Tags: [
762
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' }
763
+ ]
764
+ }
765
+ };
766
+
767
+ definition.resources.Resources.FriggNATRoute = {
768
+ Type: 'AWS::EC2::Route',
769
+ Properties: {
770
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' },
771
+ DestinationCidrBlock: '0.0.0.0/0',
772
+ NatGatewayId: { Ref: 'FriggNATGateway' }
773
+ }
774
+ };
775
+
776
+ // Associate Lambda subnets with NAT Gateway route table
777
+ definition.resources.Resources.FriggSubnet1RouteAssociation = {
778
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
779
+ Properties: {
780
+ SubnetId: '${env:AWS_DISCOVERY_SUBNET_ID_1}',
781
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' }
782
+ }
783
+ };
784
+
785
+ definition.resources.Resources.FriggSubnet2RouteAssociation = {
786
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
787
+ Properties: {
788
+ SubnetId: '${env:AWS_DISCOVERY_SUBNET_ID_2}',
789
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' }
790
+ }
791
+ };
792
+
793
+ // Add VPC endpoints for AWS service optimization (optional but recommended)
794
+ if (AppDefinition.vpc.enableVPCEndpoints !== false) {
795
+ definition.resources.Resources.VPCEndpointS3 = {
796
+ Type: 'AWS::EC2::VPCEndpoint',
797
+ Properties: {
798
+ VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
799
+ ServiceName: 'com.amazonaws.${self:provider.region}.s3',
800
+ VpcEndpointType: 'Gateway',
801
+ RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
802
+ }
803
+ };
804
+
805
+ definition.resources.Resources.VPCEndpointDynamoDB = {
806
+ Type: 'AWS::EC2::VPCEndpoint',
807
+ Properties: {
808
+ VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
809
+ ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
810
+ VpcEndpointType: 'Gateway',
811
+ RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
812
+ }
813
+ };
814
+ }
815
+ }
816
+ }
817
+
818
+ // SSM Parameter Store Configuration based on App Definition
819
+ if (AppDefinition.ssm?.enable === true) {
820
+ // Add AWS Parameters and Secrets Lambda Extension layer
821
+ definition.provider.layers = [
822
+ 'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
823
+ ];
824
+
825
+ // Add SSM IAM permissions
826
+ definition.provider.iamRoleStatements.push({
827
+ Effect: 'Allow',
828
+ Action: [
829
+ 'ssm:GetParameter',
830
+ 'ssm:GetParameters',
831
+ 'ssm:GetParametersByPath'
832
+ ],
833
+ Resource: [
834
+ 'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'
835
+ ]
836
+ });
837
+
838
+ // Add environment variable for SSM parameter prefix
839
+ definition.provider.environment.SSM_PARAMETER_PREFIX = '/${self:service}/${self:provider.stage}';
840
+ }
841
+
356
842
  // Add integration-specific functions and resources
357
843
  for (const integration of AppDefinition.integrations) {
358
844
  const integrationName = integration.Definition.name;
@@ -419,6 +905,51 @@ const composeServerlessDefinition = (AppDefinition) => {
419
905
  definition.custom[queueReference] = queueName;
420
906
  }
421
907
 
908
+ // Check if AWS discovery environment variables are missing and error
909
+ const needsDiscovery = AppDefinition.vpc?.enable ||
910
+ AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
911
+ AppDefinition.ssm?.enable;
912
+
913
+ if (needsDiscovery && !process.env.AWS_DISCOVERY_VPC_ID) {
914
+ console.error('❌ AWS discovery environment variables not found');
915
+ console.error('');
916
+ console.error('🚨 Your AppDefinition requires AWS discovery but variables are missing:');
917
+ if (AppDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
918
+ if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.error(' ❌ KMS encryption (encryption.useDefaultKMSForFieldLevelEncryption: true)');
919
+ if (AppDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
920
+ console.error('');
921
+ console.error('💡 Run AWS discovery before building:');
922
+ console.error(' node node_modules/@friggframework/devtools/infrastructure/run-discovery.js');
923
+ console.error('');
924
+ console.error('🔧 Or use the build command which runs discovery automatically:');
925
+ console.error(' npx frigg build');
926
+ throw new Error('AWS discovery required but environment variables not found');
927
+ }
928
+
929
+ // Add websocket function if enabled
930
+ if (AppDefinition.websockets?.enable === true) {
931
+ definition.functions.defaultWebsocket = {
932
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
933
+ events: [
934
+ {
935
+ websocket: {
936
+ route: '$connect',
937
+ },
938
+ },
939
+ {
940
+ websocket: {
941
+ route: '$default',
942
+ },
943
+ },
944
+ {
945
+ websocket: {
946
+ route: '$disconnect',
947
+ },
948
+ },
949
+ ],
950
+ };
951
+ }
952
+
422
953
  // Modify handler paths to point to the correct node_modules location
423
954
  definition.functions = modifyHandlerPaths(definition.functions);
424
955