@friggframework/devtools 2.0.0--canary.490.71c435d.0 → 2.0.0--canary.490.36b3031.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
|
|
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 {
|
|
@@ -640,7 +639,6 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
640
639
|
buildSecurityGroupFromDecision(decision, appDefinition, result) {
|
|
641
640
|
if (decision.ownership === ResourceOwnership.STACK) {
|
|
642
641
|
// Always create security group resource in template
|
|
643
|
-
// CloudFormation handles idempotency if it already exists
|
|
644
642
|
console.log(' → Adding Lambda Security Group to template...');
|
|
645
643
|
|
|
646
644
|
result.resources.FriggLambdaSecurityGroup = {
|
|
@@ -690,7 +688,6 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
690
688
|
}
|
|
691
689
|
|
|
692
690
|
// For STACK ownership: ALWAYS add definitions to template
|
|
693
|
-
// CloudFormation idempotency ensures existing resources won't be recreated
|
|
694
691
|
if (decision.physicalIds && decision.physicalIds.length >= 2) {
|
|
695
692
|
console.log(` → Adding subnet definitions to template (existing: ${decision.physicalIds.join(', ')})`);
|
|
696
693
|
} else {
|
|
@@ -398,6 +398,66 @@ describe('VpcBuilder', () => {
|
|
|
398
398
|
expect(result.resources.FriggS3VPCEndpoint.Properties.VpcId).toBe('vpc-123');
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
+
it('should add stack-managed security group back to template to prevent deletion', async () => {
|
|
402
|
+
const appDefinition = {
|
|
403
|
+
vpc: { enable: true },
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const discoveredResources = {
|
|
407
|
+
fromCloudFormationStack: true,
|
|
408
|
+
stackName: 'test-stack',
|
|
409
|
+
existingLogicalIds: ['FriggLambdaSecurityGroup'],
|
|
410
|
+
defaultVpcId: 'vpc-123',
|
|
411
|
+
privateSubnetId1: 'subnet-1',
|
|
412
|
+
privateSubnetId2: 'subnet-2',
|
|
413
|
+
lambdaSecurityGroupId: 'sg-existing-stack', // Existing in stack
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
417
|
+
|
|
418
|
+
// CRITICAL: Must RE-ADD stack-managed SG to template or CloudFormation will DELETE it
|
|
419
|
+
expect(result.resources.FriggLambdaSecurityGroup).toBeDefined();
|
|
420
|
+
expect(result.resources.FriggLambdaSecurityGroup.Type).toBe('AWS::EC2::SecurityGroup');
|
|
421
|
+
expect(result.resources.FriggLambdaSecurityGroup.Properties.VpcId).toBe('vpc-123');
|
|
422
|
+
expect(result.resources.FriggLambdaSecurityGroup.Properties.GroupDescription).toBeDefined();
|
|
423
|
+
|
|
424
|
+
// Should use Ref in Lambda config (not recreating)
|
|
425
|
+
expect(result.vpcConfig.securityGroupIds).toContainEqual({ Ref: 'FriggLambdaSecurityGroup' });
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should add stack-managed subnets back to template to prevent deletion', async () => {
|
|
429
|
+
const appDefinition = {
|
|
430
|
+
vpc: { enable: true },
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const discoveredResources = {
|
|
434
|
+
fromCloudFormationStack: true,
|
|
435
|
+
stackName: 'test-stack',
|
|
436
|
+
existingLogicalIds: ['FriggPrivateSubnet1', 'FriggPrivateSubnet2'],
|
|
437
|
+
defaultVpcId: 'vpc-123',
|
|
438
|
+
// Subnets exist in stack with specific IDs
|
|
439
|
+
privateSubnetId1: 'subnet-existing-1',
|
|
440
|
+
privateSubnetId2: 'subnet-existing-2',
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
444
|
+
|
|
445
|
+
// CRITICAL: Must RE-ADD stack-managed subnets to template or CloudFormation will DELETE them
|
|
446
|
+
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
447
|
+
expect(result.resources.FriggPrivateSubnet1.Type).toBe('AWS::EC2::Subnet');
|
|
448
|
+
expect(result.resources.FriggPrivateSubnet1.Properties.VpcId).toBe('vpc-123');
|
|
449
|
+
|
|
450
|
+
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
451
|
+
expect(result.resources.FriggPrivateSubnet2.Type).toBe('AWS::EC2::Subnet');
|
|
452
|
+
expect(result.resources.FriggPrivateSubnet2.Properties.VpcId).toBe('vpc-123');
|
|
453
|
+
|
|
454
|
+
// Should use Refs (not external IDs)
|
|
455
|
+
expect(result.vpcConfig.subnetIds).toEqual([
|
|
456
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
457
|
+
{ Ref: 'FriggPrivateSubnet2' }
|
|
458
|
+
]);
|
|
459
|
+
});
|
|
460
|
+
|
|
401
461
|
it('should add stack-managed VPC endpoints back to template to prevent deletion', async () => {
|
|
402
462
|
const appDefinition = {
|
|
403
463
|
vpc: { enable: true },
|
|
@@ -1288,6 +1348,105 @@ describe('VpcBuilder', () => {
|
|
|
1288
1348
|
});
|
|
1289
1349
|
});
|
|
1290
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
|
+
|
|
1291
1450
|
describe('convertFlatDiscoveryToStructured - CloudFormation query results', () => {
|
|
1292
1451
|
it('should add VPC from CloudFormation query to external array', () => {
|
|
1293
1452
|
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.
|
|
4
|
+
"version": "2.0.0--canary.490.36b3031.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.
|
|
20
|
-
"@friggframework/schemas": "2.0.0--canary.490.
|
|
21
|
-
"@friggframework/test": "2.0.0--canary.490.
|
|
19
|
+
"@friggframework/core": "2.0.0--canary.490.36b3031.0",
|
|
20
|
+
"@friggframework/schemas": "2.0.0--canary.490.36b3031.0",
|
|
21
|
+
"@friggframework/test": "2.0.0--canary.490.36b3031.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.
|
|
50
|
-
"@friggframework/prettier-config": "2.0.0--canary.490.
|
|
49
|
+
"@friggframework/eslint-config": "2.0.0--canary.490.36b3031.0",
|
|
50
|
+
"@friggframework/prettier-config": "2.0.0--canary.490.36b3031.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": "
|
|
82
|
+
"gitHead": "36b30311e062560a65a75a864b880bff01a745e7"
|
|
83
83
|
}
|