@friggframework/devtools 2.0.0--canary.461.3d6d8ad.0 → 2.0.0--canary.461.5100cd8.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.
@@ -309,6 +309,17 @@ class MigrationBuilder extends InfrastructureBuilder {
309
309
 
310
310
  console.log(' ✓ Added S3_BUCKET_NAME, DB_MIGRATION_QUEUE_URL, and DB_TYPE environment variables');
311
311
 
312
+ // Add worker function name to router environment (for Lambda invocation)
313
+ // Router needs this to invoke worker for database state checks
314
+ if (!result.functions.dbMigrationRouter.environment) {
315
+ result.functions.dbMigrationRouter.environment = {};
316
+ }
317
+ result.functions.dbMigrationRouter.environment.WORKER_FUNCTION_NAME = {
318
+ Ref: 'DbMigrationWorkerLambdaFunction',
319
+ };
320
+
321
+ console.log(' ✓ Added WORKER_FUNCTION_NAME environment variable to router');
322
+
312
323
  // Add IAM permissions for SQS
313
324
  result.iamStatements.push({
314
325
  Effect: 'Allow',
@@ -354,6 +365,16 @@ class MigrationBuilder extends InfrastructureBuilder {
354
365
 
355
366
  console.log(' ✓ Added S3 IAM permissions for migration status tracking');
356
367
 
368
+ // Add IAM permission for router to invoke worker Lambda
369
+ // Router invokes worker for database state checks (keeps router lightweight)
370
+ result.iamStatements.push({
371
+ Effect: 'Allow',
372
+ Action: ['lambda:InvokeFunction'],
373
+ Resource: { 'Fn::GetAtt': ['DbMigrationWorkerLambdaFunction', 'Arn'] },
374
+ });
375
+
376
+ console.log(' ✓ Added Lambda invocation permissions for router → worker');
377
+
357
378
  console.log(`[${this.name}] ✅ Migration infrastructure configuration completed`);
358
379
  return result;
359
380
  }
@@ -90,7 +90,7 @@ class VpcBuilder extends InfrastructureBuilder {
90
90
  if (appDefinition.vpc?.subnets?.management) ignoredOptions.push('vpc.subnets.management');
91
91
  if (appDefinition.vpc?.natGateway?.management) ignoredOptions.push('vpc.natGateway.management');
92
92
  if (appDefinition.vpc?.shareAcrossStages !== undefined) ignoredOptions.push('vpc.shareAcrossStages');
93
-
93
+
94
94
  if (ignoredOptions.length > 0) {
95
95
  console.log(` ⚠️ managementMode='managed' ignoring: ${ignoredOptions.join(', ')}`);
96
96
  }
@@ -134,13 +134,13 @@ class VpcBuilder extends InfrastructureBuilder {
134
134
  if (globalMode === 'managed') {
135
135
  // Warn about ignored granular options
136
136
  this.warnIgnoredOptions(appDefinition);
137
-
137
+
138
138
  // Clear granular options to prevent conflicts
139
139
  delete appDefinition.vpc.management;
140
140
  if (appDefinition.vpc.subnets) delete appDefinition.vpc.subnets.management;
141
141
  if (appDefinition.vpc.natGateway) delete appDefinition.vpc.natGateway.management;
142
142
  delete appDefinition.vpc.shareAcrossStages;
143
-
143
+
144
144
  // Set management based on isolation strategy
145
145
  if (vpcIsolation === 'isolated') {
146
146
  management = 'create-new';
@@ -158,7 +158,7 @@ class VpcBuilder extends InfrastructureBuilder {
158
158
  // Legacy shareAcrossStages support (backwards compatibility)
159
159
  management = appDefinition.vpc.shareAcrossStages ? 'discover' : 'create-new';
160
160
  console.log(` VPC Sharing: ${appDefinition.vpc.shareAcrossStages ? 'shared' : 'isolated'} (translated to ${management})`);
161
-
161
+
162
162
  if (!appDefinition.vpc.shareAcrossStages && !appDefinition.vpc.natGateway?.management) {
163
163
  appDefinition.vpc.natGateway = appDefinition.vpc.natGateway || {};
164
164
  appDefinition.vpc.natGateway.management = 'createAndManage';
@@ -167,7 +167,7 @@ class VpcBuilder extends InfrastructureBuilder {
167
167
  } else {
168
168
  management = management || 'discover';
169
169
  }
170
-
170
+
171
171
  console.log(` VPC Management Mode: ${management}`);
172
172
 
173
173
  // Handle self-healing if enabled
@@ -463,8 +463,8 @@ class VpcBuilder extends InfrastructureBuilder {
463
463
 
464
464
  const subnetVpcId = vpcManagement === 'create-new' ? { Ref: 'FriggVPC' } : result.vpcId;
465
465
 
466
- // Generate CIDRs
467
- const cidrs = this.generateSubnetCidrs(vpcManagement);
466
+ // Generate CIDRs - pass discovered resources to avoid conflicts
467
+ const cidrs = this.generateSubnetCidrs(vpcManagement, discoveredResources);
468
468
 
469
469
  // Private Subnet 1
470
470
  result.resources.FriggPrivateSubnet1 = {
@@ -597,8 +597,9 @@ class VpcBuilder extends InfrastructureBuilder {
597
597
 
598
598
  /**
599
599
  * Generate subnet CIDR blocks
600
+ * Finds available CIDRs that don't conflict with existing subnets
600
601
  */
601
- generateSubnetCidrs(vpcManagement) {
602
+ generateSubnetCidrs(vpcManagement, discoveredResources) {
602
603
  if (vpcManagement === 'create-new') {
603
604
  // Use CloudFormation Fn::Cidr for dynamic generation
604
605
  return {
@@ -608,13 +609,47 @@ class VpcBuilder extends InfrastructureBuilder {
608
609
  public2: { 'Fn::Select': [3, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }] },
609
610
  };
610
611
  } else {
611
- // Static CIDRs for existing VPC (default VPC range)
612
- return {
613
- private1: '172.31.240.0/24',
614
- private2: '172.31.241.0/24',
615
- public1: '172.31.250.0/24',
616
- public2: '172.31.251.0/24',
612
+ // Find available CIDRs for existing VPC by checking existing subnets
613
+ const existingCidrs = new Set();
614
+
615
+ // Collect all existing subnet CIDRs
616
+ if (discoveredResources?.subnets) {
617
+ for (const subnet of discoveredResources.subnets) {
618
+ if (subnet.CidrBlock) {
619
+ existingCidrs.add(subnet.CidrBlock);
620
+ }
621
+ }
622
+ }
623
+
624
+ console.log(` Found ${existingCidrs.size} existing subnet CIDRs in VPC`);
625
+
626
+ // Generate candidates in the default VPC range (172.31.0.0/16)
627
+ // Private subnets: 240-249, Public subnets: 250-255
628
+ const findAvailableCidr = (startOctet, endOctet) => {
629
+ for (let octet = startOctet; octet <= endOctet; octet++) {
630
+ const candidate = `172.31.${octet}.0/24`;
631
+ if (!existingCidrs.has(candidate)) {
632
+ existingCidrs.add(candidate); // Mark as used immediately
633
+ return candidate;
634
+ }
635
+ }
636
+ // Fallback if range exhausted
637
+ return `172.31.${startOctet}.0/24`;
617
638
  };
639
+
640
+ const privateRange = { start: 240, end: 249 };
641
+ const publicRange = { start: 250, end: 255 };
642
+
643
+ const cidrs = {
644
+ private1: findAvailableCidr(privateRange.start, privateRange.end),
645
+ private2: findAvailableCidr(privateRange.start, privateRange.end),
646
+ public1: findAvailableCidr(publicRange.start, publicRange.end),
647
+ public2: findAvailableCidr(publicRange.start, publicRange.end),
648
+ };
649
+
650
+ console.log(` Using available CIDRs: ${Object.values(cidrs).join(', ')}`);
651
+
652
+ return cidrs;
618
653
  }
619
654
  }
620
655
 
@@ -840,7 +840,7 @@ describe('VpcBuilder', () => {
840
840
  // Should create new isolated VPC
841
841
  expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
842
842
  expect(result.resources.FriggVPC).toBeDefined();
843
-
843
+
844
844
  // Subnets should use CloudFormation Fn::Cidr
845
845
  expect(result.resources.FriggPrivateSubnet1.Properties.CidrBlock).toEqual({
846
846
  'Fn::Select': [0, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
@@ -875,7 +875,7 @@ describe('VpcBuilder', () => {
875
875
  // Should discover existing VPC
876
876
  expect(result.vpcId).toBe('vpc-existing');
877
877
  expect(result.resources.FriggVPC).toBeUndefined();
878
-
878
+
879
879
  // Should create new stage-specific subnets
880
880
  expect(result.resources.FriggPrivateSubnet1).toBeDefined();
881
881
 
@@ -983,6 +983,93 @@ describe('VpcBuilder', () => {
983
983
  });
984
984
  });
985
985
 
986
+ describe('generateSubnetCidrs()', () => {
987
+ it('should use CloudFormation Fn::Cidr for create-new mode', () => {
988
+ const cidrs = vpcBuilder.generateSubnetCidrs('create-new', {});
989
+
990
+ expect(cidrs.private1).toEqual({
991
+ 'Fn::Select': [0, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
992
+ });
993
+ expect(cidrs.private2).toEqual({
994
+ 'Fn::Select': [1, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
995
+ });
996
+ expect(cidrs.public1).toEqual({
997
+ 'Fn::Select': [2, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
998
+ });
999
+ expect(cidrs.public2).toEqual({
1000
+ 'Fn::Select': [3, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
1001
+ });
1002
+ });
1003
+
1004
+ it('should use default static CIDRs when no existing subnets in VPC', () => {
1005
+ const discoveredResources = {
1006
+ subnets: []
1007
+ };
1008
+
1009
+ const cidrs = vpcBuilder.generateSubnetCidrs('discover', discoveredResources);
1010
+
1011
+ expect(cidrs.private1).toBe('172.31.240.0/24');
1012
+ expect(cidrs.private2).toBe('172.31.241.0/24');
1013
+ expect(cidrs.public1).toBe('172.31.250.0/24');
1014
+ expect(cidrs.public2).toBe('172.31.251.0/24');
1015
+ });
1016
+
1017
+ it('should avoid CIDR conflicts with existing subnets', () => {
1018
+ const discoveredResources = {
1019
+ subnets: [
1020
+ { CidrBlock: '172.31.240.0/24' }, // Conflicts with default private1
1021
+ { CidrBlock: '172.31.241.0/24' }, // Conflicts with default private2
1022
+ { CidrBlock: '172.31.0.0/20' }, // Default VPC subnet
1023
+ { CidrBlock: '172.31.16.0/20' }, // Default VPC subnet
1024
+ ]
1025
+ };
1026
+
1027
+ const cidrs = vpcBuilder.generateSubnetCidrs('discover', discoveredResources);
1028
+
1029
+ // Should skip 240 and 241 (already taken), use 242-243 for private, 250-251 for public
1030
+ expect(cidrs.private1).toBe('172.31.242.0/24');
1031
+ expect(cidrs.private2).toBe('172.31.243.0/24');
1032
+ expect(cidrs.public1).toBe('172.31.250.0/24'); // Public range starts at 250
1033
+ expect(cidrs.public2).toBe('172.31.251.0/24');
1034
+ });
1035
+
1036
+ it('should find first available CIDR blocks when some in range are taken', () => {
1037
+ const discoveredResources = {
1038
+ subnets: [
1039
+ { CidrBlock: '172.31.240.0/24' },
1040
+ { CidrBlock: '172.31.242.0/24' },
1041
+ { CidrBlock: '172.31.244.0/24' },
1042
+ ]
1043
+ };
1044
+
1045
+ const cidrs = vpcBuilder.generateSubnetCidrs('discover', discoveredResources);
1046
+
1047
+ // Should use 241, 243 for private (filling gaps), 250, 251 for public
1048
+ expect(cidrs.private1).toBe('172.31.241.0/24');
1049
+ expect(cidrs.private2).toBe('172.31.243.0/24');
1050
+ expect(cidrs.public1).toBe('172.31.250.0/24'); // Public range starts at 250
1051
+ expect(cidrs.public2).toBe('172.31.251.0/24');
1052
+ });
1053
+
1054
+ it('should handle missing discoveredResources gracefully', () => {
1055
+ const cidrs = vpcBuilder.generateSubnetCidrs('discover', null);
1056
+
1057
+ // Should fallback to default CIDRs
1058
+ expect(cidrs.private1).toBe('172.31.240.0/24');
1059
+ expect(cidrs.private2).toBe('172.31.241.0/24');
1060
+ });
1061
+
1062
+ it('should handle discoveredResources without subnets array', () => {
1063
+ const discoveredResources = { vpcId: 'vpc-123' };
1064
+
1065
+ const cidrs = vpcBuilder.generateSubnetCidrs('discover', discoveredResources);
1066
+
1067
+ // Should fallback to default CIDRs
1068
+ expect(cidrs.private1).toBe('172.31.240.0/24');
1069
+ expect(cidrs.private2).toBe('172.31.241.0/24');
1070
+ });
1071
+ });
1072
+
986
1073
  describe('Outputs', () => {
987
1074
  it.skip('should generate VPC ID output', async () => {
988
1075
  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.3d6d8ad.0",
4
+ "version": "2.0.0--canary.461.5100cd8.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.3d6d8ad.0",
15
- "@friggframework/test": "2.0.0--canary.461.3d6d8ad.0",
14
+ "@friggframework/schemas": "2.0.0--canary.461.5100cd8.0",
15
+ "@friggframework/test": "2.0.0--canary.461.5100cd8.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.3d6d8ad.0",
38
- "@friggframework/prettier-config": "2.0.0--canary.461.3d6d8ad.0",
37
+ "@friggframework/eslint-config": "2.0.0--canary.461.5100cd8.0",
38
+ "@friggframework/prettier-config": "2.0.0--canary.461.5100cd8.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": "3d6d8ad065c2530fee34dcda5ee304979c718a65"
73
+ "gitHead": "5100cd81ceb3fe903e128b5f9530e09cb3e89495"
74
74
  }