@friggframework/devtools 2.0.0-next.37 → 2.0.0-next.38

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.
@@ -54,7 +54,7 @@ describe('Generate Command', () => {
54
54
  // Mock app definition
55
55
  jest.doMock(mockAppDefinitionPath, () => ({
56
56
  vpc: { enable: true },
57
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
57
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
58
58
  ssm: { enable: true },
59
59
  websockets: { enable: false }
60
60
  }), { virtual: true });
@@ -228,7 +228,7 @@ async function generateCommand(options = {}) {
228
228
  function analyzeAppFeatures(appDefinition) {
229
229
  const features = {
230
230
  vpc: appDefinition.vpc?.enable === true,
231
- kms: appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true,
231
+ kms: appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms',
232
232
  ssm: appDefinition.ssm?.enable === true,
233
233
  websockets: appDefinition.websockets?.enable === true,
234
234
  // Add more feature detection as needed
@@ -670,7 +670,7 @@ To integrate Frigg into your production application:
670
670
  const appDefinition = {
671
671
  integrations: [], // Will be populated based on selected integrations
672
672
  user: { password: true },
673
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
673
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
674
674
  vpc: { enable: true },
675
675
  security: {
676
676
  cors: {
@@ -9,7 +9,7 @@ AWS Discovery automatically finds your default AWS resources (VPC, subnets, secu
9
9
  AWS Discovery runs automatically during `frigg build` and `frigg deploy` when your AppDefinition includes:
10
10
 
11
11
  - `vpc.enable: true` - VPC support
12
- - `encryption.useDefaultKMSForFieldLevelEncryption: true` - KMS encryption
12
+ - `encryption.fieldLevelEncryptionMethod: 'kms'` - KMS encryption
13
13
  - `ssm.enable: true` - SSM Parameter Store
14
14
 
15
15
  ## Fail-Fast Behavior
@@ -222,7 +222,7 @@ If you're stuck, try this recovery process:
222
222
  // backend/index.js - temporarily disable problematic features
223
223
  const appDefinition = {
224
224
  vpc: { enable: false },
225
- encryption: { useDefaultKMSForFieldLevelEncryption: false },
225
+ encryption: { fieldLevelEncryptionMethod: 'aes' },
226
226
  ssm: { enable: false }
227
227
  };
228
228
  ```
@@ -354,7 +354,7 @@ Additional permissions needed when your app definition includes `vpc: { enable:
354
354
 
355
355
  ### KMS Support
356
356
 
357
- Additional permissions needed when your app definition includes `encryption: { useDefaultKMSForFieldLevelEncryption: true }`:
357
+ Additional permissions needed when your app definition includes `encryption: { fieldLevelEncryptionMethod: 'kms' }`:
358
358
 
359
359
  ```json
360
360
  {
@@ -58,7 +58,7 @@ The command analyzes your `backend/index.js` AppDefinition and generates IAM pol
58
58
  - Route table and security group management
59
59
  - Elastic IP allocation
60
60
 
61
- #### KMS Encryption (`encryption.useDefaultKMSForFieldLevelEncryption: true`)
61
+ #### KMS Encryption (`encryption.fieldLevelEncryptionMethod: 'kms'`)
62
62
 
63
63
  - KMS key usage for Lambda and S3
64
64
  - Data encryption and decryption permissions
@@ -85,7 +85,7 @@ const appDefinition = {
85
85
  enable: true,
86
86
  },
87
87
  encryption: {
88
- useDefaultKMSForFieldLevelEncryption: true,
88
+ fieldLevelEncryptionMethod: 'kms',
89
89
  },
90
90
  ssm: {
91
91
  enable: false,
@@ -155,7 +155,8 @@ const appDefinition = {
155
155
 
156
156
  // KMS encryption
157
157
  encryption: {
158
- useDefaultKMSForFieldLevelEncryption: true
158
+ fieldLevelEncryptionMethod: 'kms',
159
+ createResourceIfNoneFound: true
159
160
  },
160
161
 
161
162
  // SSM Parameter Store
@@ -217,7 +218,7 @@ const serverlessConfig = await composeServerlessDefinition(appDefinition);
217
218
  const appDefinition = {
218
219
  name: 'secure-app',
219
220
  vpc: { enable: true },
220
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
221
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
221
222
  ssm: { enable: true },
222
223
  integrations: [{ Definition: { name: 'salesforce' } }],
223
224
  };
@@ -151,7 +151,7 @@ const mockAppDefinitions = {
151
151
 
152
152
  kmsOnly: {
153
153
  name: 'kms-test-app',
154
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
154
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
155
155
  integrations: []
156
156
  },
157
157
 
@@ -164,7 +164,7 @@ const mockAppDefinitions = {
164
164
  allFeatures: {
165
165
  name: 'full-feature-app',
166
166
  vpc: { enable: true },
167
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
167
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
168
168
  ssm: { enable: true },
169
169
  integrations: [{
170
170
  Definition: {
@@ -281,7 +281,7 @@ const mockEnvironmentVariables = {
281
281
  AWS_DISCOVERY_SUBNET_ID_1: mockSubnets[0].SubnetId,
282
282
  AWS_DISCOVERY_SUBNET_ID_2: mockSubnets[1].SubnetId,
283
283
  AWS_DISCOVERY_ROUTE_TABLE_ID: mockRouteTables[0].RouteTableId,
284
- AWS_DISCOVERY_KMS_KEY_ID: mockKmsKeyMetadata.Arn
284
+ AWS_DISCOVERY_KMS_KEY_ID:mockKmsKeyMetadata.Arn
285
285
  };
286
286
 
287
287
  // Fallback environment variables for error scenarios
@@ -291,7 +291,7 @@ const mockFallbackEnvironmentVariables = {
291
291
  AWS_DISCOVERY_SUBNET_ID_1: 'subnet-fallback-1',
292
292
  AWS_DISCOVERY_SUBNET_ID_2: 'subnet-fallback-2',
293
293
  AWS_DISCOVERY_ROUTE_TABLE_ID: 'rtb-fallback',
294
- AWS_DISCOVERY_KMS_KEY_ID: 'arn:aws:kms:*:*:key/*'
294
+ AWS_DISCOVERY_KMS_KEY_ID:'arn:aws:kms:*:*:key/*'
295
295
  };
296
296
 
297
297
  // Mock AWS SDK responses
@@ -115,7 +115,7 @@ function createMockAppDefinition(features = {}, integrations = []) {
115
115
  }
116
116
 
117
117
  if (features.kms) {
118
- appDefinition.encryption = { useDefaultKMSForFieldLevelEncryption: true };
118
+ appDefinition.encryption = { fieldLevelEncryptionMethod: 'kms' };
119
119
  }
120
120
 
121
121
  if (features.ssm) {
@@ -453,18 +453,16 @@ class AWSDiscovery {
453
453
 
454
454
  /**
455
455
  * Find the default KMS key for the account
456
- * @returns {Promise<string>} KMS key ARN or wildcard pattern as fallback
456
+ * @returns {Promise<string|null>} KMS key ARN or null if no key found
457
457
  */
458
458
  async findDefaultKmsKey() {
459
459
  try {
460
- // First try to find a key with alias/aws/lambda
461
460
  const command = new ListKeysCommand({});
462
461
  const response = await this.kmsClient.send(command);
463
462
 
464
463
  if (!response.Keys || response.Keys.length === 0) {
465
- // Return AWS managed key ARN pattern as fallback
466
- const accountId = await this.getAccountId();
467
- return `arn:aws:kms:${this.region}:${accountId}:key/*`;
464
+ console.log('No KMS keys found in account');
465
+ return null;
468
466
  }
469
467
 
470
468
  // Look for customer managed keys first
@@ -476,21 +474,21 @@ class AWSDiscovery {
476
474
  if (keyDetails.KeyMetadata &&
477
475
  keyDetails.KeyMetadata.KeyManager === 'CUSTOMER' &&
478
476
  keyDetails.KeyMetadata.KeyState === 'Enabled') {
477
+ console.log(`Found customer managed KMS key: ${keyDetails.KeyMetadata.Arn}`);
479
478
  return keyDetails.KeyMetadata.Arn;
480
479
  }
481
480
  } catch (error) {
482
481
  // Continue to next key if we can't describe this one
482
+ console.warn(`Could not describe key ${key.KeyId}:`, error.message);
483
483
  continue;
484
484
  }
485
485
  }
486
486
 
487
- // Fallback to wildcard pattern for AWS managed keys
488
- const accountId = await this.getAccountId();
489
- return `arn:aws:kms:${this.region}:${accountId}:key/*`;
487
+ console.log('No customer managed KMS keys found');
488
+ return null;
490
489
  } catch (error) {
491
490
  console.error('Error finding default KMS key:', error);
492
- // Return wildcard pattern as ultimate fallback
493
- return '*';
491
+ return null;
494
492
  }
495
493
  }
496
494
 
@@ -503,7 +501,7 @@ class AWSDiscovery {
503
501
  * @returns {string} return.privateSubnetId2 - Second private subnet ID
504
502
  * @returns {string} return.publicSubnetId - Public subnet ID for NAT Gateway
505
503
  * @returns {string} return.privateRouteTableId - Private route table ID
506
- * @returns {string} return.defaultKmsKeyId - Default KMS key ARN
504
+ * @returns {string|null} return.defaultKmsKeyId - Default KMS key ARN or null if not found
507
505
  * @throws {Error} If resource discovery fails
508
506
  */
509
507
  async discoverResources() {
@@ -526,7 +524,11 @@ class AWSDiscovery {
526
524
  console.log(`Found route table: ${routeTable.RouteTableId}`);
527
525
 
528
526
  const kmsKeyArn = await this.findDefaultKmsKey();
529
- console.log(`Found KMS key: ${kmsKeyArn}`);
527
+ if (kmsKeyArn) {
528
+ console.log(`Found KMS key: ${kmsKeyArn}`);
529
+ } else {
530
+ console.log('No KMS key found');
531
+ }
530
532
 
531
533
  // Try to find existing NAT Gateway
532
534
  const existingNatGateway = await this.findExistingNatGateway(vpc.VpcId);
@@ -137,8 +137,8 @@ class BuildTimeDiscovery {
137
137
  console.log('Running pre-build AWS discovery hook...');
138
138
 
139
139
  // Only run discovery if VPC, KMS, or SSM features are enabled
140
- const needsDiscovery = appDefinition.vpc?.enable ||
141
- appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
140
+ const needsDiscovery = appDefinition.vpc?.enable ||
141
+ appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
142
142
  appDefinition.ssm?.enable;
143
143
 
144
144
  if (!needsDiscovery) {
@@ -159,7 +159,7 @@ class BuildTimeDiscovery {
159
159
  AWS_DISCOVERY_SUBNET_ID_2: resources.privateSubnetId2,
160
160
  AWS_DISCOVERY_PUBLIC_SUBNET_ID: resources.publicSubnetId,
161
161
  AWS_DISCOVERY_ROUTE_TABLE_ID: resources.privateRouteTableId,
162
- AWS_DISCOVERY_KMS_KEY_ID: resources.defaultKmsKeyId
162
+ AWS_DISCOVERY_KMS_KEY_ID: resources.defaultKmsKeyId // Keep consistent naming convention (even though it's an ARN)
163
163
  };
164
164
 
165
165
  // Set environment variables for serverless to use
@@ -250,7 +250,7 @@ describe('BuildTimeDiscovery', () => {
250
250
 
251
251
  it('should run discovery when KMS is enabled', async () => {
252
252
  const appDefinition = {
253
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
253
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
254
254
  integrations: []
255
255
  };
256
256
 
@@ -35,7 +35,7 @@ function generateIAMCloudFormation(appDefinition, options = {}) {
35
35
  vpc: appDefinition.vpc?.enable === true,
36
36
  kms:
37
37
  appDefinition.encryption
38
- ?.useDefaultKMSForFieldLevelEncryption === true,
38
+ ?.fieldLevelEncryptionMethod === 'kms',
39
39
  ssm: appDefinition.ssm?.enable === true,
40
40
  websockets: appDefinition.websockets?.enable === true,
41
41
  };
@@ -605,6 +605,19 @@ function generateIAMCloudFormation(appDefinition, options = {}) {
605
605
  },
606
606
  },
607
607
  },
608
+ {
609
+ Sid: 'FriggKMSManagement',
610
+ Effect: 'Allow',
611
+ Action: [
612
+ 'kms:CreateKey',
613
+ 'kms:PutKeyPolicy',
614
+ 'kms:EnableKeyRotation',
615
+ 'kms:TagResource',
616
+ 'kms:UntagResource',
617
+ 'kms:ListResourceTags',
618
+ ],
619
+ Resource: '*',
620
+ },
608
621
  ],
609
622
  },
610
623
  },
@@ -724,8 +737,7 @@ function getFeatureSummary(appDefinition) {
724
737
  core: true, // Always enabled
725
738
  vpc: appDefinition.vpc?.enable === true,
726
739
  kms:
727
- appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ===
728
- true,
740
+ appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms',
729
741
  ssm: appDefinition.ssm?.enable === true,
730
742
  websockets: appDefinition.websockets?.enable === true,
731
743
  };
@@ -7,7 +7,7 @@ describe('IAM Generator', () => {
7
7
  name: 'test-app',
8
8
  integrations: ['Integration1', 'Integration2'],
9
9
  vpc: { enable: true },
10
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
10
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
11
11
  ssm: { enable: true },
12
12
  websockets: { enable: true }
13
13
  };
@@ -46,7 +46,7 @@ describe('IAM Generator', () => {
46
46
  name: 'test-app',
47
47
  integrations: [],
48
48
  vpc: { enable: false },
49
- encryption: { useDefaultKMSForFieldLevelEncryption: false },
49
+ encryption: { fieldLevelEncryptionMethod: 'aes' },
50
50
  ssm: { enable: false },
51
51
  websockets: { enable: false }
52
52
  };
@@ -77,7 +77,7 @@ describe('IAM Generator', () => {
77
77
  const appDefinition = {
78
78
  name: 'test-app',
79
79
  integrations: [],
80
- encryption: { useDefaultKMSForFieldLevelEncryption: true }
80
+ encryption: { fieldLevelEncryptionMethod: 'kms' }
81
81
  };
82
82
 
83
83
  const yaml = generateIAMCloudFormation(appDefinition);
@@ -106,7 +106,7 @@ describe('IAM Generator', () => {
106
106
  name: 'test-app',
107
107
  integrations: [],
108
108
  vpc: { enable: true },
109
- encryption: { useDefaultKMSForFieldLevelEncryption: false },
109
+ encryption: { fieldLevelEncryptionMethod: 'aes' },
110
110
  ssm: { enable: true }
111
111
  };
112
112
 
@@ -49,7 +49,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
49
49
  const appDefinition = {
50
50
  name: 'test-frigg-app',
51
51
  vpc: { enable: true },
52
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
52
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
53
53
  ssm: { enable: true },
54
54
  integrations: [{
55
55
  Definition: {
@@ -67,7 +67,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
67
67
  process.env.AWS_DISCOVERY_SUBNET_ID_1 = discoveredResources.privateSubnetId1;
68
68
  process.env.AWS_DISCOVERY_SUBNET_ID_2 = discoveredResources.privateSubnetId2;
69
69
  process.env.AWS_DISCOVERY_ROUTE_TABLE_ID = discoveredResources.privateRouteTableId;
70
- process.env.AWS_DISCOVERY_KMS_KEY_ID = discoveredResources.defaultKmsKeyId;
70
+ process.env.AWS_DISCOVERY_KMS_KEY_ID =discoveredResources.defaultKmsKeyId;
71
71
 
72
72
  // Generate serverless configuration
73
73
  const serverlessConfig = composeServerlessDefinition(appDefinition);
@@ -173,11 +173,11 @@ describe('VPC/KMS/SSM Integration Tests', () => {
173
173
  it('should generate config with only KMS enabled', async () => {
174
174
  const appDefinition = {
175
175
  name: 'kms-only-app',
176
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
176
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
177
177
  integrations: []
178
178
  };
179
179
 
180
- process.env.AWS_DISCOVERY_KMS_KEY_ID = mockAWSResources.defaultKmsKeyId;
180
+ process.env.AWS_DISCOVERY_KMS_KEY_ID =mockAWSResources.defaultKmsKeyId;
181
181
 
182
182
  const serverlessConfig = composeServerlessDefinition(appDefinition);
183
183
 
@@ -231,7 +231,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
231
231
  expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
232
232
  expect.objectContaining({
233
233
  vpc: { enable: true },
234
- encryption: { useDefaultKMSForFieldLevelEncryption: true }
234
+ encryption: { fieldLevelEncryptionMethod: 'kms' }
235
235
  }),
236
236
  'us-east-1'
237
237
  );
@@ -302,7 +302,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
302
302
  process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = mockAWSResources.defaultSecurityGroupId;
303
303
  process.env.AWS_DISCOVERY_SUBNET_ID_1 = mockAWSResources.privateSubnetId1;
304
304
  process.env.AWS_DISCOVERY_SUBNET_ID_2 = mockAWSResources.privateSubnetId2;
305
- process.env.AWS_DISCOVERY_KMS_KEY_ID = mockAWSResources.defaultKmsKeyId;
305
+ process.env.AWS_DISCOVERY_KMS_KEY_ID =mockAWSResources.defaultKmsKeyId;
306
306
 
307
307
  // In a real deployment, serverless framework would resolve these environment variables
308
308
  // For testing, we can verify the placeholders are correctly formatted
@@ -353,7 +353,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
353
353
 
354
354
  const appDefinition = {
355
355
  vpc: { enable: true },
356
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
356
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
357
357
  integrations: []
358
358
  };
359
359
 
@@ -41,7 +41,7 @@ async function runDiscovery() {
41
41
 
42
42
  // Check if discovery is needed
43
43
  const needsDiscovery = appDefinition.vpc?.enable ||
44
- appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
44
+ appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
45
45
  appDefinition.ssm?.enable;
46
46
 
47
47
  if (!needsDiscovery) {
@@ -51,7 +51,7 @@ async function runDiscovery() {
51
51
 
52
52
  console.log('📋 App requires AWS discovery for:');
53
53
  if (appDefinition.vpc?.enable) console.log(' ✅ VPC support');
54
- if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.log(' ✅ KMS encryption');
54
+ if (appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') console.log(' ✅ KMS encryption');
55
55
  if (appDefinition.ssm?.enable) console.log(' ✅ SSM parameters');
56
56
 
57
57
  // Run discovery
@@ -82,7 +82,7 @@ async function runDiscovery() {
82
82
  } else {
83
83
  console.error('🚨 Discovery is required because your AppDefinition has these features enabled:');
84
84
  if (appDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
85
- if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.error(' ❌ KMS encryption (encryption.useDefaultKMSForFieldLevelEncryption: true)');
85
+ if (appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') console.error(' ❌ KMS encryption (encryption.fieldLevelEncryptionMethod: \'kms\')');
86
86
  if (appDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
87
87
  console.error('');
88
88
  console.error('💡 To fix this issue:');
@@ -95,7 +95,7 @@ async function runDiscovery() {
95
95
 
96
96
  console.error('🔧 Or disable features in backend/index.js:');
97
97
  console.error(' vpc: { enable: false }');
98
- console.error(' encryption: { useDefaultKMSForFieldLevelEncryption: false }');
98
+ console.error(' encryption: { fieldLevelEncryptionMethod: \'aes\' }');
99
99
  console.error(' ssm: { enable: false }');
100
100
 
101
101
  process.exit(1);
@@ -10,8 +10,7 @@ const { AWSDiscovery } = require('./aws-discovery');
10
10
  const shouldRunDiscovery = (AppDefinition) => {
11
11
  return (
12
12
  AppDefinition.vpc?.enable === true ||
13
- AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ===
14
- true ||
13
+ AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
15
14
  AppDefinition.ssm?.enable === true
16
15
  );
17
16
  };
@@ -493,10 +492,7 @@ const createVPCInfrastructure = (AppDefinition) => {
493
492
  };
494
493
 
495
494
  // KMS Interface Endpoint (paid, but useful if using KMS)
496
- if (
497
- AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ===
498
- true
499
- ) {
495
+ if (AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
500
496
  vpcResources.FriggKMSVPCEndpoint = {
501
497
  Type: 'AWS::EC2::VPCEndpoint',
502
498
  Properties: {
@@ -891,9 +887,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
891
887
  };
892
888
 
893
889
  // KMS Configuration based on App Definition
894
- if (
895
- AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true
896
- ) {
890
+ if (AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
897
891
  // Check if a KMS key was discovered
898
892
  if (discoveredResources.defaultKmsKeyId) {
899
893
  // Use the existing discovered KMS key
@@ -907,55 +901,112 @@ const composeServerlessDefinition = async (AppDefinition) => {
907
901
  Resource: [discoveredResources.defaultKmsKeyId],
908
902
  });
909
903
 
910
- definition.provider.environment.KMS_KEY_ARN =
911
- discoveredResources.defaultKmsKeyId;
904
+ // KMS_KEY_ARN will be set later from custom.kmsGrants for consistency
912
905
  } else {
913
- // No existing key found, provision a dedicated KMS key
914
- console.log('No existing KMS key found, creating a new one...');
906
+ // No existing key found - check if we should create one or error
907
+ if (AppDefinition.encryption?.createResourceIfNoneFound === true) {
908
+ // Create a new KMS key
909
+ console.log('No existing KMS key found, creating a new one...');
915
910
 
916
- definition.resources.Resources.FriggKMSKey = {
917
- Type: 'AWS::KMS::Key',
918
- Properties: {
919
- EnableKeyRotation: true,
920
- KeyPolicy: {
921
- Version: '2012-10-17',
922
- Statement: [
923
- {
924
- Sid: 'AllowRootAccountAdmin',
925
- Effect: 'Allow',
926
- Principal: {
927
- AWS: {
928
- 'Fn::Sub':
929
- 'arn:aws:iam::${AWS::AccountId}:root',
911
+ definition.resources.Resources.FriggKMSKey = {
912
+ Type: 'AWS::KMS::Key',
913
+ Properties: {
914
+ EnableKeyRotation: true,
915
+ Description: 'Frigg KMS key for field-level encryption',
916
+ KeyPolicy: {
917
+ Version: '2012-10-17',
918
+ Statement: [
919
+ {
920
+ Sid: 'AllowRootAccountAdmin',
921
+ Effect: 'Allow',
922
+ Principal: {
923
+ AWS: {
924
+ 'Fn::Sub':
925
+ 'arn:aws:iam::${AWS::AccountId}:root',
926
+ },
927
+ },
928
+ Action: 'kms:*',
929
+ Resource: '*',
930
+ },
931
+ {
932
+ Sid: 'AllowLambdaService',
933
+ Effect: 'Allow',
934
+ Principal: {
935
+ Service: 'lambda.amazonaws.com',
936
+ },
937
+ Action: [
938
+ 'kms:GenerateDataKey',
939
+ 'kms:Decrypt',
940
+ 'kms:DescribeKey',
941
+ ],
942
+ Resource: '*',
943
+ Condition: {
944
+ StringEquals: {
945
+ 'kms:ViaService': `lambda.${
946
+ process.env.AWS_REGION ||
947
+ 'us-east-1'
948
+ }.amazonaws.com`,
949
+ },
930
950
  },
931
951
  },
932
- Action: 'kms:*',
933
- Resource: '*',
952
+ ],
953
+ },
954
+ Tags: [
955
+ {
956
+ Key: 'Name',
957
+ Value: '${self:service}-${self:provider.stage}-frigg-kms-key',
958
+ },
959
+ {
960
+ Key: 'Purpose',
961
+ Value: 'Field-level encryption for Frigg application',
934
962
  },
935
963
  ],
936
964
  },
937
- },
938
- };
965
+ };
939
966
 
940
- definition.provider.iamRoleStatements.push({
941
- Effect: 'Allow',
942
- Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
943
- Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }],
944
- });
967
+ definition.provider.iamRoleStatements.push({
968
+ Effect: 'Allow',
969
+ Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
970
+ Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }],
971
+ });
945
972
 
946
- definition.provider.environment.KMS_KEY_ARN = {
947
- 'Fn::GetAtt': ['FriggKMSKey', 'Arn'],
948
- };
973
+ definition.provider.environment.KMS_KEY_ARN = {
974
+ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'],
975
+ };
976
+
977
+ // Configure KMS grants to reference the created key
978
+ definition.custom.kmsGrants = {
979
+ kmsKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }
980
+ };
981
+ } else {
982
+ // No key found and createIfNoneFound is not enabled - error
983
+ throw new Error(
984
+ 'KMS field-level encryption is enabled but no KMS key was found. ' +
985
+ 'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
986
+ );
987
+ }
949
988
  }
950
989
 
951
990
  definition.plugins.push('serverless-kms-grants');
952
991
 
953
- // Configure KMS grants with discovered default key or environment variable
954
- definition.custom.kmsGrants = {
955
- kmsKeyId:
992
+ // Configure KMS grants if not already set (when using existing key)
993
+ if (!definition.custom.kmsGrants) {
994
+ definition.custom.kmsGrants = {
995
+ kmsKeyId:
996
+ discoveredResources.defaultKmsKeyId ||
997
+ '${env:AWS_DISCOVERY_KMS_KEY_ID}',
998
+ };
999
+ }
1000
+
1001
+ // Always set KMS_KEY_ARN from custom.kmsGrants for consistency
1002
+ // This translates AWS_DISCOVERY_KMS_KEY_ID to the runtime variable KMS_KEY_ARN
1003
+ if (!definition.provider.environment.KMS_KEY_ARN) {
1004
+ // Use the discovered value directly when available (from in-process discovery)
1005
+ // Otherwise fall back to environment variable (from separate discovery process)
1006
+ definition.provider.environment.KMS_KEY_ARN =
956
1007
  discoveredResources.defaultKmsKeyId ||
957
- '${env:AWS_DISCOVERY_KMS_KEY_ID}',
958
- };
1008
+ '${env:AWS_DISCOVERY_KMS_KEY_ID}';
1009
+ }
959
1010
  }
960
1011
 
961
1012
  // VPC Configuration based on App Definition
@@ -1252,62 +1303,6 @@ const composeServerlessDefinition = async (AppDefinition) => {
1252
1303
  definition.custom[queueReference] = queueName;
1253
1304
  }
1254
1305
  }
1255
-
1256
- // Discovery has already run successfully at this point if needed
1257
- // The discoveredResources object contains all the necessary AWS resources
1258
-
1259
- // Add websocket function if enabled
1260
- if (AppDefinition.websockets?.enable === true) {
1261
- definition.functions.defaultWebsocket = {
1262
- handler:
1263
- 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
1264
- events: [
1265
- {
1266
- websocket: {
1267
- route: '$connect',
1268
- },
1269
- },
1270
- {
1271
- websocket: {
1272
- route: '$default',
1273
- },
1274
- },
1275
- {
1276
- websocket: {
1277
- route: '$disconnect',
1278
- },
1279
- },
1280
- ],
1281
- };
1282
- }
1283
-
1284
- // Discovery has already run successfully at this point if needed
1285
- // The discoveredResources object contains all the necessary AWS resources
1286
-
1287
- // Add websocket function if enabled
1288
- if (AppDefinition.websockets?.enable === true) {
1289
- definition.functions.defaultWebsocket = {
1290
- handler:
1291
- 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
1292
- events: [
1293
- {
1294
- websocket: {
1295
- route: '$connect',
1296
- },
1297
- },
1298
- {
1299
- websocket: {
1300
- route: '$default',
1301
- },
1302
- },
1303
- {
1304
- websocket: {
1305
- route: '$disconnect',
1306
- },
1307
- },
1308
- ],
1309
- };
1310
- }
1311
1306
  }
1312
1307
 
1313
1308
  // Discovery has already run successfully at this point if needed
@@ -1338,9 +1333,6 @@ const composeServerlessDefinition = async (AppDefinition) => {
1338
1333
  };
1339
1334
  }
1340
1335
 
1341
- // Discovery has already run successfully at this point if needed
1342
- // The discoveredResources object contains all the necessary AWS resources
1343
-
1344
1336
  // Modify handler paths to point to the correct node_modules location
1345
1337
  definition.functions = modifyHandlerPaths(definition.functions);
1346
1338
 
@@ -118,16 +118,9 @@ describe('composeServerlessDefinition', () => {
118
118
 
119
119
  const result = await composeServerlessDefinition(appDefinition);
120
120
 
121
- expect(result.provider.vpc).toBe('${self:custom.vpc.${self:provider.stage}}');
122
- expect(result.custom.vpc).toEqual({
123
- '${self:provider.stage}': {
124
- securityGroupIds: ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}'],
125
- subnetIds: [
126
- '${env:AWS_DISCOVERY_SUBNET_ID_1}',
127
- '${env:AWS_DISCOVERY_SUBNET_ID_2}'
128
- ]
129
- }
130
- });
121
+ expect(result.provider.vpc).toBeDefined();
122
+ expect(result.provider.vpc.securityGroupIds).toEqual(['sg-123456']);
123
+ expect(result.provider.vpc.subnetIds).toEqual(['subnet-123456', 'subnet-789012']);
131
124
  });
132
125
 
133
126
  it('should add VPC endpoint for S3 when VPC is enabled', async () => {
@@ -138,15 +131,9 @@ describe('composeServerlessDefinition', () => {
138
131
 
139
132
  const result = await composeServerlessDefinition(appDefinition);
140
133
 
141
- expect(result.resources.Resources.VPCEndpointS3).toEqual({
142
- Type: 'AWS::EC2::VPCEndpoint',
143
- Properties: {
144
- VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
145
- ServiceName: 'com.amazonaws.${self:provider.region}.s3',
146
- VpcEndpointType: 'Gateway',
147
- RouteTableIds: ['${env:AWS_DISCOVERY_ROUTE_TABLE_ID}']
148
- }
149
- });
134
+ expect(result.resources.Resources.VPCEndpointS3).toBeDefined();
135
+ expect(result.resources.Resources.VPCEndpointS3.Type).toBe('AWS::EC2::VPCEndpoint');
136
+ expect(result.resources.Resources.VPCEndpointS3.Properties.VpcId).toBe('vpc-123456');
150
137
  });
151
138
 
152
139
  it('should not add VPC configuration when vpc.enable is false', async () => {
@@ -158,7 +145,6 @@ describe('composeServerlessDefinition', () => {
158
145
  const result = await composeServerlessDefinition(appDefinition);
159
146
 
160
147
  expect(result.provider.vpc).toBeUndefined();
161
- expect(result.custom.vpc).toBeUndefined();
162
148
  expect(result.resources.Resources.VPCEndpointS3).toBeUndefined();
163
149
  });
164
150
 
@@ -170,14 +156,13 @@ describe('composeServerlessDefinition', () => {
170
156
  const result = await composeServerlessDefinition(appDefinition);
171
157
 
172
158
  expect(result.provider.vpc).toBeUndefined();
173
- expect(result.custom.vpc).toBeUndefined();
174
159
  });
175
160
  });
176
161
 
177
162
  describe('KMS Configuration', () => {
178
- it('should add KMS configuration when encryption is enabled', async () => {
163
+ it('should add KMS configuration when encryption is enabled and key is found', async () => {
179
164
  const appDefinition = {
180
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
165
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
181
166
  integrations: []
182
167
  };
183
168
 
@@ -193,24 +178,188 @@ describe('composeServerlessDefinition', () => {
193
178
  'kms:GenerateDataKey',
194
179
  'kms:Decrypt'
195
180
  ],
196
- Resource: ['${self:custom.kmsGrants.kmsKeyId}']
181
+ Resource: ['arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012']
197
182
  });
198
183
 
199
184
  // Check environment variable
200
- expect(result.provider.environment.KMS_KEY_ARN).toBe('${self:custom.kmsGrants.kmsKeyId}');
185
+ expect(result.provider.environment.KMS_KEY_ARN).toBe('arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012');
201
186
 
202
187
  // Check plugin
203
188
  expect(result.plugins).toContain('serverless-kms-grants');
204
189
 
205
190
  // Check custom configuration
206
191
  expect(result.custom.kmsGrants).toEqual({
207
- kmsKeyId: '${env:AWS_DISCOVERY_KMS_KEY_ID}'
192
+ kmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
193
+ });
194
+ });
195
+
196
+ it('should create new KMS key when encryption is enabled, no key found, and createResourceIfNoneFound is true', async () => {
197
+ // Mock AWS discovery to return no KMS key
198
+ const { AWSDiscovery } = require('./aws-discovery');
199
+ const mockDiscoverResources = jest.fn().mockResolvedValue({
200
+ defaultVpcId: 'vpc-123456',
201
+ defaultSecurityGroupId: 'sg-123456',
202
+ privateSubnetId1: 'subnet-123456',
203
+ privateSubnetId2: 'subnet-789012',
204
+ publicSubnetId: 'subnet-public',
205
+ defaultRouteTableId: 'rtb-123456',
206
+ defaultKmsKeyId: null // No KMS key found
207
+ });
208
+ AWSDiscovery.mockImplementation(() => ({
209
+ discoverResources: mockDiscoverResources
210
+ }));
211
+
212
+ const appDefinition = {
213
+ encryption: {
214
+ fieldLevelEncryptionMethod: 'kms',
215
+ createResourceIfNoneFound: true
216
+ },
217
+ integrations: []
218
+ };
219
+
220
+ const result = await composeServerlessDefinition(appDefinition);
221
+
222
+ // Check that KMS key resource was created
223
+ expect(result.resources.Resources.FriggKMSKey).toEqual({
224
+ Type: 'AWS::KMS::Key',
225
+ Properties: {
226
+ EnableKeyRotation: true,
227
+ Description: 'Frigg KMS key for field-level encryption',
228
+ KeyPolicy: {
229
+ Version: '2012-10-17',
230
+ Statement: [
231
+ {
232
+ Sid: 'AllowRootAccountAdmin',
233
+ Effect: 'Allow',
234
+ Principal: {
235
+ AWS: {
236
+ 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:root'
237
+ }
238
+ },
239
+ Action: 'kms:*',
240
+ Resource: '*'
241
+ },
242
+ {
243
+ Sid: 'AllowLambdaService',
244
+ Effect: 'Allow',
245
+ Principal: {
246
+ Service: 'lambda.amazonaws.com'
247
+ },
248
+ Action: [
249
+ 'kms:GenerateDataKey',
250
+ 'kms:Decrypt',
251
+ 'kms:DescribeKey'
252
+ ],
253
+ Resource: '*',
254
+ Condition: {
255
+ StringEquals: {
256
+ 'kms:ViaService': 'lambda.us-east-1.amazonaws.com'
257
+ }
258
+ }
259
+ }
260
+ ]
261
+ },
262
+ Tags: [
263
+ {
264
+ Key: 'Name',
265
+ Value: '${self:service}-${self:provider.stage}-frigg-kms-key'
266
+ },
267
+ {
268
+ Key: 'Purpose',
269
+ Value: 'Field-level encryption for Frigg application'
270
+ }
271
+ ]
272
+ }
273
+ });
274
+
275
+ // Check IAM permissions for the new key
276
+ const kmsPermission = result.provider.iamRoleStatements.find(
277
+ statement => statement.Action.includes('kms:GenerateDataKey')
278
+ );
279
+ expect(kmsPermission).toEqual({
280
+ Effect: 'Allow',
281
+ Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
282
+ Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }]
283
+ });
284
+
285
+ // Check environment variable
286
+ expect(result.provider.environment.KMS_KEY_ARN).toEqual({
287
+ 'Fn::GetAtt': ['FriggKMSKey', 'Arn']
288
+ });
289
+
290
+ // Check plugin
291
+ expect(result.plugins).toContain('serverless-kms-grants');
292
+
293
+ // Check custom configuration
294
+ // When creating a new key, it should reference the CloudFormation resource
295
+ expect(result.custom.kmsGrants).toEqual({
296
+ kmsKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }
297
+ });
298
+ });
299
+
300
+ it('should throw error when encryption is enabled, no key found, and createResourceIfNoneFound is false', async () => {
301
+ // Mock AWS discovery to return no KMS key
302
+ const { AWSDiscovery } = require('./aws-discovery');
303
+ const mockDiscoverResources = jest.fn().mockResolvedValue({
304
+ defaultVpcId: 'vpc-123456',
305
+ defaultSecurityGroupId: 'sg-123456',
306
+ privateSubnetId1: 'subnet-123456',
307
+ privateSubnetId2: 'subnet-789012',
308
+ publicSubnetId: 'subnet-public',
309
+ defaultRouteTableId: 'rtb-123456',
310
+ defaultKmsKeyId: null // No KMS key found
311
+ });
312
+ AWSDiscovery.mockImplementation(() => ({
313
+ discoverResources: mockDiscoverResources
314
+ }));
315
+
316
+ const appDefinition = {
317
+ encryption: {
318
+ fieldLevelEncryptionMethod: 'kms',
319
+ createResourceIfNoneFound: false
320
+ },
321
+ integrations: []
322
+ };
323
+
324
+ await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
325
+ 'KMS field-level encryption is enabled but no KMS key was found. ' +
326
+ 'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
327
+ );
328
+ });
329
+
330
+ it('should throw error when encryption is enabled, no key found, and createResourceIfNoneFound is not specified', async () => {
331
+ // Mock AWS discovery to return no KMS key
332
+ const { AWSDiscovery } = require('./aws-discovery');
333
+ const mockDiscoverResources = jest.fn().mockResolvedValue({
334
+ defaultVpcId: 'vpc-123456',
335
+ defaultSecurityGroupId: 'sg-123456',
336
+ privateSubnetId1: 'subnet-123456',
337
+ privateSubnetId2: 'subnet-789012',
338
+ publicSubnetId: 'subnet-public',
339
+ defaultRouteTableId: 'rtb-123456',
340
+ defaultKmsKeyId: null // No KMS key found
208
341
  });
342
+ AWSDiscovery.mockImplementation(() => ({
343
+ discoverResources: mockDiscoverResources
344
+ }));
345
+
346
+ const appDefinition = {
347
+ encryption: {
348
+ fieldLevelEncryptionMethod: 'kms'
349
+ // createResourceIfNoneFound not specified, defaults to false
350
+ },
351
+ integrations: []
352
+ };
353
+
354
+ await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
355
+ 'KMS field-level encryption is enabled but no KMS key was found. ' +
356
+ 'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
357
+ );
209
358
  });
210
359
 
211
360
  it('should not add KMS configuration when encryption is disabled', async () => {
212
361
  const appDefinition = {
213
- encryption: { useDefaultKMSForFieldLevelEncryption: false },
362
+ encryption: { fieldLevelEncryptionMethod: 'aes' },
214
363
  integrations: []
215
364
  };
216
365
 
@@ -315,10 +464,9 @@ describe('composeServerlessDefinition', () => {
315
464
  expect(result.functions.testIntegration).toEqual({
316
465
  handler: 'node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.testIntegration.handler',
317
466
  events: [{
318
- http: {
467
+ httpApi: {
319
468
  path: '/api/testIntegration-integration/{proxy+}',
320
- method: 'ANY',
321
- cors: true
469
+ method: 'ANY'
322
470
  }
323
471
  }]
324
472
  });
@@ -389,7 +537,7 @@ describe('composeServerlessDefinition', () => {
389
537
  it('should combine VPC, KMS, and SSM configurations', async () => {
390
538
  const appDefinition = {
391
539
  vpc: { enable: true },
392
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
540
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
393
541
  ssm: { enable: true },
394
542
  integrations: [mockIntegration]
395
543
  };
@@ -398,7 +546,7 @@ describe('composeServerlessDefinition', () => {
398
546
 
399
547
  // VPC
400
548
  expect(result.provider.vpc).toBeDefined();
401
- expect(result.custom.vpc).toBeDefined();
549
+ // custom.vpc doesn't exist in the serverless template
402
550
  expect(result.resources.Resources.VPCEndpointS3).toBeDefined();
403
551
 
404
552
  // KMS
@@ -428,7 +576,7 @@ describe('composeServerlessDefinition', () => {
428
576
  it('should handle partial configuration combinations', async () => {
429
577
  const appDefinition = {
430
578
  vpc: { enable: true },
431
- encryption: { useDefaultKMSForFieldLevelEncryption: true },
579
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
432
580
  integrations: []
433
581
  };
434
582
 
@@ -459,9 +607,9 @@ describe('composeServerlessDefinition', () => {
459
607
  expect(result.resources.Resources.ApiGatewayAlarm5xx).toBeDefined();
460
608
 
461
609
  // Check default functions
462
- expect(result.functions.defaultWebsocket).toBeDefined();
463
610
  expect(result.functions.auth).toBeDefined();
464
611
  expect(result.functions.user).toBeDefined();
612
+ expect(result.functions.health).toBeDefined();
465
613
 
466
614
  // Check default plugins
467
615
  expect(result.plugins).toContain('serverless-jetpack');
@@ -496,11 +644,64 @@ describe('composeServerlessDefinition', () => {
496
644
 
497
645
  const result = await composeServerlessDefinition(appDefinition);
498
646
 
499
- expect(result.provider.environment.STAGE).toBe('${opt:stage}');
647
+ expect(result.provider.environment.STAGE).toBe('${opt:stage, "dev"}');
500
648
  expect(result.provider.environment.AWS_NODEJS_CONNECTION_REUSE_ENABLED).toBe(1);
501
649
  });
502
650
  });
503
651
 
652
+ describe('WebSocket Configuration', () => {
653
+ it('should add websocket function when websockets.enable is true', async () => {
654
+ const appDefinition = {
655
+ websockets: { enable: true },
656
+ integrations: []
657
+ };
658
+
659
+ const result = await composeServerlessDefinition(appDefinition);
660
+
661
+ expect(result.functions.defaultWebsocket).toEqual({
662
+ handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
663
+ events: [
664
+ {
665
+ websocket: {
666
+ route: '$connect',
667
+ },
668
+ },
669
+ {
670
+ websocket: {
671
+ route: '$default',
672
+ },
673
+ },
674
+ {
675
+ websocket: {
676
+ route: '$disconnect',
677
+ },
678
+ },
679
+ ],
680
+ });
681
+ });
682
+
683
+ it('should not add websocket function when websockets.enable is false', async () => {
684
+ const appDefinition = {
685
+ websockets: { enable: false },
686
+ integrations: []
687
+ };
688
+
689
+ const result = await composeServerlessDefinition(appDefinition);
690
+
691
+ expect(result.functions.defaultWebsocket).toBeUndefined();
692
+ });
693
+
694
+ it('should not add websocket function when websockets is not defined', async () => {
695
+ const appDefinition = {
696
+ integrations: []
697
+ };
698
+
699
+ const result = await composeServerlessDefinition(appDefinition);
700
+
701
+ expect(result.functions.defaultWebsocket).toBeUndefined();
702
+ });
703
+ });
704
+
504
705
  describe('Edge Cases', () => {
505
706
  it('should handle empty app definition', async () => {
506
707
  const appDefinition = {};
@@ -515,7 +716,9 @@ describe('composeServerlessDefinition', () => {
515
716
  integrations: null
516
717
  };
517
718
 
518
- await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
719
+ // Should not throw, just ignore invalid integrations
720
+ const result = await composeServerlessDefinition(appDefinition);
721
+ expect(result).toBeDefined();
519
722
  });
520
723
 
521
724
  it('should handle integration with missing Definition', async () => {
@@ -524,7 +727,7 @@ describe('composeServerlessDefinition', () => {
524
727
  integrations: [invalidIntegration]
525
728
  };
526
729
 
527
- await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
730
+ await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow('Invalid integration: missing Definition or name');
528
731
  });
529
732
 
530
733
  it('should handle integration with missing name', async () => {
@@ -535,7 +738,7 @@ describe('composeServerlessDefinition', () => {
535
738
  integrations: [invalidIntegration]
536
739
  };
537
740
 
538
- await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
741
+ await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow('Invalid integration: missing Definition or name');
539
742
  });
540
743
  });
541
744
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0-next.37",
4
+ "version": "2.0.0-next.38",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-ec2": "^3.835.0",
7
7
  "@aws-sdk/client-kms": "^3.835.0",
@@ -9,8 +9,8 @@
9
9
  "@babel/eslint-parser": "^7.18.9",
10
10
  "@babel/parser": "^7.25.3",
11
11
  "@babel/traverse": "^7.25.3",
12
- "@friggframework/schemas": "2.0.0-next.37",
13
- "@friggframework/test": "2.0.0-next.37",
12
+ "@friggframework/schemas": "2.0.0-next.38",
13
+ "@friggframework/test": "2.0.0-next.38",
14
14
  "@hapi/boom": "^10.0.1",
15
15
  "@inquirer/prompts": "^5.3.8",
16
16
  "axios": "^1.7.2",
@@ -32,8 +32,9 @@
32
32
  "serverless-http": "^2.7.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@friggframework/eslint-config": "2.0.0-next.37",
36
- "@friggframework/prettier-config": "2.0.0-next.37",
35
+ "@friggframework/eslint-config": "2.0.0-next.38",
36
+ "@friggframework/prettier-config": "2.0.0-next.38",
37
+ "jest": "^30.1.3",
37
38
  "prettier": "^2.7.1",
38
39
  "serverless": "3.39.0",
39
40
  "serverless-dotenv-plugin": "^6.0.0",
@@ -65,5 +66,5 @@
65
66
  "publishConfig": {
66
67
  "access": "public"
67
68
  },
68
- "gitHead": "39f0a48fa6bd17c4b8c5caba58c88edc6d6fdd1f"
69
+ "gitHead": "a9c9c28fd9abdc8c96a38b8ab0fbb3cbf7d89960"
69
70
  }