@friggframework/devtools 2.0.0--canary.461.9f988ca.0 → 2.0.0--canary.461.ae78cad.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.
@@ -110,7 +110,23 @@ class VpcBuilder extends InfrastructureBuilder {
110
110
  Resource: '*',
111
111
  });
112
112
 
113
- const management = appDefinition.vpc.management || 'discover';
113
+ // Normalize shareAcrossStages into management mode
114
+ // This provides a simpler API for users while maintaining backwards compatibility
115
+ let management = appDefinition.vpc.management;
116
+ if (!management && appDefinition.vpc.shareAcrossStages !== undefined) {
117
+ // Explicit shareAcrossStages setting overrides default
118
+ management = appDefinition.vpc.shareAcrossStages ? 'discover' : 'create-new';
119
+ console.log(` VPC Sharing: ${appDefinition.vpc.shareAcrossStages ? 'shared' : 'isolated'} (translated to ${management})`);
120
+
121
+ // When creating isolated VPC, also create isolated NAT Gateway
122
+ if (!appDefinition.vpc.shareAcrossStages && !appDefinition.vpc.natGateway?.management) {
123
+ appDefinition.vpc.natGateway = appDefinition.vpc.natGateway || {};
124
+ appDefinition.vpc.natGateway.management = 'createAndManage';
125
+ console.log(` NAT Gateway: creating isolated NAT (shareAcrossStages=false)`);
126
+ }
127
+ } else {
128
+ management = management || 'discover'; // Default to sharing for backwards compatibility
129
+ }
114
130
  console.log(` VPC Management Mode: ${management}`);
115
131
 
116
132
  // Handle self-healing if enabled
