@friggframework/devtools 2.0.0--canary.463.62579dd.0 → 2.0.0--canary.461.84ff4f5.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.
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +1 -1
- package/frigg-cli/db-setup-command/index.js +1 -1
- package/infrastructure/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/README.md +51 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/aws-discovery.js +507 -2
- package/infrastructure/aws-discovery.test.js +447 -1
- package/infrastructure/scripts/build-prisma-layer.js +534 -0
- package/infrastructure/serverless-template.js +602 -80
- package/infrastructure/serverless-template.test.js +91 -0
- package/package.json +8 -6
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/utils/prisma-runner.js +0 -280
|
@@ -1,6 +1,7 @@
|
|
|
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');
|
|
4
5
|
|
|
5
6
|
const shouldRunDiscovery = (AppDefinition) => {
|
|
6
7
|
console.log(
|
|
@@ -17,7 +18,8 @@ const shouldRunDiscovery = (AppDefinition) => {
|
|
|
17
18
|
return (
|
|
18
19
|
AppDefinition.vpc?.enable === true ||
|
|
19
20
|
AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
20
|
-
AppDefinition.ssm?.enable === true
|
|
21
|
+
AppDefinition.ssm?.enable === true ||
|
|
22
|
+
AppDefinition.database?.postgres?.enable === true
|
|
21
23
|
);
|
|
22
24
|
};
|
|
23
25
|
|
|
@@ -68,8 +70,7 @@ const getAppEnvironmentVars = (AppDefinition) => {
|
|
|
68
70
|
}
|
|
69
71
|
if (skippedKeys.length > 0) {
|
|
70
72
|
console.log(
|
|
71
|
-
` ⚠️ Skipped ${
|
|
72
|
-
skippedKeys.length
|
|
73
|
+
` ⚠️ Skipped ${skippedKeys.length
|
|
73
74
|
} reserved AWS Lambda variables: ${skippedKeys.join(', ')}`
|
|
74
75
|
);
|
|
75
76
|
}
|
|
@@ -544,10 +545,18 @@ const gatherDiscoveredResources = async (AppDefinition) => {
|
|
|
544
545
|
try {
|
|
545
546
|
const region = process.env.AWS_REGION || 'us-east-1';
|
|
546
547
|
const discovery = new AWSDiscovery(region);
|
|
548
|
+
// Use Serverless Framework's stage resolution (opt:stage with 'dev' as default)
|
|
549
|
+
// This matches how serverless.yml resolves ${opt:stage, "dev"}
|
|
550
|
+
// IMPORTANT: Use SLS_STAGE (not STAGE) to match actual deployment stage
|
|
551
|
+
const stage = process.env.SLS_STAGE || 'dev';
|
|
552
|
+
|
|
547
553
|
const config = {
|
|
548
554
|
vpc: AppDefinition.vpc || {},
|
|
549
555
|
encryption: AppDefinition.encryption || {},
|
|
550
556
|
ssm: AppDefinition.ssm || {},
|
|
557
|
+
database: AppDefinition.database || {},
|
|
558
|
+
serviceName: AppDefinition.name || 'create-frigg-app',
|
|
559
|
+
stage: stage,
|
|
551
560
|
};
|
|
552
561
|
|
|
553
562
|
const discoveredResources = await discovery.discoverResources(config);
|
|
@@ -603,6 +612,22 @@ const buildEnvironment = (appEnvironmentVars, discoveredResources) => {
|
|
|
603
612
|
}
|
|
604
613
|
}
|
|
605
614
|
|
|
615
|
+
// Add Aurora discovery mappings
|
|
616
|
+
if (discoveredResources.aurora) {
|
|
617
|
+
if (discoveredResources.aurora.clusterIdentifier) {
|
|
618
|
+
environment.AWS_DISCOVERY_AURORA_CLUSTER_ID = discoveredResources.aurora.clusterIdentifier;
|
|
619
|
+
}
|
|
620
|
+
if (discoveredResources.aurora.endpoint) {
|
|
621
|
+
environment.AWS_DISCOVERY_AURORA_ENDPOINT = discoveredResources.aurora.endpoint;
|
|
622
|
+
}
|
|
623
|
+
if (discoveredResources.aurora.port) {
|
|
624
|
+
environment.AWS_DISCOVERY_AURORA_PORT = discoveredResources.aurora.port.toString();
|
|
625
|
+
}
|
|
626
|
+
if (discoveredResources.aurora.secretArn) {
|
|
627
|
+
environment.AWS_DISCOVERY_AURORA_SECRET_ARN = discoveredResources.aurora.secretArn;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
606
631
|
return environment;
|
|
607
632
|
};
|
|
608
633
|
|
|
@@ -613,20 +638,72 @@ const createBaseDefinition = (
|
|
|
613
638
|
) => {
|
|
614
639
|
const region = process.env.AWS_REGION || 'us-east-1';
|
|
615
640
|
|
|
641
|
+
// Function-level package config to exclude Prisma and AWS SDK
|
|
642
|
+
// Uses native Serverless package.exclude since jetpack function-level config isn't supported in v3
|
|
643
|
+
const functionPackageConfig = {
|
|
644
|
+
exclude: [
|
|
645
|
+
// Exclude AWS SDK (already in Lambda runtime)
|
|
646
|
+
'node_modules/aws-sdk/**',
|
|
647
|
+
'node_modules/@aws-sdk/**',
|
|
648
|
+
|
|
649
|
+
// Exclude Prisma (provided via Lambda Layer)
|
|
650
|
+
'node_modules/@prisma/**',
|
|
651
|
+
'node_modules/.prisma/**',
|
|
652
|
+
'node_modules/prisma/**',
|
|
653
|
+
'node_modules/@friggframework/core/generated/**',
|
|
654
|
+
],
|
|
655
|
+
};
|
|
656
|
+
|
|
616
657
|
return {
|
|
617
658
|
frameworkVersion: '>=3.17.0',
|
|
618
659
|
service: AppDefinition.name || 'create-frigg-app',
|
|
619
660
|
package: {
|
|
620
661
|
individually: true,
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
662
|
+
// NOTE: These patterns are NOT used when serverless-jetpack is enabled with trace mode
|
|
663
|
+
// Jetpack's trace mode completely overrides package.patterns during dependency resolution
|
|
664
|
+
// These are kept commented out as a fallback if Jetpack needs to be disabled
|
|
665
|
+
patterns: [
|
|
666
|
+
// AWS SDK exclusions (already in Lambda runtime)
|
|
667
|
+
// '!**/node_modules/aws-sdk/**',
|
|
668
|
+
// '!**/node_modules/@aws-sdk/**',
|
|
669
|
+
|
|
670
|
+
// Prisma exclusions (provided via Lambda Layer)
|
|
671
|
+
// '!**/node_modules/@prisma/**',
|
|
672
|
+
// '!**/node_modules/.prisma/**',
|
|
673
|
+
// '!**/node_modules/@prisma-mongodb/**',
|
|
674
|
+
// '!**/node_modules/@prisma-postgresql/**',
|
|
675
|
+
// '!**/node_modules/prisma/**',
|
|
676
|
+
|
|
677
|
+
// Exclude Prisma generated clients from @friggframework/core
|
|
678
|
+
// '!**/node_modules/@friggframework/core/generated/**',
|
|
679
|
+
|
|
680
|
+
// Exclude development and test files
|
|
681
|
+
// '!**/test/**',
|
|
682
|
+
// '!**/tests/**',
|
|
683
|
+
// '!**/*.test.js',
|
|
684
|
+
// '!**/*.spec.js',
|
|
685
|
+
// '!**/*.map',
|
|
686
|
+
// '!**/jest.config.js',
|
|
687
|
+
// '!**/jest.unit.config.js',
|
|
688
|
+
// '!**/.eslintrc.json',
|
|
689
|
+
// '!**/.prettierrc',
|
|
690
|
+
// '!**/.prettierignore',
|
|
691
|
+
// '!**/.markdownlintignore',
|
|
692
|
+
// '!**/docker-compose.yml',
|
|
693
|
+
// '!**/package.json',
|
|
694
|
+
// '!**/README.md',
|
|
695
|
+
// '!**/*.md',
|
|
696
|
+
|
|
697
|
+
// Exclude .DS_Store and other OS files
|
|
698
|
+
// '!**/.DS_Store',
|
|
699
|
+
// '!**/.git/**',
|
|
700
|
+
// '!**/.claude-flow/**',
|
|
625
701
|
],
|
|
626
702
|
},
|
|
627
703
|
useDotenv: true,
|
|
628
704
|
provider: {
|
|
629
705
|
name: AppDefinition.provider || 'aws',
|
|
706
|
+
...(process.env.AWS_PROFILE && { profile: process.env.AWS_PROFILE }),
|
|
630
707
|
runtime: 'nodejs20.x',
|
|
631
708
|
timeout: 30,
|
|
632
709
|
region,
|
|
@@ -697,13 +774,17 @@ const createBaseDefinition = (
|
|
|
697
774
|
skipCacheInvalidation: false,
|
|
698
775
|
},
|
|
699
776
|
jetpack: {
|
|
700
|
-
base: '..',
|
|
777
|
+
base: '..', // Essential for reaching handlers in node_modules/@friggframework
|
|
778
|
+
// NOTE: Service-level preInclude applies to EVERYTHING (functions + layers)
|
|
779
|
+
// We need to ONLY exclude from functions, not from the Prisma layer
|
|
780
|
+
// Solution: Apply exclusions at function level instead
|
|
701
781
|
},
|
|
702
782
|
},
|
|
703
783
|
functions: {
|
|
704
784
|
auth: {
|
|
705
|
-
handler:
|
|
706
|
-
|
|
785
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
786
|
+
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
787
|
+
package: functionPackageConfig,
|
|
707
788
|
events: [
|
|
708
789
|
{ httpApi: { path: '/api/integrations', method: 'ANY' } },
|
|
709
790
|
{
|
|
@@ -716,20 +797,52 @@ const createBaseDefinition = (
|
|
|
716
797
|
],
|
|
717
798
|
},
|
|
718
799
|
user: {
|
|
719
|
-
handler:
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
],
|
|
800
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
|
|
801
|
+
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
802
|
+
package: functionPackageConfig,
|
|
803
|
+
events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
|
|
724
804
|
},
|
|
725
805
|
health: {
|
|
726
|
-
handler:
|
|
727
|
-
|
|
806
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
|
|
807
|
+
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
808
|
+
package: functionPackageConfig,
|
|
728
809
|
events: [
|
|
729
810
|
{ httpApi: { path: '/health', method: 'GET' } },
|
|
730
811
|
{ httpApi: { path: '/health/{proxy+}', method: 'GET' } },
|
|
731
812
|
],
|
|
732
813
|
},
|
|
814
|
+
dbMigrate: {
|
|
815
|
+
handler: 'node_modules/@friggframework/core/handlers/workers/db-migration.handler',
|
|
816
|
+
// Uses Prisma Layer (includes CLI) - simpler than standalone packaging
|
|
817
|
+
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
818
|
+
timeout: 300, // 5 minutes for long-running migrations
|
|
819
|
+
memorySize: 512, // Extra memory for Prisma CLI operations
|
|
820
|
+
reservedConcurrency: 1, // Prevent concurrent migrations
|
|
821
|
+
description: 'Runs database migrations via Prisma (invoke manually from CI/CD). Uses Prisma layer with CLI.',
|
|
822
|
+
package: functionPackageConfig, // Use same exclusions as other functions
|
|
823
|
+
// No events - this function is invoked manually via AWS CLI
|
|
824
|
+
maximumEventAge: 60, // Don't retry old migration requests (60 seconds)
|
|
825
|
+
maximumRetryAttempts: 0, // Don't auto-retry failed migrations
|
|
826
|
+
tags: {
|
|
827
|
+
Purpose: 'DatabaseMigration',
|
|
828
|
+
ManagedBy: 'Frigg',
|
|
829
|
+
},
|
|
830
|
+
// Environment variables for non-interactive Prisma CLI operation
|
|
831
|
+
environment: {
|
|
832
|
+
CI: '1', // Forces Prisma to non-interactive mode
|
|
833
|
+
PRISMA_HIDE_UPDATE_MESSAGE: '1', // Suppress update messages
|
|
834
|
+
PRISMA_MIGRATE_SKIP_SEED: '1', // Skip seeding during migrations
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
},
|
|
838
|
+
layers: {
|
|
839
|
+
prisma: {
|
|
840
|
+
path: 'layers/prisma',
|
|
841
|
+
name: '${self:service}-prisma-${sls:stage}',
|
|
842
|
+
description: 'Prisma ORM client with CLI and rhel-openssl-3.0.x binaries. Configured based on AppDefinition database settings. Used by all functions.',
|
|
843
|
+
compatibleRuntimes: ['nodejs18.x', 'nodejs20.x'],
|
|
844
|
+
retain: false, // Don't retain old layer versions
|
|
845
|
+
},
|
|
733
846
|
},
|
|
734
847
|
resources: {
|
|
735
848
|
Resources: {
|
|
@@ -828,18 +941,22 @@ const applyKmsConfiguration = (
|
|
|
828
941
|
}
|
|
829
942
|
|
|
830
943
|
if (discoveredResources.defaultKmsKeyId) {
|
|
831
|
-
console.log(
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
944
|
+
console.log(`Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`);
|
|
945
|
+
|
|
946
|
+
// Only create alias if it doesn't already exist
|
|
947
|
+
if (!discoveredResources.kmsAliasExists) {
|
|
948
|
+
console.log('Creating KMS alias for discovered key...');
|
|
949
|
+
definition.resources.Resources.FriggKMSKeyAlias = {
|
|
950
|
+
Type: 'AWS::KMS::Alias',
|
|
951
|
+
DeletionPolicy: 'Retain',
|
|
952
|
+
Properties: {
|
|
953
|
+
AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
954
|
+
TargetKeyId: discoveredResources.defaultKmsKeyId,
|
|
955
|
+
},
|
|
956
|
+
};
|
|
957
|
+
} else {
|
|
958
|
+
console.log('KMS alias already exists, skipping alias creation');
|
|
959
|
+
}
|
|
843
960
|
|
|
844
961
|
definition.provider.iamRoleStatements.push({
|
|
845
962
|
Effect: 'Allow',
|
|
@@ -850,7 +967,7 @@ const applyKmsConfiguration = (
|
|
|
850
967
|
if (AppDefinition.encryption?.createResourceIfNoneFound !== true) {
|
|
851
968
|
throw new Error(
|
|
852
969
|
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
853
|
-
|
|
970
|
+
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
854
971
|
);
|
|
855
972
|
}
|
|
856
973
|
|
|
@@ -889,9 +1006,8 @@ const applyKmsConfiguration = (
|
|
|
889
1006
|
Resource: '*',
|
|
890
1007
|
Condition: {
|
|
891
1008
|
StringEquals: {
|
|
892
|
-
'kms:ViaService': `lambda.${
|
|
893
|
-
|
|
894
|
-
}.amazonaws.com`,
|
|
1009
|
+
'kms:ViaService': `lambda.${process.env.AWS_REGION || 'us-east-1'
|
|
1010
|
+
}.amazonaws.com`,
|
|
895
1011
|
},
|
|
896
1012
|
},
|
|
897
1013
|
},
|
|
@@ -1293,13 +1409,13 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1293
1409
|
};
|
|
1294
1410
|
|
|
1295
1411
|
definition.resources.Resources.FriggPublicSubnetRouteTableAssociation =
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1412
|
+
{
|
|
1413
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1414
|
+
Properties: {
|
|
1415
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
1416
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
1417
|
+
},
|
|
1418
|
+
};
|
|
1303
1419
|
|
|
1304
1420
|
definition.resources.Resources.FriggLambdaRouteTable = {
|
|
1305
1421
|
Type: 'AWS::EC2::RouteTable',
|
|
@@ -1316,22 +1432,22 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1316
1432
|
};
|
|
1317
1433
|
|
|
1318
1434
|
definition.resources.Resources.FriggPrivateSubnet1RouteTableAssociation =
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1435
|
+
{
|
|
1436
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1437
|
+
Properties: {
|
|
1438
|
+
SubnetId: { Ref: 'FriggPrivateSubnet1' },
|
|
1439
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' },
|
|
1440
|
+
},
|
|
1441
|
+
};
|
|
1326
1442
|
|
|
1327
1443
|
definition.resources.Resources.FriggPrivateSubnet2RouteTableAssociation =
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1444
|
+
{
|
|
1445
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1446
|
+
Properties: {
|
|
1447
|
+
SubnetId: { Ref: 'FriggPrivateSubnet2' },
|
|
1448
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' },
|
|
1449
|
+
},
|
|
1450
|
+
};
|
|
1335
1451
|
}
|
|
1336
1452
|
} else if (subnetManagement === 'use-existing') {
|
|
1337
1453
|
if (
|
|
@@ -1348,12 +1464,12 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1348
1464
|
AppDefinition.vpc.subnets?.ids?.length > 0
|
|
1349
1465
|
? AppDefinition.vpc.subnets.ids
|
|
1350
1466
|
: discoveredResources.privateSubnetId1 &&
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1467
|
+
discoveredResources.privateSubnetId2
|
|
1468
|
+
? [
|
|
1469
|
+
discoveredResources.privateSubnetId1,
|
|
1470
|
+
discoveredResources.privateSubnetId2,
|
|
1471
|
+
]
|
|
1472
|
+
: [];
|
|
1357
1473
|
|
|
1358
1474
|
if (vpcConfig.subnetIds.length < 2) {
|
|
1359
1475
|
if (AppDefinition.vpc.selfHeal) {
|
|
@@ -1560,13 +1676,13 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1560
1676
|
};
|
|
1561
1677
|
|
|
1562
1678
|
definition.resources.Resources.FriggPublicSubnetRouteTableAssociation =
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1679
|
+
{
|
|
1680
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1681
|
+
Properties: {
|
|
1682
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
1683
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
1684
|
+
},
|
|
1685
|
+
};
|
|
1570
1686
|
}
|
|
1571
1687
|
|
|
1572
1688
|
definition.resources.Resources.FriggNATGateway = {
|
|
@@ -1577,11 +1693,11 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1577
1693
|
AllocationId: useExistingEip
|
|
1578
1694
|
? discoveredResources.existingElasticIpAllocationId
|
|
1579
1695
|
: {
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1696
|
+
'Fn::GetAtt': [
|
|
1697
|
+
'FriggNATGatewayEIP',
|
|
1698
|
+
'AllocationId',
|
|
1699
|
+
],
|
|
1700
|
+
},
|
|
1585
1701
|
SubnetId: discoveredResources.publicSubnetId || {
|
|
1586
1702
|
Ref: 'FriggPublicSubnet',
|
|
1587
1703
|
},
|
|
@@ -1894,7 +2010,9 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1894
2010
|
},
|
|
1895
2011
|
};
|
|
1896
2012
|
|
|
1897
|
-
if
|
|
2013
|
+
// Create Secrets Manager VPC Endpoint if explicitly enabled OR if Aurora is enabled
|
|
2014
|
+
// (Aurora requires Secrets Manager access for credential retrieval)
|
|
2015
|
+
if (AppDefinition.secretsManager?.enable === true || AppDefinition.database?.postgres?.enable === true) {
|
|
1898
2016
|
definition.resources.Resources.VPCEndpointSecretsManager = {
|
|
1899
2017
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
1900
2018
|
Properties: {
|
|
@@ -1912,6 +2030,364 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1912
2030
|
}
|
|
1913
2031
|
};
|
|
1914
2032
|
|
|
2033
|
+
const createAuroraInfrastructure = (definition, AppDefinition, discoveredResources) => {
|
|
2034
|
+
const dbConfig = AppDefinition.database.postgres;
|
|
2035
|
+
|
|
2036
|
+
console.log('🔧 Creating Aurora Serverless v2 infrastructure...');
|
|
2037
|
+
|
|
2038
|
+
// 1. DB Subnet Group (using Lambda private subnets)
|
|
2039
|
+
definition.resources.Resources.FriggDBSubnetGroup = {
|
|
2040
|
+
Type: 'AWS::RDS::DBSubnetGroup',
|
|
2041
|
+
Properties: {
|
|
2042
|
+
DBSubnetGroupDescription: 'Subnet group for Frigg Aurora cluster',
|
|
2043
|
+
SubnetIds: [
|
|
2044
|
+
discoveredResources.privateSubnetId1,
|
|
2045
|
+
discoveredResources.privateSubnetId2
|
|
2046
|
+
],
|
|
2047
|
+
Tags: [
|
|
2048
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-db-subnet-group' },
|
|
2049
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2050
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
2051
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2052
|
+
]
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
|
|
2056
|
+
// 2. Security Group (allow Lambda SG to access 5432)
|
|
2057
|
+
// In create-new VPC mode, Lambda uses FriggLambdaSecurityGroup
|
|
2058
|
+
// In other modes, use discovered default security group
|
|
2059
|
+
const lambdaSecurityGroupId = AppDefinition.vpc?.management === 'create-new'
|
|
2060
|
+
? { Ref: 'FriggLambdaSecurityGroup' }
|
|
2061
|
+
: discoveredResources.defaultSecurityGroupId;
|
|
2062
|
+
|
|
2063
|
+
definition.resources.Resources.FriggAuroraSecurityGroup = {
|
|
2064
|
+
Type: 'AWS::EC2::SecurityGroup',
|
|
2065
|
+
Properties: {
|
|
2066
|
+
GroupDescription: 'Security group for Frigg Aurora PostgreSQL',
|
|
2067
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
2068
|
+
SecurityGroupIngress: [
|
|
2069
|
+
{
|
|
2070
|
+
IpProtocol: 'tcp',
|
|
2071
|
+
FromPort: 5432,
|
|
2072
|
+
ToPort: 5432,
|
|
2073
|
+
SourceSecurityGroupId: lambdaSecurityGroupId,
|
|
2074
|
+
Description: 'PostgreSQL access from Lambda functions'
|
|
2075
|
+
}
|
|
2076
|
+
],
|
|
2077
|
+
Tags: [
|
|
2078
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-sg' },
|
|
2079
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2080
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
2081
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2082
|
+
]
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
|
|
2086
|
+
// 3. Secrets Manager Secret (database credentials)
|
|
2087
|
+
definition.resources.Resources.FriggDatabaseSecret = {
|
|
2088
|
+
Type: 'AWS::SecretsManager::Secret',
|
|
2089
|
+
Properties: {
|
|
2090
|
+
Name: '${self:service}-${self:provider.stage}-aurora-credentials',
|
|
2091
|
+
Description: 'Aurora PostgreSQL credentials for Frigg application',
|
|
2092
|
+
GenerateSecretString: {
|
|
2093
|
+
SecretStringTemplate: JSON.stringify({
|
|
2094
|
+
username: dbConfig.masterUsername || 'frigg_admin'
|
|
2095
|
+
}),
|
|
2096
|
+
GenerateStringKey: 'password',
|
|
2097
|
+
PasswordLength: 32,
|
|
2098
|
+
ExcludeCharacters: '"@/\\'
|
|
2099
|
+
},
|
|
2100
|
+
Tags: [
|
|
2101
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2102
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
2103
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2104
|
+
]
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
|
|
2108
|
+
// 4. Aurora Serverless v2 Cluster
|
|
2109
|
+
definition.resources.Resources.FriggAuroraCluster = {
|
|
2110
|
+
Type: 'AWS::RDS::DBCluster',
|
|
2111
|
+
DeletionPolicy: 'Snapshot',
|
|
2112
|
+
UpdateReplacePolicy: 'Snapshot',
|
|
2113
|
+
Properties: {
|
|
2114
|
+
Engine: 'aurora-postgresql',
|
|
2115
|
+
EngineVersion: dbConfig.engineVersion || '15.3',
|
|
2116
|
+
EngineMode: 'provisioned', // Required for Serverless v2
|
|
2117
|
+
DatabaseName: dbConfig.databaseName || 'frigg_db',
|
|
2118
|
+
MasterUsername: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
|
|
2119
|
+
MasterUserPassword: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
|
|
2120
|
+
DBSubnetGroupName: { Ref: 'FriggDBSubnetGroup' },
|
|
2121
|
+
VpcSecurityGroupIds: [{ Ref: 'FriggAuroraSecurityGroup' }],
|
|
2122
|
+
ServerlessV2ScalingConfiguration: {
|
|
2123
|
+
MinCapacity: dbConfig.scaling?.minCapacity || 0.5,
|
|
2124
|
+
MaxCapacity: dbConfig.scaling?.maxCapacity || 1.0
|
|
2125
|
+
},
|
|
2126
|
+
BackupRetentionPeriod: dbConfig.backupRetentionDays || 7,
|
|
2127
|
+
PreferredBackupWindow: dbConfig.preferredBackupWindow || '03:00-04:00',
|
|
2128
|
+
DeletionProtection: dbConfig.deletionProtection !== false,
|
|
2129
|
+
EnableCloudwatchLogsExports: ['postgresql'],
|
|
2130
|
+
Tags: [
|
|
2131
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-cluster' },
|
|
2132
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2133
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
2134
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2135
|
+
]
|
|
2136
|
+
}
|
|
2137
|
+
};
|
|
2138
|
+
|
|
2139
|
+
// 5. Aurora Serverless v2 Instance
|
|
2140
|
+
definition.resources.Resources.FriggAuroraInstance = {
|
|
2141
|
+
Type: 'AWS::RDS::DBInstance',
|
|
2142
|
+
Properties: {
|
|
2143
|
+
Engine: 'aurora-postgresql',
|
|
2144
|
+
DBInstanceClass: 'db.serverless',
|
|
2145
|
+
DBClusterIdentifier: { Ref: 'FriggAuroraCluster' },
|
|
2146
|
+
PubliclyAccessible: false,
|
|
2147
|
+
EnablePerformanceInsights: dbConfig.enablePerformanceInsights || false,
|
|
2148
|
+
Tags: [
|
|
2149
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-aurora-instance' },
|
|
2150
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2151
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
2152
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2153
|
+
]
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
|
|
2157
|
+
// 6. Secret Attachment (links cluster to secret)
|
|
2158
|
+
definition.resources.Resources.FriggSecretAttachment = {
|
|
2159
|
+
Type: 'AWS::SecretsManager::SecretTargetAttachment',
|
|
2160
|
+
Properties: {
|
|
2161
|
+
SecretId: { Ref: 'FriggDatabaseSecret' },
|
|
2162
|
+
TargetId: { Ref: 'FriggAuroraCluster' },
|
|
2163
|
+
TargetType: 'AWS::RDS::DBCluster'
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
|
|
2167
|
+
// 7. Add IAM permissions for Secrets Manager
|
|
2168
|
+
definition.provider.iamRoleStatements.push({
|
|
2169
|
+
Effect: 'Allow',
|
|
2170
|
+
Action: [
|
|
2171
|
+
'secretsmanager:GetSecretValue',
|
|
2172
|
+
'secretsmanager:DescribeSecret'
|
|
2173
|
+
],
|
|
2174
|
+
Resource: { Ref: 'FriggDatabaseSecret' }
|
|
2175
|
+
});
|
|
2176
|
+
|
|
2177
|
+
// 8. Set DATABASE_URL environment variable
|
|
2178
|
+
definition.provider.environment.DATABASE_URL = {
|
|
2179
|
+
'Fn::Sub': [
|
|
2180
|
+
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
2181
|
+
{
|
|
2182
|
+
Username: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
|
|
2183
|
+
Password: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
|
|
2184
|
+
Endpoint: { 'Fn::GetAtt': ['FriggAuroraCluster', 'Endpoint'] },
|
|
2185
|
+
Port: { 'Fn::GetAtt': ['FriggAuroraCluster', 'Port'] },
|
|
2186
|
+
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
2187
|
+
}
|
|
2188
|
+
]
|
|
2189
|
+
};
|
|
2190
|
+
|
|
2191
|
+
// 9. Set DB_TYPE for Prisma client selection
|
|
2192
|
+
definition.provider.environment.DB_TYPE = 'postgresql';
|
|
2193
|
+
|
|
2194
|
+
console.log('✅ Aurora infrastructure resources created');
|
|
2195
|
+
};
|
|
2196
|
+
|
|
2197
|
+
const useExistingAurora = (definition, AppDefinition, discoveredResources) => {
|
|
2198
|
+
const dbConfig = AppDefinition.database.postgres;
|
|
2199
|
+
const selfHeal = AppDefinition.database?.postgres?.selfHeal !== false; // Default to true
|
|
2200
|
+
|
|
2201
|
+
console.log(`🔗 Using existing Aurora cluster: ${discoveredResources.aurora.clusterIdentifier}`);
|
|
2202
|
+
console.log(`[DEBUG] discoveredResources.aurora.isFriggManaged: ${discoveredResources.aurora.isFriggManaged}`);
|
|
2203
|
+
console.log(`[DEBUG] selfHeal: ${selfHeal}`);
|
|
2204
|
+
console.log(`[DEBUG] discoveredResources.aurora.secretArn: ${discoveredResources.aurora.secretArn}`);
|
|
2205
|
+
console.log(`[DEBUG] dbConfig.secretArn: ${dbConfig.secretArn}`);
|
|
2206
|
+
|
|
2207
|
+
// Add IAM permissions for Secrets Manager if secret exists
|
|
2208
|
+
if (discoveredResources.aurora.secretArn) {
|
|
2209
|
+
definition.provider.iamRoleStatements.push({
|
|
2210
|
+
Effect: 'Allow',
|
|
2211
|
+
Action: [
|
|
2212
|
+
'secretsmanager:GetSecretValue',
|
|
2213
|
+
'secretsmanager:DescribeSecret'
|
|
2214
|
+
],
|
|
2215
|
+
Resource: discoveredResources.aurora.secretArn
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
// Set DATABASE_URL from discovered secret
|
|
2219
|
+
definition.provider.environment.DATABASE_URL = {
|
|
2220
|
+
'Fn::Sub': [
|
|
2221
|
+
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
2222
|
+
{
|
|
2223
|
+
Username: { 'Fn::Sub': `{{resolve:secretsmanager:${discoveredResources.aurora.secretArn}:SecretString:username}}` },
|
|
2224
|
+
Password: { 'Fn::Sub': `{{resolve:secretsmanager:${discoveredResources.aurora.secretArn}:SecretString:password}}` },
|
|
2225
|
+
Endpoint: discoveredResources.aurora.endpoint,
|
|
2226
|
+
Port: discoveredResources.aurora.port,
|
|
2227
|
+
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
2228
|
+
}
|
|
2229
|
+
]
|
|
2230
|
+
};
|
|
2231
|
+
} else if (dbConfig.secretArn) {
|
|
2232
|
+
// Use user-provided secret ARN
|
|
2233
|
+
definition.provider.iamRoleStatements.push({
|
|
2234
|
+
Effect: 'Allow',
|
|
2235
|
+
Action: [
|
|
2236
|
+
'secretsmanager:GetSecretValue',
|
|
2237
|
+
'secretsmanager:DescribeSecret'
|
|
2238
|
+
],
|
|
2239
|
+
Resource: dbConfig.secretArn
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
definition.provider.environment.DATABASE_URL = {
|
|
2243
|
+
'Fn::Sub': [
|
|
2244
|
+
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
2245
|
+
{
|
|
2246
|
+
Username: { 'Fn::Sub': `{{resolve:secretsmanager:${dbConfig.secretArn}:SecretString:username}}` },
|
|
2247
|
+
Password: { 'Fn::Sub': `{{resolve:secretsmanager:${dbConfig.secretArn}:SecretString:password}}` },
|
|
2248
|
+
Endpoint: discoveredResources.aurora.endpoint,
|
|
2249
|
+
Port: discoveredResources.aurora.port,
|
|
2250
|
+
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
2251
|
+
}
|
|
2252
|
+
]
|
|
2253
|
+
};
|
|
2254
|
+
} else if (selfHeal && discoveredResources.aurora?.isFriggManaged) {
|
|
2255
|
+
// Self-healing mode: recreate missing secret for Frigg-managed cluster
|
|
2256
|
+
console.log('⚠️ No database secret found for Frigg-managed cluster');
|
|
2257
|
+
console.log('🔧 Self-healing enabled: Creating new database secret with automatic password rotation');
|
|
2258
|
+
|
|
2259
|
+
// Get the current master username from the cluster
|
|
2260
|
+
const currentUsername = discoveredResources.aurora.masterUsername || dbConfig.masterUsername || 'frigg_admin';
|
|
2261
|
+
|
|
2262
|
+
// Create Secrets Manager Secret (database credentials)
|
|
2263
|
+
// Note: We generate a NEW password, which will be synced to the cluster via SecretTargetAttachment
|
|
2264
|
+
definition.resources.Resources.FriggDatabaseSecret = {
|
|
2265
|
+
Type: 'AWS::SecretsManager::Secret',
|
|
2266
|
+
Properties: {
|
|
2267
|
+
Name: '${self:service}-${self:provider.stage}-aurora-credentials',
|
|
2268
|
+
Description: 'Aurora PostgreSQL credentials for Frigg application (auto-healed)',
|
|
2269
|
+
GenerateSecretString: {
|
|
2270
|
+
SecretStringTemplate: JSON.stringify({
|
|
2271
|
+
username: currentUsername
|
|
2272
|
+
}),
|
|
2273
|
+
GenerateStringKey: 'password',
|
|
2274
|
+
PasswordLength: 32,
|
|
2275
|
+
ExcludeCharacters: '"@/\\`\''
|
|
2276
|
+
},
|
|
2277
|
+
Tags: [
|
|
2278
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
2279
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
2280
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
2281
|
+
{ Key: 'AutoHealed', Value: 'true' }
|
|
2282
|
+
]
|
|
2283
|
+
}
|
|
2284
|
+
};
|
|
2285
|
+
|
|
2286
|
+
// Create SecretTargetAttachment to link secret to existing cluster
|
|
2287
|
+
// This will automatically rotate the cluster password to match the secret!
|
|
2288
|
+
definition.resources.Resources.FriggSecretAttachment = {
|
|
2289
|
+
Type: 'AWS::SecretsManager::SecretTargetAttachment',
|
|
2290
|
+
Properties: {
|
|
2291
|
+
SecretId: { Ref: 'FriggDatabaseSecret' },
|
|
2292
|
+
TargetId: discoveredResources.aurora.clusterIdentifier,
|
|
2293
|
+
TargetType: 'AWS::RDS::DBCluster'
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
|
|
2297
|
+
// Add IAM permissions for the new secret
|
|
2298
|
+
definition.provider.iamRoleStatements.push({
|
|
2299
|
+
Effect: 'Allow',
|
|
2300
|
+
Action: [
|
|
2301
|
+
'secretsmanager:GetSecretValue',
|
|
2302
|
+
'secretsmanager:DescribeSecret'
|
|
2303
|
+
],
|
|
2304
|
+
Resource: { Ref: 'FriggDatabaseSecret' }
|
|
2305
|
+
});
|
|
2306
|
+
|
|
2307
|
+
// Set DATABASE_URL from new secret
|
|
2308
|
+
definition.provider.environment.DATABASE_URL = {
|
|
2309
|
+
'Fn::Sub': [
|
|
2310
|
+
'postgresql://${Username}:${Password}@${Endpoint}:${Port}/${DatabaseName}',
|
|
2311
|
+
{
|
|
2312
|
+
Username: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:username}}' },
|
|
2313
|
+
Password: { 'Fn::Sub': '{{resolve:secretsmanager:${FriggDatabaseSecret}:SecretString:password}}' },
|
|
2314
|
+
Endpoint: discoveredResources.aurora.endpoint,
|
|
2315
|
+
Port: discoveredResources.aurora.port,
|
|
2316
|
+
DatabaseName: dbConfig.databaseName || 'frigg_db'
|
|
2317
|
+
}
|
|
2318
|
+
]
|
|
2319
|
+
};
|
|
2320
|
+
|
|
2321
|
+
console.log('✅ Self-healing configuration complete:');
|
|
2322
|
+
console.log(' - New secret will be created with auto-generated password');
|
|
2323
|
+
console.log(' - SecretTargetAttachment will automatically update cluster password');
|
|
2324
|
+
console.log(' - No manual password sync required!');
|
|
2325
|
+
} else {
|
|
2326
|
+
throw new Error(
|
|
2327
|
+
'No database secret found. Options:\n' +
|
|
2328
|
+
' 1. Provide secretArn in database.postgres configuration\n' +
|
|
2329
|
+
' 2. Ensure Secrets Manager secret exists\n' +
|
|
2330
|
+
' 3. Enable self-healing: set database.postgres.selfHeal to true (for Frigg-managed clusters only)'
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
// Set DB_TYPE for Prisma client selection
|
|
2335
|
+
definition.provider.environment.DB_TYPE = 'postgresql';
|
|
2336
|
+
|
|
2337
|
+
console.log('✅ Existing Aurora cluster configured');
|
|
2338
|
+
};
|
|
2339
|
+
|
|
2340
|
+
const useDiscoveredAurora = (definition, AppDefinition, discoveredResources) => {
|
|
2341
|
+
console.log(`🔍 Using discovered Aurora cluster: ${discoveredResources.aurora.clusterIdentifier}`);
|
|
2342
|
+
useExistingAurora(definition, AppDefinition, discoveredResources);
|
|
2343
|
+
};
|
|
2344
|
+
|
|
2345
|
+
const configurePostgres = (definition, AppDefinition, discoveredResources) => {
|
|
2346
|
+
if (!AppDefinition.database?.postgres?.enable) {
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
// Validate VPC is enabled (required for Aurora deployment)
|
|
2351
|
+
if (!AppDefinition.vpc?.enable) {
|
|
2352
|
+
throw new Error(
|
|
2353
|
+
'Aurora PostgreSQL requires VPC deployment. ' +
|
|
2354
|
+
'Set vpc.enable to true in your app definition.'
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// Validate private subnets exist (Aurora requires at least 2 subnets in different AZs)
|
|
2359
|
+
// Skip validation if VPC management is 'create-new' (subnets will be created)
|
|
2360
|
+
const vpcManagement = AppDefinition.vpc?.management || 'discover';
|
|
2361
|
+
if (vpcManagement !== 'create-new' && (!discoveredResources.privateSubnetId1 || !discoveredResources.privateSubnetId2)) {
|
|
2362
|
+
throw new Error(
|
|
2363
|
+
'Aurora PostgreSQL requires at least 2 private subnets in different availability zones. ' +
|
|
2364
|
+
'No private subnets were discovered in your VPC. ' +
|
|
2365
|
+
'Please create private subnets or use VPC management mode "create-new".'
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
const dbConfig = AppDefinition.database.postgres;
|
|
2370
|
+
const management = dbConfig.management || 'discover';
|
|
2371
|
+
|
|
2372
|
+
console.log(`\n🐘 PostgreSQL Management Mode: ${management}`);
|
|
2373
|
+
|
|
2374
|
+
if (management === 'create-new' || discoveredResources.aurora?.needsCreation) {
|
|
2375
|
+
createAuroraInfrastructure(definition, AppDefinition, discoveredResources);
|
|
2376
|
+
} else if (management === 'use-existing') {
|
|
2377
|
+
if (!discoveredResources.aurora?.clusterIdentifier && !dbConfig.clusterIdentifier) {
|
|
2378
|
+
throw new Error('PostgreSQL management is set to "use-existing" but no clusterIdentifier was found or provided');
|
|
2379
|
+
}
|
|
2380
|
+
useExistingAurora(definition, AppDefinition, discoveredResources);
|
|
2381
|
+
} else {
|
|
2382
|
+
// discover mode
|
|
2383
|
+
if (discoveredResources.aurora?.clusterIdentifier) {
|
|
2384
|
+
useDiscoveredAurora(definition, AppDefinition, discoveredResources);
|
|
2385
|
+
} else {
|
|
2386
|
+
throw new Error('No Aurora cluster found in discovery mode. Set management to "create-new" or provide clusterIdentifier with "use-existing".');
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
|
|
1915
2391
|
const configureSsm = (definition, AppDefinition) => {
|
|
1916
2392
|
if (AppDefinition.ssm?.enable !== true) {
|
|
1917
2393
|
return;
|
|
@@ -1949,19 +2425,31 @@ const attachIntegrations = (definition, AppDefinition) => {
|
|
|
1949
2425
|
`Processing ${AppDefinition.integrations.length} integrations...`
|
|
1950
2426
|
);
|
|
1951
2427
|
|
|
2428
|
+
// Get the functionPackageConfig from the definition (defined in createBaseDefinition)
|
|
2429
|
+
const functionPackageConfig = {
|
|
2430
|
+
exclude: [
|
|
2431
|
+
'node_modules/aws-sdk/**',
|
|
2432
|
+
'node_modules/@aws-sdk/**',
|
|
2433
|
+
'node_modules/@prisma/**',
|
|
2434
|
+
'node_modules/.prisma/**',
|
|
2435
|
+
'node_modules/prisma/**',
|
|
2436
|
+
'node_modules/@friggframework/core/generated/**',
|
|
2437
|
+
],
|
|
2438
|
+
};
|
|
2439
|
+
|
|
1952
2440
|
for (const integration of AppDefinition.integrations) {
|
|
1953
2441
|
if (!integration?.Definition?.name) {
|
|
1954
2442
|
throw new Error('Invalid integration: missing Definition or name');
|
|
1955
2443
|
}
|
|
1956
2444
|
|
|
1957
2445
|
const integrationName = integration.Definition.name;
|
|
1958
|
-
const queueReference = `${
|
|
1959
|
-
|
|
1960
|
-
}Queue`;
|
|
2446
|
+
const queueReference = `${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)
|
|
2447
|
+
}Queue`;
|
|
1961
2448
|
const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
|
|
1962
2449
|
|
|
1963
2450
|
definition.functions[integrationName] = {
|
|
1964
2451
|
handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
|
|
2452
|
+
package: functionPackageConfig,
|
|
1965
2453
|
events: [
|
|
1966
2454
|
{
|
|
1967
2455
|
httpApi: {
|
|
@@ -1990,6 +2478,7 @@ const attachIntegrations = (definition, AppDefinition) => {
|
|
|
1990
2478
|
const queueWorkerName = `${integrationName}QueueWorker`;
|
|
1991
2479
|
definition.functions[queueWorkerName] = {
|
|
1992
2480
|
handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
|
|
2481
|
+
package: functionPackageConfig,
|
|
1993
2482
|
reservedConcurrency: 5,
|
|
1994
2483
|
events: [
|
|
1995
2484
|
{
|
|
@@ -2013,10 +2502,7 @@ const attachIntegrations = (definition, AppDefinition) => {
|
|
|
2013
2502
|
|
|
2014
2503
|
// Add webhook handler if enabled
|
|
2015
2504
|
const webhookConfig = integration.Definition.webhooks;
|
|
2016
|
-
if (
|
|
2017
|
-
webhookConfig &&
|
|
2018
|
-
(webhookConfig === true || webhookConfig.enabled === true)
|
|
2019
|
-
) {
|
|
2505
|
+
if (webhookConfig && (webhookConfig === true || webhookConfig.enabled === true)) {
|
|
2020
2506
|
const webhookFunctionName = `${integrationName}Webhook`;
|
|
2021
2507
|
|
|
2022
2508
|
definition.functions[webhookFunctionName] = {
|
|
@@ -2056,9 +2542,44 @@ const configureWebsockets = (definition, AppDefinition) => {
|
|
|
2056
2542
|
};
|
|
2057
2543
|
};
|
|
2058
2544
|
|
|
2545
|
+
/**
|
|
2546
|
+
* Ensure Prisma Lambda Layer exists
|
|
2547
|
+
* Automatically builds the layer if it doesn't exist in the project root
|
|
2548
|
+
* @param {Object} databaseConfig - Database configuration from AppDefinition.database
|
|
2549
|
+
*/
|
|
2550
|
+
async function ensurePrismaLayerExists(databaseConfig = {}) {
|
|
2551
|
+
const projectRoot = process.cwd();
|
|
2552
|
+
const layerPath = path.join(projectRoot, 'layers/prisma');
|
|
2553
|
+
|
|
2554
|
+
// Check if layer already exists
|
|
2555
|
+
if (fs.existsSync(layerPath)) {
|
|
2556
|
+
console.log('✓ Prisma Lambda Layer already exists at', layerPath);
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// Layer doesn't exist - build it automatically
|
|
2561
|
+
console.log('📦 Prisma Lambda Layer not found - building automatically...');
|
|
2562
|
+
console.log(' Building layer with CLI (used by all functions including dbMigrate)');
|
|
2563
|
+
console.log(' This may take a minute on first deployment.\n');
|
|
2564
|
+
|
|
2565
|
+
try {
|
|
2566
|
+
// Build layer WITH CLI (includeCLI = true) - all functions use same layer
|
|
2567
|
+
await buildPrismaLayer(databaseConfig, true);
|
|
2568
|
+
console.log('✓ Prisma Lambda Layer built successfully\n');
|
|
2569
|
+
} catch (error) {
|
|
2570
|
+
console.error('✗ Failed to build Prisma Lambda Layer:', error.message);
|
|
2571
|
+
console.error(' You may need to run: npm install @friggframework/core\n');
|
|
2572
|
+
throw error;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2059
2576
|
const composeServerlessDefinition = async (AppDefinition) => {
|
|
2060
2577
|
console.log('composeServerlessDefinition', AppDefinition);
|
|
2061
2578
|
|
|
2579
|
+
// Ensure Prisma layer exists before generating serverless config
|
|
2580
|
+
// Pass database config so layer only includes needed database clients
|
|
2581
|
+
await ensurePrismaLayerExists(AppDefinition.database || {});
|
|
2582
|
+
|
|
2062
2583
|
const discoveredResources = await gatherDiscoveredResources(AppDefinition);
|
|
2063
2584
|
const appEnvironmentVars = getAppEnvironmentVars(AppDefinition);
|
|
2064
2585
|
const definition = createBaseDefinition(
|
|
@@ -2080,6 +2601,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
2080
2601
|
if (!isLocalBuild) {
|
|
2081
2602
|
applyKmsConfiguration(definition, AppDefinition, discoveredResources);
|
|
2082
2603
|
configureVpc(definition, AppDefinition, discoveredResources);
|
|
2604
|
+
configurePostgres(definition, AppDefinition, discoveredResources);
|
|
2083
2605
|
configureSsm(definition, AppDefinition);
|
|
2084
2606
|
} else {
|
|
2085
2607
|
console.log(
|