@friggframework/devtools 2.0.0--canary.490.feacde9.0 → 2.0.0--canary.497.a3f25f9.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.
Files changed (26) hide show
  1. package/frigg-cli/deploy-command/index.js +3 -9
  2. package/infrastructure/README.md +0 -28
  3. package/infrastructure/domains/database/migration-builder.js +13 -19
  4. package/infrastructure/domains/database/migration-builder.test.js +0 -57
  5. package/infrastructure/domains/integration/integration-builder.js +14 -19
  6. package/infrastructure/domains/integration/integration-builder.test.js +74 -0
  7. package/infrastructure/domains/networking/vpc-builder.js +18 -240
  8. package/infrastructure/domains/networking/vpc-builder.test.js +13 -711
  9. package/infrastructure/domains/networking/vpc-resolver.js +40 -221
  10. package/infrastructure/domains/networking/vpc-resolver.test.js +18 -318
  11. package/infrastructure/domains/security/kms-builder.js +6 -55
  12. package/infrastructure/domains/security/kms-builder.test.js +1 -19
  13. package/infrastructure/domains/shared/cloudformation-discovery.js +13 -310
  14. package/infrastructure/domains/shared/cloudformation-discovery.test.js +0 -395
  15. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +6 -41
  16. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +0 -39
  17. package/infrastructure/domains/shared/resource-discovery.js +5 -17
  18. package/infrastructure/domains/shared/resource-discovery.test.js +0 -36
  19. package/infrastructure/domains/shared/utilities/base-definition-factory.js +17 -27
  20. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +0 -73
  21. package/infrastructure/infrastructure-composer.js +3 -11
  22. package/infrastructure/scripts/build-prisma-layer.js +81 -8
  23. package/infrastructure/scripts/build-prisma-layer.test.js +53 -1
  24. package/infrastructure/scripts/verify-prisma-layer.js +72 -0
  25. package/package.json +7 -7
  26. package/layers/prisma/.build-complete +0 -3
@@ -9,7 +9,7 @@ describe('VpcResourceResolver', () => {
9
9
  });
10
10
 
