@friggframework/devtools 2.0.0--canary.454.e2a280d.0 → 2.0.0--canary.459.51231dd.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,20 +1,23 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
3
  const { AWSDiscovery } = require('./aws-discovery');
4
- const { buildPrismaLayer } = require('./scripts/build-prisma-layer');
5
4
 
6
5
  const shouldRunDiscovery = (AppDefinition) => {
7
- console.log('⚙️ Checking FRIGG_SKIP_AWS_DISCOVERY:', process.env.FRIGG_SKIP_AWS_DISCOVERY);
6
+ console.log(
7
+ '⚙️ Checking FRIGG_SKIP_AWS_DISCOVERY:',
8
+ process.env.FRIGG_SKIP_AWS_DISCOVERY
9
+ );
8
10
  if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
9
- console.log('⚙️ Skipping AWS discovery because FRIGG_SKIP_AWS_DISCOVERY is set.');
11
+ console.log(
12
+ '⚙️ Skipping AWS discovery because FRIGG_SKIP_AWS_DISCOVERY is set.'
13
+ );
10
14
  return false;
11
15
  }
12
16
 
13
17
  return (
14
18
  AppDefinition.vpc?.enable === true ||
15
19
  AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
16
- AppDefinition.ssm?.enable === true ||
17
- AppDefinition.database?.postgres?.enable === true
20
+ AppDefinition.ssm?.enable === true
18
21
  );
19
22
  };
20
23
 
@@ -57,11 +60,17 @@ const getAppEnvironmentVars = (AppDefinition) => {
57
60
  }
58
61
 
59
62
  if (envKeys.length > 0) {
60
- console.log(` Found ${envKeys.length} environment variables: ${envKeys.join(', ')}`);
63
+ console.log(
64
+ ` Found ${envKeys.length} environment variables: ${envKeys.join(
65
+ ', '
66
+ )}`
67
+ );
61
68
  }
62
69
  if (skippedKeys.length > 0) {
63
70
  console.log(
64
- ` ⚠️ Skipped ${skippedKeys.length} reserved AWS Lambda variables: ${skippedKeys.join(', ')}`
71
+ ` ⚠️ Skipped ${
72
+ skippedKeys.length
73
+ } reserved AWS Lambda variables: ${skippedKeys.join(', ')}`
65
74
  );
66
75
  }
67
76
 
