@friggframework/devtools 2.0.0--canary.406.78e2685.0 → 2.0.0--canary.398.dd443c7.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.
Files changed (34) hide show
  1. package/frigg-cli/build-command/index.js +4 -2
  2. package/frigg-cli/deploy-command/index.js +5 -2
  3. package/frigg-cli/generate-iam-command.js +115 -0
  4. package/frigg-cli/index.js +11 -1
  5. package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
  6. package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +596 -0
  7. package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
  8. package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
  9. package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
  10. package/infrastructure/README-TESTING.md +332 -0
  11. package/infrastructure/README.md +421 -0
  12. package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
  13. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  14. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  15. package/infrastructure/aws-discovery.js +568 -0
  16. package/infrastructure/aws-discovery.test.js +373 -0
  17. package/infrastructure/build-time-discovery.js +206 -0
  18. package/infrastructure/build-time-discovery.test.js +375 -0
  19. package/infrastructure/create-frigg-infrastructure.js +2 -2
  20. package/infrastructure/frigg-deployment-iam-stack.yaml +379 -0
  21. package/infrastructure/iam-generator.js +687 -0
  22. package/infrastructure/iam-generator.test.js +169 -0
  23. package/infrastructure/iam-policy-basic.json +212 -0
  24. package/infrastructure/iam-policy-full.json +282 -0
  25. package/infrastructure/integration.test.js +383 -0
  26. package/infrastructure/run-discovery.js +110 -0
  27. package/infrastructure/serverless-template.js +514 -167
  28. package/infrastructure/serverless-template.test.js +541 -0
  29. package/management-ui/dist/assets/FriggLogo-B7Xx8ZW1.svg +1 -0
  30. package/management-ui/dist/assets/index-BA21WgFa.js +1221 -0
  31. package/management-ui/dist/assets/index-CbM64Oba.js +1221 -0
  32. package/management-ui/dist/assets/index-CkvseXTC.css +1 -0
  33. package/management-ui/dist/index.html +14 -0
  34. package/package.json +9 -5
@@ -1,7 +1,26 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
+ const { AWSDiscovery } = require('./aws-discovery');
3
4
 
