@friggframework/devtools 2.0.0--canary.487.63ed8db.0 → 2.0.0--canary.490.eec7dcd.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.
@@ -216,6 +216,27 @@ class CloudFormationDiscovery {
216
216
  discovered.natGatewayId = PhysicalResourceId;
217
217
  }
218
218
 
219
+ // Route Table (Lambda route table for external VPC pattern)
220
+ if (LogicalResourceId === 'FriggLambdaRouteTable' && ResourceType === 'AWS::EC2::RouteTable') {
221
+ discovered.routeTableId = PhysicalResourceId;
222
+ discovered.privateRouteTableId = PhysicalResourceId;
223
+ console.log(` ✓ Found route table in stack: ${PhysicalResourceId}`);
224
+ }
225
+
226
+ // NAT Route (proves NAT configuration exists)
227
+ if (LogicalResourceId === 'FriggNATRoute' && ResourceType === 'AWS::EC2::Route') {
228
+ discovered.natRoute = PhysicalResourceId;
229
+ console.log(` ✓ Found NAT route in stack`);
230
+ }
231
+
232
+ // Route Table Associations (links subnets to route table)
233
+ if (LogicalResourceId.includes('RouteAssociation') && ResourceType === 'AWS::EC2::SubnetRouteTableAssociation') {
234
+ if (!discovered.routeTableAssociations) {
235
+ discovered.routeTableAssociations = [];
236
+ }
237
+ discovered.routeTableAssociations.push(PhysicalResourceId);
238
+ }
239
+
219
240
  // VPC - direct extraction (primary method)
