@friggframework/devtools 2.0.0--canary.474.898a56c.0 → 2.0.0--canary.474.a794ea3.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.
Files changed (23) hide show
  1. package/infrastructure/domains/database/migration-builder.js +199 -1
  2. package/infrastructure/domains/database/migration-builder.test.js +73 -0
  3. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  4. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +397 -29
  5. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  6. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +6 -0
  7. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +162 -9
  8. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +19 -1
  9. package/infrastructure/domains/health/domain/entities/issue.js +50 -1
  10. package/infrastructure/domains/health/domain/entities/issue.test.js +111 -0
  11. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  12. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  13. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +55 -28
  14. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  15. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  16. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  17. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +21 -6
  18. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  19. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  20. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +407 -20
  21. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +698 -26
  22. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +392 -1
  23. package/package.json +6 -6
@@ -120,19 +120,27 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
120
120
 
121
121
  const orphanedResources = [
122
122
  {
123
- physicalId: 'vpc-0eadd96976d29ede7',
123
+ physicalId: 'vpc-12345678',
124
124
  resourceType: 'AWS::EC2::VPC',
125
- tags: [
126
- { Key: 'aws:cloudformation:stack-name', Value: 'acme-integrations-dev' },
127
- { Key: 'aws:cloudformation:logical-id', Value: 'FriggVPC' },
128
- ],
125
+ properties: {
126
+ VpcId: 'vpc-12345678',
127
+ CidrBlock: '10.0.0.0/16',
128
+ tags: {
129
+ 'aws:cloudformation:stack-name': 'acme-integrations-dev',
130
+ 'aws:cloudformation:logical-id': 'FriggVPC',
131
+ },
132
+ },
129
133
  },
130
134
  {
131
- physicalId: 'subnet-00ab9e0502e66aac3',
135
+ physicalId: 'subnet-11111111',
132
136
  resourceType: 'AWS::EC2::Subnet',
133
- tags: [
134
- { Key: 'aws:cloudformation:stack-name', Value: 'acme-integrations-dev' },
135
- ],
137
+ properties: {
138
+ SubnetId: 'subnet-11111111',
139
+ VpcId: 'vpc-12345678',
140
+ tags: {
141
+ 'aws:cloudformation:stack-name': 'acme-integrations-dev',
142
+ },
143
+ },
136
144
  },
137
145
  ];
138
146
 
@@ -150,7 +158,7 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
150
158
  Type: 'AWS::Lambda::Function',
151
159
  Properties: {
152
160
  VpcConfig: {
153
- SubnetIds: ['subnet-00ab9e0502e66aac3'],
161
+ SubnetIds: ['subnet-11111111'],
154
162
  },
155
163
  },
156
164
  },
@@ -160,14 +168,14 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
160
168
  const mappings = [
161
169
  {
162
170
  logicalId: 'FriggVPC',
163
- physicalId: 'vpc-0eadd96976d29ede7',
171
+ physicalId: 'vpc-12345678',
164
172
  resourceType: 'AWS::EC2::VPC',
165
173
  matchMethod: 'tag',
166
174
  confidence: 'high',
167
175
  },
168
176
  {
169
177
  logicalId: 'FriggPrivateSubnet1',
170
- physicalId: 'subnet-00ab9e0502e66aac3',
178
+ physicalId: 'subnet-11111111',
171
179
  resourceType: 'AWS::EC2::Subnet',
172
180
  matchMethod: 'vpc-usage',
173
181
  confidence: 'high',
@@ -219,9 +227,13 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
219
227
 
220
228
  const orphanedResources = [
221
229
  {
222
- physicalId: 'vpc-0eadd96976d29ede7',
230
+ physicalId: 'vpc-12345678',
223
231
  resourceType: 'AWS::EC2::VPC',
224
- tags: [],
232
+ properties: {
233
+ VpcId: 'vpc-12345678',
234
+ CidrBlock: '10.0.0.0/16',
235
+ tags: {},
236
+ },
225
237
  },
226
238
  ];
227
239
 
@@ -231,7 +243,7 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
231
243
  const mappings = [
232
244
  {
233
245
  logicalId: 'FriggVPC',
234
- physicalId: 'vpc-0eadd96976d29ede7',
246
+ physicalId: 'vpc-12345678',
235
247
  resourceType: 'AWS::EC2::VPC',
236
248
  matchMethod: 'tag',
237
249
  confidence: 'high',
@@ -257,7 +269,7 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
257
269
  {
258
270
  ResourceType: 'AWS::EC2::VPC',
259
271
  LogicalResourceId: 'FriggVPC',
260
- ResourceIdentifier: { VpcId: 'vpc-0eadd96976d29ede7' },
272
+ ResourceIdentifier: { VpcId: 'vpc-12345678' },
261
273
  },
262
274
  ]);
263
275
  });