@@ -372,10 +388,17 @@ class VpcBuilder extends InfrastructureBuilder {
372
388
  */
373
389
  async buildSubnets(appDefinition, discoveredResources, result) {
374
390
  const vpcManagement = appDefinition.vpc.management || 'discover';
375
- const defaultSubnetManagement = vpcManagement === 'create-new' ? 'create' : 'discover';
391
+ // Default subnet management depends on context:
392
+ // - use-existing mode with subnet IDs provided: use-existing
393
+ // - create-new mode: create
394
+ // - discover mode: create (for stage isolation)
395
+ let defaultSubnetManagement = 'create';
396
+ if (vpcManagement === 'use-existing' && appDefinition.vpc.subnets?.ids?.length >= 2) {
397
+ defaultSubnetManagement = 'use-existing';
398
+ }
376
399
  const subnetManagement = appDefinition.vpc.subnets?.management || defaultSubnetManagement;
377
400
 
378
- console.log(` Subnet Management Mode: ${subnetManagement}`);
401
+ console.log(` Subnet Management Mode: ${subnetManagement} (default: ${defaultSubnetManagement}, explicit: ${appDefinition.vpc.subnets?.management})`);
379
402
 
380
403
  switch (subnetManagement) {
381
404
  case 'create':
@@ -508,17 +531,19 @@ class VpcBuilder extends InfrastructureBuilder {
508
531
  return;
509
532
  }
510
533
 
511
- // Use discovered subnets
534
+ // User explicitly set subnets.management: 'discover', so use discovered subnets
535
+ // NOTE: This may cause route table conflicts if multiple stages share subnets
536
+ // Default behavior is now to create stage-specific subnets (subnets.management: 'create')
512
537
  if (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2) {
513
538
  result.vpcConfig.subnetIds = [
514
539
  discoveredResources.privateSubnetId1,
515
540
  discoveredResources.privateSubnetId2,
516
541
  ];
517
- console.log(' ✅ Discovered 2 private subnets');
542
+ console.log(' ✅ Using discovered subnets (backwards compatibility mode)');
518
543
  return;
519
544
  }
520
545
 
521
- // Fallback: create if self-heal enabled
546
+ // No subnets found - create if self-heal enabled
522
547
  if (appDefinition.vpc.selfHeal) {
523
548
  console.log(' ⚠️ No subnets found - self-heal will create them');
524
549
  this.createSubnets(appDefinition, discoveredResources, result, 'discover');
@@ -241,7 +241,7 @@ describe('VpcBuilder', () => {
241
241
  });
242
242
 
243
243
  describe('build() - discover mode', () => {
244
- it('should use discovered VPC resources', async () => {
244
+ it('should use discovered VPC but create stage-specific subnets by default', async () => {
245
245
  const appDefinition = {
246
246
  vpc: {
247
247
  enable: true,
@@ -251,6 +251,7 @@ describe('VpcBuilder', () => {
251
251
 
252
252
  const discoveredResources = {
253
253
  defaultVpcId: 'vpc-discovered',
254
+ // Even though subnets are discovered, we should create new ones for stage isolation
254
255
  privateSubnetId1: 'subnet-private1',
255
256
  privateSubnetId2: 'subnet-private2',
256
257
  defaultSecurityGroupId: 'sg-discovered',
@@ -258,13 +259,43 @@ describe('VpcBuilder', () => {
258
259
 
259
260
  const result = await vpcBuilder.build(appDefinition, discoveredResources);
260
261
 
261
- expect(result.vpcConfig.subnetIds).toEqual(['subnet-private1', 'subnet-private2']);
262
+ // NEW BEHAVIOR: Create stage-specific subnets for isolation (prevent route table conflicts)
263
+ expect(result.vpcConfig.subnetIds).toEqual([
264
+ { Ref: 'FriggPrivateSubnet1' },
265
+ { Ref: 'FriggPrivateSubnet2' },
266
+ ]);
267
+ expect(result.resources.FriggPrivateSubnet1).toBeDefined();
268
+ expect(result.resources.FriggPrivateSubnet2).toBeDefined();
262
269
  // In discover mode, we create FriggLambdaSecurityGroup in the discovered VPC
263
270
  expect(result.vpcConfig.securityGroupIds).toEqual([{ Ref: 'FriggLambdaSecurityGroup' }]);
264
271
  expect(result.resources.FriggLambdaSecurityGroup).toBeDefined();
265
272
  expect(result.resources.FriggLambdaSecurityGroup.Properties.VpcId).toBe('vpc-discovered');
266
273
  });
267
274
 
275
+ it('should allow sharing discovered subnets when explicitly configured', async () => {
276
+ const appDefinition = {
277
+ vpc: {
278
+ enable: true,
279
+ management: 'discover',
280
+ subnets: {
281
+ management: 'discover', // Explicitly opt-in to subnet sharing
282
+ },
283
+ },
284
+ };
285
+
286
+ const discoveredResources = {
287
+ defaultVpcId: 'vpc-discovered',
288
+ privateSubnetId1: 'subnet-shared-1',
289
+ privateSubnetId2: 'subnet-shared-2',
290
+ };
291
+
292
+ const result = await vpcBuilder.build(appDefinition, discoveredResources);
293
+
294
+ // OLD BEHAVIOR: When explicitly set to 'discover', reuse discovered subnets
295
+ expect(result.vpcConfig.subnetIds).toEqual(['subnet-shared-1', 'subnet-shared-2']);
296
+ expect(result.resources.FriggPrivateSubnet1).toBeUndefined();
297
+ });
298
+
268
299
  it('should create VPC endpoints in discover mode with selfHeal when none exist', async () => {
269
300
  const appDefinition = {
270
301
  vpc: {
@@ -778,6 +809,81 @@ describe('VpcBuilder', () => {
778
809
  });
779
810
  });
780
811
 
812
+ describe('VPC Sharing Control', () => {
813
+ it('should share VPC across stages when shareAcrossStages is true (default)', async () => {
814
+ const appDefinition = {
815
+ vpc: {
816
+ enable: true,
817
+ shareAcrossStages: true, // Explicit opt-in to sharing
818
+ },
819
+ };
820
+
821
+ const discoveredResources = {
822
+ defaultVpcId: 'vpc-shared',
823
+ natGatewayId: 'nat-shared',
824
+ };
825
+
826
+ const result = await vpcBuilder.build(appDefinition, discoveredResources);
827
+
828
+ // Should use discovered VPC (not create new one)
829
+ expect(result.vpcId).toBe('vpc-shared');
830
+ expect(result.resources.FriggVPC).toBeUndefined();
831
+
832
+ // Should create stage-specific subnets for isolation
833
+ expect(result.resources.FriggPrivateSubnet1).toBeDefined();
834
+ expect(result.resources.FriggPrivateSubnet2).toBeDefined();
835
+
836
+ // Should reuse discovered NAT Gateway
837
+ expect(result.resources.FriggNATGateway).toBeUndefined();
838
+ });
839
+
840
+ it('should create isolated VPC when shareAcrossStages is false', async () => {
841
+ const appDefinition = {
842
+ vpc: {
843
+ enable: true,
844
+ shareAcrossStages: false, // Explicit opt-out of sharing
845
+ },
846
+ };
847
+
848
+ const discoveredResources = {
849
+ defaultVpcId: 'vpc-shared',
850
+ natGatewayId: 'nat-shared',
851
+ };
852
+
853
+ const result = await vpcBuilder.build(appDefinition, discoveredResources);
854
+
855
+ // Should create new VPC (ignore discovered resources)
856
+ expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
857
+ expect(result.resources.FriggVPC).toBeDefined();
858
+
859
+ // Should create stage-specific subnets
860
+ expect(result.resources.FriggPrivateSubnet1).toBeDefined();
861
+ expect(result.resources.FriggPrivateSubnet2).toBeDefined();
862
+
863
+ // Should create new NAT Gateway
864
+ expect(result.resources.FriggNATGateway).toBeDefined();
865
+ });
866
+
867
+ it('should default to shared VPC when shareAcrossStages is not specified', async () => {
868
+ const appDefinition = {
869
+ vpc: {
870
+ enable: true,
871
+ // shareAcrossStages not specified - should default to true
872
+ },
873
+ };
874
+
875
+ const discoveredResources = {
876
+ defaultVpcId: 'vpc-discovered',
877
+ };
878
+
879
+ const result = await vpcBuilder.build(appDefinition, discoveredResources);
880
+
881
+ // Should use discovered VPC by default (backwards compatibility)
882
+ expect(result.vpcId).toBe('vpc-discovered');
883
+ expect(result.resources.FriggVPC).toBeUndefined();
884
+ });
885
+ });
886
+
781
887
  describe('Outputs', () => {
782
888
  it.skip('should generate VPC ID output', async () => {
783
889
  const appDefinition = {
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.461.9f988ca.0",
4
+ "version": "2.0.0--canary.461.ae78cad.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-ec2": "^3.835.0",
7
7
  "@aws-sdk/client-kms": "^3.835.0",
@@ -11,8 +11,8 @@
11
11
  "@babel/eslint-parser": "^7.18.9",
12
12
  "@babel/parser": "^7.25.3",
13
13
  "@babel/traverse": "^7.25.3",
14
- "@friggframework/schemas": "2.0.0--canary.461.9f988ca.0",
15
- "@friggframework/test": "2.0.0--canary.461.9f988ca.0",
14
+ "@friggframework/schemas": "2.0.0--canary.461.ae78cad.0",
15
+ "@friggframework/test": "2.0.0--canary.461.ae78cad.0",
16
16
  "@hapi/boom": "^10.0.1",
17
17
  "@inquirer/prompts": "^5.3.8",
18
18
  "axios": "^1.7.2",
@@ -34,8 +34,8 @@
34
34
  "serverless-http": "^2.7.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@friggframework/eslint-config": "2.0.0--canary.461.9f988ca.0",
38
- "@friggframework/prettier-config": "2.0.0--canary.461.9f988ca.0",
37
+ "@friggframework/eslint-config": "2.0.0--canary.461.ae78cad.0",
38
+ "@friggframework/prettier-config": "2.0.0--canary.461.ae78cad.0",
39
39
  "aws-sdk-client-mock": "^4.1.0",
40
40
  "aws-sdk-client-mock-jest": "^4.1.0",
41
41
  "jest": "^30.1.3",
@@ -70,5 +70,5 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  },
73
- "gitHead": "9f988cad38fe00d1953292ea77dbcd46da275f23"
73
+ "gitHead": "ae78cad00740f759a7a99f5e6565bdfbea31b01e"
74
74
  }