220
241
  if (LogicalResourceId === 'FriggVPC' && ResourceType === 'AWS::EC2::VPC') {
221
242
  discovered.defaultVpcId = PhysicalResourceId;
@@ -278,21 +299,46 @@ class CloudFormationDiscovery {
278
299
  discovered.vpcEndpointSecurityGroupId = PhysicalResourceId;
279
300
  }
280
301
 
281
- // VPC Endpoints
282
- if (LogicalResourceId === 'FriggS3VPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
302
+ // VPC Endpoints - support both old and new naming conventions
303
+ // Initialize vpcEndpoints object for structured access
304
+ if (!discovered.vpcEndpoints) {
305
+ discovered.vpcEndpoints = {};
306
+ }
307
+
308
+ // S3 Endpoint (both naming patterns)
309
+ if ((LogicalResourceId === 'FriggS3VPCEndpoint' || LogicalResourceId === 'VPCEndpointS3') &&
310
+ ResourceType === 'AWS::EC2::VPCEndpoint') {
283
311
  discovered.s3VpcEndpointId = PhysicalResourceId;
312
+ discovered.vpcEndpoints.s3 = PhysicalResourceId;
313
+ console.log(` ✓ Found S3 VPC endpoint in stack: ${PhysicalResourceId}`);
284
314
  }
285
- if (LogicalResourceId === 'FriggDynamoDBVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
315
+
316
+ // DynamoDB Endpoint (both naming patterns)
317
+ if ((LogicalResourceId === 'FriggDynamoDBVPCEndpoint' || LogicalResourceId === 'VPCEndpointDynamoDB') &&
318
+ ResourceType === 'AWS::EC2::VPCEndpoint') {
286
319
  discovered.dynamoDbVpcEndpointId = PhysicalResourceId;
320
+ discovered.vpcEndpoints.dynamodb = PhysicalResourceId;
321
+ console.log(` ✓ Found DynamoDB VPC endpoint in stack: ${PhysicalResourceId}`);
287
322
  }
288
- if (LogicalResourceId === 'FriggKMSVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
323
+
324
+ // KMS Endpoint (both naming patterns)
325
+ if ((LogicalResourceId === 'FriggKMSVPCEndpoint' || LogicalResourceId === 'VPCEndpointKMS') &&
326
+ ResourceType === 'AWS::EC2::VPCEndpoint') {
289
327
  discovered.kmsVpcEndpointId = PhysicalResourceId;
328
+ discovered.vpcEndpoints.kms = PhysicalResourceId;
329
+ console.log(` ✓ Found KMS VPC endpoint in stack: ${PhysicalResourceId}`);
290
330
  }
331
+
332
+ // Secrets Manager Endpoint
291
333
  if (LogicalResourceId === 'FriggSecretsManagerVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
292
334
  discovered.secretsManagerVpcEndpointId = PhysicalResourceId;
335
+ discovered.vpcEndpoints.secretsManager = PhysicalResourceId;
293
336
  }
337
+
338
+ // SQS Endpoint
294
339
  if (LogicalResourceId === 'FriggSQSVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
295
340
  discovered.sqsVpcEndpointId = PhysicalResourceId;
341
+ discovered.vpcEndpoints.sqs = PhysicalResourceId;
296
342
  }
297
343
  }
298
344
 
@@ -586,5 +586,113 @@ describe('CloudFormationDiscovery', () => {
586
586
  expect(mockProvider.describeKmsKey).toHaveBeenCalledWith('alias/test-service-dev-frigg-kms');
587
587
  });
588
588
  });
589
+
590
+ describe('External VPC with routing infrastructure pattern', () => {
591
+ it('should discover routing resources when VPC is external', async () => {
592
+ // This tests the Frontify pattern: external VPC/subnets/KMS,
593
+ // but stack creates routing infrastructure (route table, NAT route, VPC endpoints)
594
+ const mockStack = {
595
+ StackName: 'create-frigg-app-production',
596
+ Outputs: [],
597
+ };
598
+
599
+ const mockResources = [
600
+ {
601
+ LogicalResourceId: 'FriggLambdaRouteTable',
602
+ PhysicalResourceId: 'rtb-0b83aca77ccde20a6',
603
+ ResourceType: 'AWS::EC2::RouteTable',
604
+ ResourceStatus: 'UPDATE_COMPLETE',
605
+ },
606
+ {
607
+ LogicalResourceId: 'FriggNATRoute',
608
+ PhysicalResourceId: 'rtb-0b83aca77ccde20a6|0.0.0.0/0',
609
+ ResourceType: 'AWS::EC2::Route',
610
+ ResourceStatus: 'UPDATE_COMPLETE',
611
+ },
612
+ {
613
+ LogicalResourceId: 'FriggSubnet1RouteAssociation',
614
+ PhysicalResourceId: 'rtbassoc-07245da0b447ca469',
615
+ ResourceType: 'AWS::EC2::SubnetRouteTableAssociation',
616
+ ResourceStatus: 'CREATE_COMPLETE',
617
+ },
618
+ {
619
+ LogicalResourceId: 'FriggSubnet2RouteAssociation',
620
+ PhysicalResourceId: 'rtbassoc-0806f9783c4ea181f',
621
+ ResourceType: 'AWS::EC2::SubnetRouteTableAssociation',
622
+ ResourceStatus: 'CREATE_COMPLETE',
623
+ },
624
+ {
625
+ LogicalResourceId: 'VPCEndpointS3',
626
+ PhysicalResourceId: 'vpce-0352ceac2124c14be',
627
+ ResourceType: 'AWS::EC2::VPCEndpoint',
628
+ ResourceStatus: 'CREATE_COMPLETE',
629
+ },
630
+ {
631
+ LogicalResourceId: 'VPCEndpointDynamoDB',
632
+ PhysicalResourceId: 'vpce-0b06c4f631199ea68',
633
+ ResourceType: 'AWS::EC2::VPCEndpoint',
634
+ ResourceStatus: 'CREATE_COMPLETE',
635
+ },
636
+ ];
637
+
638
+ mockProvider.describeStack.mockResolvedValue(mockStack);
639
+ mockProvider.listStackResources.mockResolvedValue(mockResources);
640
+
641
+ const result = await cfDiscovery.discoverFromStack('create-frigg-app-production');
642
+
643
+ // Verify routing infrastructure was discovered
644
+ expect(result.routeTableId).toBe('rtb-0b83aca77ccde20a6');
645
+ expect(result.privateRouteTableId).toBe('rtb-0b83aca77ccde20a6');
646
+ expect(result.natRoute).toBe('rtb-0b83aca77ccde20a6|0.0.0.0/0');
647
+ expect(result.routeTableAssociations).toEqual([
648
+ 'rtbassoc-07245da0b447ca469',
649
+ 'rtbassoc-0806f9783c4ea181f',
650
+ ]);
651
+
652
+ // Verify VPC endpoints were discovered (both naming conventions)
653
+ expect(result.vpcEndpoints).toBeDefined();
654
+ expect(result.vpcEndpoints.s3).toBe('vpce-0352ceac2124c14be');
655
+ expect(result.vpcEndpoints.dynamodb).toBe('vpce-0b06c4f631199ea68');
656
+ expect(result.s3VpcEndpointId).toBe('vpce-0352ceac2124c14be');
657
+ expect(result.dynamoDbVpcEndpointId).toBe('vpce-0b06c4f631199ea68');
658
+
659
+ // Verify NO VPC/KMS resources (they're external)
660
+ expect(result.defaultVpcId).toBeUndefined();
661
+ expect(result.defaultKmsKeyId).toBeUndefined();
662
+ });
663
+
664
+ it('should work with legacy VPC endpoint naming (FriggS3VPCEndpoint)', async () => {
665
+ const mockStack = {
666
+ StackName: 'test-stack',
667
+ Outputs: [],
668
+ };
669
+
670
+ const mockResources = [
671
+ {
672
+ LogicalResourceId: 'FriggS3VPCEndpoint',
673
+ PhysicalResourceId: 'vpce-legacy-s3',
674
+ ResourceType: 'AWS::EC2::VPCEndpoint',
675
+ ResourceStatus: 'CREATE_COMPLETE',
676
+ },
677
+ {
678
+ LogicalResourceId: 'FriggDynamoDBVPCEndpoint',
679
+ PhysicalResourceId: 'vpce-legacy-ddb',
680
+ ResourceType: 'AWS::EC2::VPCEndpoint',
681
+ ResourceStatus: 'CREATE_COMPLETE',
682
+ },
683
+ ];
684
+
685
+ mockProvider.describeStack.mockResolvedValue(mockStack);
686
+ mockProvider.listStackResources.mockResolvedValue(mockResources);
687
+
688
+ const result = await cfDiscovery.discoverFromStack('test-stack');
689
+
690
+ // Both naming conventions should work
691
+ expect(result.vpcEndpoints.s3).toBe('vpce-legacy-s3');
692
+ expect(result.vpcEndpoints.dynamodb).toBe('vpce-legacy-ddb');
693
+ expect(result.s3VpcEndpointId).toBe('vpce-legacy-s3');
694
+ expect(result.dynamoDbVpcEndpointId).toBe('vpce-legacy-ddb');
695
+ });
696
+ });
589
697
  });
