@friggframework/devtools 2.0.0--canary.428.d004aeb.0 → 2.0.0--canary.428.1a6e465.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.
@@ -55,6 +55,37 @@ describe('composeServerlessDefinition', () => {
55
55
  jest.restoreAllMocks();
56
56
  // Restore env
57
57
  delete process.env.AWS_REGION;
58
+ process.argv = ['node', 'test'];
59
+ });
60
+
61
+ describe('AWS discovery gating', () => {
62
+ it('should skip AWS discovery when no features require it', async () => {
63
+ AWSDiscovery.mockClear();
64
+
65
+ const appDefinition = {
66
+ integrations: [],
67
+ vpc: { enable: false },
68
+ encryption: { fieldLevelEncryptionMethod: 'aes' },
69
+ ssm: { enable: false },
70
+ };
71
+
72
+ await composeServerlessDefinition(appDefinition);
73
+
74
+ expect(AWSDiscovery).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it('should run AWS discovery when VPC features are enabled', async () => {
78
+ AWSDiscovery.mockClear();
79
+
80
+ const appDefinition = {
81
+ integrations: [],
82
+ vpc: { enable: true },
83
+ };
84
+
85
+ await composeServerlessDefinition(appDefinition);
86
+
87
+ expect(AWSDiscovery).toHaveBeenCalledTimes(1);
88
+ });
58
89
  });
59
90
 
60
91
  describe('Basic Configuration', () => {
@@ -124,6 +155,38 @@ describe('composeServerlessDefinition', () => {
124
155
  });
125
156
  });
126
157
 
158
+ describe('Environment variables', () => {
159
+ it('should include only non-reserved environment flags', async () => {
160
+ const appDefinition = {
161
+ integrations: [],
162
+ environment: {
163
+ CUSTOM_FLAG: true,
164
+ AWS_REGION: true,
165
+ OPTIONAL_DISABLED: false,
166
+ },
167
+ };
168
+
169
+ const result = await composeServerlessDefinition(appDefinition);
170
+
171
+ expect(result.provider.environment.CUSTOM_FLAG).toBe("${env:CUSTOM_FLAG, ''}");
172
+ expect(result.provider.environment).not.toHaveProperty('AWS_REGION');
173
+ expect(result.provider.environment).not.toHaveProperty('OPTIONAL_DISABLED');
174
+ });
175
+
176
+ it('should ignore string-valued environment entries', async () => {
177
+ const appDefinition = {
178
+ integrations: [],
179
+ environment: {
180
+ CUSTOM_FLAG: 'enabled',
181
+ },
182
+ };
183
+
184
+ const result = await composeServerlessDefinition(appDefinition);
185
+
186
+ expect(result.provider.environment).not.toHaveProperty('CUSTOM_FLAG');
187
+ });
188
+ });
189
+
127
190
  describe('VPC Configuration', () => {
128
191
  it('should add VPC configuration when vpc.enable is true with discover mode', async () => {
129
192
  const appDefinition = {
@@ -261,6 +324,49 @@ describe('composeServerlessDefinition', () => {
261
324
  expect(result.provider.vpc.subnetIds).toEqual(['subnet-explicit1', 'subnet-explicit2']);
262
325
  });
263
326
 
327
+ it('should respect provided security group IDs when supplied', async () => {
328
+ const appDefinition = {
329
+ vpc: {
330
+ enable: true,
331
+ management: 'discover',
332
+ securityGroupIds: ['sg-custom-1', 'sg-custom-2'],
333
+ },
334
+ integrations: [],
335
+ };
336
+
337
+ const result = await composeServerlessDefinition(appDefinition);
338
+
339
+ expect(result.provider.vpc.securityGroupIds).toEqual([
340
+ 'sg-custom-1',
341
+ 'sg-custom-2',
342
+ ]);
343
+ });
344
+
345
+ it('should throw when discover mode finds no subnets and self-heal is disabled', async () => {
346
+ const discoveryInstance = {
347
+ discoverResources: jest.fn().mockResolvedValue(
348
+ createDiscoveryResponse({
349
+ privateSubnetId1: null,
350
+ privateSubnetId2: null,
351
+ })
352
+ ),
353
+ };
354
+ AWSDiscovery.mockImplementation(() => discoveryInstance);
355
+
356
+ const appDefinition = {
357
+ vpc: {
358
+ enable: true,
359
+ management: 'discover',
360
+ subnets: { management: 'discover' },
361
+ },
362
+ integrations: [],
363
+ };
364
+
365
+ await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
366
+ 'No subnets discovered and subnets.management is "discover". Either enable vpc.selfHeal, set subnets.management to "create", or provide subnet IDs.'
367
+ );
368
+ });
369
+
264
370
  it('should use Fn::Cidr for subnet CIDR blocks in new VPC to avoid conflicts', async () => {
265
371
  const appDefinition = {
266
372
  vpc: {
@@ -357,6 +463,39 @@ describe('composeServerlessDefinition', () => {
357
463
  expect(result.resources.Resources.VPCEndpointS3.Properties.VpcId).toBe('vpc-123456');
358
464
  });
359
465
 
466
+ it('should skip creating VPC endpoints when disabled explicitly', async () => {
467
+ const appDefinition = {
468
+ vpc: {
469
+ enable: true,
470
+ management: 'discover',
471
+ enableVPCEndpoints: false,
472
+ },
473
+ integrations: [],
474
+ };
475
+
476
+ const result = await composeServerlessDefinition(appDefinition);
477
+
478
+ expect(result.resources.Resources.VPCEndpointS3).toBeUndefined();
479
+ expect(result.resources.Resources.VPCEndpointKMS).toBeUndefined();
480
+ expect(result.resources.Resources.VPCEndpointSecretsManager).toBeUndefined();
481
+ });
482
+
483
+ it('should add Secrets Manager endpoint only when enabled', async () => {
484
+ const appDefinition = {
485
+ vpc: {
486
+ enable: true,
487
+ management: 'discover',
488
+ },
489
+ secretsManager: { enable: true },
490
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
491
+ integrations: [],
492
+ };
493
+
494
+ const result = await composeServerlessDefinition(appDefinition);
495
+
496
+ expect(result.resources.Resources.VPCEndpointSecretsManager).toBeDefined();
497
+ });
498
+
360
499
  it('should allow Lambda security group access for VPC endpoints when security group is discovered', async () => {
361
500
  const appDefinition = {
362
501
  vpc: {
@@ -511,6 +650,114 @@ describe('composeServerlessDefinition', () => {
511
650
  });
512
651
  });
513
652
 
653
+ describe('NAT Gateway behaviour', () => {
654
+ it('should reuse discovered NAT gateway in discover mode without creating new resources', async () => {
655
+ const discoveryInstance = {
656
+ discoverResources: jest.fn().mockResolvedValue(
657
+ createDiscoveryResponse({
658
+ existingNatGatewayId: 'nat-existing123',
659
+ natGatewayInPrivateSubnet: false,
660
+ })
661
+ ),
662
+ };
663
+ AWSDiscovery.mockImplementation(() => discoveryInstance);
664
+
665
+ const appDefinition = {
666
+ vpc: {
667
+ enable: true,
668
+ management: 'discover',
669
+ natGateway: { management: 'discover' },
670
+ },
671
+ integrations: [],
672
+ };
673
+
674
+ const result = await composeServerlessDefinition(appDefinition);
675
+
676
+ expect(result.resources.Resources.FriggNATGateway).toBeUndefined();
677
+ expect(result.resources.Resources.FriggNATRoute).toBeDefined();
678
+ });
679
+
680
+ it('should reference provided NAT gateway when management set to useExisting', async () => {
681
+ const discoveryInstance = {
682
+ discoverResources: jest.fn().mockResolvedValue(
683
+ createDiscoveryResponse({ existingNatGatewayId: null })
684
+ ),
685
+ };
686
+ AWSDiscovery.mockImplementation(() => discoveryInstance);
687
+
688
+ const appDefinition = {
689
+ vpc: {
690
+ enable: true,
691
+ management: 'discover',
692
+ natGateway: { management: 'useExisting', id: 'nat-custom-001' },
693
+ },
694
+ integrations: [],
695
+ };
696
+
697
+ const result = await composeServerlessDefinition(appDefinition);
698
+
699
+ expect(result.resources.Resources.FriggNATGateway).toBeUndefined();
700
+ expect(result.resources.Resources.FriggNATRoute.Properties.NatGatewayId).toBe('nat-custom-001');
701
+ });
702
+
703
+ it('should reuse existing elastic IP allocation when creating managed NAT', async () => {
704
+ const discoveryInstance = {
705
+ discoverResources: jest.fn().mockResolvedValue(
706
+ createDiscoveryResponse({
707
+ existingNatGatewayId: null,
708
+ existingElasticIpAllocationId: 'eip-alloc-123',
709
+ })
710
+ ),
711
+ };
712
+ AWSDiscovery.mockImplementation(() => discoveryInstance);
713
+
714
+ const appDefinition = {
715
+ vpc: {
716
+ enable: true,
717
+ management: 'discover',
718
+ natGateway: { management: 'createAndManage' },
719
+ selfHeal: true,
720
+ },
721
+ integrations: [],
722
+ };
723
+
724
+ const result = await composeServerlessDefinition(appDefinition);
725
+
726
+ expect(result.resources.Resources.FriggNATGatewayEIP).toBeUndefined();
727
+ expect(result.resources.Resources.FriggNATGateway.Properties.AllocationId).toBe(
728
+ 'eip-alloc-123'
729
+ );
730
+ });
731
+
732
+ it('should create a public subnet when discovery provides none', async () => {
733
+ const discoveryInstance = {
734
+ discoverResources: jest.fn().mockResolvedValue(
735
+ createDiscoveryResponse({
736
+ publicSubnetId: null,
737
+ internetGatewayId: null,
738
+ existingNatGatewayId: null,
739
+ })
740
+ ),
741
+ };
742
+ AWSDiscovery.mockImplementation(() => discoveryInstance);
743
+
744
+ const appDefinition = {
745
+ vpc: {
746
+ enable: true,
747
+ management: 'discover',
748
+ natGateway: { management: 'createAndManage' },
749
+ selfHeal: true,
750
+ },
751
+ integrations: [],
752
+ };
753
+
754
+ const result = await composeServerlessDefinition(appDefinition);
755
+
756
+ expect(result.resources.Resources.FriggPublicSubnet).toBeDefined();
757
+ expect(result.resources.Resources.FriggPublicRouteTable).toBeDefined();
758
+ });
759
+ });
760
+
514
761
  describe('KMS Configuration', () => {
515
762
  it('should add KMS configuration when encryption is enabled and key is found', async () => {
516
763
  const appDefinition = {
@@ -913,6 +1160,27 @@ describe('composeServerlessDefinition', () => {
913
1160
  });
914
1161
  });
915
1162
 
1163
+ describe('Handler path adjustments', () => {
1164
+ const fs = require('fs');
1165
+ const path = require('path');
1166
+
1167
+ it('should rewrite handler paths in offline mode', async () => {
1168
+ process.argv = ['node', 'test', 'offline'];
1169
+ const existsSpy = jest.spyOn(fs, 'existsSync');
1170
+ const fallbackNodeModules = path.resolve(process.cwd(), '..', 'node_modules');
1171
+ existsSpy.mockImplementation((p) => p === fallbackNodeModules);
1172
+
1173
+ const appDefinition = { integrations: [] };
1174
+
1175
+ const result = await composeServerlessDefinition(appDefinition);
1176
+
1177
+ expect(existsSpy).toHaveBeenCalled();
1178
+ expect(result.functions.auth.handler.startsWith('../node_modules/')).toBe(true);
1179
+
1180
+ existsSpy.mockRestore();
1181
+ });
1182
+ });
1183
+
916
1184
  describe('NAT Gateway Management', () => {
917
1185
  it('should handle NAT Gateway with createAndManage mode', async () => {
918
1186
  const appDefinition = {
@@ -932,6 +1200,24 @@ describe('composeServerlessDefinition', () => {
932
1200
  expect(result.resources.Resources.FriggNATRoute).toBeDefined();
933
1201
  });
934
1202
 
1203
+ it('should mark managed NAT Gateway resources for retention', async () => {
1204
+ const appDefinition = {
1205
+ vpc: {
1206
+ enable: true,
1207
+ management: 'discover',
1208
+ natGateway: { management: 'createAndManage' },
1209
+ selfHeal: true,
1210
+ },
1211
+ integrations: [],
1212
+ };
1213
+
1214
+ const result = await composeServerlessDefinition(appDefinition);
1215
+
1216
+ expect(result.resources.Resources.FriggNATGateway).toBeDefined();
1217
+ expect(result.resources.Resources.FriggNATGateway.DeletionPolicy).toBe('Retain');
1218
+ expect(result.resources.Resources.FriggNATGateway.UpdateReplacePolicy).toBe('Retain');
1219
+ });
1220
+
935
1221
  it('should handle NAT Gateway with discover mode', async () => {
936
1222
  // Mock discovery to return existing NAT Gateway
937
1223
  const { AWSDiscovery } = require('./aws-discovery');
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--canary.428.d004aeb.0",
4
+ "version": "2.0.0--canary.428.1a6e465.0",
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--canary.428.d004aeb.0",
13
- "@friggframework/test": "2.0.0--canary.428.d004aeb.0",
12
+ "@friggframework/schemas": "2.0.0--canary.428.1a6e465.0",
13
+ "@friggframework/test": "2.0.0--canary.428.1a6e465.0",
14
14
  "@hapi/boom": "^10.0.1",
15
15
  "@inquirer/prompts": "^5.3.8",
16
16
  "axios": "^1.7.2",
@@ -32,8 +32,8 @@
32
32
  "serverless-http": "^2.7.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@friggframework/eslint-config": "2.0.0--canary.428.d004aeb.0",
36
- "@friggframework/prettier-config": "2.0.0--canary.428.d004aeb.0",
35
+ "@friggframework/eslint-config": "2.0.0--canary.428.1a6e465.0",
36
+ "@friggframework/prettier-config": "2.0.0--canary.428.1a6e465.0",
37
37
  "aws-sdk-client-mock": "^4.1.0",
38
38
  "aws-sdk-client-mock-jest": "^4.1.0",
39
39
  "jest": "^30.1.3",
@@ -68,5 +68,5 @@
68
68
  "publishConfig": {
69
69
  "access": "public"
70
70
  },
71
- "gitHead": "d004aeb0c0ca1bd8a94b7f9881dde227862dce9f"
71
+ "gitHead": "1a6e465d8c4c0d552ca460371a7739b8e06e001e"
72
72
  }