@friggframework/devtools 2.0.0-next.23 → 2.0.0-next.25

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.
@@ -7,7 +7,7 @@ const findNodeModulesPath = () => {
7
7
  // Method 1: Try to find node_modules by traversing up from current directory
8
8
  let currentDir = process.cwd();
9
9
  let nodeModulesPath = null;
10
-
10
+
11
11
  // Traverse up to 5 levels to find node_modules
12
12
  for (let i = 0; i < 5; i++) {
13
13
  const potentialPath = path.join(currentDir, 'node_modules');
@@ -24,7 +24,7 @@ const findNodeModulesPath = () => {
24
24
  }
25
25
  currentDir = parentDir;
26
26
  }
27
-
27
+
28
28
  // Method 2: If method 1 fails, try using npm root command
29
29
  if (!nodeModulesPath) {
30
30
  try {
@@ -39,7 +39,7 @@ const findNodeModulesPath = () => {
39
39
  console.error('Error executing npm root:', npmError);
40
40
  }
41
41
  }
42
-
42
+
43
43
  // Method 3: If all else fails, check for a package.json and assume node_modules is adjacent
44
44
  if (!nodeModulesPath) {
45
45
  currentDir = process.cwd();
@@ -62,11 +62,11 @@ const findNodeModulesPath = () => {
62
62
  currentDir = parentDir;
63
63
  }
64
64
  }
65
-
65
+
66
66
  if (nodeModulesPath) {
67
67
  return nodeModulesPath;
68
68
  }
69
-
69
+
70
70
  console.warn('Could not find node_modules path, falling back to default');
71
71
  return path.resolve(process.cwd(), '../node_modules');
72
72
  } catch (error) {
@@ -80,7 +80,7 @@ const modifyHandlerPaths = (functions) => {
80
80
  // Check if we're running in offline mode
81
81
  const isOffline = process.argv.includes('offline');
82
82
  console.log('isOffline', isOffline);
83
-
83
+
84
84
  if (!isOffline) {
85
85
  console.log('Not in offline mode, skipping handler path modification');
86
86
  return functions;
@@ -88,7 +88,7 @@ const modifyHandlerPaths = (functions) => {
88
88
 
89
89
  const nodeModulesPath = findNodeModulesPath();
90
90
  const modifiedFunctions = { ...functions };
91
-
91
+
92
92
  for (const functionName of Object.keys(modifiedFunctions)) {
93
93
  console.log('functionName', functionName);
94
94
  const functionDef = modifiedFunctions[functionName];
@@ -98,10 +98,313 @@ const modifyHandlerPaths = (functions) => {
98
98
  console.log(`Updated handler for ${functionName}: ${functionDef.handler}`);
99
99
  }
100
100
  }
101
-
101
+
102
102
  return modifiedFunctions;
103
103
  };
104
104
 
105
+ // Helper function to create VPC infrastructure resources
106
+ const createVPCInfrastructure = (AppDefinition) => {
107
+ const vpcResources = {
108
+ // VPC
109
+ FriggVPC: {
110
+ Type: 'AWS::EC2::VPC',
111
+ Properties: {
112
+ CidrBlock: AppDefinition.vpc.cidrBlock || '10.0.0.0/16',
113
+ EnableDnsHostnames: true,
114
+ EnableDnsSupport: true,
115
+ Tags: [
116
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc' }
117
+ ]
118
+ }
119
+ },
120
+
121
+ // Internet Gateway
122
+ FriggInternetGateway: {
123
+ Type: 'AWS::EC2::InternetGateway',
124
+ Properties: {
125
+ Tags: [
126
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' }
127
+ ]
128
+ }
129
+ },
130
+
131
+ // Attach Internet Gateway to VPC
132
+ FriggVPCGatewayAttachment: {
133
+ Type: 'AWS::EC2::VPCGatewayAttachment',
134
+ Properties: {
135
+ VpcId: { Ref: 'FriggVPC' },
136
+ InternetGatewayId: { Ref: 'FriggInternetGateway' }
137
+ }
138
+ },
139
+
140
+ // Public Subnet for NAT Gateway
141
+ FriggPublicSubnet: {
142
+ Type: 'AWS::EC2::Subnet',
143
+ Properties: {
144
+ VpcId: { Ref: 'FriggVPC' },
145
+ CidrBlock: '10.0.1.0/24',
146
+ AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
147
+ MapPublicIpOnLaunch: true,
148
+ Tags: [
149
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-subnet' }
150
+ ]
151
+ }
152
+ },
153
+
154
+ // Private Subnet 1 for Lambda
155
+ FriggPrivateSubnet1: {
156
+ Type: 'AWS::EC2::Subnet',
157
+ Properties: {
158
+ VpcId: { Ref: 'FriggVPC' },
159
+ CidrBlock: '10.0.2.0/24',
160
+ AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
161
+ Tags: [
162
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-1' }
163
+ ]
164
+ }
165
+ },
166
+
167
+ // Private Subnet 2 for Lambda (different AZ for redundancy)
168
+ FriggPrivateSubnet2: {
169
+ Type: 'AWS::EC2::Subnet',
170
+ Properties: {
171
+ VpcId: { Ref: 'FriggVPC' },
172
+ CidrBlock: '10.0.3.0/24',
173
+ AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
174
+ Tags: [
175
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-2' }
176
+ ]
177
+ }
178
+ },
179
+
180
+ // Elastic IP for NAT Gateway
181
+ FriggNATGatewayEIP: {
182
+ Type: 'AWS::EC2::EIP',
183
+ Properties: {
184
+ Domain: 'vpc',
185
+ Tags: [
186
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
187
+ ]
188
+ },
189
+ DependsOn: 'FriggVPCGatewayAttachment'
190
+ },
191
+
192
+ // NAT Gateway for private subnet internet access
193
+ FriggNATGateway: {
194
+ Type: 'AWS::EC2::NatGateway',
195
+ Properties: {
196
+ AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
197
+ SubnetId: { Ref: 'FriggPublicSubnet' },
198
+ Tags: [
199
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
200
+ ]
201
+ }
202
+ },
203
+
204
+ // Public Route Table
205
+ FriggPublicRouteTable: {
206
+ Type: 'AWS::EC2::RouteTable',
207
+ Properties: {
208
+ VpcId: { Ref: 'FriggVPC' },
209
+ Tags: [
210
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' }
211
+ ]
212
+ }
213
+ },
214
+
215
+ // Public Route to Internet Gateway
216
+ FriggPublicRoute: {
217
+ Type: 'AWS::EC2::Route',
218
+ Properties: {
219
+ RouteTableId: { Ref: 'FriggPublicRouteTable' },
220
+ DestinationCidrBlock: '0.0.0.0/0',
221
+ GatewayId: { Ref: 'FriggInternetGateway' }
222
+ },
223
+ DependsOn: 'FriggVPCGatewayAttachment'
224
+ },
225
+
226
+ // Associate Public Subnet with Public Route Table
227
+ FriggPublicSubnetRouteTableAssociation: {
228
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
229
+ Properties: {
230
+ SubnetId: { Ref: 'FriggPublicSubnet' },
231
+ RouteTableId: { Ref: 'FriggPublicRouteTable' }
232
+ }
233
+ },
234
+
235
+ // Private Route Table
236
+ FriggPrivateRouteTable: {
237
+ Type: 'AWS::EC2::RouteTable',
238
+ Properties: {
239
+ VpcId: { Ref: 'FriggVPC' },
240
+ Tags: [
241
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-rt' }
242
+ ]
243
+ }
244
+ },
245
+
246
+ // Private Route to NAT Gateway
247
+ FriggPrivateRoute: {
248
+ Type: 'AWS::EC2::Route',
249
+ Properties: {
250
+ RouteTableId: { Ref: 'FriggPrivateRouteTable' },
251
+ DestinationCidrBlock: '0.0.0.0/0',
252
+ NatGatewayId: { Ref: 'FriggNATGateway' }
253
+ }
254
+ },
255
+
256
+ // Associate Private Subnet 1 with Private Route Table
257
+ FriggPrivateSubnet1RouteTableAssociation: {
258
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
259
+ Properties: {
260
+ SubnetId: { Ref: 'FriggPrivateSubnet1' },
261
+ RouteTableId: { Ref: 'FriggPrivateRouteTable' }
262
+ }
263
+ },
264
+
265
+ // Associate Private Subnet 2 with Private Route Table
266
+ FriggPrivateSubnet2RouteTableAssociation: {
267
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
268
+ Properties: {
269
+ SubnetId: { Ref: 'FriggPrivateSubnet2' },
270
+ RouteTableId: { Ref: 'FriggPrivateRouteTable' }
271
+ }
272
+ },
273
+
274
+ // Security Group for Lambda functions
275
+ FriggLambdaSecurityGroup: {
276
+ Type: 'AWS::EC2::SecurityGroup',
277
+ Properties: {
278
+ GroupDescription: 'Security group for Frigg Lambda functions',
279
+ VpcId: { Ref: 'FriggVPC' },
280
+ SecurityGroupEgress: [
281
+ {
282
+ IpProtocol: 'tcp',
283
+ FromPort: 443,
284
+ ToPort: 443,
285
+ CidrIp: '0.0.0.0/0',
286
+ Description: 'HTTPS outbound'
287
+ },
288
+ {
289
+ IpProtocol: 'tcp',
290
+ FromPort: 80,
291
+ ToPort: 80,
292
+ CidrIp: '0.0.0.0/0',
293
+ Description: 'HTTP outbound'
294
+ },
295
+ {
296
+ IpProtocol: 'tcp',
297
+ FromPort: 53,
298
+ ToPort: 53,
299
+ CidrIp: '0.0.0.0/0',
300
+ Description: 'DNS TCP'
301
+ },
302
+ {
303
+ IpProtocol: 'udp',
304
+ FromPort: 53,
305
+ ToPort: 53,
306
+ CidrIp: '0.0.0.0/0',
307
+ Description: 'DNS UDP'
308
+ }
309
+ ],
310
+ Tags: [
311
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-sg' }
312
+ ]
313
+ }
314
+ }
315
+ };
316
+
317
+ // Add VPC Endpoints for cost optimization
318
+ if (AppDefinition.vpc.enableVPCEndpoints !== false) {
319
+ // S3 Gateway Endpoint (free)
320
+ vpcResources.FriggS3VPCEndpoint = {
321
+ Type: 'AWS::EC2::VPCEndpoint',
322
+ Properties: {
323
+ VpcId: { Ref: 'FriggVPC' },
324
+ ServiceName: 'com.amazonaws.${self:provider.region}.s3',
325
+ VpcEndpointType: 'Gateway',
326
+ RouteTableIds: [
327
+ { Ref: 'FriggPrivateRouteTable' }
328
+ ]
329
+ }
330
+ };
331
+
332
+ // DynamoDB Gateway Endpoint (free)
333
+ vpcResources.FriggDynamoDBVPCEndpoint = {
334
+ Type: 'AWS::EC2::VPCEndpoint',
335
+ Properties: {
336
+ VpcId: { Ref: 'FriggVPC' },
337
+ ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
338
+ VpcEndpointType: 'Gateway',
339
+ RouteTableIds: [
340
+ { Ref: 'FriggPrivateRouteTable' }
341
+ ]
342
+ }
343
+ };
344
+
345
+ // KMS Interface Endpoint (paid, but useful if using KMS)
346
+ if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
347
+ vpcResources.FriggKMSVPCEndpoint = {
348
+ Type: 'AWS::EC2::VPCEndpoint',
349
+ Properties: {
350
+ VpcId: { Ref: 'FriggVPC' },
351
+ ServiceName: 'com.amazonaws.${self:provider.region}.kms',
352
+ VpcEndpointType: 'Interface',
353
+ SubnetIds: [
354
+ { Ref: 'FriggPrivateSubnet1' },
355
+ { Ref: 'FriggPrivateSubnet2' }
356
+ ],
357
+ SecurityGroupIds: [
358
+ { Ref: 'FriggVPCEndpointSecurityGroup' }
359
+ ],
360
+ PrivateDnsEnabled: true
361
+ }
362
+ };
363
+ }
364
+
365
+ // Secrets Manager Interface Endpoint (paid, but useful for secrets)
366
+ vpcResources.FriggSecretsManagerVPCEndpoint = {
367
+ Type: 'AWS::EC2::VPCEndpoint',
368
+ Properties: {
369
+ VpcId: { Ref: 'FriggVPC' },
370
+ ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
371
+ VpcEndpointType: 'Interface',
372
+ SubnetIds: [
373
+ { Ref: 'FriggPrivateSubnet1' },
374
+ { Ref: 'FriggPrivateSubnet2' }
375
+ ],
376
+ SecurityGroupIds: [
377
+ { Ref: 'FriggVPCEndpointSecurityGroup' }
378
+ ],
379
+ PrivateDnsEnabled: true
380
+ }
381
+ };
382
+
383
+ // Security Group for VPC Endpoints
384
+ vpcResources.FriggVPCEndpointSecurityGroup = {
385
+ Type: 'AWS::EC2::SecurityGroup',
386
+ Properties: {
387
+ GroupDescription: 'Security group for Frigg VPC Endpoints',
388
+ VpcId: { Ref: 'FriggVPC' },
389
+ SecurityGroupIngress: [
390
+ {
391
+ IpProtocol: 'tcp',
392
+ FromPort: 443,
393
+ ToPort: 443,
394
+ SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
395
+ Description: 'HTTPS from Lambda'
396
+ }
397
+ ],
398
+ Tags: [
399
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' }
400
+ ]
401
+ }
402
+ };
403
+ }
404
+
405
+ return vpcResources;
406
+ };
407
+
105
408
  const composeServerlessDefinition = (AppDefinition) => {
106
409
  const definition = {
107
410
  frameworkVersion: '>=3.17.0',
@@ -329,6 +632,75 @@ const composeServerlessDefinition = (AppDefinition) => {
329
632
  },
330
633
  };
331
634
 
635
+ // KMS Configuration based on App Definition
636
+ if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
637
+ // Add KMS IAM permissions
638
+ definition.provider.iamRoleStatements.push({
639
+ Effect: 'Allow',
640
+ Action: [
641
+ 'kms:GenerateDataKey',
642
+ 'kms:Decrypt'
643
+ ],
644
+ Resource: ['${self:custom.kmsGrants.kmsKeyId}']
645
+ });
646
+
647
+ // Add KMS_KEY_ARN environment variable for Frigg Encrypt module
648
+ definition.provider.environment.KMS_KEY_ARN = '${self:custom.kmsGrants.kmsKeyId}';
649
+
650
+ // Add serverless-kms-grants plugin
651
+ definition.plugins.push('serverless-kms-grants');
652
+
653
+ // Configure KMS grants with default key
654
+ definition.custom.kmsGrants = {
655
+ kmsKeyId: '*'
656
+ };
657
+ }
658
+
659
+ // VPC Configuration based on App Definition
660
+ if (AppDefinition.vpc?.enable === true) {
661
+ // Create VPC config from App Definition or use auto-created resources
662
+ const vpcConfig = {};
663
+
664
+ if (AppDefinition.vpc.securityGroupIds) {
665
+ // User provided custom security groups
666
+ vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
667
+ } else {
668
+ // Use auto-created security group
669
+ vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
670
+ }
671
+
672
+ if (AppDefinition.vpc.subnetIds) {
673
+ // User provided custom subnets
674
+ vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
675
+ } else {
676
+ // Use auto-created private subnets
677
+ vpcConfig.subnetIds = [
678
+ { Ref: 'FriggPrivateSubnet1' },
679
+ { Ref: 'FriggPrivateSubnet2' }
680
+ ];
681
+ }
682
+
683
+ // Set VPC config for Lambda functions
684
+ definition.provider.vpc = vpcConfig;
685
+
686
+ // Add VPC-related IAM permissions
687
+ definition.provider.iamRoleStatements.push({
688
+ Effect: 'Allow',
689
+ Action: [
690
+ 'ec2:CreateNetworkInterface',
691
+ 'ec2:DescribeNetworkInterfaces',
692
+ 'ec2:DeleteNetworkInterface',
693
+ 'ec2:AttachNetworkInterface',
694
+ 'ec2:DetachNetworkInterface'
695
+ ],
696
+ Resource: '*'
697
+ });
698
+
699
+ // Add VPC infrastructure resources to CloudFormation
700
+ const vpcResources = createVPCInfrastructure(AppDefinition);
701
+ Object.assign(definition.resources.Resources, vpcResources);
702
+ }
703
+
332
704
  // Add integration-specific functions and resources
333
705
  for (const integration of AppDefinition.integrations) {
334
706
  const integrationName = integration.Definition.name;
@@ -348,9 +720,8 @@ const composeServerlessDefinition = (AppDefinition) => {
348
720
  };
349
721
 
350
722
  // Add SQS Queue for the integration
351
- const queueReference = `${
352
- integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
353
- }Queue`;
723
+ const queueReference = `${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
724
+ }Queue`;
354
725
  const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
355
726
  definition.resources.Resources[queueReference] = {
356
727
  Type: 'AWS::SQS::Queue',
@@ -398,7 +769,7 @@ const composeServerlessDefinition = (AppDefinition) => {
398
769
 
399
770
  // Modify handler paths to point to the correct node_modules location
400
771
  definition.functions = modifyHandlerPaths(definition.functions);
401
-
772
+
402
773
  return definition;
403
774
  };
404
775
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0-next.23",
4
+ "version": "2.0.0-next.25",
5
5
  "dependencies": {
6
6
  "@babel/eslint-parser": "^7.18.9",
7
7
  "@babel/parser": "^7.25.3",
8
8
  "@babel/traverse": "^7.25.3",
9
- "@friggframework/test": "2.0.0-next.23",
9
+ "@friggframework/test": "2.0.0-next.25",
10
10
  "@hapi/boom": "^10.0.1",
11
11
  "@inquirer/prompts": "^5.3.8",
12
12
  "axios": "^1.7.2",
@@ -27,12 +27,13 @@
27
27
  "serverless-http": "^2.7.0"
28
28
  },
29
29
  "devDependencies": {
30
- "@friggframework/eslint-config": "2.0.0-next.23",
31
- "@friggframework/prettier-config": "2.0.0-next.23",
30
+ "@friggframework/eslint-config": "2.0.0-next.25",
31
+ "@friggframework/prettier-config": "2.0.0-next.25",
32
32
  "prettier": "^2.7.1",
33
33
  "serverless": "3.39.0",
34
34
  "serverless-dotenv-plugin": "^6.0.0",
35
35
  "serverless-jetpack": "^0.11.2",
36
+ "serverless-kms-grants": "^1.0.0",
36
37
  "serverless-offline": "^13.8.0",
37
38
  "serverless-offline-sqs": "^8.0.0",
38
39
  "serverless-plugin-monorepo": "^0.11.0"
@@ -59,5 +60,5 @@
59
60
  "publishConfig": {
60
61
  "access": "public"
61
62
  },
62
- "gitHead": "73a095711956729566b4eb0cc588c886fa74087e"
63
+ "gitHead": "d758d225a2cfbe4038ecc2b777cd6826949312fb"
63
64
  }