@friggframework/devtools 2.0.0--canary.454.e2a280d.0 → 2.0.0--canary.458.c150d9a.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/__tests__/unit/utils/prisma-runner.test.js +486 -0
- package/frigg-cli/build-command/index.js +1 -12
- package/frigg-cli/db-setup-command/index.js +1 -8
- package/frigg-cli/index.js +0 -1
- package/frigg-cli/utils/database-validator.js +1 -4
- package/frigg-cli/utils/prisma-runner.js +280 -0
- package/infrastructure/README.md +0 -51
- package/infrastructure/aws-discovery.js +2 -504
- package/infrastructure/aws-discovery.test.js +1 -447
- package/infrastructure/serverless-template.js +21 -459
- package/infrastructure/serverless-template.test.js +0 -91
- package/management-ui/src/App.jsx +1 -85
- package/management-ui/src/hooks/useFrigg.jsx +1 -215
- package/package.json +6 -8
- package/infrastructure/POSTGRES-CONFIGURATION.md +0 -645
- package/infrastructure/__tests__/postgres-config.test.js +0 -914
- package/infrastructure/scripts/build-prisma-layer.js +0 -394
|
@@ -1,7 +1,6 @@
|
|
|
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
6
|
console.log('⚙️ Checking FRIGG_SKIP_AWS_DISCOVERY:', process.env.FRIGG_SKIP_AWS_DISCOVERY);
|
|
@@ -13,8 +12,7 @@ const shouldRunDiscovery = (AppDefinition) => {
|
|
|
13
12
|
return (
|
|
14
13
|
AppDefinition.vpc?.enable === true ||
|
|
15
14
|
AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
16
|
-
AppDefinition.ssm?.enable === true
|
|
17
|
-
AppDefinition.database?.postgres?.enable === true
|
|
15
|
+
AppDefinition.ssm?.enable === true
|
|
18
16
|
);
|
|
19
17
|
};
|
|
20
18
|
|
|
@@ -437,17 +435,10 @@ const gatherDiscoveredResources = async (AppDefinition) => {
|
|
|
437
435
|
try {
|
|
438
436
|
const region = process.env.AWS_REGION || 'us-east-1';
|
|
439
437
|
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
438
|
const config = {
|
|
446
439
|
vpc: AppDefinition.vpc || {},
|
|
447
440
|
encryption: AppDefinition.encryption || {},
|
|
448
441
|
ssm: AppDefinition.ssm || {},
|
|
449
|
-
serviceName: AppDefinition.name || 'create-frigg-app',
|
|
450
|
-
stage: stage,
|
|
451
442
|
};
|
|
452
443
|
|
|
453
444
|
const discoveredResources = await discovery.discoverResources(config);
|
|
@@ -498,22 +489,6 @@ const buildEnvironment = (appEnvironmentVars, discoveredResources) => {
|
|
|
498
489
|
}
|
|
499
490
|
}
|
|
500
491
|
|
|
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
492
|
return environment;
|
|
518
493
|
};
|
|
519
494
|
|
|
@@ -525,49 +500,11 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
|
|
|
525
500
|
service: AppDefinition.name || 'create-frigg-app',
|
|
526
501
|
package: {
|
|
527
502
|
individually: true,
|
|
528
|
-
|
|
529
|
-
// AWS SDK exclusions (already in Lambda runtime)
|
|
530
|
-
'!**/node_modules/aws-sdk/**',
|
|
531
|
-
'!**/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/**',
|
|
565
|
-
],
|
|
503
|
+
exclude: ['!**/node_modules/aws-sdk/**', '!**/node_modules/@aws-sdk/**', '!package.json'],
|
|
566
504
|
},
|
|
567
505
|
useDotenv: true,
|
|
568
506
|
provider: {
|
|
569
507
|
name: AppDefinition.provider || 'aws',
|
|
570
|
-
...(process.env.AWS_PROFILE && { profile: process.env.AWS_PROFILE }),
|
|
571
508
|
runtime: 'nodejs20.x',
|
|
572
509
|
timeout: 30,
|
|
573
510
|
region,
|
|
@@ -606,8 +543,7 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
|
|
|
606
543
|
},
|
|
607
544
|
},
|
|
608
545
|
plugins: [
|
|
609
|
-
|
|
610
|
-
// 'serverless-jetpack',
|
|
546
|
+
'serverless-jetpack',
|
|
611
547
|
'serverless-dotenv-plugin',
|
|
612
548
|
'serverless-offline-sqs',
|
|
613
549
|
'serverless-offline',
|
|
@@ -628,15 +564,13 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
|
|
|
628
564
|
secretAccessKey: 'root',
|
|
629
565
|
skipCacheInvalidation: false,
|
|
630
566
|
},
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
// },
|
|
567
|
+
jetpack: {
|
|
568
|
+
base: '..',
|
|
569
|
+
},
|
|
635
570
|
},
|
|
636
571
|
functions: {
|
|
637
572
|
auth: {
|
|
638
573
|
handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
639
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
640
574
|
events: [
|
|
641
575
|
{ httpApi: { path: '/api/integrations', method: 'ANY' } },
|
|
642
576
|
{ httpApi: { path: '/api/integrations/{proxy+}', method: 'ANY' } },
|
|
@@ -645,56 +579,15 @@ const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResou
|
|
|
645
579
|
},
|
|
646
580
|
user: {
|
|
647
581
|
handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
|
|
648
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
649
582
|
events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
|
|
650
583
|
},
|
|
651
584
|
health: {
|
|
652
585
|
handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
|
|
653
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
654
586
|
events: [
|
|
655
587
|
{ httpApi: { path: '/health', method: 'GET' } },
|
|
656
588
|
{ httpApi: { path: '/health/{proxy+}', method: 'GET' } },
|
|
657
589
|
],
|
|
658
590
|
},
|
|
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
591
|
},
|
|
699
592
|
resources: {
|
|
700
593
|
Resources: {
|
|
@@ -773,21 +666,14 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
|
|
|
773
666
|
|
|
774
667
|
if (discoveredResources.defaultKmsKeyId) {
|
|
775
668
|
console.log(`Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`);
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
-
}
|
|
669
|
+
definition.resources.Resources.FriggKMSKeyAlias = {
|
|
670
|
+
Type: 'AWS::KMS::Alias',
|
|
671
|
+
DeletionPolicy: 'Retain',
|
|
672
|
+
Properties: {
|
|
673
|
+
AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
674
|
+
TargetKeyId: discoveredResources.defaultKmsKeyId,
|
|
675
|
+
},
|
|
676
|
+
};
|
|
791
677
|
|
|
792
678
|
definition.provider.iamRoleStatements.push({
|
|
793
679
|
Effect: 'Allow',
|
|
@@ -798,7 +684,7 @@ const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) =
|
|
|
798
684
|
if (AppDefinition.encryption?.createResourceIfNoneFound !== true) {
|
|
799
685
|
throw new Error(
|
|
800
686
|
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
801
|
-
|
|
687
|
+
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
802
688
|
);
|
|
803
689
|
}
|
|
804
690
|
|
|
@@ -1197,8 +1083,8 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1197
1083
|
AppDefinition.vpc.subnets?.ids?.length > 0
|
|
1198
1084
|
? AppDefinition.vpc.subnets.ids
|
|
1199
1085
|
: discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2
|
|
1200
|
-
|
|
1201
|
-
|
|
1086
|
+
? [discoveredResources.privateSubnetId1, discoveredResources.privateSubnetId2]
|
|
1087
|
+
: [];
|
|
1202
1088
|
|
|
1203
1089
|
if (vpcConfig.subnetIds.length < 2) {
|
|
1204
1090
|
if (AppDefinition.vpc.selfHeal) {
|
|
@@ -1624,9 +1510,7 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1624
1510
|
},
|
|
1625
1511
|
};
|
|
1626
1512
|
|
|
1627
|
-
|
|
1628
|
-
// (Aurora requires Secrets Manager access for credential retrieval)
|
|
1629
|
-
if (AppDefinition.secretsManager?.enable === true || AppDefinition.database?.postgres?.enable === true) {
|
|
1513
|
+
if (AppDefinition.secretsManager?.enable === true) {
|
|
1630
1514
|
definition.resources.Resources.VPCEndpointSecretsManager = {
|
|
1631
1515
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
1632
1516
|
Properties: {
|
|
@@ -1643,283 +1527,6 @@ const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
|
1643
1527
|
}
|
|
1644
1528
|
};
|
|
1645
1529
|
|
|
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
1530
|
const configureSsm = (definition, AppDefinition) => {
|
|
1924
1531
|
if (AppDefinition.ssm?.enable !== true) {
|
|
1925
1532
|
return;
|
|
@@ -2018,61 +1625,16 @@ const configureWebsockets = (definition, AppDefinition) => {
|
|
|
2018
1625
|
};
|
|
2019
1626
|
};
|
|
2020
1627
|
|
|
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
1628
|
const composeServerlessDefinition = async (AppDefinition) => {
|
|
2050
1629
|
console.log('composeServerlessDefinition', AppDefinition);
|
|
2051
1630
|
|
|
2052
|
-
// Ensure Prisma layer exists before generating serverless config
|
|
2053
|
-
await ensurePrismaLayerExists();
|
|
2054
|
-
|
|
2055
1631
|
const discoveredResources = await gatherDiscoveredResources(AppDefinition);
|
|
2056
1632
|
const appEnvironmentVars = getAppEnvironmentVars(AppDefinition);
|
|
2057
1633
|
const definition = createBaseDefinition(AppDefinition, appEnvironmentVars, discoveredResources);
|
|
2058
1634
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
if (isLocalBuild) {
|
|
2063
|
-
console.log('🏠 Local build mode detected - skipping AWS-dependent configurations');
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
// Apply configurations (skip AWS-dependent ones in local build mode)
|
|
2067
|
-
if (!isLocalBuild) {
|
|
2068
|
-
applyKmsConfiguration(definition, AppDefinition, discoveredResources);
|
|
2069
|
-
configureVpc(definition, AppDefinition, discoveredResources);
|
|
2070
|
-
configurePostgres(definition, AppDefinition, discoveredResources);
|
|
2071
|
-
configureSsm(definition, AppDefinition);
|
|
2072
|
-
} else {
|
|
2073
|
-
console.log(' ⏭️ Skipping: KMS, VPC, PostgreSQL, SSM configurations');
|
|
2074
|
-
}
|
|
2075
|
-
|
|
1635
|
+
applyKmsConfiguration(definition, AppDefinition, discoveredResources);
|
|
1636
|
+
configureVpc(definition, AppDefinition, discoveredResources);
|
|
1637
|
+
configureSsm(definition, AppDefinition);
|
|
2076
1638
|
attachIntegrations(definition, AppDefinition);
|
|
2077
1639
|
configureWebsockets(definition, AppDefinition);
|
|
2078
1640
|
|