@@ -77,7 +86,9 @@ const findNodeModulesPath = () => {
77
86
  const potentialPath = path.join(currentDir, 'node_modules');
78
87
  if (fs.existsSync(potentialPath)) {
79
88
  nodeModulesPath = potentialPath;
80
- console.log(`Found node_modules at: ${nodeModulesPath} (method 1)`);
89
+ console.log(
90
+ `Found node_modules at: ${nodeModulesPath} (method 1)`
91
+ );
81
92
  break;
82
93
  }
83
94
  const parentDir = path.dirname(currentDir);
@@ -88,10 +99,14 @@ const findNodeModulesPath = () => {
88
99
  if (!nodeModulesPath) {
89
100
  try {
90
101
  const { execSync } = require('node:child_process');
91
- const npmRoot = execSync('npm root', { encoding: 'utf8' }).trim();
102
+ const npmRoot = execSync('npm root', {
103
+ encoding: 'utf8',
104
+ }).trim();
92
105
  if (fs.existsSync(npmRoot)) {
93
106
  nodeModulesPath = npmRoot;
94
- console.log(`Found node_modules at: ${nodeModulesPath} (method 2)`);
107
+ console.log(
108
+ `Found node_modules at: ${nodeModulesPath} (method 2)`
109
+ );
95
110
  }
96
111
  } catch (npmError) {
97
112
  console.error('Error executing npm root:', npmError);
@@ -103,10 +118,15 @@ const findNodeModulesPath = () => {
103
118
  for (let i = 0; i < 5; i++) {
104
119
  const packageJsonPath = path.join(currentDir, 'package.json');
105
120
  if (fs.existsSync(packageJsonPath)) {
106
- const potentialNodeModules = path.join(currentDir, 'node_modules');
121
+ const potentialNodeModules = path.join(
122
+ currentDir,
123
+ 'node_modules'
124
+ );
107
125
  if (fs.existsSync(potentialNodeModules)) {
108
126
  nodeModulesPath = potentialNodeModules;
109
- console.log(`Found node_modules at: ${nodeModulesPath} (method 3)`);
127
+ console.log(
128
+ `Found node_modules at: ${nodeModulesPath} (method 3)`
129
+ );
110
130
  break;
111
131
  }
112
132
  }
@@ -120,7 +140,9 @@ const findNodeModulesPath = () => {
120
140
  return nodeModulesPath;
121
141
  }
122
142
 
123
- console.warn('Could not find node_modules path, falling back to default');
143
+ console.warn(
144
+ 'Could not find node_modules path, falling back to default'
145
+ );
124
146
  return path.resolve(process.cwd(), '../node_modules');
125
147
  } catch (error) {
126
148
  console.error('Error finding node_modules path:', error);
@@ -145,8 +167,13 @@ const modifyHandlerPaths = (functions) => {
145
167
  const functionDef = modifiedFunctions[functionName];
146
168
  if (functionDef?.handler?.includes('node_modules/')) {
147
169
  const relativePath = path.relative(process.cwd(), nodeModulesPath);
148
- functionDef.handler = functionDef.handler.replace('node_modules/', `${relativePath}/`);
149
- console.log(`Updated handler for ${functionName}: ${functionDef.handler}`);
170
+ functionDef.handler = functionDef.handler.replace(
171
+ 'node_modules/',
172
+ `${relativePath}/`
173
+ );
174
+ console.log(
175
+ `Updated handler for ${functionName}: ${functionDef.handler}`
176
+ );
150
177
  }
151
178
  }
152
179
 
@@ -162,7 +189,10 @@ const createVPCInfrastructure = (AppDefinition) => {
162
189
  EnableDnsHostnames: true,
163
190
  EnableDnsSupport: true,
164
191
  Tags: [
165
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc' },
192
+ {
193
+ Key: 'Name',
194
+ Value: '${self:service}-${self:provider.stage}-vpc',
195
+ },
166
196
  { Key: 'ManagedBy', Value: 'Frigg' },
167
197
  { Key: 'Service', Value: '${self:service}' },
168
198
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -173,7 +203,10 @@ const createVPCInfrastructure = (AppDefinition) => {
173
203
  Type: 'AWS::EC2::InternetGateway',
174
204
  Properties: {
175
205
  Tags: [
176
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' },
206
+ {
207
+ Key: 'Name',
208
+ Value: '${self:service}-${self:provider.stage}-igw',
209
+ },
177
210
  { Key: 'ManagedBy', Value: 'Frigg' },
178
211
  { Key: 'Service', Value: '${self:service}' },
179
212
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -195,7 +228,10 @@ const createVPCInfrastructure = (AppDefinition) => {
195
228
  AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
196
229
  MapPublicIpOnLaunch: true,
197
230
  Tags: [
198
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-subnet' },
231
+ {
232
+ Key: 'Name',
233
+ Value: '${self:service}-${self:provider.stage}-public-subnet',
234
+ },
199
235
  { Key: 'ManagedBy', Value: 'Frigg' },
200
236
  { Key: 'Service', Value: '${self:service}' },
201
237
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -210,7 +246,10 @@ const createVPCInfrastructure = (AppDefinition) => {
210
246
  CidrBlock: '10.0.2.0/24',
211
247
  AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
212
248
  Tags: [
213
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-1' },
249
+ {
250
+ Key: 'Name',
251
+ Value: '${self:service}-${self:provider.stage}-private-subnet-1',
252
+ },
214
253
  { Key: 'ManagedBy', Value: 'Frigg' },
215
254
  { Key: 'Service', Value: '${self:service}' },
216
255
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -225,7 +264,10 @@ const createVPCInfrastructure = (AppDefinition) => {
225
264
  CidrBlock: '10.0.3.0/24',
226
265
  AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
227
266
  Tags: [
228
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-2' },
267
+ {
268
+ Key: 'Name',
269
+ Value: '${self:service}-${self:provider.stage}-private-subnet-2',
270
+ },
229
271
  { Key: 'ManagedBy', Value: 'Frigg' },
230
272
  { Key: 'Service', Value: '${self:service}' },
231
273
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -238,7 +280,10 @@ const createVPCInfrastructure = (AppDefinition) => {
238
280
  Properties: {
239
281
  Domain: 'vpc',
240
282
  Tags: [
241
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' },
283
+ {
284
+ Key: 'Name',
285
+ Value: '${self:service}-${self:provider.stage}-nat-eip',
286
+ },
242
287
  { Key: 'ManagedBy', Value: 'Frigg' },
243
288
  { Key: 'Service', Value: '${self:service}' },
244
289
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -249,10 +294,15 @@ const createVPCInfrastructure = (AppDefinition) => {
249
294
  FriggNATGateway: {
250
295
  Type: 'AWS::EC2::NatGateway',
251
296
  Properties: {
252
- AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
297
+ AllocationId: {
298
+ 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'],
299
+ },
253
300
  SubnetId: { Ref: 'FriggPublicSubnet' },
254
301
  Tags: [
255
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' },
302
+ {
303
+ Key: 'Name',
304
+ Value: '${self:service}-${self:provider.stage}-nat-gateway',
305
+ },
256
306
  { Key: 'ManagedBy', Value: 'Frigg' },
257
307
  { Key: 'Service', Value: '${self:service}' },
258
308
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -264,7 +314,10 @@ const createVPCInfrastructure = (AppDefinition) => {
264
314
  Properties: {
265
315
  VpcId: { Ref: 'FriggVPC' },
266
316
  Tags: [
267
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' },
317
+ {
318
+ Key: 'Name',
319
+ Value: '${self:service}-${self:provider.stage}-public-rt',
320
+ },
268
321
  { Key: 'ManagedBy', Value: 'Frigg' },
269
322
  { Key: 'Service', Value: '${self:service}' },
270
323
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -293,7 +346,10 @@ const createVPCInfrastructure = (AppDefinition) => {
293
346
  Properties: {
294
347
  VpcId: { Ref: 'FriggVPC' },
295
348
  Tags: [
296
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-rt' },
349
+ {
350
+ Key: 'Name',
351
+ Value: '${self:service}-${self:provider.stage}-private-rt',
352
+ },
297
353
  { Key: 'ManagedBy', Value: 'Frigg' },
298
354
  { Key: 'Service', Value: '${self:service}' },
299
355
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -329,14 +385,47 @@ const createVPCInfrastructure = (AppDefinition) => {
329
385
  GroupDescription: 'Security group for Frigg Lambda functions',
330
386
  VpcId: { Ref: 'FriggVPC' },
331
387
  SecurityGroupEgress: [
332
- { IpProtocol: 'tcp', FromPort: 443, ToPort: 443, CidrIp: '0.0.0.0/0', Description: 'HTTPS outbound' },
333
- { IpProtocol: 'tcp', FromPort: 80, ToPort: 80, CidrIp: '0.0.0.0/0', Description: 'HTTP outbound' },
334
- { IpProtocol: 'tcp', FromPort: 53, ToPort: 53, CidrIp: '0.0.0.0/0', Description: 'DNS TCP' },
335
- { IpProtocol: 'udp', FromPort: 53, ToPort: 53, CidrIp: '0.0.0.0/0', Description: 'DNS UDP' },
336
- { IpProtocol: 'tcp', FromPort: 27017, ToPort: 27017, CidrIp: '0.0.0.0/0', Description: 'MongoDB outbound' },
388
+ {
389
+ IpProtocol: 'tcp',
390
+ FromPort: 443,
391
+ ToPort: 443,
392
+ CidrIp: '0.0.0.0/0',
393
+ Description: 'HTTPS outbound',
394
+ },
395
+ {
396
+ IpProtocol: 'tcp',
397
+ FromPort: 80,
398
+ ToPort: 80,
399
+ CidrIp: '0.0.0.0/0',
400
+ Description: 'HTTP outbound',
401
+ },
402
+ {
403
+ IpProtocol: 'tcp',
404
+ FromPort: 53,
405
+ ToPort: 53,
406
+ CidrIp: '0.0.0.0/0',
407
+ Description: 'DNS TCP',
408
+ },
409
+ {
410
+ IpProtocol: 'udp',
411
+ FromPort: 53,
412
+ ToPort: 53,
413
+ CidrIp: '0.0.0.0/0',
414
+ Description: 'DNS UDP',
415
+ },
416
+ {
417
+ IpProtocol: 'tcp',
418
+ FromPort: 27017,
419
+ ToPort: 27017,
420
+ CidrIp: '0.0.0.0/0',
421
+ Description: 'MongoDB outbound',
422
+ },
337
423
  ],
338
424
  Tags: [
339
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-sg' },
425
+ {
426
+ Key: 'Name',
427
+ Value: '${self:service}-${self:provider.stage}-lambda-sg',
428
+ },
340
429
  { Key: 'ManagedBy', Value: 'Frigg' },
341
430
  { Key: 'Service', Value: '${self:service}' },
342
431
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -373,8 +462,13 @@ const createVPCInfrastructure = (AppDefinition) => {
373
462
  VpcId: { Ref: 'FriggVPC' },
374
463
  ServiceName: 'com.amazonaws.${self:provider.region}.kms',
375
464
  VpcEndpointType: 'Interface',
376
- SubnetIds: [{ Ref: 'FriggPrivateSubnet1' }, { Ref: 'FriggPrivateSubnet2' }],
377
- SecurityGroupIds: [{ Ref: 'FriggVPCEndpointSecurityGroup' }],
465
+ SubnetIds: [
466
+ { Ref: 'FriggPrivateSubnet1' },
467
+ { Ref: 'FriggPrivateSubnet2' },
468
+ ],
469
+ SecurityGroupIds: [
470
+ { Ref: 'FriggVPCEndpointSecurityGroup' },
471
+ ],
378
472
  PrivateDnsEnabled: true,
379
473
  },
380
474
  };
@@ -384,9 +478,13 @@ const createVPCInfrastructure = (AppDefinition) => {
384
478
  Type: 'AWS::EC2::VPCEndpoint',
385
479
  Properties: {
386
480
  VpcId: { Ref: 'FriggVPC' },
387
- ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
481
+ ServiceName:
482
+ 'com.amazonaws.${self:provider.region}.secretsmanager',
388
483
  VpcEndpointType: 'Interface',
389
- SubnetIds: [{ Ref: 'FriggPrivateSubnet1' }, { Ref: 'FriggPrivateSubnet2' }],
484
+ SubnetIds: [
485
+ { Ref: 'FriggPrivateSubnet1' },
486
+ { Ref: 'FriggPrivateSubnet2' },
487
+ ],
390
488
  SecurityGroupIds: [{ Ref: 'FriggVPCEndpointSecurityGroup' }],
391
489
  PrivateDnsEnabled: true,
392
490
  },
@@ -395,14 +493,17 @@ const createVPCInfrastructure = (AppDefinition) => {
395
493
  vpcResources.FriggVPCEndpointSecurityGroup = {
396
494
  Type: 'AWS::EC2::SecurityGroup',
397
495
  Properties: {
398
- GroupDescription: 'Security group for Frigg VPC Endpoints - allows HTTPS from Lambda functions',
496
+ GroupDescription:
497
+ 'Security group for Frigg VPC Endpoints - allows HTTPS from Lambda functions',
399
498
  VpcId: { Ref: 'FriggVPC' },
400
499
  SecurityGroupIngress: [
401
500
  {
402
501
  IpProtocol: 'tcp',
403
502
  FromPort: 443,
404
503
  ToPort: 443,
405
- SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
504
+ SourceSecurityGroupId: {
505
+ Ref: 'FriggLambdaSecurityGroup',
506
+ },
406
507
  Description: 'HTTPS from Lambda security group',
407
508
  },
408
509
  {
@@ -414,12 +515,18 @@ const createVPCInfrastructure = (AppDefinition) => {
414
515
  },
415
516
  ],
416
517
  Tags: [
417
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' },
518
+ {
519
+ Key: 'Name',
520
+ Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg',
521
+ },
418
522
  { Key: 'ManagedBy', Value: 'Frigg' },
419
523
  { Key: 'Service', Value: '${self:service}' },
420
524
  { Key: 'Stage', Value: '${self:provider.stage}' },
421
525
  { Key: 'Type', Value: 'VPCEndpoint' },
422
- { Key: 'Purpose', Value: 'Allow Lambda functions to access VPC endpoints' },
526
+ {
527
+ Key: 'Purpose',
528
+ Value: 'Allow Lambda functions to access VPC endpoints',
529
+ },
423
530
  ],
424
531
  },
425
532
  };
@@ -437,17 +544,10 @@ const gatherDiscoveredResources = async (AppDefinition) => {
437
544
  try {
438
545
  const region = process.env.AWS_REGION || 'us-east-1';
439
546
  const discovery = new AWSDiscovery(region);
440
- // Use Serverless Framework's stage resolution (opt:stage with 'dev' as default)
441
- // This matches how serverless.yml resolves ${opt:stage, "dev"}
442
- // IMPORTANT: Use SLS_STAGE (not STAGE) to match actual deployment stage
443
- const stage = process.env.SLS_STAGE || 'dev';
444
-
445
547
  const config = {
446
548
  vpc: AppDefinition.vpc || {},
447
549
  encryption: AppDefinition.encryption || {},
448
550
  ssm: AppDefinition.ssm || {},
449
- serviceName: AppDefinition.name || 'create-frigg-app',
450
- stage: stage,
451
551
  };
452
552
 
453
553
  const discoveredResources = await discovery.discoverResources(config);
@@ -456,13 +556,18 @@ const gatherDiscoveredResources = async (AppDefinition) => {
456
556
  if (discoveredResources.defaultVpcId) {
457
557
  console.log(` VPC: ${discoveredResources.defaultVpcId}`);
458
558
  }
459
- if (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2) {
559
+ if (
560
+ discoveredResources.privateSubnetId1 &&
561
+ discoveredResources.privateSubnetId2
562
+ ) {
460
563
  console.log(
461
564
  ` Subnets: ${discoveredResources.privateSubnetId1}, ${discoveredResources.privateSubnetId2}`
462
565
  );
463
566
  }
464
567
  if (discoveredResources.defaultSecurityGroupId) {
465
- console.log(` Security Group: ${discoveredResources.defaultSecurityGroupId}`);
568
+ console.log(
569
+ ` Security Group: ${discoveredResources.defaultSecurityGroupId}`
570
+ );
466
571
  }
467
572
  if (discoveredResources.defaultKmsKeyId) {
468
573
  console.log(` KMS Key: ${discoveredResources.defaultKmsKeyId}`);
@@ -498,26 +603,14 @@ const buildEnvironment = (appEnvironmentVars, discoveredResources) => {
498
603
  }
499
604
  }
500
605
 
501
- // Add Aurora discovery mappings
502
- if (discoveredResources.aurora) {
503
- if (discoveredResources.aurora.clusterIdentifier) {
504
- environment.AWS_DISCOVERY_AURORA_CLUSTER_ID = discoveredResources.aurora.clusterIdentifier;
505
- }
506
- if (discoveredResources.aurora.endpoint) {
507
- environment.AWS_DISCOVERY_AURORA_ENDPOINT = discoveredResources.aurora.endpoint;
508
- }
509
- if (discoveredResources.aurora.port) {
510
- environment.AWS_DISCOVERY_AURORA_PORT = discoveredResources.aurora.port.toString();
511
- }
512
- if (discoveredResources.aurora.secretArn) {
513
- environment.AWS_DISCOVERY_AURORA_SECRET_ARN = discoveredResources.aurora.secretArn;
514
- }
515
- }
516
-
517
606
  return environment;
518
607
  };
519
608
 
520
- const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResources) => {
609
+ const createBaseDefinition = (
610
+ AppDefinition,
611
+ appEnvironmentVars,
612
+ discoveredResources
613
+ ) => {
521
614
  const region = process.env.AWS_REGION || 'us-east-1';
522
615
 
523
616
  return {
@@ -525,54 +618,23 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
525
618
  service: AppDefinition.name || 'create-frigg-app',
526
619
  package: {
527
620
  individually: true,
528
- patterns: [
529
- // AWS SDK exclusions (already in Lambda runtime)
621
+ exclude: [
530
622
  '!**/node_modules/aws-sdk/**',
531
623
  '!**/node_modules/@aws-sdk/**',
532
-
533
- // Prisma exclusions (provided via Lambda Layer)
534
- '!**/node_modules/@prisma/**',
535
- '!**/node_modules/.prisma/**',
536
- '!**/node_modules/@prisma-mongodb/**',
537
- '!**/node_modules/@prisma-postgresql/**',
538
- '!**/node_modules/prisma/**',
539
-
540
- // Exclude Prisma generated clients from @friggframework/core
541
- // These are 81MB and provided via Lambda Layer instead
542
- '!**/node_modules/@friggframework/core/generated/**',
543
-
544
- // Exclude development and test files
545
- '!**/test/**',
546
- '!**/tests/**',
547
- '!**/*.test.js',
548
- '!**/*.spec.js',
549
- '!**/*.map',
550
- '!**/jest.config.js',
551
- '!**/jest.unit.config.js',
552
- '!**/.eslintrc.json',
553
- '!**/.prettierrc',
554
- '!**/.prettierignore',
555
- '!**/.markdownlintignore',
556
- '!**/docker-compose.yml',
557
- '!**/package.json',
558
- '!**/README.md',
559
- '!**/*.md',
560
-
561
- // Exclude .DS_Store and other OS files
562
- '!**/.DS_Store',
563
- '!**/.git/**',
564
- '!**/.claude-flow/**',
624
+ '!package.json',
565
625
  ],
566
626
  },
567
627
  useDotenv: true,
568
628
  provider: {
569
629
  name: AppDefinition.provider || 'aws',
570
- ...(process.env.AWS_PROFILE && { profile: process.env.AWS_PROFILE }),
571
630
  runtime: 'nodejs20.x',
572
631
  timeout: 30,
573
632
  region,
574
633
  stage: '${opt:stage}',
575
- environment: buildEnvironment(appEnvironmentVars, discoveredResources),
634
+ environment: buildEnvironment(
635
+ appEnvironmentVars,
636
+ discoveredResources
637
+ ),
576
638
  iamRoleStatements: [
577
639
  {
578
640
  Effect: 'Allow',
@@ -581,13 +643,20 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
581
643
  },
582
644
  {
583
645
  Effect: 'Allow',
584
- Action: ['sqs:SendMessage', 'sqs:SendMessageBatch', 'sqs:GetQueueUrl', 'sqs:GetQueueAttributes'],
646
+ Action: [
647
+ 'sqs:SendMessage',
648
+ 'sqs:SendMessageBatch',
649
+ 'sqs:GetQueueUrl',
650
+ 'sqs:GetQueueAttributes',
651
+ ],
585
652
  Resource: [
586
653
  { 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'] },
587
654
  {
588
655
  'Fn::Join': [
589
656
  ':',
590
- ['arn:aws:sqs:${self:provider.region}:*:${self:service}--${self:provider.stage}-*Queue'],
657
+ [
658
+ 'arn:aws:sqs:${self:provider.region}:*:${self:service}--${self:provider.stage}-*Queue',
659
+ ],
591
660
  ],
592
661
  },
593
662
  ],
@@ -635,73 +704,42 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
635
704
  },
636
705
  functions: {
637
706
  auth: {
638
- handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
639
- layers: [{ Ref: 'PrismaLambdaLayer' }],
707
+ handler:
708
+ 'node_modules/@friggframework/core/handlers/routers/auth.handler',
640
709
  events: [
641
710
  { httpApi: { path: '/api/integrations', method: 'ANY' } },
642
- { httpApi: { path: '/api/integrations/{proxy+}', method: 'ANY' } },
711
+ {
712
+ httpApi: {
713
+ path: '/api/integrations/{proxy+}',
714
+ method: 'ANY',
715
+ },
716
+ },
643
717
  { httpApi: { path: '/api/authorize', method: 'ANY' } },
644
718
  ],
645
719
  },
646
720
  user: {
647
- handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
648
- layers: [{ Ref: 'PrismaLambdaLayer' }],
649
- events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
721
+ handler:
722
+ 'node_modules/@friggframework/core/handlers/routers/user.handler',
723
+ events: [
724
+ { httpApi: { path: '/user/{proxy+}', method: 'ANY' } },
725
+ ],
650
726
  },
651
727
  health: {
652
- handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
653
- layers: [{ Ref: 'PrismaLambdaLayer' }],
728
+ handler:
729
+ 'node_modules/@friggframework/core/handlers/routers/health.handler',
654
730
  events: [
655
731
  { httpApi: { path: '/health', method: 'GET' } },
656
732
  { httpApi: { path: '/health/{proxy+}', method: 'GET' } },
657
733
  ],
658
734
  },
659
- dbMigrate: {
660
- handler: 'node_modules/@friggframework/core/handlers/workers/db-migration.handler',
661
- layers: [{ Ref: 'PrismaLambdaLayer' }],
662
- timeout: 300, // 5 minutes for long-running migrations
663
- memorySize: 512, // Extra memory for Prisma CLI operations
664
- reservedConcurrency: 1, // Prevent concurrent migrations
665
- description: 'Runs database migrations via Prisma (invoke manually from CI/CD)',
666
- // No events - this function is invoked manually via AWS CLI
667
- maximumEventAge: 60, // Don't retry old migration requests (60 seconds)
668
- maximumRetryAttempts: 0, // Don't auto-retry failed migrations
669
- tags: {
670
- Purpose: 'DatabaseMigration',
671
- ManagedBy: 'Frigg',
672
- },
673
- // Environment variables for non-interactive Prisma CLI operation
674
- environment: {
675
- CI: '1', // Forces Prisma to non-interactive mode
676
- PRISMA_HIDE_UPDATE_MESSAGE: '1', // Suppress update messages
677
- PRISMA_MIGRATE_SKIP_SEED: '1', // Skip seeding during migrations
678
- },
679
- // Function-specific packaging: Include Prisma schemas (CLI from layer)
680
- package: {
681
- patterns: [
682
- // Include Prisma schemas from @friggframework/core
683
- // Note: Prisma CLI and clients come from Lambda Layer
684
- 'node_modules/@friggframework/core/prisma-mongodb/**',
685
- 'node_modules/@friggframework/core/prisma-postgresql/**',
686
- ],
687
- },
688
- },
689
- },
690
- layers: {
691
- prisma: {
692
- path: 'layers/prisma',
693
- name: '${self:service}-prisma-${sls:stage}',
694
- description: 'Prisma ORM clients for MongoDB and PostgreSQL with rhel-openssl-3.0.x binaries. Reduces function sizes by ~60% (120MB → 45MB). See LAMBDA-LAYER-PRISMA.md for details.',
695
- compatibleRuntimes: ['nodejs18.x', 'nodejs20.x'],
696
- retain: false, // Don't retain old layer versions
697
- },
698
735
  },
699
736
  resources: {
700
737
  Resources: {
701
738
  InternalErrorQueue: {
702
739
  Type: 'AWS::SQS::Queue',
703
740
  Properties: {
704
- QueueName: '${self:service}-internal-error-queue-${self:provider.stage}',
741
+ QueueName:
742
+ '${self:service}-internal-error-queue-${self:provider.stage}',
705
743
  MessageRetentionPeriod: 300,
706
744
  },
707
745
  },
@@ -711,7 +749,9 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
711
749
  Subscription: [
712
750
  {
713
751
  Protocol: 'sqs',
714
- Endpoint: { 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'] },
752
+ Endpoint: {
753
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
754
+ },
715
755
  },
716
756
  ],
717
757
  },
@@ -727,10 +767,22 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
727
767
  Sid: 'Allow Dead Letter SNS to publish to SQS',
728
768
  Effect: 'Allow',
729
769
  Principal: { Service: 'sns.amazonaws.com' },
730
- Resource: { 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'] },
731
- Action: ['SQS:SendMessage', 'SQS:SendMessageBatch'],
770
+ Resource: {
771
+ 'Fn::GetAtt': [
772
+ 'InternalErrorQueue',
773
+ 'Arn',
774
+ ],
775
+ },
776
+ Action: [
777
+ 'SQS:SendMessage',
778
+ 'SQS:SendMessageBatch',
779
+ ],
732
780
  Condition: {
733
- ArnEquals: { 'aws:SourceArn': { Ref: 'InternalErrorBridgeTopic' } },
781
+ ArnEquals: {
782
+ 'aws:SourceArn': {
783
+ Ref: 'InternalErrorBridgeTopic',
784
+ },
785
+ },
734
786
  },
735
787
  },
736
788
  ],
@@ -760,34 +812,36 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
760
812
  };
761
813
  };
762
814
 
763
- const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) => {
815
+ const applyKmsConfiguration = (
816
+ definition,
817
+ AppDefinition,
818
+ discoveredResources
819
+ ) => {
764
820
  if (AppDefinition.encryption?.fieldLevelEncryptionMethod !== 'kms') {
765
821
  return;
766
822
  }
767
823
 
768
824
  // Skip KMS configuration for local development when AWS discovery is disabled
769
825
  if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
770
- console.log('⚙️ Skipping KMS configuration for local development (FRIGG_SKIP_AWS_DISCOVERY is set)');
826
+ console.log(
827
+ '⚙️ Skipping KMS configuration for local development (FRIGG_SKIP_AWS_DISCOVERY is set)'
828
+ );
771
829
  return;
772
830
  }
773
831
 
774
832
  if (discoveredResources.defaultKmsKeyId) {
775
- console.log(`Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`);
776
-
777
- // Only create alias if it doesn't already exist
778
- if (!discoveredResources.kmsAliasExists) {
779
- console.log('Creating KMS alias for discovered key...');
780
- definition.resources.Resources.FriggKMSKeyAlias = {
781
- Type: 'AWS::KMS::Alias',
782
- DeletionPolicy: 'Retain',
783
- Properties: {
784
- AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
785
- TargetKeyId: discoveredResources.defaultKmsKeyId,
786
- },
787
- };
788
- } else {
789
- console.log('KMS alias already exists, skipping alias creation');
790
- }
833
+ console.log(
834
+ `Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`
835
+ );
836
+ definition.resources.Resources.FriggKMSKeyAlias = {
837
+ Type: 'AWS::KMS::Alias',
838
+ DeletionPolicy: 'Retain',
839
+ Properties: {
840
+ AliasName:
841
+ 'alias/${self:service}-${self:provider.stage}-frigg-kms',
842
+ TargetKeyId: discoveredResources.defaultKmsKeyId,
843
+ },
844
+ };
791
845
 
792
846
  definition.provider.iamRoleStatements.push({
793
847
  Effect: 'Allow',
@@ -798,7 +852,7 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
798
852
  if (AppDefinition.encryption?.createResourceIfNoneFound !== true) {
799
853
  throw new Error(
800
854
  'KMS field-level encryption is enabled but no KMS key was found. ' +
801
- 'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
855
+ 'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
802
856
  );
803
857
  }
804
858
 
@@ -817,7 +871,10 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
817
871
  Sid: 'AllowRootAccountAdmin',
818
872
  Effect: 'Allow',
819
873
  Principal: {
820
- AWS: { 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:root' },
874
+ AWS: {
875
+ 'Fn::Sub':
876
+ 'arn:aws:iam::${AWS::AccountId}:root',
877
+ },
821
878
  },
822
879
  Action: 'kms:*',
823
880
  Resource: '*',
@@ -826,20 +883,32 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
826
883
  Sid: 'AllowLambdaService',
827
884
  Effect: 'Allow',
828
885
  Principal: { Service: 'lambda.amazonaws.com' },
829
- Action: ['kms:GenerateDataKey', 'kms:Decrypt', 'kms:DescribeKey'],
886
+ Action: [
887
+ 'kms:GenerateDataKey',
888
+ 'kms:Decrypt',
889
+ 'kms:DescribeKey',
890
+ ],
830
891
  Resource: '*',
831
892
  Condition: {
832
893
  StringEquals: {
833
- 'kms:ViaService': `lambda.${process.env.AWS_REGION || 'us-east-1'}.amazonaws.com`,
894
+ 'kms:ViaService': `lambda.${
895
+ process.env.AWS_REGION || 'us-east-1'
896
+ }.amazonaws.com`,
834
897
  },
835
898
  },
836
899
  },
837
900
  ],
838
901
  },
839
902
  Tags: [
840
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-frigg-kms-key' },
903
+ {
904
+ Key: 'Name',
905
+ Value: '${self:service}-${self:provider.stage}-frigg-kms-key',
906
+ },
841
907
  { Key: 'ManagedBy', Value: 'Frigg' },
842
- { Key: 'Purpose', Value: 'Field-level encryption for Frigg application' },
908
+ {
909
+ Key: 'Purpose',
910
+ Value: 'Field-level encryption for Frigg application',
911
+ },
843
912
  ],
844
913
  },
845
914
  };
@@ -848,7 +917,8 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
848
917
  Type: 'AWS::KMS::Alias',
849
918
  DeletionPolicy: 'Retain',
850
919
  Properties: {
851
- AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
920
+ AliasName:
921
+ 'alias/${self:service}-${self:provider.stage}-frigg-kms',
852
922
  TargetKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] },
853
923
  },
854
924
  };
@@ -859,7 +929,9 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
859
929
  Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }],
860
930
  });
861
931
 
862
- definition.provider.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
932
+ definition.provider.environment.KMS_KEY_ARN = {
933
+ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'],
934
+ };
863
935
  definition.custom.kmsGrants = {
864
936
  kmsKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] },
865
937
  };
@@ -868,13 +940,16 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
868
940
  definition.plugins.push('serverless-kms-grants');
869
941
  if (!definition.custom.kmsGrants) {
870
942
  definition.custom.kmsGrants = {
871
- kmsKeyId: discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}',
943
+ kmsKeyId:
944
+ discoveredResources.defaultKmsKeyId ||
945
+ '${env:AWS_DISCOVERY_KMS_KEY_ID}',
872
946
  };
873
947
  }
874
948
 
875
949
  if (!definition.provider.environment.KMS_KEY_ARN) {
876
950
  definition.provider.environment.KMS_KEY_ARN =
877
- discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}';
951
+ discoveredResources.defaultKmsKeyId ||
952
+ '${env:AWS_DISCOVERY_KMS_KEY_ID}';
878
953
  }
879
954
  };
880
955
 
@@ -891,7 +966,9 @@ const healVpcConfiguration = (discoveredResources, AppDefinition) => {
891
966
  return healingReport;
892
967
  }
893
968
 
894
- console.log('🔧 Self-healing mode enabled - checking for VPC misconfigurations...');
969
+ console.log(
970
+ '🔧 Self-healing mode enabled - checking for VPC misconfigurations...'
971
+ );
895
972
 
896
973
  if (discoveredResources.natGatewayInPrivateSubnet) {
897
974
  healingReport.warnings.push(
@@ -901,7 +978,9 @@ const healVpcConfiguration = (discoveredResources, AppDefinition) => {
901
978
  'NAT Gateway should be recreated in a public subnet for proper internet connectivity'
902
979
  );
903
980
  discoveredResources.needsNewNatGateway = true;
904
- healingReport.healed.push('Marked NAT Gateway for recreation in public subnet');
981
+ healingReport.healed.push(
982
+ 'Marked NAT Gateway for recreation in public subnet'
983
+ );
905
984
  }
906
985
 
907
986
  if (discoveredResources.elasticIpAlreadyAssociated) {
@@ -910,10 +989,14 @@ const healVpcConfiguration = (discoveredResources, AppDefinition) => {
910
989
  );
911
990
 
912
991
  if (discoveredResources.existingNatGatewayId) {
913
- healingReport.healed.push('Will reuse existing NAT Gateway instead of creating a new one');
992
+ healingReport.healed.push(
993
+ 'Will reuse existing NAT Gateway instead of creating a new one'
994
+ );
914
995
  discoveredResources.reuseExistingNatGateway = true;
915
996
  } else {
916
- healingReport.healed.push('Will allocate a new Elastic IP for NAT Gateway');
997
+ healingReport.healed.push(
998
+ 'Will allocate a new Elastic IP for NAT Gateway'
999
+ );
917
1000
  discoveredResources.allocateNewElasticIp = true;
918
1001
  }
919
1002
  }
@@ -937,19 +1020,25 @@ const healVpcConfiguration = (discoveredResources, AppDefinition) => {
937
1020
  healingReport.warnings.push(
938
1021
  'Subnet configuration mismatch detected - Lambda functions require private subnets'
939
1022
  );
940
- healingReport.healed.push('Will create proper route table configuration for subnet isolation');
1023
+ healingReport.healed.push(
1024
+ 'Will create proper route table configuration for subnet isolation'
1025
+ );
941
1026
  }
942
1027
 
943
1028
  if (discoveredResources.orphanedElasticIps?.length > 0) {
944
1029
  healingReport.warnings.push(
945
1030
  `Found ${discoveredResources.orphanedElasticIps.length} orphaned Elastic IPs`
946
1031
  );
947
- healingReport.recommendations.push('Consider releasing orphaned Elastic IPs to avoid charges');
1032
+ healingReport.recommendations.push(
1033
+ 'Consider releasing orphaned Elastic IPs to avoid charges'
1034
+ );
948
1035
  }
949
1036
 
950
1037
  if (healingReport.criticalActions.length > 0) {
951
1038
  console.log('🚨 CRITICAL ACTIONS:');
952
- healingReport.criticalActions.forEach((action) => console.log(` - ${action}`));
1039
+ healingReport.criticalActions.forEach((action) =>
1040
+ console.log(` - ${action}`)
1041
+ );
953
1042
  }
954
1043
 
955
1044
  if (healingReport.healed.length > 0) {
@@ -959,12 +1048,16 @@ const healVpcConfiguration = (discoveredResources, AppDefinition) => {
959
1048
 
960
1049
  if (healingReport.warnings.length > 0) {
961
1050
  console.log('⚠️ Issues detected:');
962
- healingReport.warnings.forEach((warning) => console.log(` - ${warning}`));
1051
+ healingReport.warnings.forEach((warning) =>
1052
+ console.log(` - ${warning}`)
1053
+ );
963
1054
  }
964
1055
 
965
1056
  if (healingReport.recommendations.length > 0) {
966
1057
  console.log('💡 Recommendations:');
967
- healingReport.recommendations.forEach((rec) => console.log(` - ${rec}`));
1058
+ healingReport.recommendations.forEach((rec) =>
1059
+ console.log(` - ${rec}`)
1060
+ );
968
1061
  }
969
1062
 
970
1063
  return healingReport;
@@ -977,7 +1070,9 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
977
1070
 
978
1071
  // Skip VPC configuration for local development when AWS discovery is disabled
979
1072
  if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
980
- console.log('⚙️ Skipping VPC configuration for local development (FRIGG_SKIP_AWS_DISCOVERY is set)');
1073
+ console.log(
1074
+ '⚙️ Skipping VPC configuration for local development (FRIGG_SKIP_AWS_DISCOVERY is set)'
1075
+ );
981
1076
  return;
982
1077
  }
983
1078
 
@@ -994,9 +1089,16 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
994
1089
  });
995
1090
 
996
1091
  if (Object.keys(discoveredResources).length > 0) {
997
- const healingReport = healVpcConfiguration(discoveredResources, AppDefinition);
1092
+ const healingReport = healVpcConfiguration(
1093
+ discoveredResources,
1094
+ AppDefinition
1095
+ );
998
1096
  if (healingReport.errors.length > 0 && !AppDefinition.vpc?.selfHeal) {
999
- throw new Error(`VPC configuration errors detected: ${healingReport.errors.join(', ')}`);
1097
+ throw new Error(
1098
+ `VPC configuration errors detected: ${healingReport.errors.join(
1099
+ ', '
1100
+ )}`
1101
+ );
1000
1102
  }
1001
1103
  }
1002
1104
 
@@ -1013,15 +1115,21 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1013
1115
  const vpcResources = createVPCInfrastructure(AppDefinition);
1014
1116
  Object.assign(definition.resources.Resources, vpcResources);
1015
1117
  vpcId = { Ref: 'FriggVPC' };
1016
- vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds || [{ Ref: 'FriggLambdaSecurityGroup' }];
1118
+ vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds || [
1119
+ { Ref: 'FriggLambdaSecurityGroup' },
1120
+ ];
1017
1121
  } else if (vpcManagement === 'use-existing') {
1018
1122
  if (!AppDefinition.vpc.vpcId) {
1019
- throw new Error('VPC management is set to "use-existing" but no vpcId was provided');
1123
+ throw new Error(
1124
+ 'VPC management is set to "use-existing" but no vpcId was provided'
1125
+ );
1020
1126
  }
1021
1127
  vpcId = AppDefinition.vpc.vpcId;
1022
1128
  vpcConfig.securityGroupIds =
1023
1129
  AppDefinition.vpc.securityGroupIds ||
1024
- (discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []);
1130
+ (discoveredResources.defaultSecurityGroupId
1131
+ ? [discoveredResources.defaultSecurityGroupId]
1132
+ : []);
1025
1133
  } else {
1026
1134
  if (!discoveredResources.defaultVpcId) {
1027
1135
  throw new Error(
@@ -1031,11 +1139,15 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1031
1139
  vpcId = discoveredResources.defaultVpcId;
1032
1140
  vpcConfig.securityGroupIds =
1033
1141
  AppDefinition.vpc.securityGroupIds ||
1034
- (discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []);
1142
+ (discoveredResources.defaultSecurityGroupId
1143
+ ? [discoveredResources.defaultSecurityGroupId]
1144
+ : []);
1035
1145
  }
1036
1146
 
1037
- const defaultSubnetManagement = vpcManagement === 'create-new' ? 'create' : 'discover';
1038
- let subnetManagement = AppDefinition.vpc.subnets?.management || defaultSubnetManagement;
1147
+ const defaultSubnetManagement =
1148
+ vpcManagement === 'create-new' ? 'create' : 'discover';
1149
+ let subnetManagement =
1150
+ AppDefinition.vpc.subnets?.management || defaultSubnetManagement;
1039
1151
  console.log(`Subnet Management Mode: ${subnetManagement}`);
1040
1152
 
1041
1153
  const effectiveVpcId = vpcId || discoveredResources.defaultVpcId;
@@ -1045,7 +1157,10 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1045
1157
 
1046
1158
  if (subnetManagement === 'create') {
1047
1159
  console.log('Creating new subnets...');
1048
- const subnetVpcId = vpcManagement === 'create-new' ? { Ref: 'FriggVPC' } : effectiveVpcId;
1160
+ const subnetVpcId =
1161
+ vpcManagement === 'create-new'
1162
+ ? { Ref: 'FriggVPC' }
1163
+ : effectiveVpcId;
1049
1164
  let subnet1Cidr;
1050
1165
  let subnet2Cidr;
1051
1166
  let publicSubnetCidr;
@@ -1068,7 +1183,10 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1068
1183
  CidrBlock: subnet1Cidr,
1069
1184
  AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
1070
1185
  Tags: [
1071
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-1' },
1186
+ {
1187
+ Key: 'Name',
1188
+ Value: '${self:service}-${self:provider.stage}-private-1',
1189
+ },
1072
1190
  { Key: 'Type', Value: 'Private' },
1073
1191
  { Key: 'ManagedBy', Value: 'Frigg' },
1074
1192
  ],
@@ -1082,7 +1200,10 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1082
1200
  CidrBlock: subnet2Cidr,
1083
1201
  AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
1084
1202
  Tags: [
1085
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-2' },
1203
+ {
1204
+ Key: 'Name',
1205
+ Value: '${self:service}-${self:provider.stage}-private-2',
1206
+ },
1086
1207
  { Key: 'Type', Value: 'Private' },
1087
1208
  { Key: 'ManagedBy', Value: 'Frigg' },
1088
1209
  ],
@@ -1097,23 +1218,38 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1097
1218
  MapPublicIpOnLaunch: true,
1098
1219
  AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
1099
1220
  Tags: [
1100
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public' },
1221
+ {
1222
+ Key: 'Name',
1223
+ Value: '${self:service}-${self:provider.stage}-public',
1224
+ },
1101
1225
  { Key: 'Type', Value: 'Public' },
1102
1226
  { Key: 'ManagedBy', Value: 'Frigg' },
1103
1227
  ],
1104
1228
  },
1105
1229
  };
1106
1230
 
1107
- vpcConfig.subnetIds = [{ Ref: 'FriggPrivateSubnet1' }, { Ref: 'FriggPrivateSubnet2' }];
1231
+ vpcConfig.subnetIds = [
1232
+ { Ref: 'FriggPrivateSubnet1' },
1233
+ { Ref: 'FriggPrivateSubnet2' },
1234
+ ];
1108
1235
 
1109
- if (!AppDefinition.vpc.natGateway || AppDefinition.vpc.natGateway.management === 'discover') {
1110
- if (vpcManagement === 'create-new' || !discoveredResources.internetGatewayId) {
1236
+ if (
1237
+ !AppDefinition.vpc.natGateway ||
1238
+ AppDefinition.vpc.natGateway.management === 'discover'
1239
+ ) {
1240
+ if (
1241
+ vpcManagement === 'create-new' ||
1242
+ !discoveredResources.internetGatewayId
1243
+ ) {
1111
1244
  if (!definition.resources.Resources.FriggInternetGateway) {
1112
1245
  definition.resources.Resources.FriggInternetGateway = {
1113
1246
  Type: 'AWS::EC2::InternetGateway',
1114
1247
  Properties: {
1115
1248
  Tags: [
1116
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' },
1249
+ {
1250
+ Key: 'Name',
1251
+ Value: '${self:service}-${self:provider.stage}-igw',
1252
+ },
1117
1253
  { Key: 'ManagedBy', Value: 'Frigg' },
1118
1254
  ],
1119
1255
  },
@@ -1134,7 +1270,10 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1134
1270
  Properties: {
1135
1271
  VpcId: subnetVpcId,
1136
1272
  Tags: [
1137
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' },
1273
+ {
1274
+ Key: 'Name',
1275
+ Value: '${self:service}-${self:provider.stage}-public-rt',
1276
+ },
1138
1277
  { Key: 'ManagedBy', Value: 'Frigg' },
1139
1278
  ],
1140
1279
  },
@@ -1142,51 +1281,65 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1142
1281
 
1143
1282
  definition.resources.Resources.FriggPublicRoute = {
1144
1283
  Type: 'AWS::EC2::Route',
1145
- DependsOn: vpcManagement === 'create-new' ? 'FriggIGWAttachment' : undefined,
1284
+ DependsOn:
1285
+ vpcManagement === 'create-new'
1286
+ ? 'FriggIGWAttachment'
1287
+ : undefined,
1146
1288
  Properties: {
1147
1289
  RouteTableId: { Ref: 'FriggPublicRouteTable' },
1148
1290
  DestinationCidrBlock: '0.0.0.0/0',
1149
- GatewayId: discoveredResources.internetGatewayId || { Ref: 'FriggInternetGateway' },
1291
+ GatewayId: discoveredResources.internetGatewayId || {
1292
+ Ref: 'FriggInternetGateway',
1293
+ },
1150
1294
  },
1151
1295
  };
1152
1296
 
1153
- definition.resources.Resources.FriggPublicSubnetRouteTableAssociation = {
1154
- Type: 'AWS::EC2::SubnetRouteTableAssociation',
1155
- Properties: {
1156
- SubnetId: { Ref: 'FriggPublicSubnet' },
1157
- RouteTableId: { Ref: 'FriggPublicRouteTable' },
1158
- },
1159
- };
1297
+ definition.resources.Resources.FriggPublicSubnetRouteTableAssociation =
1298
+ {
1299
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1300
+ Properties: {
1301
+ SubnetId: { Ref: 'FriggPublicSubnet' },
1302
+ RouteTableId: { Ref: 'FriggPublicRouteTable' },
1303
+ },
1304
+ };
1160
1305
 
1161
1306
  definition.resources.Resources.FriggLambdaRouteTable = {
1162
1307
  Type: 'AWS::EC2::RouteTable',
1163
1308
  Properties: {
1164
1309
  VpcId: subnetVpcId,
1165
1310
  Tags: [
1166
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' },
1311
+ {
1312
+ Key: 'Name',
1313
+ Value: '${self:service}-${self:provider.stage}-lambda-rt',
1314
+ },
1167
1315
  { Key: 'ManagedBy', Value: 'Frigg' },
1168
1316
  ],
1169
1317
  },
1170
1318
  };
1171
1319
 
1172
- definition.resources.Resources.FriggPrivateSubnet1RouteTableAssociation = {
1173
- Type: 'AWS::EC2::SubnetRouteTableAssociation',
1174
- Properties: {
1175
- SubnetId: { Ref: 'FriggPrivateSubnet1' },
1176
- RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1177
- },
1178
- };
1320
+ definition.resources.Resources.FriggPrivateSubnet1RouteTableAssociation =
1321
+ {
1322
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1323
+ Properties: {
1324
+ SubnetId: { Ref: 'FriggPrivateSubnet1' },
1325
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1326
+ },
1327
+ };
1179
1328
 
1180
- definition.resources.Resources.FriggPrivateSubnet2RouteTableAssociation = {
1181
- Type: 'AWS::EC2::SubnetRouteTableAssociation',
1182
- Properties: {
1183
- SubnetId: { Ref: 'FriggPrivateSubnet2' },
1184
- RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1185
- },
1186
- };
1329
+ definition.resources.Resources.FriggPrivateSubnet2RouteTableAssociation =
1330
+ {
1331
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1332
+ Properties: {
1333
+ SubnetId: { Ref: 'FriggPrivateSubnet2' },
1334
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1335
+ },
1336
+ };
1187
1337
  }
1188
1338
  } else if (subnetManagement === 'use-existing') {
1189
- if (!AppDefinition.vpc.subnets?.ids || AppDefinition.vpc.subnets.ids.length < 2) {
1339
+ if (
1340
+ !AppDefinition.vpc.subnets?.ids ||
1341
+ AppDefinition.vpc.subnets.ids.length < 2
1342
+ ) {
1190
1343
  throw new Error(
1191
1344
  'Subnet management is "use-existing" but less than 2 subnet IDs provided. Provide at least 2 subnet IDs in vpc.subnets.ids.'
1192
1345
  );
@@ -1196,13 +1349,19 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1196
1349
  vpcConfig.subnetIds =
1197
1350
  AppDefinition.vpc.subnets?.ids?.length > 0
1198
1351
  ? AppDefinition.vpc.subnets.ids
1199
- : discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2
1200
- ? [discoveredResources.privateSubnetId1, discoveredResources.privateSubnetId2]
1201
- : [];
1352
+ : discoveredResources.privateSubnetId1 &&
1353
+ discoveredResources.privateSubnetId2
1354
+ ? [
1355
+ discoveredResources.privateSubnetId1,
1356
+ discoveredResources.privateSubnetId2,
1357
+ ]
1358
+ : [];
1202
1359
 
1203
1360
  if (vpcConfig.subnetIds.length < 2) {
1204
1361
  if (AppDefinition.vpc.selfHeal) {
1205
- console.log('No subnets found but self-heal enabled - creating minimal subnet setup');
1362
+ console.log(
1363
+ 'No subnets found but self-heal enabled - creating minimal subnet setup'
1364
+ );
1206
1365
  subnetManagement = 'create';
1207
1366
  discoveredResources.createSubnets = true;
1208
1367
  } else {
@@ -1214,19 +1373,22 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1214
1373
  }
1215
1374
 
1216
1375
  if (subnetManagement === 'create' && discoveredResources.createSubnets) {
1217
- definition.resources.Resources.FriggLambdaRouteTable =
1218
- definition.resources.Resources.FriggLambdaRouteTable || {
1219
- Type: 'AWS::EC2::RouteTable',
1220
- Properties: {
1221
- VpcId: effectiveVpcId,
1222
- Tags: [
1223
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' },
1224
- { Key: 'ManagedBy', Value: 'Frigg' },
1225
- { Key: 'Environment', Value: '${self:provider.stage}' },
1226
- { Key: 'Service', Value: '${self:service}' },
1227
- ],
1228
- },
1229
- };
1376
+ definition.resources.Resources.FriggLambdaRouteTable = definition
1377
+ .resources.Resources.FriggLambdaRouteTable || {
1378
+ Type: 'AWS::EC2::RouteTable',
1379
+ Properties: {
1380
+ VpcId: effectiveVpcId,
1381
+ Tags: [
1382
+ {
1383
+ Key: 'Name',
1384
+ Value: '${self:service}-${self:provider.stage}-lambda-rt',
1385
+ },
1386
+ { Key: 'ManagedBy', Value: 'Frigg' },
1387
+ { Key: 'Environment', Value: '${self:provider.stage}' },
1388
+ { Key: 'Service', Value: '${self:service}' },
1389
+ ],
1390
+ },
1391
+ };
1230
1392
  }
1231
1393
 
1232
1394
  if (
@@ -1235,7 +1397,8 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1235
1397
  ) {
1236
1398
  definition.provider.vpc = vpcConfig;
1237
1399
 
1238
- const natGatewayManagement = AppDefinition.vpc.natGateway?.management || 'discover';
1400
+ const natGatewayManagement =
1401
+ AppDefinition.vpc.natGateway?.management || 'discover';
1239
1402
  let needsNewNatGateway =
1240
1403
  natGatewayManagement === 'createAndManage' ||
1241
1404
  discoveredResources.needsNewNatGateway === true;
@@ -1246,7 +1409,9 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1246
1409
  let useExistingEip = false;
1247
1410
 
1248
1411
  if (needsNewNatGateway) {
1249
- console.log('Create mode: Creating dedicated EIP, public subnet, and NAT Gateway...');
1412
+ console.log(
1413
+ 'Create mode: Creating dedicated EIP, public subnet, and NAT Gateway...'
1414
+ );
1250
1415
 
1251
1416
  if (
1252
1417
  discoveredResources.existingNatGatewayId &&
@@ -1254,12 +1419,18 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1254
1419
  ) {
1255
1420
  console.log('Found existing Frigg-managed NAT Gateway and EIP');
1256
1421
  if (!discoveredResources.natGatewayInPrivateSubnet) {
1257
- console.log('✅ Existing NAT Gateway is in PUBLIC subnet, will reuse it');
1422
+ console.log(
1423
+ '✅ Existing NAT Gateway is in PUBLIC subnet, will reuse it'
1424
+ );
1258
1425
  reuseExistingNatGateway = true;
1259
1426
  } else {
1260
- console.log('❌ NAT Gateway is in PRIVATE subnet - MUST create new one in PUBLIC subnet');
1427
+ console.log(
1428
+ '❌ NAT Gateway is in PRIVATE subnet - MUST create new one in PUBLIC subnet'
1429
+ );
1261
1430
  if (AppDefinition.vpc.selfHeal) {
1262
- console.log('Self-heal enabled: Creating new NAT Gateway in PUBLIC subnet');
1431
+ console.log(
1432
+ 'Self-heal enabled: Creating new NAT Gateway in PUBLIC subnet'
1433
+ );
1263
1434
  reuseExistingNatGateway = false;
1264
1435
  useExistingEip = false;
1265
1436
  discoveredResources.needsCleanup = true;
@@ -1273,12 +1444,16 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1273
1444
  discoveredResources.existingElasticIpAllocationId &&
1274
1445
  !discoveredResources.existingNatGatewayId
1275
1446
  ) {
1276
- console.log('Found orphaned EIP, will reuse it for new NAT Gateway in PUBLIC subnet');
1447
+ console.log(
1448
+ 'Found orphaned EIP, will reuse it for new NAT Gateway in PUBLIC subnet'
1449
+ );
1277
1450
  useExistingEip = true;
1278
1451
  }
1279
1452
 
1280
1453
  if (reuseExistingNatGateway) {
1281
- console.log('Reusing existing NAT Gateway - skipping resource creation');
1454
+ console.log(
1455
+ 'Reusing existing NAT Gateway - skipping resource creation'
1456
+ );
1282
1457
  } else {
1283
1458
  if (!useExistingEip) {
1284
1459
  definition.resources.Resources.FriggNATGatewayEIP = {
@@ -1288,10 +1463,16 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1288
1463
  Properties: {
1289
1464
  Domain: 'vpc',
1290
1465
  Tags: [
1291
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' },
1466
+ {
1467
+ Key: 'Name',
1468
+ Value: '${self:service}-${self:provider.stage}-nat-eip',
1469
+ },
1292
1470
  { Key: 'ManagedBy', Value: 'Frigg' },
1293
1471
  { Key: 'Service', Value: '${self:service}' },
1294
- { Key: 'Stage', Value: '${self:provider.stage}' },
1472
+ {
1473
+ Key: 'Stage',
1474
+ Value: '${self:provider.stage}',
1475
+ },
1295
1476
  ],
1296
1477
  },
1297
1478
  };
@@ -1299,25 +1480,34 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1299
1480
 
1300
1481
  if (!discoveredResources.publicSubnetId) {
1301
1482
  if (discoveredResources.internetGatewayId) {
1302
- console.log('Reusing existing Internet Gateway for NAT Gateway');
1483
+ console.log(
1484
+ 'Reusing existing Internet Gateway for NAT Gateway'
1485
+ );
1303
1486
  } else {
1304
1487
  definition.resources.Resources.FriggInternetGateway =
1305
- definition.resources.Resources.FriggInternetGateway || {
1488
+ definition.resources.Resources
1489
+ .FriggInternetGateway || {
1306
1490
  Type: 'AWS::EC2::InternetGateway',
1307
1491
  Properties: {
1308
1492
  Tags: [
1309
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' },
1493
+ {
1494
+ Key: 'Name',
1495
+ Value: '${self:service}-${self:provider.stage}-igw',
1496
+ },
1310
1497
  { Key: 'ManagedBy', Value: 'Frigg' },
1311
1498
  ],
1312
1499
  },
1313
1500
  };
1314
1501
 
1315
1502
  definition.resources.Resources.FriggIGWAttachment =
1316
- definition.resources.Resources.FriggIGWAttachment || {
1503
+ definition.resources.Resources
1504
+ .FriggIGWAttachment || {
1317
1505
  Type: 'AWS::EC2::VPCGatewayAttachment',
1318
1506
  Properties: {
1319
1507
  VpcId: discoveredResources.defaultVpcId,
1320
- InternetGatewayId: { Ref: 'FriggInternetGateway' },
1508
+ InternetGatewayId: {
1509
+ Ref: 'FriggInternetGateway',
1510
+ },
1321
1511
  },
1322
1512
  };
1323
1513
  }
@@ -1327,11 +1517,17 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1327
1517
  Properties: {
1328
1518
  VpcId: discoveredResources.defaultVpcId,
1329
1519
  CidrBlock:
1330
- AppDefinition.vpc.natGateway?.publicSubnetCidr || '172.31.250.0/24',
1331
- AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
1520
+ AppDefinition.vpc.natGateway
1521
+ ?.publicSubnetCidr || '172.31.250.0/24',
1522
+ AvailabilityZone: {
1523
+ 'Fn::Select': [0, { 'Fn::GetAZs': '' }],
1524
+ },
1332
1525
  MapPublicIpOnLaunch: true,
1333
1526
  Tags: [
1334
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-subnet' },
1527
+ {
1528
+ Key: 'Name',
1529
+ Value: '${self:service}-${self:provider.stage}-public-subnet',
1530
+ },
1335
1531
  { Key: 'Type', Value: 'Public' },
1336
1532
  ],
1337
1533
  },
@@ -1342,28 +1538,37 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1342
1538
  Properties: {
1343
1539
  VpcId: discoveredResources.defaultVpcId,
1344
1540
  Tags: [
1345
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' },
1541
+ {
1542
+ Key: 'Name',
1543
+ Value: '${self:service}-${self:provider.stage}-public-rt',
1544
+ },
1346
1545
  ],
1347
1546
  },
1348
1547
  };
1349
1548
 
1350
1549
  definition.resources.Resources.FriggPublicRoute = {
1351
1550
  Type: 'AWS::EC2::Route',
1352
- DependsOn: discoveredResources.internetGatewayId ? [] : 'FriggIGWAttachment',
1551
+ DependsOn: discoveredResources.internetGatewayId
1552
+ ? []
1553
+ : 'FriggIGWAttachment',
1353
1554
  Properties: {
1354
1555
  RouteTableId: { Ref: 'FriggPublicRouteTable' },
1355
1556
  DestinationCidrBlock: '0.0.0.0/0',
1356
- GatewayId: discoveredResources.internetGatewayId || { Ref: 'FriggInternetGateway' },
1557
+ GatewayId:
1558
+ discoveredResources.internetGatewayId || {
1559
+ Ref: 'FriggInternetGateway',
1560
+ },
1357
1561
  },
1358
1562
  };
1359
1563
 
1360
- definition.resources.Resources.FriggPublicSubnetRouteTableAssociation = {
1361
- Type: 'AWS::EC2::SubnetRouteTableAssociation',
1362
- Properties: {
1363
- SubnetId: { Ref: 'FriggPublicSubnet' },
1364
- RouteTableId: { Ref: 'FriggPublicRouteTable' },
1365
- },
1366
- };
1564
+ definition.resources.Resources.FriggPublicSubnetRouteTableAssociation =
1565
+ {
1566
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1567
+ Properties: {
1568
+ SubnetId: { Ref: 'FriggPublicSubnet' },
1569
+ RouteTableId: { Ref: 'FriggPublicRouteTable' },
1570
+ },
1571
+ };
1367
1572
  }
1368
1573
 
1369
1574
  definition.resources.Resources.FriggNATGateway = {
@@ -1373,11 +1578,20 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1373
1578
  Properties: {
1374
1579
  AllocationId: useExistingEip
1375
1580
  ? discoveredResources.existingElasticIpAllocationId
1376
- : { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
1377
- SubnetId:
1378
- discoveredResources.publicSubnetId || { Ref: 'FriggPublicSubnet' },
1581
+ : {
1582
+ 'Fn::GetAtt': [
1583
+ 'FriggNATGatewayEIP',
1584
+ 'AllocationId',
1585
+ ],
1586
+ },
1587
+ SubnetId: discoveredResources.publicSubnetId || {
1588
+ Ref: 'FriggPublicSubnet',
1589
+ },
1379
1590
  Tags: [
1380
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' },
1591
+ {
1592
+ Key: 'Name',
1593
+ Value: '${self:service}-${self:provider.stage}-nat-gateway',
1594
+ },
1381
1595
  { Key: 'ManagedBy', Value: 'Frigg' },
1382
1596
  { Key: 'Service', Value: '${self:service}' },
1383
1597
  { Key: 'Stage', Value: '${self:provider.stage}' },
@@ -1389,9 +1603,15 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1389
1603
  natGatewayManagement === 'discover' ||
1390
1604
  natGatewayManagement === 'useExisting'
1391
1605
  ) {
1392
- if (natGatewayManagement === 'useExisting' && AppDefinition.vpc.natGateway?.id) {
1393
- console.log(`Using explicitly provided NAT Gateway: ${AppDefinition.vpc.natGateway.id}`);
1394
- discoveredResources.existingNatGatewayId = AppDefinition.vpc.natGateway.id;
1606
+ if (
1607
+ natGatewayManagement === 'useExisting' &&
1608
+ AppDefinition.vpc.natGateway?.id
1609
+ ) {
1610
+ console.log(
1611
+ `Using explicitly provided NAT Gateway: ${AppDefinition.vpc.natGateway.id}`
1612
+ );
1613
+ discoveredResources.existingNatGatewayId =
1614
+ AppDefinition.vpc.natGateway.id;
1395
1615
  }
1396
1616
 
1397
1617
  if (discoveredResources.existingNatGatewayId) {
@@ -1401,14 +1621,20 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1401
1621
  );
1402
1622
 
1403
1623
  if (discoveredResources.natGatewayInPrivateSubnet) {
1404
- console.log('❌ CRITICAL: NAT Gateway is in PRIVATE subnet - Internet connectivity will NOT work!');
1624
+ console.log(
1625
+ '❌ CRITICAL: NAT Gateway is in PRIVATE subnet - Internet connectivity will NOT work!'
1626
+ );
1405
1627
 
1406
1628
  if (AppDefinition.vpc.selfHeal === true) {
1407
- console.log('Self-heal enabled: Will create new NAT Gateway in PUBLIC subnet');
1629
+ console.log(
1630
+ 'Self-heal enabled: Will create new NAT Gateway in PUBLIC subnet'
1631
+ );
1408
1632
  needsNewNatGateway = true;
1409
1633
  discoveredResources.existingNatGatewayId = null;
1410
1634
  if (!discoveredResources.publicSubnetId) {
1411
- console.log('No public subnet found - will create one for NAT Gateway');
1635
+ console.log(
1636
+ 'No public subnet found - will create one for NAT Gateway'
1637
+ );
1412
1638
  discoveredResources.createPublicSubnet = true;
1413
1639
  }
1414
1640
  } else {
@@ -1417,52 +1643,73 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1417
1643
  );
1418
1644
  }
1419
1645
  } else {
1420
- console.log(`Using discovered NAT Gateway for routing: ${discoveredResources.existingNatGatewayId}`);
1646
+ console.log(
1647
+ `Using discovered NAT Gateway for routing: ${discoveredResources.existingNatGatewayId}`
1648
+ );
1421
1649
  }
1422
- } else if (!needsNewNatGateway && AppDefinition.vpc.natGateway?.id) {
1423
- console.log(`Using explicitly provided NAT Gateway: ${AppDefinition.vpc.natGateway.id}`);
1424
- discoveredResources.existingNatGatewayId = AppDefinition.vpc.natGateway.id;
1650
+ } else if (
1651
+ !needsNewNatGateway &&
1652
+ AppDefinition.vpc.natGateway?.id
1653
+ ) {
1654
+ console.log(
1655
+ `Using explicitly provided NAT Gateway: ${AppDefinition.vpc.natGateway.id}`
1656
+ );
1657
+ discoveredResources.existingNatGatewayId =
1658
+ AppDefinition.vpc.natGateway.id;
1425
1659
  }
1426
1660
  }
1427
1661
 
1428
- definition.resources.Resources.FriggLambdaRouteTable =
1429
- definition.resources.Resources.FriggLambdaRouteTable || {
1430
- Type: 'AWS::EC2::RouteTable',
1431
- Properties: {
1432
- VpcId: discoveredResources.defaultVpcId || vpcId,
1433
- Tags: [
1434
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' },
1435
- { Key: 'ManagedBy', Value: 'Frigg' },
1436
- { Key: 'Environment', Value: '${self:provider.stage}' },
1437
- { Key: 'Service', Value: '${self:service}' },
1438
- ],
1439
- },
1440
- };
1662
+ definition.resources.Resources.FriggLambdaRouteTable = definition
1663
+ .resources.Resources.FriggLambdaRouteTable || {
1664
+ Type: 'AWS::EC2::RouteTable',
1665
+ Properties: {
1666
+ VpcId: discoveredResources.defaultVpcId || vpcId,
1667
+ Tags: [
1668
+ {
1669
+ Key: 'Name',
1670
+ Value: '${self:service}-${self:provider.stage}-lambda-rt',
1671
+ },
1672
+ { Key: 'ManagedBy', Value: 'Frigg' },
1673
+ { Key: 'Environment', Value: '${self:provider.stage}' },
1674
+ { Key: 'Service', Value: '${self:service}' },
1675
+ ],
1676
+ },
1677
+ };
1441
1678
 
1442
1679
  const routeTableId = { Ref: 'FriggLambdaRouteTable' };
1443
1680
  let natGatewayIdForRoute;
1444
1681
 
1445
1682
  if (reuseExistingNatGateway) {
1446
1683
  natGatewayIdForRoute = discoveredResources.existingNatGatewayId;
1447
- console.log(`Using discovered NAT Gateway for routing: ${natGatewayIdForRoute}`);
1684
+ console.log(
1685
+ `Using discovered NAT Gateway for routing: ${natGatewayIdForRoute}`
1686
+ );
1448
1687
  } else if (needsNewNatGateway && !reuseExistingNatGateway) {
1449
1688
  natGatewayIdForRoute = { Ref: 'FriggNATGateway' };
1450
1689
  console.log('Using newly created NAT Gateway for routing');
1451
1690
  } else if (discoveredResources.existingNatGatewayId) {
1452
1691
  natGatewayIdForRoute = discoveredResources.existingNatGatewayId;
1453
- console.log(`Using discovered NAT Gateway for routing: ${natGatewayIdForRoute}`);
1692
+ console.log(
1693
+ `Using discovered NAT Gateway for routing: ${natGatewayIdForRoute}`
1694
+ );
1454
1695
  } else if (AppDefinition.vpc.natGateway?.id) {
1455
1696
  natGatewayIdForRoute = AppDefinition.vpc.natGateway.id;
1456
- console.log(`Using explicitly provided NAT Gateway for routing: ${natGatewayIdForRoute}`);
1697
+ console.log(
1698
+ `Using explicitly provided NAT Gateway for routing: ${natGatewayIdForRoute}`
1699
+ );
1457
1700
  } else if (AppDefinition.vpc.selfHeal === true) {
1458
1701
  natGatewayIdForRoute = null;
1459
- console.log('No NAT Gateway available - skipping NAT route creation');
1702
+ console.log(
1703
+ 'No NAT Gateway available - skipping NAT route creation'
1704
+ );
1460
1705
  } else {
1461
1706
  throw new Error('No existing NAT Gateway found in discovery mode');
1462
1707
  }
1463
1708
 
1464
1709
  if (natGatewayIdForRoute) {
1465
- console.log(`Configuring NAT route: 0.0.0.0/0 → ${natGatewayIdForRoute}`);
1710
+ console.log(
1711
+ `Configuring NAT route: 0.0.0.0/0 → ${natGatewayIdForRoute}`
1712
+ );
1466
1713
  definition.resources.Resources.FriggNATRoute = {
1467
1714
  Type: 'AWS::EC2::Route',
1468
1715
  DependsOn: 'FriggLambdaRouteTable',
@@ -1473,7 +1720,9 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1473
1720
  },
1474
1721
  };
1475
1722
  } else {
1476
- console.warn('⚠️ No NAT Gateway configured - Lambda functions will not have internet access');
1723
+ console.warn(
1724
+ '⚠️ No NAT Gateway configured - Lambda functions will not have internet access'
1725
+ );
1477
1726
  }
1478
1727
 
1479
1728
  if (typeof vpcConfig.subnetIds[0] === 'string') {
@@ -1498,25 +1747,37 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1498
1747
  };
1499
1748
  }
1500
1749
 
1501
- if (typeof vpcConfig.subnetIds[0] === 'object' && vpcConfig.subnetIds[0].Ref) {
1750
+ if (
1751
+ typeof vpcConfig.subnetIds[0] === 'object' &&
1752
+ vpcConfig.subnetIds[0].Ref
1753
+ ) {
1502
1754
  definition.resources.Resources.FriggNewSubnet1RouteAssociation = {
1503
1755
  Type: 'AWS::EC2::SubnetRouteTableAssociation',
1504
1756
  Properties: {
1505
1757
  SubnetId: vpcConfig.subnetIds[0],
1506
1758
  RouteTableId: routeTableId,
1507
1759
  },
1508
- DependsOn: ['FriggLambdaRouteTable', vpcConfig.subnetIds[0].Ref],
1760
+ DependsOn: [
1761
+ 'FriggLambdaRouteTable',
1762
+ vpcConfig.subnetIds[0].Ref,
1763
+ ],
1509
1764
  };
1510
1765
  }
1511
1766
 
1512
- if (typeof vpcConfig.subnetIds[1] === 'object' && vpcConfig.subnetIds[1].Ref) {
1767
+ if (
1768
+ typeof vpcConfig.subnetIds[1] === 'object' &&
1769
+ vpcConfig.subnetIds[1].Ref
1770
+ ) {
1513
1771
  definition.resources.Resources.FriggNewSubnet2RouteAssociation = {
1514
1772
  Type: 'AWS::EC2::SubnetRouteTableAssociation',
1515
1773
  Properties: {
1516
1774
  SubnetId: vpcConfig.subnetIds[1],
1517
1775
  RouteTableId: routeTableId,
1518
1776
  },
1519
- DependsOn: ['FriggLambdaRouteTable', vpcConfig.subnetIds[1].Ref],
1777
+ DependsOn: [
1778
+ 'FriggLambdaRouteTable',
1779
+ vpcConfig.subnetIds[1].Ref,
1780
+ ],
1520
1781
  };
1521
1782
  }
1522
1783
 
@@ -1535,7 +1796,8 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1535
1796
  Type: 'AWS::EC2::VPCEndpoint',
1536
1797
  Properties: {
1537
1798
  VpcId: discoveredResources.defaultVpcId,
1538
- ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
1799
+ ServiceName:
1800
+ 'com.amazonaws.${self:provider.region}.dynamodb',
1539
1801
  VpcEndpointType: 'Gateway',
1540
1802
  RouteTableIds: [routeTableId],
1541
1803
  },
@@ -1552,7 +1814,10 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1552
1814
  if (!definition.resources.Resources.VPCEndpointSecurityGroup) {
1553
1815
  const vpcEndpointIngressRules = [];
1554
1816
 
1555
- if (vpcConfig.securityGroupIds && vpcConfig.securityGroupIds.length > 0) {
1817
+ if (
1818
+ vpcConfig.securityGroupIds &&
1819
+ vpcConfig.securityGroupIds.length > 0
1820
+ ) {
1556
1821
  for (const sg of vpcConfig.securityGroupIds) {
1557
1822
  if (typeof sg === 'string') {
1558
1823
  vpcEndpointIngressRules.push({
@@ -1600,13 +1865,20 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1600
1865
  definition.resources.Resources.VPCEndpointSecurityGroup = {
1601
1866
  Type: 'AWS::EC2::SecurityGroup',
1602
1867
  Properties: {
1603
- GroupDescription: 'Security group for VPC endpoints - allows HTTPS from Lambda functions',
1868
+ GroupDescription:
1869
+ 'Security group for VPC endpoints - allows HTTPS from Lambda functions',
1604
1870
  VpcId: discoveredResources.defaultVpcId,
1605
1871
  SecurityGroupIngress: vpcEndpointIngressRules,
1606
1872
  Tags: [
1607
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoints-sg' },
1873
+ {
1874
+ Key: 'Name',
1875
+ Value: '${self:service}-${self:provider.stage}-vpc-endpoints-sg',
1876
+ },
1608
1877
  { Key: 'ManagedBy', Value: 'Frigg' },
1609
- { Key: 'Purpose', Value: 'Allow Lambda functions to access VPC endpoints' },
1878
+ {
1879
+ Key: 'Purpose',
1880
+ Value: 'Allow Lambda functions to access VPC endpoints',
1881
+ },
1610
1882
  ],
1611
1883
  },
1612
1884
  };
@@ -1624,14 +1896,13 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1624
1896
  },
1625
1897
  };
1626
1898
 
1627
- // Create Secrets Manager VPC Endpoint if explicitly enabled OR if Aurora is enabled
1628
- // (Aurora requires Secrets Manager access for credential retrieval)
1629
- if (AppDefinition.secretsManager?.enable === true || AppDefinition.database?.postgres?.enable === true) {
1899
+ if (AppDefinition.secretsManager?.enable === true) {
1630
1900
  definition.resources.Resources.VPCEndpointSecretsManager = {
1631
1901
  Type: 'AWS::EC2::VPCEndpoint',
1632
1902
  Properties: {
1633
1903
  VpcId: discoveredResources.defaultVpcId,
1634
- ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
1904
+ ServiceName:
1905
+ 'com.amazonaws.${self:provider.region}.secretsmanager',
1635
1906
  VpcEndpointType: 'Interface',
1636
1907
  SubnetIds: vpcConfig.subnetIds,
1637
1908
  SecurityGroupIds: [{ Ref: 'VPCEndpointSecurityGroup' }],
@@ -1643,283 +1914,6 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
1643
1914
  }
1644
1915
  };
1645
1916
 
1646
- const createAuroraInfrastructure = (definition, AppDefinition, discoveredResources) => {
1647
- const dbConfig = AppDefinition.database.postgres;
1648
-
1649
- console.log('🔧 Creating Aurora Serverless v2 infrastructure...');
1650
-
1651
- // 1. DB Subnet Group (using Lambda private subnets)
1652
- definition.resources.Resources.FriggDBSubnetGroup = {
1653
- Type: 'AWS::RDS::DBSubnetGroup',
1654
- Properties: {
1655
- DBSubnetGroupDescription: 'Subnet group for Frigg Aurora cluster',
1656
- SubnetIds: [
1657
- discoveredResources.privateSubnetId1,
1658
- discoveredResources.privateSubnetId2
1659
- ],
1660
- Tags: [
1661
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-db-subnet-group' },
1662
- { Key: 'ManagedBy', Value: 'Frigg' },
1663
- { Key: 'Service', Value: '${self:service}' },
1664
- { Key: 'Stage', Value: '${self:provider.stage}' },
1665
- ]
1666
- }
1667
- };
1668
-
1669
- // 2. Security Group (allow Lambda SG to access 5432)
1670
- // In create-new VPC mode, Lambda uses FriggLambdaSecurityGroup
1671
- // In other modes, use discovered default security group
1672
- const lambdaSecurityGroupId = AppDefinition.vpc?.management === 'create-new'
1673
- ? { Ref: 'FriggLambdaSecurityGroup' }
1674
- : discoveredResources.defaultSecurityGroupId;
1675
-
1676
- definition.resources.Resources.FriggAuroraSecurityGroup = {
1677
- Type: 'AWS::EC2::SecurityGroup',
1678
- Properties: {
1679
- GroupDescription: 'Security group for Frigg Aurora PostgreSQL',
1680
- VpcId: discoveredResources.defaultVpcId,
1681
- SecurityGroupIngress: [
1682
- {
1683
- IpProtocol: 'tcp',
1684
- FromPort: 5432,
1685
- ToPort: 5432,
1686
- SourceSecurityGroupId: lambdaSecurityGroupId,
1687
- Description: 'PostgreSQL access from Lambda functions'
1688
- }
1689
- ],
1690
- Tags: [
1691
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-sg' },
1692
- { Key: 'ManagedBy', Value: 'Frigg' },
1693
- { Key: 'Service', Value: '${self:service}' },
1694
- { Key: 'Stage', Value: '${self:provider.stage}' },
1695
- ]
1696
- }
1697
- };
1698
-
1699
- // 3. Secrets Manager Secret (database credentials)
1700
- definition.resources.Resources.FriggDatabaseSecret = {
1701
- Type: 'AWS::SecretsManager::Secret',
1702
- Properties: {
1703
- Name: '${self:service}-${self:provider.stage}-aurora-credentials',
1704
- Description: 'Aurora PostgreSQL credentials for Frigg application',
1705
- GenerateSecretString: {
1706
- SecretStringTemplate: JSON.stringify({
1707
- username: dbConfig.masterUsername || 'frigg_admin'
1708
- }),
1709
- GenerateStringKey: 'password',
1710
- PasswordLength: 32,
1711
- ExcludeCharacters: '"@/\\'
1712
- },
1713
- Tags: [
1714
- { Key: 'ManagedBy', Value: 'Frigg' },
1715
- { Key: 'Service', Value: '${self:service}' },
1716
- { Key: 'Stage', Value: '${self:provider.stage}' },
1717
- ]
1718
- }
1719
- };
1720
-
1721
- // 4. Aurora Serverless v2 Cluster
1722
- definition.resources.Resources.FriggAuroraCluster = {
1723
- Type: 'AWS::RDS::DBCluster',
1724
- DeletionPolicy: 'Snapshot',
1725
- UpdateReplacePolicy: 'Snapshot',
1726
- Properties: {
1727
- Engine: 'aurora-postgresql',
1728
- EngineVersion: dbConfig.engineVersion || '15.3',
1729
- EngineMode: 'provisioned', // Required for Serverless v2
1730
- DatabaseName: dbConfig.databaseName || 'frigg_db',
1731
- MasterUsername: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
1732
- MasterUserPassword: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
1733
- DBSubnetGroupName: { Ref: 'FriggDBSubnetGroup' },
1734
- VpcSecurityGroupIds: [{ Ref: 'FriggAuroraSecurityGroup' }],
1735
- ServerlessV2ScalingConfiguration: {
1736
- MinCapacity: dbConfig.scaling?.minCapacity || 0.5,
1737
- MaxCapacity: dbConfig.scaling?.maxCapacity || 1.0
1738
- },
1739
- BackupRetentionPeriod: dbConfig.backupRetentionDays || 7,
1740
- PreferredBackupWindow: dbConfig.preferredBackupWindow || '03:00-04:00',
1741
- DeletionProtection: dbConfig.deletionProtection !== false,
1742
- EnableCloudwatchLogsExports: ['postgresql'],
1743
- Tags: [
1744
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-cluster' },
1745
- { Key: 'ManagedBy', Value: 'Frigg' },
1746
- { Key: 'Service', Value: '${self:service}' },
1747
- { Key: 'Stage', Value: '${self:provider.stage}' },
1748
- ]
1749
- }
1750
- };
1751
-
1752
- // 5. Aurora Serverless v2 Instance
1753
- definition.resources.Resources.FriggAuroraInstance = {
1754
- Type: 'AWS::RDS::DBInstance',
1755
- Properties: {
1756
- Engine: 'aurora-postgresql',
1757
- DBInstanceClass: 'db.serverless',
1758
- DBClusterIdentifier: { Ref: 'FriggAuroraCluster' },
1759
- PubliclyAccessible: false,
1760
- EnablePerformanceInsights: dbConfig.enablePerformanceInsights || false,
1761
- Tags: [
1762
- { Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-instance' },
1763
- { Key: 'ManagedBy', Value: 'Frigg' },
1764
- { Key: 'Service', Value: '${self:service}' },
1765
- { Key: 'Stage', Value: '${self:provider.stage}' },
1766
- ]
1767
- }
1768
- };
1769
-
1770
- // 6. Secret Attachment (links cluster to secret)
1771
- definition.resources.Resources.FriggSecretAttachment = {
1772
- Type: 'AWS::SecretsManager::SecretTargetAttachment',
1773
- Properties: {
1774
- SecretId: { Ref: 'FriggDatabaseSecret' },
1775
- TargetId: { Ref: 'FriggAuroraCluster' },
1776
- TargetType: 'AWS::RDS::DBCluster'
1777
- }
1778
- };
1779
-
1780
- // 7. Add IAM permissions for Secrets Manager
1781
- definition.provider.iamRoleStatements.push({
1782
- Effect: 'Allow',
1783
- Action: [
1784
- 'secretsmanager:GetSecretValue',
1785
- 'secretsmanager:DescribeSecret'
1786
- ],
1787
- Resource: { Ref: 'FriggDatabaseSecret' }
1788
- });
1789
-
1790
- // 8. Set DATABASE_URL environment variable
1791
- definition.provider.environment.DATABASE_URL = {
1792
- 'Fn::Sub': [
1793
- 'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
1794
- {
1795
- Username: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
1796
- Password: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
1797
- Endpoint: { 'Fn::GetAtt': ['FriggAuroraCluster', 'Endpoint'] },
1798
- Port: { 'Fn::GetAtt': ['FriggAuroraCluster', 'Port'] },
1799
- DatabaseName: dbConfig.databaseName || 'frigg_db'
1800
- }
1801
- ]
1802
- };
1803
-
1804
- // 9. Set DB_TYPE for Prisma client selection
1805
- definition.provider.environment.DB_TYPE = 'postgresql';
1806
-
1807
- console.log('✅ Aurora infrastructure resources created');
1808
- };
1809
-
1810
- const useExistingAurora = (definition, AppDefinition, discoveredResources) => {
1811
- const dbConfig = AppDefinition.database.postgres;
1812
-
1813
- console.log(`🔗 Using existing Aurora cluster: ${discoveredResources.aurora.clusterIdentifier}`);
1814
-
1815
- // Add IAM permissions for Secrets Manager if secret exists
1816
- if (discoveredResources.aurora.secretArn) {
1817
- definition.provider.iamRoleStatements.push({
1818
- Effect: 'Allow',
1819
- Action: [
1820
- 'secretsmanager:GetSecretValue',
1821
- 'secretsmanager:DescribeSecret'
1822
- ],
1823
- Resource: discoveredResources.aurora.secretArn
1824
- });
1825
-
1826
- // Set DATABASE_URL from discovered secret
1827
- definition.provider.environment.DATABASE_URL = {
1828
- 'Fn::Sub': [
1829
- 'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
1830
- {
1831
- Username: { 'Fn::Sub': `{{resolve:secretsmanager:${discoveredResources.aurora.secretArn}:SecretString:username}}` },
1832
- Password: { 'Fn::Sub': `{{resolve:secretsmanager:${discoveredResources.aurora.secretArn}:SecretString:password}}` },
1833
- Endpoint: discoveredResources.aurora.endpoint,
1834
- Port: discoveredResources.aurora.port,
1835
- DatabaseName: dbConfig.databaseName || 'frigg_db'
1836
- }
1837
- ]
1838
- };
1839
- } else if (dbConfig.secretArn) {
1840
- // Use user-provided secret ARN
1841
- definition.provider.iamRoleStatements.push({
1842
- Effect: 'Allow',
1843
- Action: [
1844
- 'secretsmanager:GetSecretValue',
1845
- 'secretsmanager:DescribeSecret'
1846
- ],
1847
- Resource: dbConfig.secretArn
1848
- });
1849
-
1850
- definition.provider.environment.DATABASE_URL = {
1851
- 'Fn::Sub': [
1852
- 'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
1853
- {
1854
- Username: { 'Fn::Sub': `{{resolve:secretsmanager:${dbConfig.secretArn}:SecretString:username}}` },
1855
- Password: { 'Fn::Sub': `{{resolve:secretsmanager:${dbConfig.secretArn}:SecretString:password}}` },
1856
- Endpoint: discoveredResources.aurora.endpoint,
1857
- Port: discoveredResources.aurora.port,
1858
- DatabaseName: dbConfig.databaseName || 'frigg_db'
1859
- }
1860
- ]
1861
- };
1862
- } else {
1863
- throw new Error('No database secret found. Provide secretArn in database.postgres configuration or ensure Secrets Manager secret exists.');
1864
- }
1865
-
1866
- // Set DB_TYPE for Prisma client selection
1867
- definition.provider.environment.DB_TYPE = 'postgresql';
1868
-
1869
- console.log('✅ Existing Aurora cluster configured');
1870
- };
1871
-
1872
- const useDiscoveredAurora = (definition, AppDefinition, discoveredResources) => {
1873
- console.log(`🔍 Using discovered Aurora cluster: ${discoveredResources.aurora.clusterIdentifier}`);
1874
- useExistingAurora(definition, AppDefinition, discoveredResources);
1875
- };
1876
-
1877
- const configurePostgres = (definition, AppDefinition, discoveredResources) => {
1878
- if (!AppDefinition.database?.postgres?.enable) {
1879
- return;
1880
- }
1881
-
1882
- // Validate VPC is enabled (required for Aurora deployment)
1883
- if (!AppDefinition.vpc?.enable) {
1884
- throw new Error(
1885
- 'Aurora PostgreSQL requires VPC deployment. ' +
1886
- 'Set vpc.enable to true in your app definition.'
1887
- );
1888
- }
1889
-
1890
- // Validate private subnets exist (Aurora requires at least 2 subnets in different AZs)
1891
- // Skip validation if VPC management is 'create-new' (subnets will be created)
1892
- const vpcManagement = AppDefinition.vpc?.management || 'discover';
1893
- if (vpcManagement !== 'create-new' && (!discoveredResources.privateSubnetId1 || !discoveredResources.privateSubnetId2)) {
1894
- throw new Error(
1895
- 'Aurora PostgreSQL requires at least 2 private subnets in different availability zones. ' +
1896
- 'No private subnets were discovered in your VPC. ' +
1897
- 'Please create private subnets or use VPC management mode "create-new".'
1898
- );
1899
- }
1900
-
1901
- const dbConfig = AppDefinition.database.postgres;
1902
- const management = dbConfig.management || 'discover';
1903
-
1904
- console.log(`\n🐘 PostgreSQL Management Mode: ${management}`);
1905
-
1906
- if (management === 'create-new' || discoveredResources.aurora?.needsCreation) {
1907
- createAuroraInfrastructure(definition, AppDefinition, discoveredResources);
1908
- } else if (management === 'use-existing') {
1909
- if (!discoveredResources.aurora?.clusterIdentifier && !dbConfig.clusterIdentifier) {
1910
- throw new Error('PostgreSQL management is set to "use-existing" but no clusterIdentifier was found or provided');
1911
- }
1912
- useExistingAurora(definition, AppDefinition, discoveredResources);
1913
- } else {
1914
- // discover mode
1915
- if (discoveredResources.aurora?.clusterIdentifier) {
1916
- useDiscoveredAurora(definition, AppDefinition, discoveredResources);
1917
- } else {
1918
- throw new Error('No Aurora cluster found in discovery mode. Set management to "create-new" or provide clusterIdentifier with "use-existing".');
1919
- }
1920
- }
1921
- };
1922
-
1923
1917
  const configureSsm = (definition, AppDefinition) => {
1924
1918
  if (AppDefinition.ssm?.enable !== true) {
1925
1919
  return;
@@ -1931,19 +1925,31 @@ const configureSsm = (definition, AppDefinition) => {
1931
1925
 
1932
1926
  definition.provider.iamRoleStatements.push({
1933
1927
  Effect: 'Allow',
1934
- Action: ['ssm:GetParameter', 'ssm:GetParameters', 'ssm:GetParametersByPath'],
1935
- Resource: ['arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'],
1928
+ Action: [
1929
+ 'ssm:GetParameter',
1930
+ 'ssm:GetParameters',
1931
+ 'ssm:GetParametersByPath',
1932
+ ],
1933
+ Resource: [
1934
+ 'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*',
1935
+ ],
1936
1936
  });
1937
1937
 
1938
- definition.provider.environment.SSM_PARAMETER_PREFIX = '/${self:service}/${self:provider.stage}';
1938
+ definition.provider.environment.SSM_PARAMETER_PREFIX =
1939
+ '/${self:service}/${self:provider.stage}';
1939
1940
  };
1940
1941
 
1941
1942
  const attachIntegrations = (definition, AppDefinition) => {
1942
- if (!Array.isArray(AppDefinition.integrations) || AppDefinition.integrations.length === 0) {
1943
+ if (
1944
+ !Array.isArray(AppDefinition.integrations) ||
1945
+ AppDefinition.integrations.length === 0
1946
+ ) {
1943
1947
  return;
1944
1948
  }
1945
1949
 
1946
- console.log(`Processing ${AppDefinition.integrations.length} integrations...`);
1950
+ console.log(
1951
+ `Processing ${AppDefinition.integrations.length} integrations...`
1952
+ );
1947
1953
 
1948
1954
  for (const integration of AppDefinition.integrations) {
1949
1955
  if (!integration?.Definition?.name) {
@@ -1951,7 +1957,9 @@ const attachIntegrations = (definition, AppDefinition) => {
1951
1957
  }
1952
1958
 
1953
1959
  const integrationName = integration.Definition.name;
1954
- const queueReference = `${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)}Queue`;
1960
+ const queueReference = `${
1961
+ integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
1962
+ }Queue`;
1955
1963
  const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
1956
1964
 
1957
1965
  definition.functions[integrationName] = {
@@ -1974,7 +1982,9 @@ const attachIntegrations = (definition, AppDefinition) => {
1974
1982
  VisibilityTimeout: 1800,
1975
1983
  RedrivePolicy: {
1976
1984
  maxReceiveCount: 1,
1977
- deadLetterTargetArn: { 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'] },
1985
+ deadLetterTargetArn: {
1986
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
1987
+ },
1978
1988
  },
1979
1989
  },
1980
1990
  };
@@ -1996,7 +2006,9 @@ const attachIntegrations = (definition, AppDefinition) => {
1996
2006
 
1997
2007
  definition.provider.environment = {
1998
2008
  ...definition.provider.environment,
1999
- [`${integrationName.toUpperCase()}_QUEUE_URL`]: { Ref: queueReference },
2009
+ [`${integrationName.toUpperCase()}_QUEUE_URL`]: {
2010
+ Ref: queueReference,
2011
+ },
2000
2012
  };
2001
2013
 
2002
2014
  definition.custom[queueReference] = queueName;
@@ -2009,7 +2021,8 @@ const configureWebsockets = (definition, AppDefinition) => {
2009
2021
  }
2010
2022
 
2011
2023
  definition.functions.defaultWebsocket = {
2012
- handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
2024
+ handler:
2025
+ 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
2013
2026
  events: [
2014
2027
  { websocket: { route: '$connect' } },
2015
2028
  { websocket: { route: '$default' } },
@@ -2018,49 +2031,24 @@ const configureWebsockets = (definition, AppDefinition) => {
2018
2031
  };
2019
2032
  };
2020
2033
 
2021
- /**
2022
- * Ensure Prisma Lambda Layer exists
2023
- * Automatically builds the layer if it doesn't exist in the project root
2024
- */
2025
- async function ensurePrismaLayerExists() {
2026
- const projectRoot = process.cwd();
2027
- const layerPath = path.join(projectRoot, 'layers/prisma');
2028
-
2029
- // Check if layer already exists
2030
- if (fs.existsSync(layerPath)) {
2031
- console.log('✓ Prisma Lambda Layer already exists at', layerPath);
2032
- return;
2033
- }
2034
-
2035
- // Layer doesn't exist - build it automatically
2036
- console.log('📦 Prisma Lambda Layer not found - building automatically...');
2037
- console.log(' This may take a minute on first deployment.\n');
2038
-
2039
- try {
2040
- await buildPrismaLayer();
2041
- console.log('✓ Prisma Lambda Layer built successfully\n');
2042
- } catch (error) {
2043
- console.error('✗ Failed to build Prisma Lambda Layer:', error.message);
2044
- console.error(' You may need to run: npm install @friggframework/core\n');
2045
- throw error;
2046
- }
2047
- }
2048
-
2049
2034
  const composeServerlessDefinition = async (AppDefinition) => {
2050
2035
  console.log('composeServerlessDefinition', AppDefinition);
2051
2036
 
2052
- // Ensure Prisma layer exists before generating serverless config
2053
- await ensurePrismaLayerExists();
2054
-
2055
2037
  const discoveredResources = await gatherDiscoveredResources(AppDefinition);
2056
2038
  const appEnvironmentVars = getAppEnvironmentVars(AppDefinition);
2057
- const definition = createBaseDefinition(AppDefinition, appEnvironmentVars, discoveredResources);
2039
+ const definition = createBaseDefinition(
2040
+ AppDefinition,
2041
+ appEnvironmentVars,
2042
+ discoveredResources
2043
+ );
2058
2044
 
2059
2045
  // Check if we're in local build mode (AWS discovery was skipped)
2060
2046
  const isLocalBuild = !shouldRunDiscovery(AppDefinition);
2061
2047
 
2062
2048
  if (isLocalBuild) {
2063
- console.log('🏠 Local build mode detected - skipping AWS-dependent configurations');
2049
+ console.log(
2050
+ '🏠 Local build mode detected - skipping AWS-dependent configurations'
2051
+ );
2064
2052
  }
2065
2053
 
2066
2054
  // Apply configurations (skip AWS-dependent ones in local build mode)
@@ -2070,7 +2058,9 @@ const composeServerlessDefinition = async (AppDefinition) => {
2070
2058
  configurePostgres(definition, AppDefinition, discoveredResources);
2071
2059
  configureSsm(definition, AppDefinition);
2072
2060
  } else {
2073
- console.log(' ⏭️ Skipping: KMS, VPC, PostgreSQL, SSM configurations');
2061
+ console.log(
2062
+ ' ⏭️ Skipping: KMS, VPC, PostgreSQL, SSM configurations'
2063
+ );
2074
2064
  }
2075
2065
 
2076
2066
  attachIntegrations(definition, AppDefinition);