@friggframework/devtools 2.0.0--canary.490.a6abe40.0 → 2.0.0--canary.490.e6d93e8.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.
@@ -577,11 +577,10 @@ class VpcBuilder extends InfrastructureBuilder {
577
577
  * Build VPC based on ownership decision
578
578
  *
579
579
  * For STACK ownership: ALWAYS add definitions to template.
580
- * CloudFormation idempotency ensures existing resources aren't recreated.
581
580
  */
582
581
  buildVpcFromDecision(decision, appDefinition, result) {
583
582
  if (decision.ownership === ResourceOwnership.STACK) {
584
- // For STACK ownership: ALWAYS create definitions (CloudFormation idempotency)
583
+ // For STACK ownership: ALWAYS create definitions
585
584
  if (decision.physicalId) {
586
585
  console.log(` → Adding VPC definition to template (existing: ${decision.physicalId})`);
587
586
  } else {
@@ -1348,6 +1348,221 @@ describe('VpcBuilder', () => {
1348
1348
  });
1349
1349
  });
1350
1350
 
1351
+ describe('Full Frontify deployment pattern integration test', () => {
1352
+ it('should correctly handle Frontify production scenario: external VPC + stack routing', async () => {
1353
+ // This test replicates the EXACT Frontify production deployment scenario
1354
+ const appDefinition = {
1355
+ vpc: { enable: true },
1356
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
1357
+ };
1358
+
1359
+ // Actual Frontify discovery results (from production logs)
1360
+ const discoveredResources = {
1361
+ fromCloudFormationStack: true,
1362
+ stackName: 'create-frigg-app-production',
1363
+ existingLogicalIds: [
1364
+ 'FriggLambdaSecurityGroup',
1365
+ 'FriggLambdaRouteTable',
1366
+ 'FriggPrivateRoute',
1367
+ 'FriggPrivateSubnet1RouteTableAssociation',
1368
+ 'FriggPrivateSubnet2RouteTableAssociation',
1369
+ 'FriggS3VPCEndpoint',
1370
+ 'FriggDynamoDBVPCEndpoint',
1371
+ 'FriggKMSVPCEndpoint'
1372
+ ],
1373
+ // Stack resources (from CloudFormation)
1374
+ lambdaSecurityGroupId: 'sg-01002240c6a446202',
1375
+ routeTableId: 'rtb-08af43bbf0775602d',
1376
+ s3VpcEndpointId: 'vpce-0d1ecb2c53ce9b4b8',
1377
+ dynamodbVpcEndpointId: 'vpce-0fb749b207f1020b0',
1378
+ kmsVpcEndpointId: 'vpce-0e38c25155b86de22',
1379
+ // External resources (discovered via queries)
1380
+ defaultVpcId: 'vpc-0cd17c0e06cb28b28',
1381
+ privateSubnetId1: 'subnet-034f6562dbbc16348',
1382
+ privateSubnetId2: 'subnet-0b8be2b82aeb5cdec',
1383
+ existingNatGatewayId: 'nat-022660c36a47e2d79'
1384
+ };
1385
+
1386
+ const result = await vpcBuilder.build(appDefinition, discoveredResources);
1387
+
1388
+ // === ASSERTIONS: Template Structure ===
1389
+
1390
+ // 1. VPC should be external (not in template)
1391
+ expect(result.resources.FriggVPC).toBeUndefined();
1392
+ expect(result.vpcId).toBe('vpc-0cd17c0e06cb28b28');
1393
+
1394
+ // 2. Security Group MUST be in template (stack-managed)
1395
+ expect(result.resources.FriggLambdaSecurityGroup).toBeDefined();
1396
+ expect(result.resources.FriggLambdaSecurityGroup.Type).toBe('AWS::EC2::SecurityGroup');
1397
+ expect(result.vpcConfig.securityGroupIds).toEqual([{ Ref: 'FriggLambdaSecurityGroup' }]);
1398
+
1399
+ // 3. Subnets should be external (use hardcoded IDs, not in template)
1400
+ expect(result.resources.FriggPrivateSubnet1).toBeUndefined();
1401
+ expect(result.resources.FriggPrivateSubnet2).toBeUndefined();
1402
+ expect(result.vpcConfig.subnetIds).toEqual([
1403
+ 'subnet-034f6562dbbc16348',
1404
+ 'subnet-0b8be2b82aeb5cdec'
1405
+ ]);
1406
+
1407
+ // 4. NAT Gateway should be external (not in template)
1408
+ expect(result.resources.FriggNATGateway).toBeUndefined();
1409
+ expect(result.resources.FriggNATGatewayEIP).toBeUndefined();
1410
+ expect(result.natGatewayId).toBe('nat-022660c36a47e2d79');
1411
+
1412
+ // 5. Route table MUST be in template (stack-managed)
1413
+ expect(result.resources.FriggLambdaRouteTable).toBeDefined();
1414
+ expect(result.resources.FriggLambdaRouteTable.Type).toBe('AWS::EC2::RouteTable');
1415
+
1416
+ // 6. Route table associations MUST be in template
1417
+ expect(result.resources.FriggPrivateSubnet1RouteTableAssociation).toBeDefined();
1418
+ expect(result.resources.FriggPrivateSubnet2RouteTableAssociation).toBeDefined();
1419
+
1420
+ // 7. VPC Endpoints MUST be in template (stack-managed, prevents deletion)
1421
+ expect(result.resources.FriggS3VPCEndpoint).toBeDefined();
1422
+ expect(result.resources.FriggS3VPCEndpoint.Properties.VpcEndpointType).toBe('Gateway');
1423
+
1424
+ expect(result.resources.FriggDynamoDBVPCEndpoint).toBeDefined();
1425
+ expect(result.resources.FriggDynamoDBVPCEndpoint.Properties.VpcEndpointType).toBe('Gateway');
1426
+
1427
+ expect(result.resources.FriggKMSVPCEndpoint).toBeDefined();
1428
+ expect(result.resources.FriggKMSVPCEndpoint.Properties.VpcEndpointType).toBe('Interface');
1429
+
1430
+ // 8. VPC Endpoint Security Group needed for interface endpoints
1431
+ expect(result.resources.FriggVPCEndpointSecurityGroup).toBeDefined();
1432
+
1433
+ // === ASSERTIONS: Resource Count ===
1434
+ const resourceKeys = Object.keys(result.resources);
1435
+ const friggResources = resourceKeys.filter(k => k.startsWith('Frigg') || k.startsWith('VPC'));
1436
+
1437
+ // Should have routing infrastructure + endpoints + security groups
1438
+ // NOT full VPC (no FriggVPC, FriggPrivateSubnet1/2, FriggNATGateway)
1439
+ expect(friggResources).toContain('FriggLambdaSecurityGroup');
1440
+ expect(friggResources).toContain('FriggLambdaRouteTable');
1441
+ expect(friggResources).toContain('FriggS3VPCEndpoint');
1442
+ expect(friggResources).toContain('FriggDynamoDBVPCEndpoint');
1443
+ expect(friggResources).toContain('FriggKMSVPCEndpoint');
1444
+ expect(friggResources).not.toContain('FriggVPC');
1445
+ expect(friggResources).not.toContain('FriggPrivateSubnet1');
1446
+ expect(friggResources).not.toContain('FriggNATGateway');
1447
+ });
1448
+ });
1449
+
1450
+ describe('convertFlatDiscoveryToStructured - VPC Endpoints from CloudFormation', () => {
1451
+ it('should add VPC endpoints to stackManaged when in existingLogicalIds', () => {
1452
+ const flatDiscovery = {
1453
+ fromCloudFormationStack: true,
1454
+ stackName: 'test-stack',
1455
+ existingLogicalIds: [
1456
+ 'FriggS3VPCEndpoint',
1457
+ 'FriggDynamoDBVPCEndpoint',
1458
+ 'FriggKMSVPCEndpoint'
1459
+ ],
1460
+ s3VpcEndpointId: 'vpce-s3-stack',
1461
+ dynamodbVpcEndpointId: 'vpce-ddb-stack',
1462
+ kmsVpcEndpointId: 'vpce-kms-stack'
1463
+ };
1464
+
1465
+ const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
1466
+
1467
+ // VPC endpoints should be in stackManaged (not external)
1468
+ expect(result.stackManaged).toContainEqual(
1469
+ expect.objectContaining({
1470
+ logicalId: 'FriggS3VPCEndpoint',
1471
+ physicalId: 'vpce-s3-stack',
1472
+ resourceType: 'AWS::EC2::VPCEndpoint'
1473
+ })
1474
+ );
1475
+ expect(result.stackManaged).toContainEqual(
1476
+ expect.objectContaining({
1477
+ logicalId: 'FriggDynamoDBVPCEndpoint',
1478
+ physicalId: 'vpce-ddb-stack',
1479
+ resourceType: 'AWS::EC2::VPCEndpoint'
1480
+ })
1481
+ );
1482
+ expect(result.stackManaged).toContainEqual(
1483
+ expect.objectContaining({
1484
+ logicalId: 'FriggKMSVPCEndpoint',
1485
+ physicalId: 'vpce-kms-stack',
1486
+ resourceType: 'AWS::EC2::VPCEndpoint'
1487
+ })
1488
+ );
1489
+
1490
+ // Should NOT be in external array
1491
+ expect(result.external.some(r => r.physicalId === 'vpce-s3-stack')).toBe(false);
1492
+ expect(result.external.some(r => r.physicalId === 'vpce-ddb-stack')).toBe(false);
1493
+ expect(result.external.some(r => r.physicalId === 'vpce-kms-stack')).toBe(false);
1494
+ });
1495
+
1496
+ it('should add VPC endpoints to external when NOT in existingLogicalIds', () => {
1497
+ const flatDiscovery = {
1498
+ fromCloudFormationStack: false, // AWS API discovery
1499
+ s3VpcEndpointId: 'vpce-s3-external',
1500
+ dynamodbVpcEndpointId: 'vpce-ddb-external'
1501
+ };
1502
+
1503
+ const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
1504
+
1505
+ // Should be in external (AWS discovery)
1506
+ expect(result.external).toContainEqual(
1507
+ expect.objectContaining({
1508
+ physicalId: 'vpce-s3-external',
1509
+ resourceType: 'AWS::EC2::VPCEndpoint',
1510
+ source: 'aws-discovery'
1511
+ })
1512
+ );
1513
+
1514
+ // Should NOT be in stackManaged
1515
+ expect(result.stackManaged.some(r => r.physicalId === 'vpce-s3-external')).toBe(false);
1516
+ });
1517
+
1518
+ it('should preserve existing VPC endpoints and only create missing ones', async () => {
1519
+ const appDefinition = {
1520
+ vpc: { enable: true },
1521
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
1522
+ };
1523
+
1524
+ const discoveredResources = {
1525
+ fromCloudFormationStack: true,
1526
+ stackName: 'test-stack',
1527
+ existingLogicalIds: [
1528
+ 'FriggS3VPCEndpoint', // In stack
1529
+ 'FriggDynamoDBVPCEndpoint', // In stack
1530
+ 'FriggKMSVPCEndpoint' // In stack
1531
+ // SecretsManager and SQS NOT in stack (were deleted)
1532
+ ],
1533
+ defaultVpcId: 'vpc-123',
1534
+ privateSubnetId1: 'subnet-1',
1535
+ privateSubnetId2: 'subnet-2',
1536
+ lambdaSecurityGroupId: 'sg-123',
1537
+ routeTableId: 'rtb-123',
1538
+ // Endpoints in stack
1539
+ s3VpcEndpointId: 'vpce-s3-existing',
1540
+ dynamodbVpcEndpointId: 'vpce-ddb-existing',
1541
+ kmsVpcEndpointId: 'vpce-kms-existing'
1542
+ // secretsManagerVpcEndpointId and sqsVpcEndpointId NOT present
1543
+ };
1544
+
1545
+ const result = await vpcBuilder.build(appDefinition, discoveredResources);
1546
+
1547
+ // Existing endpoints MUST be in template (re-added)
1548
+ expect(result.resources.FriggS3VPCEndpoint).toBeDefined();
1549
+ expect(result.resources.FriggS3VPCEndpoint.Properties.VpcId).toBe('vpc-123');
1550
+
1551
+ expect(result.resources.FriggDynamoDBVPCEndpoint).toBeDefined();
1552
+ expect(result.resources.FriggDynamoDBVPCEndpoint.Properties.VpcId).toBe('vpc-123');
1553
+
1554
+ expect(result.resources.FriggKMSVPCEndpoint).toBeDefined();
1555
+ expect(result.resources.FriggKMSVPCEndpoint.Properties.VpcId).toBe('vpc-123');
1556
+
1557
+ // Missing endpoints should also be created
1558
+ expect(result.resources.FriggSecretsManagerVPCEndpoint).toBeDefined();
1559
+ expect(result.resources.FriggSQSVPCEndpoint).toBeDefined();
1560
+
1561
+ // VPC Endpoint Security Group should be created
1562
+ expect(result.resources.FriggVPCEndpointSecurityGroup).toBeDefined();
1563
+ });
1564
+ });
1565
+
1351
1566
  describe('convertFlatDiscoveryToStructured - CloudFormation query results', () => {
1352
1567
  it('should add VPC from CloudFormation query to external array', () => {
1353
1568
  const flatDiscovery = {
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.490.a6abe40.0",
4
+ "version": "2.0.0--canary.490.e6d93e8.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.490.a6abe40.0",
20
- "@friggframework/schemas": "2.0.0--canary.490.a6abe40.0",
21
- "@friggframework/test": "2.0.0--canary.490.a6abe40.0",
19
+ "@friggframework/core": "2.0.0--canary.490.e6d93e8.0",
20
+ "@friggframework/schemas": "2.0.0--canary.490.e6d93e8.0",
21
+ "@friggframework/test": "2.0.0--canary.490.e6d93e8.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.490.a6abe40.0",
50
- "@friggframework/prettier-config": "2.0.0--canary.490.a6abe40.0",
49
+ "@friggframework/eslint-config": "2.0.0--canary.490.e6d93e8.0",
50
+ "@friggframework/prettier-config": "2.0.0--canary.490.e6d93e8.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": "a6abe40b6a1d1a48bc28accdf401e2583e73c60c"
82
+ "gitHead": "e6d93e89ac26842505162381f435720d7923baf4"
83
83
  }