@@ -270,10 +282,41 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
270
282
  };
271
283
 
272
284
  const orphanedResources = [
273
- { physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC', tags: [] },
274
- { physicalId: 'subnet-456', resourceType: 'AWS::EC2::Subnet', tags: [] },
275
- { physicalId: 'sg-789', resourceType: 'AWS::EC2::SecurityGroup', tags: [] },
276
- { physicalId: 'igw-abc', resourceType: 'AWS::EC2::InternetGateway', tags: [] },
285
+ {
286
+ physicalId: 'vpc-123',
287
+ resourceType: 'AWS::EC2::VPC',
288
+ properties: {
289
+ VpcId: 'vpc-123',
290
+ CidrBlock: '10.0.0.0/16',
291
+ tags: {},
292
+ },
293
+ },
294
+ {
295
+ physicalId: 'subnet-456',
296
+ resourceType: 'AWS::EC2::Subnet',
297
+ properties: {
298
+ SubnetId: 'subnet-456',
299
+ VpcId: 'vpc-123',
300
+ tags: {},
301
+ },
302
+ },
303
+ {
304
+ physicalId: 'sg-789',
305
+ resourceType: 'AWS::EC2::SecurityGroup',
306
+ properties: {
307
+ GroupId: 'sg-789',
308
+ VpcId: 'vpc-123',
309
+ tags: {},
310
+ },
311
+ },
312
+ {
313
+ physicalId: 'igw-abc',
314
+ resourceType: 'AWS::EC2::InternetGateway',
315
+ properties: {
316
+ InternetGatewayId: 'igw-abc',
317
+ tags: {},
318
+ },
319
+ },
277
320
  ];
278
321
 
279
322
  const buildTemplate = { resources: {} };
@@ -357,9 +400,33 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
357
400
  };
358
401
 
359
402
  const orphanedResources = [
360
- { physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC', tags: [] },
361
- { physicalId: 'vpc-456', resourceType: 'AWS::EC2::VPC', tags: [] }, // No match
362
- { physicalId: 'subnet-789', resourceType: 'AWS::EC2::Subnet', tags: [] },
403
+ {
404
+ physicalId: 'vpc-123',
405
+ resourceType: 'AWS::EC2::VPC',
406
+ properties: {
407
+ VpcId: 'vpc-123',
408
+ CidrBlock: '10.0.0.0/16',
409
+ tags: {},
410
+ },
411
+ },
412
+ {
413
+ physicalId: 'vpc-456',
414
+ resourceType: 'AWS::EC2::VPC',
415
+ properties: {
416
+ VpcId: 'vpc-456',
417
+ CidrBlock: '10.0.0.0/16',
418
+ tags: {},
419
+ },
420
+ }, // No match
421
+ {
422
+ physicalId: 'subnet-789',
423
+ resourceType: 'AWS::EC2::Subnet',
424
+ properties: {
425
+ SubnetId: 'subnet-789',
426
+ VpcId: 'vpc-123',
427
+ tags: {},
428
+ },
429
+ },
363
430
  ];
364
431
 
365
432
  const buildTemplate = { resources: {} };
@@ -420,8 +487,24 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
420
487
  };
421
488
 