590
698
 
@@ -95,11 +95,23 @@ async function gatherDiscoveredResources(appDefinition) {
95
95
  const cfDiscovery = new CloudFormationDiscovery(provider, { serviceName, stage });
96
96
  const stackResources = await cfDiscovery.discoverFromStack(stackName);
97
97
 
98
- // Validate CF discovery results - only use if contains useful data
99
- const hasVpcData = stackResources?.defaultVpcId;
100
- const hasKmsData = stackResources?.defaultKmsKeyId;
101
- const hasAuroraData = stackResources?.auroraClusterId;
102
- const hasSomeUsefulData = hasVpcData || hasKmsData || hasAuroraData;
98
+ // Validate CF discovery results - check for ANY useful infrastructure
99
+ const hasVpcData = stackResources?.defaultVpcId; // VPC resource in stack
100
+ const hasKmsData = stackResources?.defaultKmsKeyId; // KMS resource in stack
101
+ const hasAuroraData = stackResources?.auroraClusterId; // Aurora in stack
102
+
103
+ // Check for routing infrastructure (proves VPC config exists even with external VPC)
104
+ const hasRoutingInfra = stackResources?.routeTableId || // FriggLambdaRouteTable
105
+ stackResources?.natRoute || // FriggNATRoute
106
+ stackResources?.vpcEndpoints?.s3 || // VPC endpoints
107
+ stackResources?.vpcEndpoints?.dynamodb;
108
+
109
+ // Stack is useful if it has EITHER actual resources OR routing infrastructure
110
+ const hasSomeUsefulData = hasVpcData || hasKmsData || hasAuroraData || hasRoutingInfra;
111
+
112
+ if (hasRoutingInfra && !hasVpcData) {
113
+ console.log(' ✓ Found VPC routing infrastructure in stack (external VPC pattern)');
114
+ }
103
115
 
104
116
  // Check if we're in isolated mode (each stage gets its own VPC/Aurora)
105
117
  const isIsolatedMode = appDefinition.managementMode === 'managed' &&
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.487.63ed8db.0",
4
+ "version": "2.0.0--canary.490.eec7dcd.0",
5
5
  "bin": {
6
6
  "frigg": "./frigg-cli/index.js"
7
7
  },
@@ -16,9 +16,9 @@
16
16
  "@babel/eslint-parser": "^7.18.9",
17
17
  "@babel/parser": "^7.25.3",
18
18
  "@babel/traverse": "^7.25.3",
19
- "@friggframework/core": "2.0.0--canary.487.63ed8db.0",
20
- "@friggframework/schemas": "2.0.0--canary.487.63ed8db.0",
21
- "@friggframework/test": "2.0.0--canary.487.63ed8db.0",
19
+ "@friggframework/core": "2.0.0--canary.490.eec7dcd.0",
20
+ "@friggframework/schemas": "2.0.0--canary.490.eec7dcd.0",
21
+ "@friggframework/test": "2.0.0--canary.490.eec7dcd.0",
22
22
  "@hapi/boom": "^10.0.1",
23
23
  "@inquirer/prompts": "^5.3.8",
24
24
  "axios": "^1.7.2",
@@ -46,8 +46,8 @@
46
46
  "validate-npm-package-name": "^5.0.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@friggframework/eslint-config": "2.0.0--canary.487.63ed8db.0",
50
- "@friggframework/prettier-config": "2.0.0--canary.487.63ed8db.0",
49
+ "@friggframework/eslint-config": "2.0.0--canary.490.eec7dcd.0",
50
+ "@friggframework/prettier-config": "2.0.0--canary.490.eec7dcd.0",
51
51
  "aws-sdk-client-mock": "^4.1.0",
52
52
  "aws-sdk-client-mock-jest": "^4.1.0",
53
53
  "jest": "^30.1.3",
@@ -79,5 +79,5 @@
79
79
  "publishConfig": {
80
80
  "access": "public"
81
81
  },
82
- "gitHead": "63ed8db39a59f2be928d8255be6b4afde423123f"
82
+ "gitHead": "eec7dcdaf12585353103eb99ebc141dfa60a6743"
83
83
  }