11
11
  describe('resolveVpc', () => {
12
- it('should resolve to EXTERNAL with hardcoded vpcId', () => {
12
+ it('should resolve to EXTERNAL when user specifies external', () => {
13
13
  const appDefinition = {
14
14
  vpc: {
15
15
  ownership: { vpc: 'external' },
@@ -22,46 +22,20 @@ describe('VpcResourceResolver', () => {
22
22
 
23
23
  expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
24
24
  expect(decision.physicalId).toBe('vpc-external-123');
25
- expect(decision.reason).toContain('hardcoded vpcId');
25
+ expect(decision.reason).toContain('User specified ownership=external');
26
26
  });
27
27
 
28
- it('should resolve to EXTERNAL using discovered VPC when no hardcoded ID', () => {
28
+ it('should throw when external specified but vpcId missing', () => {
29
29
  const appDefinition = {
30
30
  vpc: {
31
- ownership: { vpc: 'external' }
32
- // No external.vpcId provided
33
- }
34
- };
35
- const discovery = {
36
- stackManaged: [],
37
- external: [],
38
- fromCloudFormation: false,
39
- defaultVpcId: 'vpc-discovered-123'
40
- };
41
-
42
- const decision = resolver.resolveVpc(appDefinition, discovery);
43
-
44
- expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
45
- expect(decision.physicalId).toBe('vpc-discovered-123');
46
- expect(decision.reason).toContain('discovered VPC');
47
- });
48
-
49
- it('should throw when external specified but no vpcId and no discovery', () => {
50
- const appDefinition = {
51
- vpc: {
52
- ownership: { vpc: 'external' }
53
- // No external.vpcId provided
31
+ ownership: { vpc: 'external' },
32
+ external: {}
54
33
  }
55
34
  };
56
- const discovery = {
57
- stackManaged: [],
58
- external: [],
59
- fromCloudFormation: false
60
- // No defaultVpcId discovered
61
- };
35
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
62
36
 
63
37
  expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
64
- /ownership='external' for VPC requires either/
38
+ "ownership='external' for vpcId requires external.vpcId"
65
39
  );
66
40
  });
67
41
 
@@ -123,63 +97,22 @@ describe('VpcResourceResolver', () => {
123
97
  expect(decision.physicalId).toBe('vpc-external');
124
98
  });
125
99
 
126
- it('should throw error when auto mode finds no VPC (changed behavior)', () => {
100
+ it('should auto-resolve to STACK when not found (create new)', () => {
127
101
  const appDefinition = {
128
102
  vpc: { ownership: { vpc: 'auto' } }
129
- // No management specified - defaults to discover
130
103
  };
131
104
  const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
132
105
 
133
- // NEW BEHAVIOR: Auto mode with no VPC found should throw error (prevent accidental VPC creation)
134
- expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
135
- 'VPC discovery failed: No VPC found'
136
- );
137
- });
138
-
139
- it('should throw error when auto mode finds no VPC and management is not create-new', () => {
140
- const appDefinition = {
141
- vpc: {
142
- enable: true,
143
- // No management specified - defaults to discover
144
- // No ownership specified - defaults to auto
145
- }
146
- };
147
- const discovery = {
148
- stackManaged: [],
149
- external: [],
150
- fromCloudFormation: false
151
- };
152
-
153
- // Should throw error instead of trying to create VPC
154
- expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
155
- 'VPC discovery failed: No VPC found'
156
- );
157
- });
158
-
159
- it('should allow creating VPC when management is create-new', () => {
160
- const appDefinition = {
161
- vpc: {
162
- enable: true,
163
- management: 'create-new',
164
- ownership: { vpc: 'auto' }
165
- }
166
- };
167
- const discovery = {
168
- stackManaged: [],
169
- external: [],
170
- fromCloudFormation: false
171
- };
172
-
173
- // Should NOT throw - create-new explicitly allows VPC creation
174
106
  const decision = resolver.resolveVpc(appDefinition, discovery);
175
-
107
+
176
108
  expect(decision.ownership).toBe(ResourceOwnership.STACK);
177
- expect(decision.physicalId).toBeUndefined(); // resolveResourceOwnership returns undefined, not null
109
+ expect(decision.physicalId).toBeUndefined();
110
+ expect(decision.reason).toContain('No existing resource found');
178
111
  });
179
112
  });
180
113
 
181
114
  describe('resolveSecurityGroup', () => {
182
- it('should resolve to EXTERNAL with user-provided hardcoded IDs', () => {
115
+ it('should resolve to EXTERNAL with user-provided IDs', () => {
183
116
  const appDefinition = {
184
117
  vpc: {
185
118
  ownership: { securityGroup: 'external' },
@@ -192,71 +125,6 @@ describe('VpcResourceResolver', () => {
192
125
 
193
126
  expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
194
127
  expect(decision.physicalIds).toEqual(['sg-1', 'sg-2']);
195
- expect(decision.reason).toContain('hardcoded');
196
- });
197
-
198
- it('should resolve to EXTERNAL using discovered default SG when no hardcoded IDs', () => {
199
- const appDefinition = {
200
- vpc: {
201
- ownership: { securityGroup: 'external' }
202
- // No external.securityGroupIds provided
203
- }
204
- };
205
- const discovery = {
206
- stackManaged: [],
207
- external: [],
208
- fromCloudFormation: false,
209
- defaultSecurityGroupId: 'sg-discovered-default'
210
- };
211
-
212
- const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
213
-
214
- expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
215
- expect(decision.physicalIds).toEqual(['sg-discovered-default']);
216
- expect(decision.reason).toContain('discovered default security group');
217
- });
218
-
219
- it('should throw error when ownership=external but no IDs and no discovery', () => {
220
- const appDefinition = {
221
- vpc: {
222
- ownership: { securityGroup: 'external' }
223
- // No external.securityGroupIds provided
224
- }
225
- };
226
- const discovery = {
227
- stackManaged: [],
228
- external: [],
229
- fromCloudFormation: false
230
- // No defaultSecurityGroupId discovered
231
- };
232
-
233
- expect(() => resolver.resolveSecurityGroup(appDefinition, discovery)).toThrow(
234
- /ownership='external' for securityGroup requires either/
235
- );
236
- });
237
-
238
- it('should prefer default SG over stack-managed SG when ownership=external and both discovered', () => {
239
- const appDefinition = {
240
- vpc: {
241
- ownership: { securityGroup: 'external' }
242
- }
243
- };
244
- const discovery = {
245
- stackManaged: [
246
- { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-stack-managed', resourceType: 'AWS::EC2::SecurityGroup' }
247
- ],
248
- external: [],
249
- fromCloudFormation: true,
250
- lambdaSecurityGroupId: 'sg-stack-managed', // Stack-managed SG
251
- defaultSecurityGroupId: 'sg-default-vpc' // Default VPC SG
252
- };
253
-
254
- const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
255
-
256
- // Should use default SG, NOT the stack-managed one
257
- expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
258
- expect(decision.physicalIds).toEqual(['sg-default-vpc']);
259
- expect(decision.reason).toContain('discovered default security group');
260
128
  });
261
129
 
262
130
  it('should auto-resolve to STACK when FriggLambdaSecurityGroup in stack', () => {
@@ -278,7 +146,7 @@ describe('VpcResourceResolver', () => {
278
146
  });
279
147
 
280
148
  describe('resolveSubnets', () => {
281
- it('should resolve to EXTERNAL with user-provided hardcoded subnet IDs', () => {
149
+ it('should resolve to EXTERNAL with user-provided subnet IDs', () => {
282
150
  const appDefinition = {
283
151
  vpc: {
284
152
  ownership: { subnets: 'external' },
@@ -291,48 +159,6 @@ describe('VpcResourceResolver', () => {
291
159
 
292
160
  expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
293
161
  expect(decision.physicalIds).toEqual(['subnet-1', 'subnet-2', 'subnet-3']);
294
- expect(decision.reason).toContain('hardcoded');
295
- });
296
-
297
- it('should resolve to EXTERNAL using discovered subnets when no hardcoded IDs', () => {
298
- const appDefinition = {
299
- vpc: {
300
- ownership: { subnets: 'external' }
301
- // No external.subnetIds provided
302
- }
303
- };
304
- const discovery = {
305
- stackManaged: [],
306
- external: [],
307
- fromCloudFormation: false,
308
- privateSubnetId1: 'subnet-discovered-1',
309
- privateSubnetId2: 'subnet-discovered-2'
310
- };
311
-
312
- const decision = resolver.resolveSubnets(appDefinition, discovery);
313
-
314
- expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
315
- expect(decision.physicalIds).toEqual(['subnet-discovered-1', 'subnet-discovered-2']);
316
- expect(decision.reason).toContain('discovered subnets');
317
- });
318
-
319
- it('should throw error when ownership=external but no IDs and no discovery', () => {
320
- const appDefinition = {
321
- vpc: {
322
- ownership: { subnets: 'external' }
323
- // No external.subnetIds provided
324
- }
325
- };
326
- const discovery = {
327
- stackManaged: [],
328
- external: [],
329
- fromCloudFormation: false
330
- // No privateSubnetId1/2 discovered
331
- };
332
-
333
- expect(() => resolver.resolveSubnets(appDefinition, discovery)).toThrow(
334
- /ownership='external' for subnets requires either/
335
- );
336
162
  });
337
163
 
338
164
  it('should resolve to STACK when subnets found in stack', () => {
@@ -401,7 +227,7 @@ describe('VpcResourceResolver', () => {
401
227
  expect(decision.reason).toContain('NAT Gateway disabled');
402
228
  });
403
229
 
404
- it('should resolve to EXTERNAL with user-provided hardcoded ID', () => {
230
+ it('should resolve to EXTERNAL with user-provided ID', () => {
405
231
  const appDefinition = {
406
232
  vpc: {
407
233
  ownership: { natGateway: 'external' },
@@ -414,47 +240,6 @@ describe('VpcResourceResolver', () => {
414
240
 
415
241
  expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
416
242
  expect(decision.physicalId).toBe('nat-external-123');
417
- expect(decision.reason).toContain('hardcoded');
418
- });
419
-
420
- it('should resolve to EXTERNAL using discovered NAT when no hardcoded ID', () => {
421
- const appDefinition = {
422
- vpc: {
423
- ownership: { natGateway: 'external' }
424
- // No external.natGatewayId provided
425
- }
426
- };
427
- const discovery = {
428
- stackManaged: [],
429
- external: [],
430
- fromCloudFormation: false,
431
- natGatewayId: 'nat-discovered-123'
432
- };
433
-
434
- const decision = resolver.resolveNatGateway(appDefinition, discovery);
435
-
436
- expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
437
- expect(decision.physicalId).toBe('nat-discovered-123');
438
- expect(decision.reason).toContain('discovered NAT gateway');
439
- });
440
-
441
- it('should throw error when ownership=external but no ID and no discovery', () => {
442
- const appDefinition = {
443
- vpc: {
444
- ownership: { natGateway: 'external' }
445
- // No external.natGatewayId provided
446
- }
447
- };
448
- const discovery = {
449
- stackManaged: [],
450
- external: [],
451
- fromCloudFormation: false
452
- // No natGatewayId discovered
453
- };
454
-
455
- expect(() => resolver.resolveNatGateway(appDefinition, discovery)).toThrow(
456
- /ownership='external' for NAT gateway requires either/
457
- );
458
243
  });
459
244
 
460
245
  it('should auto-resolve to STACK when found in stack', () => {
@@ -475,82 +260,6 @@ describe('VpcResourceResolver', () => {
475
260
  });
476
261
 
477
262
  describe('resolveVpcEndpoints', () => {
478
- it('should skip DynamoDB endpoint when application uses MongoDB', () => {
479
- const appDefinition = {
480
- vpc: { ownership: { vpcEndpoints: 'auto' } },
481
- database: { mongoDB: { enable: true } }, // Using MongoDB, not DynamoDB
482
- encryption: { fieldLevelEncryptionMethod: 'kms' }
483
- };
484
- const discovery = {
485
- stackManaged: [],
486
- external: [],
487
- fromCloudFormation: false
488
- };
489
-
490
- const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
491
-
492
- expect(decisions.s3.ownership).toBe('stack'); // S3 always needed
493
- expect(decisions.dynamodb.ownership).toBeNull(); // DynamoDB NOT needed
494
- expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
495
- expect(decisions.kms.ownership).toBe('stack'); // KMS needed (encryption enabled)
496
- expect(decisions.secretsManager.ownership).toBe('stack'); // SM always needed
497
- expect(decisions.sqs.ownership).toBe('stack'); // SQS always needed
498
- });
499
-
500
- it('should skip DynamoDB endpoint when application uses PostgreSQL', () => {
501
- const appDefinition = {
502
- vpc: { ownership: { vpcEndpoints: 'auto' } },
503
- database: { postgres: { enable: true } }, // Using PostgreSQL, not DynamoDB
504
- };
505
- const discovery = {
506
- stackManaged: [],
507
- external: [],
508
- fromCloudFormation: false
509
- };
510
-
511
- const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
512
-
513
- expect(decisions.dynamodb.ownership).toBeNull();
514
- expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
515
- });
516
-
517
- it('should create DynamoDB endpoint when explicitly enabled', () => {
518
- const appDefinition = {
519
- vpc: { ownership: { vpcEndpoints: 'auto' } },
520
- database: { dynamodb: { enable: true } }, // Explicitly using DynamoDB
521
- };
522
- const discovery = {
523
- stackManaged: [],
524
- external: [],
525
- fromCloudFormation: false
526
- };
527
-
528
- const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
529
-
530
- expect(decisions.dynamodb.ownership).toBe('stack'); // DynamoDB needed
531
- });
532
-
533
- it('should allow deletion of DynamoDB endpoint when not needed', () => {
534
- const appDefinition = {
535
- vpc: { ownership: { vpcEndpoints: 'auto' } },
536
- database: { mongoDB: { enable: true } }, // Using MongoDB (DynamoDB not needed)
537
- };
538
- const discovery = {
539
- stackManaged: [
540
- { logicalId: 'FriggDynamoDBVPCEndpoint', physicalId: 'vpce-ddb-legacy', resourceType: 'AWS::EC2::VPCEndpoint' }
541
- ],
542
- external: [],
543
- fromCloudFormation: true
544
- };
545
-
546
- const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
547
-
548
- // Should return null (allow CloudFormation to delete it since not needed)
549
- expect(decisions.dynamodb.ownership).toBeNull();
550
- expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
551
- });
552
-
553
-
554
263
  it('should return null decisions when endpoints disabled', () => {
555
264
  const appDefinition = {
556
265
  vpc: {
@@ -579,8 +288,7 @@ describe('VpcResourceResolver', () => {
579
288
  dynamodb: 'vpce-ddb-456'
580
289
  }
581
290
  }
582
- },
583
- database: { dynamodb: { enable: true } } // Enable DynamoDB
291
+ }
584
292
  };
585
293
  const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
586
294
 
@@ -594,10 +302,7 @@ describe('VpcResourceResolver', () => {
594
302
  });
595
303
 
596
304
  it('should auto-resolve to STACK when endpoints found in stack', () => {
597
- const appDefinition = {
598
- vpc: { ownership: { vpcEndpoints: 'auto' } },
599
- database: { dynamodb: { enable: true } } // Enable DynamoDB
600
- };
305
+ const appDefinition = { vpc: { ownership: { vpcEndpoints: 'auto' } } };
601
306
  const discovery = {
602
307
  stackManaged: [
603
308
  { logicalId: 'FriggS3VPCEndpoint', physicalId: 'vpce-s3-stack', resourceType: 'AWS::EC2::VPCEndpoint' },
@@ -618,8 +323,7 @@ describe('VpcResourceResolver', () => {
618
323
  it('should auto-resolve mixed: some in stack, some new', () => {
619
324
  const appDefinition = {
620
325
  vpc: { ownership: { vpcEndpoints: 'auto' } },
621
- encryption: { fieldLevelEncryptionMethod: 'kms' }, // Enable KMS endpoint
622
- database: { dynamodb: { enable: true } } // Enable DynamoDB
326
+ encryption: { fieldLevelEncryptionMethod: 'kms' } // Enable KMS endpoint
623
327
  };
624
328
  const discovery = {
625
329
  stackManaged: [
@@ -716,11 +420,7 @@ describe('VpcResourceResolver', () => {
716
420
  describe('real-world scenarios', () => {
717
421
  it('scenario: fresh deploy, no resources exist', () => {
718
422
  const appDefinition = {
719
- vpc: {
720
- enable: true,
721
- ownership: {},
722
- management: 'create-new' // Explicitly allow VPC creation
723
- }
423
+ vpc: { enable: true, ownership: {} }
724
424
  };
725
425
  const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
726
426
 
@@ -78,20 +78,8 @@ class KmsBuilder extends InfrastructureBuilder {
78
78
  const resolver = new KmsResourceResolver();
79
79
  const decisions = resolver.resolveAll(appDefinition, discovery);
80
80
 
81
- // Check if external key exists (for accurate logging)
82
- const externalKmsKey = discoveredResources?.defaultKmsKeyId ||
83
- discoveredResources?.kmsKeyArn ||
84
- discoveredResources?.kmsKeyId;
85
- const willUseExternal = decisions.key.ownership === ResourceOwnership.STACK &&
86
- !decisions.key.physicalId &&
87
- externalKmsKey;
88
-
89
81
  console.log('\n 📋 Resource Ownership Decisions:');
90
- if (willUseExternal) {
91
- console.log(` Key: external - Found external KMS key (not in stack)`);
92
- } else {
93
- console.log(` Key: ${decisions.key.ownership} - ${decisions.key.reason}`);
94
- }
82
+ console.log(` Key: ${decisions.key.ownership} - ${decisions.key.reason}`);
95
83
 
96
84
  // Build resources based on ownership decisions
97
85
  await this.buildFromDecisions(decisions, appDefinition, discoveredResources, result);
@@ -253,30 +241,13 @@ class KmsBuilder extends InfrastructureBuilder {
253
241
  if (decisions.key.ownership === ResourceOwnership.STACK && decisions.key.physicalId) {
254
242
  // Key exists in stack - add definitions (CloudFormation idempotency)
255
243
  console.log(' → Adding KMS definitions to template (existing in stack)');
256
-
257
- // CRITICAL: Check if alias exists in stack before trying to create it
258
- // Matches old serverless-template.js behavior: only create alias if it doesn't exist
259
- const aliasExistsInStack = discoveredResources?.existingLogicalIds?.includes('FriggKMSKeyAlias');
260
- if (!aliasExistsInStack) {
261
- if (appDefinition.encryption?.kmsKeyAlias !== true) {
262
- // Alias doesn't exist in stack - skip creation unless explicitly enabled
263
- // This avoids kms:CreateAlias permission errors
264
- console.log(' ℹ KMS alias not in stack - skipping creation (set kmsKeyAlias: true to force)');
265
- appDefinition.encryption = appDefinition.encryption || {};
266
- appDefinition.encryption.kmsKeyAlias = false;
267
- } else {
268
- console.log(' → Will create KMS alias (kmsKeyAlias: true explicitly set)');
269
- }
270
- } else {
271
- console.log(' ✓ KMS alias found in stack - will keep in template');
272
- }
273
-
274
244
  result.resources = this.createKmsKey(appDefinition);
275
245
  result.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
276
246
  console.log(' ✅ KMS key resources created');
277
247
  } else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && externalKmsKey) {
278
248
  // ORPHANED KEY FIX: Key exists externally but not in stack
279
249
  // Use it as external instead of trying to create (would fail with "already exists")
250
+ console.log(' ⚠️ KMS key exists externally but not in stack - using as external resource');
280
251
  console.log(` → Using external KMS key: ${externalKmsKey}`);
281
252
 
282
253
  // Format as ARN if it's just a key ID
@@ -288,17 +259,6 @@ class KmsBuilder extends InfrastructureBuilder {
288
259
  } else if (decisions.key.ownership === ResourceOwnership.STACK && !decisions.key.physicalId && !useEnvVarFallback) {
289
260
  // Create new KMS key (only if not using env var fallback and no external key found)
290
261
  console.log(' → Creating new KMS key in stack');
291
-
292
- // CRITICAL: Don't create alias by default to avoid kms:CreateAlias permission errors
293
- // Matches old serverless-template.js behavior: only create alias if explicitly requested
294
- if (appDefinition.encryption?.kmsKeyAlias !== true) {
295
- console.log(' ℹ Skipping KMS alias creation by default (set kmsKeyAlias: true to enable)');
296
- appDefinition.encryption = appDefinition.encryption || {};
297
- appDefinition.encryption.kmsKeyAlias = false;
298
- } else {
299
- console.log(' → Will create KMS alias (kmsKeyAlias: true explicitly set)');
300
- }
301
-
302
262
  result.resources = this.createKmsKey(appDefinition);
303
263
  result.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
304
264
  console.log(' ✅ KMS key resources created');
@@ -336,7 +296,7 @@ class KmsBuilder extends InfrastructureBuilder {
336
296
  * Create KMS key CloudFormation resources
337
297
  */
338
298
  createKmsKey(appDefinition) {
339
- const resources = {
299
+ return {
340
300
  FriggKMSKey: {
341
301
  Type: 'AWS::KMS::Key',
342
302
  DeletionPolicy: 'Retain',
@@ -390,24 +350,15 @@ class KmsBuilder extends InfrastructureBuilder {
390
350
  ],
391
351
  },
392
352
  },
393
- };
394
-
395
- // Only create alias if explicitly enabled (default: true for backwards compatibility)
396
- const createAlias = appDefinition.encryption?.kmsKeyAlias !== false;
397
- if (createAlias) {
398
- resources.FriggKMSKeyAlias = {
353
+ FriggKMSKeyAlias: {
399
354
  Type: 'AWS::KMS::Alias',
400
355
  DeletionPolicy: 'Retain',
401
356
  Properties: {
402
357
  AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
403
358
  TargetKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] },
404
359
  },
405
- };
406
- } else {
407
- console.log(' ℹ Skipping KMS key alias creation (kmsKeyAlias: false)');
408
- }
409
-
410
- return resources;
360
+ },
361
+ };
411
362
  }
412
363
  }
413
364
 
@@ -209,12 +209,11 @@ describe('KmsBuilder', () => {
209
209
  expect(result.resources.FriggKMSKey.Type).toBe('AWS::KMS::Key');
210
210
  });
211
211
 
212
- it('should create KMS key alias when explicitly enabled', async () => {
212
+ it('should create KMS key alias', async () => {
213
213
  const appDefinition = {
214
214
  encryption: {
215
215
  fieldLevelEncryptionMethod: 'kms',
216
216
  createResourceIfNoneFound: true,
217
- kmsKeyAlias: true, // Explicitly enable alias creation
218
217
  },
219
218
  };
220
219
 
@@ -226,23 +225,6 @@ describe('KmsBuilder', () => {
226
225
  expect(result.resources.FriggKMSKeyAlias.Type).toBe('AWS::KMS::Alias');
227
226
  });
228
227
 
229
- it('should skip alias creation when kmsKeyAlias: false', async () => {
230
- const appDefinition = {
231
- encryption: {
232
- fieldLevelEncryptionMethod: 'kms',
233
- createResourceIfNoneFound: true,
234
- kmsKeyAlias: false,
235
- },
236
- };
237
-
238
- const discoveredResources = {};
239
-
240
- const result = await kmsBuilder.build(appDefinition, discoveredResources);
241
-
242
- expect(result.resources.FriggKMSKey).toBeDefined();
243
- expect(result.resources.FriggKMSKeyAlias).toBeUndefined();
244
- });
245
-
246
228
  it('should enable key rotation for new keys', async () => {
247
229
  const appDefinition = {
248
230
  encryption: {