422
489
  const orphanedResources = [
423
- { physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC', tags: [] },
424
- { physicalId: 'vpc-456', resourceType: 'AWS::EC2::VPC', tags: [] },
490
+ {
491
+ physicalId: 'vpc-123',
492
+ resourceType: 'AWS::EC2::VPC',
493
+ properties: {
494
+ VpcId: 'vpc-123',
495
+ CidrBlock: '10.0.0.0/16',
496
+ tags: {},
497
+ },
498
+ },
499
+ {
500
+ physicalId: 'vpc-456',
501
+ resourceType: 'AWS::EC2::VPC',
502
+ properties: {
503
+ VpcId: 'vpc-456',
504
+ CidrBlock: '10.0.0.0/16',
505
+ tags: {},
506
+ },
507
+ },
425
508
  ];
426
509
 
427
510
  const buildTemplate = { resources: {} };
@@ -473,9 +556,33 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
473
556
  };
474
557
 
475
558
  const orphanedResources = [
476
- { physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC', tags: [] },
477
- { physicalId: 'vpc-456', resourceType: 'AWS::EC2::VPC', tags: [] },
478
- { physicalId: 'vpc-789', resourceType: 'AWS::EC2::VPC', tags: [] },
559
+ {
560
+ physicalId: 'vpc-123',
561
+ resourceType: 'AWS::EC2::VPC',
562
+ properties: {
563
+ VpcId: 'vpc-123',
564
+ CidrBlock: '10.0.0.0/16',
565
+ tags: {},
566
+ },
567
+ },
568
+ {
569
+ physicalId: 'vpc-456',
570
+ resourceType: 'AWS::EC2::VPC',
571
+ properties: {
572
+ VpcId: 'vpc-456',
573
+ CidrBlock: '10.0.0.0/16',
574
+ tags: {},
575
+ },
576
+ },
577
+ {
578
+ physicalId: 'vpc-789',
579
+ resourceType: 'AWS::EC2::VPC',
580
+ properties: {
581
+ VpcId: 'vpc-789',
582
+ CidrBlock: '10.0.0.0/16',
583
+ tags: {},
584
+ },
585
+ },
479
586
  ];
480
587
 
481
588
  const buildTemplate = { resources: {} };
@@ -538,7 +645,15 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
538
645
  };
539
646
 
540
647
  const orphanedResources = [
541
- { physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC', tags: [] },
648
+ {
649
+ physicalId: 'vpc-123',
650
+ resourceType: 'AWS::EC2::VPC',
651
+ properties: {
652
+ VpcId: 'vpc-123',
653
+ CidrBlock: '10.0.0.0/16',
654
+ tags: {},
655
+ },
656
+ },
542
657
  ];
543
658
 
544
659
  const buildTemplate = { resources: { FriggVPC: { Type: 'AWS::EC2::VPC' } } };
@@ -759,4 +874,257 @@ describe('RepairViaImportUseCase - importWithLogicalIdMapping', () => {
759
874
  expect(warnings[1].count).toBe(2);
760
875
  });
761
876
  });