4
- // Function to find the actual path to node_modules
5
+ /**
6
+ * Check if AWS discovery should run based on AppDefinition
7
+ * @param {Object} AppDefinition - Application definition
8
+ * @returns {boolean} True if discovery should run
9
+ */
10
+ const shouldRunDiscovery = (AppDefinition) => {
11
+ return AppDefinition.vpc?.enable === true ||
12
+ AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true ||
13
+ AppDefinition.ssm?.enable === true;
14
+ };
15
+
16
+ /**
17
+ * Find the actual path to node_modules directory
18
+ * Tries multiple methods to locate node_modules:
19
+ * 1. Traversing up from current directory
20
+ * 2. Using npm root command
21
+ * 3. Looking for package.json and adjacent node_modules
22
+ * @returns {string} Path to node_modules directory
23
+ */
5
24
  const findNodeModulesPath = () => {
6
25
  try {
7
26
  // Method 1: Try to find node_modules by traversing up from current directory
@@ -75,7 +94,18 @@ const findNodeModulesPath = () => {
75
94
  }
76
95
  };
77
96
 
78
- // Function to modify handler paths to point to the correct node_modules
97
+ /**
98
+ * Modify handler paths to point to the correct node_modules location
99
+ * Only modifies paths when running in offline mode
100
+ * @param {Object} functions - Serverless functions configuration object
101
+ * @returns {Object} Modified functions object with updated handler paths
102
+ */
103
+ /**
104
+ * Modify handler paths to point to the correct node_modules location
105
+ * Only modifies paths when running in offline mode
106
+ * @param {Object} functions - Serverless functions configuration object
107
+ * @returns {Object} Modified functions object with updated handler paths
108
+ */
79
109
  const modifyHandlerPaths = (functions) => {
80
110
  // Check if we're running in offline mode
81
111
  const isOffline = process.argv.includes('offline');
@@ -94,7 +124,10 @@ const modifyHandlerPaths = (functions) => {
94
124
  const functionDef = modifiedFunctions[functionName];
95
125
  if (functionDef?.handler?.includes('node_modules/')) {
96
126
  // Replace node_modules/ with the actual path to node_modules/
97
- functionDef.handler = functionDef.handler.replace('node_modules/', '../node_modules/');
127
+ const relativePath = path.relative(process.cwd(), nodeModulesPath);
128
+ functionDef.handler = functionDef.handler.replace('node_modules/', `${relativePath}/`);
129
+ const relativePath = path.relative(process.cwd(), nodeModulesPath);
130
+ functionDef.handler = functionDef.handler.replace('node_modules/', `${relativePath}/`);
98
131
  console.log(`Updated handler for ${functionName}: ${functionDef.handler}`);
99
132
  }
100
133
  }
@@ -102,7 +135,22 @@ const modifyHandlerPaths = (functions) => {
102
135
  return modifiedFunctions;
103
136
  };
104
137
 
105
- // Helper function to create VPC infrastructure resources
138
+ /**
139
+ * Create VPC infrastructure resources for CloudFormation
140
+ * Creates VPC, subnets, NAT gateway, route tables, and security groups
141
+ * @param {Object} AppDefinition - Application definition object
142
+ * @param {Object} AppDefinition.vpc - VPC configuration
143
+ * @param {string} [AppDefinition.vpc.cidrBlock='10.0.0.0/16'] - CIDR block for VPC
144
+ * @returns {Object} CloudFormation resources for VPC infrastructure
145
+ */
146
+ /**
147
+ * Create VPC infrastructure resources for CloudFormation
148
+ * Creates VPC, subnets, NAT gateway, route tables, and security groups
149
+ * @param {Object} AppDefinition - Application definition object
150
+ * @param {Object} AppDefinition.vpc - VPC configuration
151
+ * @param {string} [AppDefinition.vpc.cidrBlock='10.0.0.0/16'] - CIDR block for VPC
152
+ * @returns {Object} CloudFormation resources for VPC infrastructure
153
+ */
106
154
  const createVPCInfrastructure = (AppDefinition) => {
107
155
  const vpcResources = {
108
156
  // VPC
@@ -292,13 +340,6 @@ const createVPCInfrastructure = (AppDefinition) => {
292
340
  CidrIp: '0.0.0.0/0',
293
341
  Description: 'HTTP outbound'
294
342
  },
295
- {
296
- IpProtocol: 'tcp',
297
- FromPort: 27017,
298
- ToPort: 27017,
299
- CidrIp: '0.0.0.0/0',
300
- Description: 'MongoDB Atlas TLS outbound'
301
- },
302
343
  {
303
344
  IpProtocol: 'tcp',
304
345
  FromPort: 53,
@@ -412,7 +453,57 @@ const createVPCInfrastructure = (AppDefinition) => {
412
453
  return vpcResources;
413
454
  };
414
455
 
415
- const composeServerlessDefinition = (AppDefinition) => {
456
+ /**
457
+ * Compose a complete serverless framework configuration from app definition
458
+ * @param {Object} AppDefinition - Application definition object
459
+ * @param {string} [AppDefinition.name] - Application name
460
+ * @param {string} [AppDefinition.provider='aws'] - Cloud provider
461
+ * @param {Array} AppDefinition.integrations - Array of integration definitions
462
+ * @param {Object} [AppDefinition.vpc] - VPC configuration
463
+ * @param {Object} [AppDefinition.encryption] - KMS encryption configuration
464
+ * @param {Object} [AppDefinition.ssm] - SSM parameter store configuration
465
+ * @param {Object} [AppDefinition.websockets] - WebSocket configuration
466
+ * @param {boolean} [AppDefinition.websockets.enable=false] - Enable WebSocket support for live update streaming
467
+ * @returns {Object} Complete serverless framework configuration
468
+ */
469
+ const composeServerlessDefinition = async (AppDefinition) => {
470
+ // Store discovered resources
471
+ let discoveredResources = {};
472
+
473
+ // Run AWS discovery if needed
474
+ if (shouldRunDiscovery(AppDefinition)) {
475
+ console.log('🔍 Running AWS resource discovery for serverless template...');
476
+ try {
477
+ const region = process.env.AWS_REGION || 'us-east-1';
478
+ const discovery = new AWSDiscovery(region);
479
+
480
+ const config = {
481
+ vpc: AppDefinition.vpc || {},
482
+ encryption: AppDefinition.encryption || {},
483
+ ssm: AppDefinition.ssm || {}
484
+ };
485
+
486
+ discoveredResources = await discovery.discoverResources(config);
487
+
488
+ console.log('✅ AWS discovery completed successfully!');
489
+ if (discoveredResources.defaultVpcId) {
490
+ console.log(` VPC: ${discoveredResources.defaultVpcId}`);
491
+ }
492
+ if (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2) {
493
+ console.log(` Subnets: ${discoveredResources.privateSubnetId1}, ${discoveredResources.privateSubnetId2}`);
494
+ }
495
+ if (discoveredResources.defaultSecurityGroupId) {
496
+ console.log(` Security Group: ${discoveredResources.defaultSecurityGroupId}`);
497
+ }
498
+ if (discoveredResources.defaultKmsKeyId) {
499
+ console.log(` KMS Key: ${discoveredResources.defaultKmsKeyId}`);
500
+ }
501
+ } catch (error) {
502
+ console.error('❌ AWS discovery failed:', error.message);
503
+ throw new Error(`AWS discovery failed: ${error.message}`);
504
+ }
505
+ }
506
+
416
507
  const definition = {
417
508
  frameworkVersion: '>=3.17.0',
418
509
  service: AppDefinition.name || 'create-frigg-app',
@@ -425,11 +516,28 @@ const composeServerlessDefinition = (AppDefinition) => {
425
516
  name: AppDefinition.provider || 'aws',
426
517
  runtime: 'nodejs20.x',
427
518
  timeout: 30,
428
- region: 'us-east-1',
429
- stage: '${opt:stage, "dev"}',
519
+ region: process.env.AWS_REGION || 'us-east-1',
520
+ region: process.env.AWS_REGION || 'us-east-1',
521
+ stage: '${opt:stage}',
430
522
  environment: {
431
523
  STAGE: '${opt:stage, "dev"}',
432
524
  AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1,
525
+ // Add discovered resources to environment if available
526
+ ...(discoveredResources.defaultVpcId && { AWS_DISCOVERY_VPC_ID: discoveredResources.defaultVpcId }),
527
+ ...(discoveredResources.defaultSecurityGroupId && { AWS_DISCOVERY_SECURITY_GROUP_ID: discoveredResources.defaultSecurityGroupId }),
528
+ ...(discoveredResources.privateSubnetId1 && { AWS_DISCOVERY_SUBNET_ID_1: discoveredResources.privateSubnetId1 }),
529
+ ...(discoveredResources.privateSubnetId2 && { AWS_DISCOVERY_SUBNET_ID_2: discoveredResources.privateSubnetId2 }),
530
+ ...(discoveredResources.publicSubnetId && { AWS_DISCOVERY_PUBLIC_SUBNET_ID: discoveredResources.publicSubnetId }),
531
+ ...(discoveredResources.defaultRouteTableId && { AWS_DISCOVERY_ROUTE_TABLE_ID: discoveredResources.defaultRouteTableId }),
532
+ ...(discoveredResources.defaultKmsKeyId && { AWS_DISCOVERY_KMS_KEY_ID: discoveredResources.defaultKmsKeyId }),
533
+ // Add discovered resources to environment if available
534
+ ...(discoveredResources.defaultVpcId && { AWS_DISCOVERY_VPC_ID: discoveredResources.defaultVpcId }),
535
+ ...(discoveredResources.defaultSecurityGroupId && { AWS_DISCOVERY_SECURITY_GROUP_ID: discoveredResources.defaultSecurityGroupId }),
536
+ ...(discoveredResources.privateSubnetId1 && { AWS_DISCOVERY_SUBNET_ID_1: discoveredResources.privateSubnetId1 }),
537
+ ...(discoveredResources.privateSubnetId2 && { AWS_DISCOVERY_SUBNET_ID_2: discoveredResources.privateSubnetId2 }),
538
+ ...(discoveredResources.publicSubnetId && { AWS_DISCOVERY_PUBLIC_SUBNET_ID: discoveredResources.publicSubnetId }),
539
+ ...(discoveredResources.defaultRouteTableId && { AWS_DISCOVERY_ROUTE_TABLE_ID: discoveredResources.defaultRouteTableId }),
540
+ ...(discoveredResources.defaultKmsKeyId && { AWS_DISCOVERY_KMS_KEY_ID: discoveredResources.defaultKmsKeyId }),
433
541
  },
434
542
  iamRoleStatements: [
435
543
  {
@@ -491,7 +599,8 @@ const composeServerlessDefinition = (AppDefinition) => {
491
599
  autoCreate: false,
492
600
  apiVersion: '2012-11-05',
493
601
  endpoint: 'http://localhost:4566',
494
- region: 'us-east-1',
602
+ region: process.env.AWS_REGION || 'us-east-1',
603
+ region: process.env.AWS_REGION || 'us-east-1',
495
604
  accessKeyId: 'root',
496
605
  secretAccessKey: 'root',
497
606
  skipCacheInvalidation: false,
@@ -501,26 +610,6 @@ const composeServerlessDefinition = (AppDefinition) => {
501
610
  },
502
611
  },
503
612
  functions: {
504
- defaultWebsocket: {
505
- handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
506
- events: [
507
- {
508
- websocket: {
509
- route: '$connect',
510
- },
511
- },
512
- {
513
- websocket: {
514
- route: '$default',
515
- },
516
- },
517
- {
518
- websocket: {
519
- route: '$disconnect',
520
- },
521
- },
522
- ],
523
- },
524
613
  auth: {
525
614
  handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
526
615
  events: [
@@ -579,7 +668,8 @@ const composeServerlessDefinition = (AppDefinition) => {
579
668
  Type: 'AWS::SQS::Queue',
580
669
  Properties: {
581
670
  QueueName:
582
- 'internal-error-queue-${self:provider.stage}',
671
+ '${self:service}-internal-error-queue-${self:provider.stage}',
672
+ '${self:service}-internal-error-queue-${self:provider.stage}',
583
673
  MessageRetentionPeriod: 300,
584
674
  },
585
675
  },
@@ -659,62 +749,7 @@ const composeServerlessDefinition = (AppDefinition) => {
659
749
  },
660
750
  };
661
751
 
662
- // Configure BASE_URL based on custom domain or API Gateway
663
- if (process.env.CUSTOM_DOMAIN) {
664
-
665
- // Configure custom domain
666
- definition.custom.customDomain = {
667
- domainName: process.env.CUSTOM_DOMAIN,
668
- basePath: process.env.CUSTOM_BASE_PATH || '',
669
- stage: '${opt:stage, "dev"}',
670
- createRoute53Record: process.env.CREATE_ROUTE53_RECORD !== 'false', // Default true
671
- certificateName: process.env.CERTIFICATE_NAME || process.env.CUSTOM_DOMAIN,
672
- endpointType: process.env.ENDPOINT_TYPE || 'edge', // edge, regional, or private
673
- securityPolicy: process.env.SECURITY_POLICY || 'tls_1_2',
674
- apiType: 'http',
675
- autoDomain: process.env.AUTO_DOMAIN === 'true', // Auto create domain if it doesn't exist
676
- };
677
-
678
- // Set BASE_URL to custom domain
679
- definition.provider.environment.BASE_URL = `https://${process.env.CUSTOM_DOMAIN}`;
680
- } else {
681
- // Default BASE_URL using API Gateway generated URL
682
- // For HTTP API, don't include stage as it uses $default behavior
683
- definition.provider.environment.BASE_URL = {
684
- 'Fn::GetAtt': ['HttpApi', 'ApiEndpoint']
685
- };
686
- }
687
-
688
- // REDIRECT_PATH is required for OAuth integrations
689
- if (!process.env.REDIRECT_PATH) {
690
- throw new Error(
691
- 'REDIRECT_PATH environment variable is required. ' +
692
- 'Please set REDIRECT_PATH in your .env file (e.g., REDIRECT_PATH=/oauth/callback)'
693
- );
694
- }
695
-
696
- // Set REDIRECT_URI based on domain configuration
697
- if (process.env.CUSTOM_DOMAIN) {
698
- definition.provider.environment.REDIRECT_URI = `https://${process.env.CUSTOM_DOMAIN}${process.env.REDIRECT_PATH}`;
699
- } else {
700
- definition.provider.environment.REDIRECT_URI = {
701
- 'Fn::Join': [
702
- '',
703
- [
704
- { 'Fn::GetAtt': ['HttpApi', 'ApiEndpoint'] },
705
- process.env.REDIRECT_PATH,
706
- ],
707
- ],
708
- };
709
- }
710
-
711
- // Add REDIRECT_URI to CloudFormation outputs
712
- definition.resources.Outputs = {
713
- RedirectURI: {
714
- Description: 'OAuth Redirect URI to register with providers',
715
- Value: definition.provider.environment.REDIRECT_URI,
716
- },
717
- };
752
+
718
753
 
719
754
  // KMS Configuration based on App Definition
720
755
  if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
@@ -747,36 +782,17 @@ const composeServerlessDefinition = (AppDefinition) => {
747
782
  definition.provider.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
748
783
 
749
784
  definition.plugins.push('serverless-kms-grants');
750
- definition.custom.kmsGrants = { kmsKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] } };
785
+
786
+ // Configure KMS grants with discovered default key
787
+ // Configure KMS grants with discovered default key
788
+ definition.custom.kmsGrants = {
789
+ kmsKeyId: discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}'
790
+ kmsKeyId: discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}'
791
+ };
751
792
  }
752
793
 
753
794
  // VPC Configuration based on App Definition
754
795
  if (AppDefinition.vpc?.enable === true) {
755
- // Create VPC config from App Definition or use auto-created resources
756
- const vpcConfig = {};
757
-
758
- if (AppDefinition.vpc.securityGroupIds) {
759
- // User provided custom security groups
760
- vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
761
- } else {
762
- // Use auto-created security group
763
- vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
764
- }
765
-
766
- if (AppDefinition.vpc.subnetIds) {
767
- // User provided custom subnets
768
- vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
769
- } else {
770
- // Use auto-created private subnets
771
- vpcConfig.subnetIds = [
772
- { Ref: 'FriggPrivateSubnet1' },
773
- { Ref: 'FriggPrivateSubnet2' }
774
- ];
775
- }
776
-
777
- // Set VPC config for Lambda functions
778
- definition.provider.vpc = vpcConfig;
779
-
780
796
  // Add VPC-related IAM permissions
781
797
  definition.provider.iamRoleStatements.push({
782
798
  Effect: 'Allow',
@@ -790,74 +806,405 @@ const composeServerlessDefinition = (AppDefinition) => {
790
806
  Resource: '*'
791
807
  });
792
808
 
793
- // Add VPC infrastructure resources to CloudFormation
794
- const vpcResources = createVPCInfrastructure(AppDefinition);
795
- Object.assign(definition.resources.Resources, vpcResources);
809
+ // Default approach: Use AWS Discovery to find existing VPC resources
810
+ if (AppDefinition.vpc.createNew === true) {
811
+ // Option 1: Create new VPC infrastructure (explicit opt-in)
812
+ const vpcConfig = {};
813
+ // Add VPC-related IAM permissions
814
+ definition.provider.iamRoleStatements.push({
815
+ Effect: 'Allow',
816
+ Action: [
817
+ 'ec2:CreateNetworkInterface',
818
+ 'ec2:DescribeNetworkInterfaces',
819
+ 'ec2:DeleteNetworkInterface',
820
+ 'ec2:AttachNetworkInterface',
821
+ 'ec2:DetachNetworkInterface'
822
+ ],
823
+ Resource: '*'
824
+ });
825
+
826
+ // Default approach: Use AWS Discovery to find existing VPC resources
827
+ if (AppDefinition.vpc.createNew === true) {
828
+ // Option 1: Create new VPC infrastructure (explicit opt-in)
829
+ const vpcConfig = {};
830
+
831
+ if (AppDefinition.vpc.securityGroupIds) {
832
+ // User provided custom security groups
833
+ vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
834
+ } else {
835
+ // Use auto-created security group
836
+ vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
837
+ }
838
+ if (AppDefinition.vpc.securityGroupIds) {
839
+ // User provided custom security groups
840
+ vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
841
+ } else {
842
+ // Use auto-created security group
843
+ vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
844
+ }
845
+
846
+ if (AppDefinition.vpc.subnetIds) {
847
+ // User provided custom subnets
848
+ vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
849
+ } else {
850
+ // Use auto-created private subnets
851
+ vpcConfig.subnetIds = [
852
+ { Ref: 'FriggPrivateSubnet1' },
853
+ { Ref: 'FriggPrivateSubnet2' }
854
+ ];
855
+ }
856
+ if (AppDefinition.vpc.subnetIds) {
857
+ // User provided custom subnets
858
+ vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
859
+ } else {
860
+ // Use auto-created private subnets
861
+ vpcConfig.subnetIds = [
862
+ { Ref: 'FriggPrivateSubnet1' },
863
+ { Ref: 'FriggPrivateSubnet2' }
864
+ ];
865
+ }
866
+
867
+ // Set VPC config for Lambda functions
868
+ definition.provider.vpc = vpcConfig;
869
+ // Set VPC config for Lambda functions
870
+ definition.provider.vpc = vpcConfig;
871
+
872
+ // Add VPC infrastructure resources to CloudFormation
873
+ const vpcResources = createVPCInfrastructure(AppDefinition);
874
+ Object.assign(definition.resources.Resources, vpcResources);
875
+ } else {
876
+ // Option 2: Use AWS Discovery (default behavior)
877
+ // VPC configuration using discovered or explicitly provided resources
878
+ const vpcConfig = {
879
+ securityGroupIds: AppDefinition.vpc.securityGroupIds ||
880
+ (discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []),
881
+ subnetIds: AppDefinition.vpc.subnetIds ||
882
+ (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2 ?
883
+ [discoveredResources.privateSubnetId1, discoveredResources.privateSubnetId2] :
884
+ [])
885
+ };
886
+
887
+ // Set VPC config for Lambda functions only if we have valid subnet IDs
888
+ if (vpcConfig.subnetIds.length >= 2 && vpcConfig.securityGroupIds.length > 0) {
889
+ definition.provider.vpc = vpcConfig;
890
+
891
+ // Check if we have an existing NAT Gateway to use
892
+ if (!discoveredResources.existingNatGatewayId) {
893
+ // No existing NAT Gateway, create new resources
894
+
895
+ // Only create EIP if we don't have an existing one available
896
+ if (!discoveredResources.existingElasticIpAllocationId) {
897
+ definition.resources.Resources.FriggNATGatewayEIP = {
898
+ Type: 'AWS::EC2::EIP',
899
+ Properties: {
900
+ Domain: 'vpc',
901
+ Tags: [
902
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
903
+ ]
904
+ }
905
+ };
906
+ }
907
+
908
+ definition.resources.Resources.FriggNATGateway = {
909
+ Type: 'AWS::EC2::NatGateway',
910
+ Properties: {
911
+ AllocationId: discoveredResources.existingElasticIpAllocationId ||
912
+ { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
913
+ SubnetId: discoveredResources.publicSubnetId || discoveredResources.privateSubnetId1, // Use first discovered subnet if no public subnet found
914
+ Tags: [
915
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
916
+ ]
917
+ }
918
+ };
919
+ }
920
+
921
+ // Create route table for Lambda subnets to use NAT Gateway
922
+ definition.resources.Resources.FriggLambdaRouteTable = {
923
+ Type: 'AWS::EC2::RouteTable',
924
+ Properties: {
925
+ VpcId: discoveredResources.defaultVpcId || { Ref: 'FriggVPC' },
926
+ Tags: [
927
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' }
928
+ ]
929
+ }
930
+ };
931
+
932
+ definition.resources.Resources.FriggNATRoute = {
933
+ Type: 'AWS::EC2::Route',
934
+ Properties: {
935
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' },
936
+ DestinationCidrBlock: '0.0.0.0/0',
937
+ NatGatewayId: discoveredResources.existingNatGatewayId || { Ref: 'FriggNATGateway' }
938
+ }
939
+ };
940
+
941
+ // Associate Lambda subnets with NAT Gateway route table
942
+ definition.resources.Resources.FriggSubnet1RouteAssociation = {
943
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
944
+ Properties: {
945
+ SubnetId: vpcConfig.subnetIds[0],
946
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' }
947
+ }
948
+ };
949
+
950
+ definition.resources.Resources.FriggSubnet2RouteAssociation = {
951
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
952
+ Properties: {
953
+ SubnetId: vpcConfig.subnetIds[1],
954
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' }
955
+ }
956
+ };
957
+
958
+ // Add VPC endpoints for AWS service optimization (optional but recommended)
959
+ if (AppDefinition.vpc.enableVPCEndpoints !== false) {
960
+ definition.resources.Resources.VPCEndpointS3 = {
961
+ Type: 'AWS::EC2::VPCEndpoint',
962
+ Properties: {
963
+ VpcId: discoveredResources.defaultVpcId,
964
+ ServiceName: 'com.amazonaws.${self:provider.region}.s3',
965
+ VpcEndpointType: 'Gateway',
966
+ RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
967
+ }
968
+ };
969
+
970
+ definition.resources.Resources.VPCEndpointDynamoDB = {
971
+ Type: 'AWS::EC2::VPCEndpoint',
972
+ Properties: {
973
+ VpcId: discoveredResources.defaultVpcId,
974
+ ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
975
+ VpcEndpointType: 'Gateway',
976
+ RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
977
+ }
978
+ };
979
+ }
980
+ }
981
+ }
982
+ }
983
+
984
+ // SSM Parameter Store Configuration based on App Definition
985
+ if (AppDefinition.ssm?.enable === true) {
986
+ // Add AWS Parameters and Secrets Lambda Extension layer
987
+ definition.provider.layers = [
988
+ 'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
989
+ ];
990
+
991
+ // Add SSM IAM permissions
992
+ definition.provider.iamRoleStatements.push({
993
+ Effect: 'Allow',
994
+ Action: [
995
+ 'ssm:GetParameter',
996
+ 'ssm:GetParameters',
997
+ 'ssm:GetParametersByPath'
998
+ ],
999
+ Resource: [
1000
+ 'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'
1001
+ ]
1002
+ 'ssm:GetParameter',
1003
+ 'ssm:GetParameters',
1004
+ 'ssm:GetParametersByPath'
1005
+ ],
1006
+ Resource: [
1007
+ 'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'
1008
+ ]
1009
+ });
1010
+
1011
+ // Add environment variable for SSM parameter prefix
1012
+ definition.provider.environment.SSM_PARAMETER_PREFIX = '/${self:service}/${self:provider.stage}';
1013
+ // Add environment variable for SSM parameter prefix
1014
+ definition.provider.environment.SSM_PARAMETER_PREFIX = '/${self:service}/${self:provider.stage}';
796
1015
  }
797
1016
 
798
- // Add integration-specific functions and resources
1017
+ // Add integration-specific functions and resources
1018
+ if (AppDefinition.integrations && Array.isArray(AppDefinition.integrations)) {
799
1019
  for (const integration of AppDefinition.integrations) {
1020
+ if (!integration || !integration.Definition || !integration.Definition.name) {
1021
+ throw new Error('Invalid integration: missing Definition or name');
1022
+ }
800
1023
  const integrationName = integration.Definition.name;
1024
+ if (AppDefinition.integrations && Array.isArray(AppDefinition.integrations)) {
1025
+ for (const integration of AppDefinition.integrations) {
1026
+ if (!integration || !integration.Definition || !integration.Definition.name) {
1027
+ throw new Error('Invalid integration: missing Definition or name');
1028
+ }
1029
+ const integrationName = integration.Definition.name;
1030
+
1031
+ // Add function for the integration
1032
+ definition.functions[integrationName] = {
1033
+ handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
1034
+ events: [
1035
+ {
1036
+ httpApi: {
1037
+ path: `/api/${integrationName}-integration/{proxy+}`,
1038
+ method: 'ANY',
1039
+ },
1040
+ },
1041
+ ],
1042
+ };
1043
+
1044
+ // Add SQS Queue for the integration
1045
+ const queueReference = `${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
1046
+ }Queue`;
1047
+ const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
1048
+ definition.resources.Resources[queueReference] = {
1049
+ Type: 'AWS::SQS::Queue',
1050
+ Properties: {
1051
+ QueueName: `\${self:custom.${queueReference}}`,
1052
+ MessageRetentionPeriod: 60,
1053
+ VisibilityTimeout: 1800, // 30 minutes
1054
+ RedrivePolicy: {
1055
+ maxReceiveCount: 1,
1056
+ deadLetterTargetArn: {
1057
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
1058
+ },
1059
+ },
1060
+ },
1061
+ };
1062
+
1063
+ // Add Queue Worker for the integration
1064
+ const queueWorkerName = `${integrationName}QueueWorker`;
1065
+ definition.functions[queueWorkerName] = {
1066
+ handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
1067
+ reservedConcurrency: 5,
1068
+ events: [
1069
+ {
1070
+ sqs: {
1071
+ arn: {
1072
+ 'Fn::GetAtt': [queueReference, 'Arn'],
1073
+ },
1074
+ batchSize: 1,
1075
+ },
1076
+ },
1077
+ ],
1078
+ timeout: 600,
1079
+ };
1080
+
1081
+ // Add Queue URL for the integration to the ENVironment variables
1082
+ definition.provider.environment = {
1083
+ ...definition.provider.environment,
1084
+ [`${integrationName.toUpperCase()}_QUEUE_URL`]: {
1085
+ Ref: queueReference,
1086
+ },
1087
+ };
1088
+ // Add Queue URL for the integration to the ENVironment variables
1089
+ definition.provider.environment = {
1090
+ ...definition.provider.environment,
1091
+ [`${integrationName.toUpperCase()}_QUEUE_URL`]: {
1092
+ Ref: queueReference,
1093
+ },
1094
+ };
1095
+
1096
+ definition.custom[queueReference] = queueName;
1097
+ }
1098
+ }
1099
+
1100
+ // Discovery has already run successfully at this point if needed
1101
+ // The discoveredResources object contains all the necessary AWS resources
1102
+
1103
+ // Add websocket function if enabled
1104
+ if (AppDefinition.websockets?.enable === true) {
1105
+ definition.functions.defaultWebsocket = {
1106
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
1107
+ events: [
1108
+ {
1109
+ websocket: {
1110
+ route: '$connect',
1111
+ },
1112
+ },
1113
+ {
1114
+ websocket: {
1115
+ route: '$default',
1116
+ },
1117
+ },
1118
+ {
1119
+ websocket: {
1120
+ route: '$disconnect',
1121
+ },
1122
+ },
1123
+ ],
1124
+ };
1125
+ }
801
1126
 
802
- // Add function for the integration
803
- definition.functions[integrationName] = {
804
- handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
1127
+ // Discovery has already run successfully at this point if needed
1128
+ // The discoveredResources object contains all the necessary AWS resources
1129
+
1130
+ // Add websocket function if enabled
1131
+ if (AppDefinition.websockets?.enable === true) {
1132
+ definition.functions.defaultWebsocket = {
1133
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
1134
+ events: [
1135
+ {
1136
+ websocket: {
1137
+ route: '$connect',
1138
+ },
1139
+ },
1140
+ {
1141
+ websocket: {
1142
+ route: '$default',
1143
+ },
1144
+ },
1145
+ {
1146
+ websocket: {
1147
+ route: '$disconnect',
1148
+ },
1149
+ },
1150
+ ],
1151
+ };
1152
+ definition.custom[queueReference] = queueName;
1153
+ }
1154
+ }
1155
+
1156
+ // Discovery has already run successfully at this point if needed
1157
+ // The discoveredResources object contains all the necessary AWS resources
1158
+
1159
+ // Add websocket function if enabled
1160
+ if (AppDefinition.websockets?.enable === true) {
1161
+ definition.functions.defaultWebsocket = {
1162
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
805
1163
  events: [
806
1164
  {
807
- httpApi: {
808
- path: `/api/${integrationName}-integration/{proxy+}`,
809
- method: 'ANY',
1165
+ websocket: {
1166
+ route: '$connect',
810
1167
  },
811
1168
  },
812
- ],
813
- };
814
-
815
- // Add SQS Queue for the integration
816
- const queueReference = `${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
817
- }Queue`;
818
- const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
819
- definition.resources.Resources[queueReference] = {
820
- Type: 'AWS::SQS::Queue',
821
- Properties: {
822
- QueueName: `\${self:custom.${queueReference}}`,
823
- MessageRetentionPeriod: 60,
824
- VisibilityTimeout: 1800, // 30 minutes
825
- RedrivePolicy: {
826
- maxReceiveCount: 1,
827
- deadLetterTargetArn: {
828
- 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
1169
+ {
1170
+ websocket: {
1171
+ route: '$default',
829
1172
  },
830
1173
  },
831
- },
1174
+ {
1175
+ websocket: {
1176
+ route: '$disconnect',
1177
+ },
1178
+ },
1179
+ ],
832
1180
  };
1181
+ }
1182
+
1183
+ // Discovery has already run successfully at this point if needed
1184
+ // The discoveredResources object contains all the necessary AWS resources
833
1185
 
834
- // Add Queue Worker for the integration
835
- const queueWorkerName = `${integrationName}QueueWorker`;
836
- definition.functions[queueWorkerName] = {
837
- handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
838
- reservedConcurrency: 5,
1186
+ // Add websocket function if enabled
1187
+ if (AppDefinition.websockets?.enable === true) {
1188
+ definition.functions.defaultWebsocket = {
1189
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
839
1190
  events: [
840
1191
  {
841
- sqs: {
842
- arn: {
843
- 'Fn::GetAtt': [queueReference, 'Arn'],
844
- },
845
- batchSize: 1,
1192
+ websocket: {
1193
+ route: '$connect',
1194
+ },
1195
+ },
1196
+ {
1197
+ websocket: {
1198
+ route: '$default',
1199
+ },
1200
+ },
1201
+ {
1202
+ websocket: {
1203
+ route: '$disconnect',
846
1204
  },
847
1205
  },
848
1206
  ],
849
- timeout: 600,
850
1207
  };
851
-
852
- // Add Queue URL for the integration to the ENVironment variables
853
- definition.provider.environment = {
854
- ...definition.provider.environment,
855
- [`${integrationName.toUpperCase()}_QUEUE_URL`]: {
856
- Ref: queueReference,
857
- },
858
- };
859
-
860
- definition.custom[queueReference] = queueName;
861
1208
  }
862
1209
 
863
1210
  // Modify handler paths to point to the correct node_modules location