877
+
878
+ describe('_deduplicateResourcesByLogicalId', () => {
879
+ it('should select ONE resource per logical ID when multiple map to same ID', () => {
880
+ // Arrange: 3 VPCs all tagged with "FriggVPC", but only vpc-123 is in deployed template
881
+ const mappedResources = [
882
+ {
883
+ logicalId: 'FriggVPC',
884
+ physicalId: 'vpc-123',
885
+ resourceType: 'AWS::EC2::VPC',
886
+ matchMethod: 'tag',
887
+ confidence: 'high',
888
+ },
889
+ {
890
+ logicalId: 'FriggVPC',
891
+ physicalId: 'vpc-456',
892
+ resourceType: 'AWS::EC2::VPC',
893
+ matchMethod: 'tag',
894
+ confidence: 'high',
895
+ },
896
+ {
897
+ logicalId: 'FriggVPC',
898
+ physicalId: 'vpc-789',
899
+ resourceType: 'AWS::EC2::VPC',
900
+ matchMethod: 'tag',
901
+ confidence: 'high',
902
+ },
903
+ ];
904
+
905
+ const deployedTemplate = {
906
+ resources: {
907
+ MyLambda: {
908
+ Type: 'AWS::Lambda::Function',
909
+ Properties: {
910
+ VpcConfig: {
911
+ SubnetIds: ['subnet-in-vpc-123'], // This subnet belongs to vpc-123
912
+ SecurityGroupIds: [],
913
+ },
914
+ },
915
+ },
916
+ },
917
+ };
918
+
919
+ // Act
920
+ const result = useCase._deduplicateResourcesByLogicalId(
921
+ mappedResources,
922
+ deployedTemplate
923
+ );
924
+
925
+ // Assert: Should select ONLY the VPC that's actually referenced
926
+ expect(result.selectedResources).toHaveLength(1);
927
+ expect(result.selectedResources[0].physicalId).toBe('vpc-123');
928
+ expect(result.selectedResources[0].logicalId).toBe('FriggVPC');
929
+
930
+ // The other 2 VPCs should be marked as duplicates
931
+ expect(result.duplicates).toHaveLength(2);
932
+ expect(result.duplicates.map((d) => d.physicalId)).toEqual(
933
+ expect.arrayContaining(['vpc-456', 'vpc-789'])
934
+ );
935
+ });
936
+
937
+ it('should keep all resources when they have unique logical IDs', () => {
938
+ // Arrange: Different logical IDs, no duplication
939
+ const mappedResources = [
940
+ {
941
+ logicalId: 'FriggVPC',
942
+ physicalId: 'vpc-123',
943
+ resourceType: 'AWS::EC2::VPC',
944
+ },
945
+ {
946
+ logicalId: 'FriggPrivateSubnet1',
947
+ physicalId: 'subnet-456',
948
+ resourceType: 'AWS::EC2::Subnet',
949
+ },
950
+ {
951
+ logicalId: 'FriggLambdaSecurityGroup',
952
+ physicalId: 'sg-789',
953
+ resourceType: 'AWS::EC2::SecurityGroup',
954
+ },
955
+ ];
956
+
957
+ const deployedTemplate = { resources: {} };
958
+
959
+ // Act
960
+ const result = useCase._deduplicateResourcesByLogicalId(
961
+ mappedResources,
962
+ deployedTemplate
963
+ );
964
+
965
+ // Assert: All 3 resources should be selected
966
+ expect(result.selectedResources).toHaveLength(3);
967
+ expect(result.duplicates).toHaveLength(0);
968
+ });
969
+
970
+ it('should use deployed template references to select correct resource', () => {
971
+ // Arrange: 5 subnets with same logical ID, need to pick the ones in deployed template
972
+ const mappedResources = [
973
+ {
974
+ logicalId: 'FriggPrivateSubnet1',
975
+ physicalId: 'subnet-111',
976
+ resourceType: 'AWS::EC2::Subnet',
977
+ },
978
+ {
979
+ logicalId: 'FriggPrivateSubnet1',
980
+ physicalId: 'subnet-222', // THIS ONE is in deployed template
981
+ resourceType: 'AWS::EC2::Subnet',
982
+ },
983
+ {
984
+ logicalId: 'FriggPrivateSubnet1',
985
+ physicalId: 'subnet-333',
986
+ resourceType: 'AWS::EC2::Subnet',
987
+ },
988
+ {
989
+ logicalId: 'FriggPrivateSubnet1',
990
+ physicalId: 'subnet-444',
991
+ resourceType: 'AWS::EC2::Subnet',
992
+ },
993
+ {
994
+ logicalId: 'FriggPrivateSubnet1',
995
+ physicalId: 'subnet-555',
996
+ resourceType: 'AWS::EC2::Subnet',
997
+ },
998
+ ];
999
+
1000
+ const deployedTemplate = {
1001
+ resources: {
1002
+ MyLambda: {
1003
+ Type: 'AWS::Lambda::Function',
1004
+ Properties: {
1005
+ VpcConfig: {
1006
+ SubnetIds: ['subnet-222', 'subnet-other'], // subnet-222 is THE correct one
1007
+ SecurityGroupIds: [],
1008
+ },
1009
+ },
1010
+ },
1011
+ },
1012
+ };
1013
+
1014
+ // Act
1015
+ const result = useCase._deduplicateResourcesByLogicalId(
1016
+ mappedResources,
1017
+ deployedTemplate
1018
+ );
1019
+
1020
+ // Assert: Should select subnet-222 because it's in deployed template
1021
+ expect(result.selectedResources).toHaveLength(1);
1022
+ expect(result.selectedResources[0].physicalId).toBe('subnet-222');
1023
+
1024
+ // Other 4 subnets should be duplicates
1025
+ expect(result.duplicates).toHaveLength(4);
1026
+ });
1027
+
1028
+ it('should handle multiple logical IDs with duplicates', () => {
1029
+ // Arrange: 3 VPCs + 2 SecurityGroups, all duplicates
1030
+ // Note: VPC selection falls back to first when no direct reference exists
1031
+ const mappedResources = [
1032
+ {
1033
+ logicalId: 'FriggVPC',
1034
+ physicalId: 'vpc-111', // Will be selected (fallback to first)
1035
+ resourceType: 'AWS::EC2::VPC',
1036
+ },
1037
+ {
1038
+ logicalId: 'FriggVPC',
1039
+ physicalId: 'vpc-222',
1040
+ resourceType: 'AWS::EC2::VPC',
1041
+ },
1042
+ {
1043
+ logicalId: 'FriggVPC',
1044
+ physicalId: 'vpc-333',
1045
+ resourceType: 'AWS::EC2::VPC',
1046
+ },
1047
+ {
1048
+ logicalId: 'FriggLambdaSecurityGroup',
1049
+ physicalId: 'sg-aaa', // Will be selected (in deployed template)
1050
+ resourceType: 'AWS::EC2::SecurityGroup',
1051
+ },
1052
+ {
1053
+ logicalId: 'FriggLambdaSecurityGroup',
1054
+ physicalId: 'sg-bbb',
1055
+ resourceType: 'AWS::EC2::SecurityGroup',
1056
+ },
1057
+ ];
1058
+
1059
+ const deployedTemplate = {
1060
+ resources: {
1061
+ MyLambda: {
1062
+ Type: 'AWS::Lambda::Function',
1063
+ Properties: {
1064
+ VpcConfig: {
1065
+ SubnetIds: ['subnet-xxx'], // Not matching any VPC
1066
+ SecurityGroupIds: ['sg-aaa'], // sg-aaa is the correct one
1067
+ },
1068
+ },
1069
+ },
1070
+ },
1071
+ };
1072
+
1073
+ // Act
1074
+ const result = useCase._deduplicateResourcesByLogicalId(
1075
+ mappedResources,
1076
+ deployedTemplate
1077
+ );
1078
+
1079
+ // Assert: Should select ONE of each logical ID
1080
+ expect(result.selectedResources).toHaveLength(2);
1081
+ expect(result.selectedResources.map((r) => r.physicalId)).toEqual(
1082
+ expect.arrayContaining(['vpc-111', 'sg-aaa']) // VPC fallback + SG match
1083
+ );
1084
+
1085
+ // 3 resources should be duplicates (2 VPCs + 1 SG)
1086
+ expect(result.duplicates).toHaveLength(3);
1087
+ });
1088
+
1089
+ it('should fall back to first resource if none match deployed template', () => {
1090
+ // Arrange: Multiple resources but none are in deployed template
1091
+ const mappedResources = [
1092
+ {
1093
+ logicalId: 'FriggVPC',
1094
+ physicalId: 'vpc-111',
1095
+ resourceType: 'AWS::EC2::VPC',
1096
+ },
1097
+ {
1098
+ logicalId: 'FriggVPC',
1099
+ physicalId: 'vpc-222',
1100
+ resourceType: 'AWS::EC2::VPC',
1101
+ },
1102
+ ];
1103
+
1104
+ const deployedTemplate = {
1105
+ resources: {
1106
+ MyLambda: {
1107
+ Type: 'AWS::Lambda::Function',
1108
+ Properties: {
1109
+ VpcConfig: {
1110
+ SubnetIds: ['subnet-other'], // Different VPC
1111
+ SecurityGroupIds: [],
1112
+ },
1113
+ },
1114
+ },
1115
+ },
1116
+ };
1117
+
1118
+ // Act
1119
+ const result = useCase._deduplicateResourcesByLogicalId(
1120
+ mappedResources,
1121
+ deployedTemplate
1122
+ );
1123
+
1124
+ // Assert: Should pick first one as fallback
1125
+ expect(result.selectedResources).toHaveLength(1);
1126
+ expect(result.selectedResources[0].physicalId).toBe('vpc-111');
1127
+ expect(result.duplicates).toHaveLength(1);
1128
+ });
1129
+ });
762
1130
  });