@friggframework/devtools 2.0.0-next.45 → 2.0.0-next.47

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 (212) hide show
  1. package/infrastructure/ARCHITECTURE.md +487 -0
  2. package/infrastructure/HEALTH.md +468 -0
  3. package/infrastructure/README.md +51 -0
  4. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  5. package/infrastructure/__tests__/template-generation.test.js +687 -0
  6. package/infrastructure/create-frigg-infrastructure.js +1 -1
  7. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  8. package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
  9. package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
  10. package/infrastructure/domains/database/aurora-builder.js +809 -0
  11. package/infrastructure/domains/database/aurora-builder.test.js +950 -0
  12. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  13. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  14. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  15. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  16. package/infrastructure/domains/database/migration-builder.js +695 -0
  17. package/infrastructure/domains/database/migration-builder.test.js +294 -0
  18. package/infrastructure/domains/database/migration-resolver.js +163 -0
  19. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  20. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  21. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  22. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  23. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  24. package/infrastructure/domains/health/application/ports/index.js +26 -0
  25. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  26. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  27. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  28. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  29. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  30. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  31. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  32. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  33. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  34. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  35. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  36. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  37. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  38. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  39. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  40. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  41. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  42. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  43. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  44. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  45. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  46. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  47. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  48. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  49. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  50. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  51. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  52. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  53. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  54. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  55. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  56. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  57. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  58. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  59. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  60. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  61. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  62. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  63. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  64. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  65. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  66. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  67. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  68. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  69. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  70. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  71. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  72. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  73. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  74. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  75. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  76. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  77. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  78. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  79. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  80. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  81. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  82. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  83. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  84. package/infrastructure/domains/integration/integration-builder.js +397 -0
  85. package/infrastructure/domains/integration/integration-builder.test.js +593 -0
  86. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  87. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  88. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  89. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  90. package/infrastructure/domains/networking/vpc-builder.js +1829 -0
  91. package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
  92. package/infrastructure/domains/networking/vpc-discovery.js +177 -0
  93. package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
  94. package/infrastructure/domains/networking/vpc-resolver.js +324 -0
  95. package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
  96. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  97. package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
  98. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  99. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  100. package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
  101. package/infrastructure/domains/security/kms-builder.js +366 -0
  102. package/infrastructure/domains/security/kms-builder.test.js +374 -0
  103. package/infrastructure/domains/security/kms-discovery.js +80 -0
  104. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  105. package/infrastructure/domains/security/kms-resolver.js +96 -0
  106. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  107. package/infrastructure/domains/shared/base-builder.js +112 -0
  108. package/infrastructure/domains/shared/base-resolver.js +186 -0
  109. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  110. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  111. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  112. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  113. package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
  114. package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
  115. package/infrastructure/domains/shared/environment-builder.js +119 -0
  116. package/infrastructure/domains/shared/environment-builder.test.js +247 -0
  117. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
  118. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
  119. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  120. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  121. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  122. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  123. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  124. package/infrastructure/domains/shared/resource-discovery.js +192 -0
  125. package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
  126. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  127. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  128. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  129. package/infrastructure/domains/shared/types/index.js +46 -0
  130. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  131. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  132. package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
  133. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  134. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
  135. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  136. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  137. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
  138. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
  139. package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
  140. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  141. package/infrastructure/esbuild.config.js +53 -0
  142. package/infrastructure/infrastructure-composer.js +87 -0
  143. package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
  144. package/infrastructure/scripts/build-prisma-layer.js +553 -0
  145. package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
  146. package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
  147. package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
  148. package/layers/prisma/nodejs/package.json +8 -0
  149. package/management-ui/server/utils/cliIntegration.js +1 -1
  150. package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
  151. package/package.json +11 -11
  152. package/frigg-cli/.eslintrc.js +0 -141
  153. package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
  154. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
  155. package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
  156. package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
  157. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
  158. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
  159. package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
  160. package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
  161. package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
  162. package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
  163. package/frigg-cli/__tests__/utils/test-setup.js +0 -287
  164. package/frigg-cli/build-command/index.js +0 -65
  165. package/frigg-cli/db-setup-command/index.js +0 -193
  166. package/frigg-cli/deploy-command/index.js +0 -175
  167. package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
  168. package/frigg-cli/generate-command/azure-generator.js +0 -43
  169. package/frigg-cli/generate-command/gcp-generator.js +0 -47
  170. package/frigg-cli/generate-command/index.js +0 -332
  171. package/frigg-cli/generate-command/terraform-generator.js +0 -555
  172. package/frigg-cli/generate-iam-command.js +0 -118
  173. package/frigg-cli/index.js +0 -75
  174. package/frigg-cli/index.test.js +0 -158
  175. package/frigg-cli/init-command/backend-first-handler.js +0 -756
  176. package/frigg-cli/init-command/index.js +0 -93
  177. package/frigg-cli/init-command/template-handler.js +0 -143
  178. package/frigg-cli/install-command/backend-js.js +0 -33
  179. package/frigg-cli/install-command/commit-changes.js +0 -16
  180. package/frigg-cli/install-command/environment-variables.js +0 -127
  181. package/frigg-cli/install-command/environment-variables.test.js +0 -136
  182. package/frigg-cli/install-command/index.js +0 -54
  183. package/frigg-cli/install-command/install-package.js +0 -13
  184. package/frigg-cli/install-command/integration-file.js +0 -30
  185. package/frigg-cli/install-command/logger.js +0 -12
  186. package/frigg-cli/install-command/template.js +0 -90
  187. package/frigg-cli/install-command/validate-package.js +0 -75
  188. package/frigg-cli/jest.config.js +0 -124
  189. package/frigg-cli/package.json +0 -54
  190. package/frigg-cli/start-command/index.js +0 -149
  191. package/frigg-cli/start-command/start-command.test.js +0 -297
  192. package/frigg-cli/test/init-command.test.js +0 -180
  193. package/frigg-cli/test/npm-registry.test.js +0 -319
  194. package/frigg-cli/ui-command/index.js +0 -154
  195. package/frigg-cli/utils/app-resolver.js +0 -319
  196. package/frigg-cli/utils/backend-path.js +0 -25
  197. package/frigg-cli/utils/database-validator.js +0 -161
  198. package/frigg-cli/utils/error-messages.js +0 -257
  199. package/frigg-cli/utils/npm-registry.js +0 -167
  200. package/frigg-cli/utils/prisma-runner.js +0 -280
  201. package/frigg-cli/utils/process-manager.js +0 -199
  202. package/frigg-cli/utils/repo-detection.js +0 -405
  203. package/infrastructure/aws-discovery.js +0 -1176
  204. package/infrastructure/aws-discovery.test.js +0 -1220
  205. package/infrastructure/serverless-template.js +0 -2094
  206. /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
  207. /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
  208. /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
  209. /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
  210. /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
  211. /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
  212. /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
@@ -0,0 +1,305 @@
1
+ const BaseResourceResolver = require('./base-resolver');
2
+ const { ResourceOwnership } = require('./types');
3
+
4
+ describe('BaseResourceResolver', () => {
5
+ let resolver;
6
+
7
+ beforeEach(() => {
8
+ resolver = new BaseResourceResolver();
9
+ });
10
+
11
+ describe('helper methods', () => {
12
+ const mockDiscovery = {
13
+ stackManaged: [
14
+ { logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' },
15
+ { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-456', resourceType: 'AWS::EC2::SecurityGroup' }
16
+ ],
17
+ external: [
18
+ { physicalId: 'vpc-external', resourceType: 'AWS::EC2::VPC', source: 'tag-search' }
19
+ ],
20
+ fromCloudFormation: true
21
+ };
22
+
23
+ describe('findInStack', () => {
24
+ it('should find resource in stack', () => {
25
+ const resource = resolver.findInStack('FriggVPC', mockDiscovery);
26
+
27
+ expect(resource).toEqual({
28
+ logicalId: 'FriggVPC',
29
+ physicalId: 'vpc-123',
30
+ resourceType: 'AWS::EC2::VPC'
31
+ });
32
+ });
33
+
34
+ it('should return null if not found', () => {
35
+ const resource = resolver.findInStack('NonExistent', mockDiscovery);
36
+ expect(resource).toBeNull();
37
+ });
38
+ });
39
+
40
+ describe('findExternal', () => {
41
+ it('should find external resource by type', () => {
42
+ const resource = resolver.findExternal('AWS::EC2::VPC', mockDiscovery);
43
+
44
+ expect(resource).toEqual({
45
+ physicalId: 'vpc-external',
46
+ resourceType: 'AWS::EC2::VPC',
47
+ source: 'tag-search'
48
+ });
49
+ });
50
+
51
+ it('should return null if not found', () => {
52
+ const resource = resolver.findExternal('AWS::RDS::DBCluster', mockDiscovery);
53
+ expect(resource).toBeNull();
54
+ });
55
+ });
56
+
57
+ describe('isInStack', () => {
58
+ it('should return true if resource is in stack', () => {
59
+ expect(resolver.isInStack('FriggVPC', mockDiscovery)).toBe(true);
60
+ expect(resolver.isInStack('FriggLambdaSecurityGroup', mockDiscovery)).toBe(true);
61
+ });
62
+
63
+ it('should return false if resource is not in stack', () => {
64
+ expect(resolver.isInStack('NonExistent', mockDiscovery)).toBe(false);
65
+ });
66
+ });
67
+
68
+ describe('requireExternalIds', () => {
69
+ it('should not throw if IDs are provided', () => {
70
+ expect(() => resolver.requireExternalIds('vpc-123', 'vpcId')).not.toThrow();
71
+ expect(() => resolver.requireExternalIds(['sg-1', 'sg-2'], 'securityGroupIds')).not.toThrow();
72
+ });
73
+
74
+ it('should throw if IDs are missing', () => {
75
+ expect(() => resolver.requireExternalIds(undefined, 'vpcId')).toThrow(
76
+ "ownership='external' for vpcId requires external.vpcId"
77
+ );
78
+ expect(() => resolver.requireExternalIds(null, 'vpcId')).toThrow();
79
+ expect(() => resolver.requireExternalIds([], 'securityGroupIds')).toThrow();
80
+ });
81
+ });
82
+ });
83
+
84
+ describe('resolveResourceOwnership', () => {
85
+ describe('with explicit stack intent', () => {
86
+ it('should return STACK ownership', () => {
87
+ const discovery = {
88
+ stackManaged: [],
89
+ external: [{ physicalId: 'vpc-ext', resourceType: 'AWS::EC2::VPC', source: 'tag-search' }],
90
+ fromCloudFormation: false
91
+ };
92
+
93
+ const decision = resolver.resolveResourceOwnership(
94
+ 'stack',
95
+ 'FriggVPC',
96
+ 'AWS::EC2::VPC',
97
+ discovery
98
+ );
99
+
100
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
101
+ expect(decision.reason).toContain('User explicitly specified ownership=stack');
102
+ });
103
+ });
104
+
105
+ describe('with explicit external intent', () => {
106
+ it('should return EXTERNAL ownership', () => {
107
+ const discovery = {
108
+ stackManaged: [
109
+ { logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' }
110
+ ],
111
+ external: [],
112
+ fromCloudFormation: true
113
+ };
114
+
115
+ const decision = resolver.resolveResourceOwnership(
116
+ 'external',
117
+ 'FriggVPC',
118
+ 'AWS::EC2::VPC',
119
+ discovery
120
+ );
121
+
122
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
123
+ expect(decision.reason).toContain('User explicitly specified ownership=external');
124
+ });
125
+ });
126
+
127
+ describe('with auto intent', () => {
128
+ it('should return STACK if resource is in stack (CRITICAL)', () => {
129
+ const discovery = {
130
+ stackManaged: [
131
+ { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-069629001ade41c9a', resourceType: 'AWS::EC2::SecurityGroup' }
132
+ ],
133
+ external: [],
134
+ fromCloudFormation: true
135
+ };
136
+
137
+ const decision = resolver.resolveResourceOwnership(
138
+ 'auto',
139
+ 'FriggLambdaSecurityGroup',
140
+ 'AWS::EC2::SecurityGroup',
141
+ discovery
142
+ );
143
+
144
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
145
+ expect(decision.physicalId).toBe('sg-069629001ade41c9a');
146
+ expect(decision.reason).toContain('Found in CloudFormation stack');
147
+ expect(decision.reason).toContain('must keep in template to avoid deletion');
148
+ });
149
+
150
+ it('should return EXTERNAL if found externally but not in stack', () => {
151
+ const discovery = {
152
+ stackManaged: [],
153
+ external: [
154
+ { physicalId: 'vpc-external', resourceType: 'AWS::EC2::VPC', source: 'tag-search' }
155
+ ],
156
+ fromCloudFormation: false
157
+ };
158
+
159
+ const decision = resolver.resolveResourceOwnership(
160
+ 'auto',
161
+ 'FriggVPC',
162
+ 'AWS::EC2::VPC',
163
+ discovery
164
+ );
165
+
166
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
167
+ expect(decision.physicalId).toBe('vpc-external');
168
+ expect(decision.reason).toContain('Found external resource via discovery');
169
+ });
170
+
171
+ it('should return STACK if not found anywhere (create new)', () => {
172
+ const discovery = {
173
+ stackManaged: [],
174
+ external: [],
175
+ fromCloudFormation: false
176
+ };
177
+
178
+ const decision = resolver.resolveResourceOwnership(
179
+ 'auto',
180
+ 'FriggVPC',
181
+ 'AWS::EC2::VPC',
182
+ discovery
183
+ );
184
+
185
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
186
+ expect(decision.physicalId).toBeUndefined();
187
+ expect(decision.reason).toContain('No existing resource found - will create in stack');
188
+ });
189
+ });
190
+
191
+ describe('metadata', () => {
192
+ it('should include complete metadata', () => {
193
+ const discovery = {
194
+ stackManaged: [
195
+ { logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' }
196
+ ],
197
+ external: [],
198
+ fromCloudFormation: true
199
+ };
200
+
201
+ const decision = resolver.resolveResourceOwnership(
202
+ 'auto',
203
+ 'FriggVPC',
204
+ 'AWS::EC2::VPC',
205
+ discovery
206
+ );
207
+
208
+ expect(decision.metadata).toEqual({
209
+ logicalId: 'FriggVPC',
210
+ resourceType: 'AWS::EC2::VPC',
211
+ userIntent: 'auto',
212
+ inStack: true,
213
+ foundExternal: false
214
+ });
215
+ });
216
+ });
217
+ });
218
+
219
+ describe('createExternalDecision', () => {
220
+ it('should create external decision with single ID', () => {
221
+ const decision = resolver.createExternalDecision('vpc-external');
222
+
223
+ expect(decision).toEqual({
224
+ ownership: ResourceOwnership.EXTERNAL,
225
+ physicalId: 'vpc-external',
226
+ physicalIds: ['vpc-external'],
227
+ reason: 'Using external resource reference',
228
+ metadata: {
229
+ source: 'user-provided'
230
+ }
231
+ });
232
+ });
233
+
234
+ it('should create external decision with multiple IDs', () => {
235
+ const decision = resolver.createExternalDecision(['sg-1', 'sg-2'], 'Custom reason');
236
+
237
+ expect(decision).toEqual({
238
+ ownership: ResourceOwnership.EXTERNAL,
239
+ physicalId: 'sg-1',
240
+ physicalIds: ['sg-1', 'sg-2'],
241
+ reason: 'Custom reason',
242
+ metadata: {
243
+ source: 'user-provided'
244
+ }
245
+ });
246
+ });
247
+ });
248
+
249
+ describe('createStackDecision', () => {
250
+ it('should create stack decision for new resource', () => {
251
+ const decision = resolver.createStackDecision();
252
+
253
+ expect(decision).toEqual({
254
+ ownership: ResourceOwnership.STACK,
255
+ physicalId: null,
256
+ reason: 'Managed by CloudFormation stack',
257
+ metadata: {
258
+ source: 'new'
259
+ }
260
+ });
261
+ });
262
+
263
+ it('should create stack decision for existing resource', () => {
264
+ const decision = resolver.createStackDecision('vpc-123', 'Found in stack');
265
+
266
+ expect(decision).toEqual({
267
+ ownership: ResourceOwnership.STACK,
268
+ physicalId: 'vpc-123',
269
+ reason: 'Found in stack',
270
+ metadata: {
271
+ source: 'discovered'
272
+ }
273
+ });
274
+ });
275
+ });
276
+
277
+ describe('backwards compatibility with flat discovery', () => {
278
+ it('should work with old flat discovery structure', () => {
279
+ const flatDiscovery = {
280
+ fromCloudFormationStack: true,
281
+ existingLogicalIds: ['FriggVPC', 'FriggLambdaSecurityGroup'],
282
+ defaultVpcId: 'vpc-123',
283
+ securityGroupId: 'sg-456',
284
+ _structured: {
285
+ stackManaged: [
286
+ { logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' },
287
+ { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-456', resourceType: 'AWS::EC2::SecurityGroup' }
288
+ ],
289
+ external: [],
290
+ fromCloudFormation: true
291
+ }
292
+ };
293
+
294
+ const decision = resolver.resolveResourceOwnership(
295
+ 'auto',
296
+ 'FriggLambdaSecurityGroup',
297
+ 'AWS::EC2::SecurityGroup',
298
+ flatDiscovery
299
+ );
300
+
301
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
302
+ expect(decision.physicalId).toBe('sg-456');
303
+ });
304
+ });
305
+ });
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Infrastructure Builder Orchestrator
3
+ *
4
+ * Application Layer - Hexagonal Architecture
5
+ *
6
+ * Orchestrates the execution of all infrastructure builders with:
7
+ * - Dependency resolution
8
+ * - Parallel execution where possible
9
+ * - Error handling and validation
10
+ * - Progress reporting
11
+ */
12
+
13
+ const { gatherDiscoveredResources } = require('./resource-discovery');
14
+ const { getAppEnvironmentVars, buildEnvironment } = require('./environment-builder');
15
+
16
+ class BuilderOrchestrator {
17
+ constructor(builders = []) {
18
+ this.builders = new Map();
19
+ builders.forEach(builder => this.registerBuilder(builder));
20
+ }
21
+
22
+ /**
23
+ * Register a builder
24
+ */
25
+ registerBuilder(builder) {
26
+ this.builders.set(builder.getName(), builder);
27
+ }
28
+
29
+ /**
30
+ * Validate all applicable builders
31
+ */
32
+ async validateAll(appDefinition) {
33
+ console.log('\n🔍 Validating infrastructure configuration...');
34
+
35
+ const validationResults = [];
36
+ let hasErrors = false;
37
+
38
+ for (const [name, builder] of this.builders) {
39
+ if (builder.shouldExecute(appDefinition)) {
40
+ const result = builder.validate(appDefinition);
41
+ validationResults.push({ builder: name, result });
42
+
43
+ if (result.hasErrors()) {
44
+ hasErrors = true;
45
+ console.log(`❌ ${name} validation failed:`);
46
+ result.errors.forEach(error => console.log(` - ${error}`));
47
+ }
48
+
49
+ if (result.hasWarnings()) {
50
+ console.log(`⚠️ ${name} warnings:`);
51
+ result.warnings.forEach(warning => console.log(` - ${warning}`));
52
+ }
53
+ }
54
+ }
55
+
56
+ if (hasErrors) {
57
+ throw new Error('Infrastructure validation failed. See errors above.');
58
+ }
59
+
60
+ console.log('✅ All infrastructure validation passed\n');
61
+ return validationResults;
62
+ }
63
+
64
+ /**
65
+ * Resolve builder execution order based on dependencies
66
+ */
67
+ resolveBuildOrder(appDefinition) {
68
+ const executionOrder = [];
69
+ const visited = new Set();
70
+ const visiting = new Set();
71
+
72
+ const visit = (builderName) => {
73
+ if (visited.has(builderName)) return;
74
+ if (visiting.has(builderName)) {
75
+ throw new Error(`Circular dependency detected: ${builderName}`);
76
+ }
77
+
78
+ const builder = this.builders.get(builderName);
79
+ if (!builder || !builder.shouldExecute(appDefinition)) {
80
+ return;
81
+ }
82
+
83
+ visiting.add(builderName);
84
+
85
+ // Visit dependencies first
86
+ const dependencies = builder.getDependencies() || [];
87
+ dependencies.forEach(dep => visit(dep));
88
+
89
+ visiting.delete(builderName);
90
+ visited.add(builderName);
91
+ executionOrder.push(builderName);
92
+ };
93
+
94
+ // Visit all builders
95
+ for (const [builderName] of this.builders) {
96
+ visit(builderName);
97
+ }
98
+
99
+ return executionOrder;
100
+ }
101
+
102
+ /**
103
+ * Build all infrastructure
104
+ */
105
+ async buildAll(appDefinition) {
106
+ console.log('\n🏗️ Building infrastructure...');
107
+
108
+ // Step 1: Validate configuration
109
+ await this.validateAll(appDefinition);
110
+
111
+ // Step 2: Discover AWS resources
112
+ const discoveredResources = await gatherDiscoveredResources(appDefinition);
113
+
114
+ // Step 3: Resolve build order
115
+ const buildOrder = this.resolveBuildOrder(appDefinition);
116
+ console.log(`📋 Build order: ${buildOrder.join(' → ')}\n`);
117
+
118
+ // Step 4: Execute builders in order
119
+ const buildResults = {};
120
+
121
+ for (const builderName of buildOrder) {
122
+ const builder = this.builders.get(builderName);
123
+ try {
124
+ const result = await builder.build(appDefinition, discoveredResources);
125
+ buildResults[builderName] = result;
126
+ } catch (error) {
127
+ console.error(`❌ ${builderName} build failed:`, error.message);
128
+ throw error;
129
+ }
130
+ }
131
+
132
+ // Step 5: Merge results
133
+ return this.mergeResults(buildResults, appDefinition, discoveredResources);
134
+ }
135
+
136
+ /**
137
+ * Merge builder results into cohesive definition
138
+ */
139
+ mergeResults(buildResults, appDefinition, discoveredResources) {
140
+ console.log('\n🔗 Merging infrastructure results...');
141
+
142
+ const merged = {
143
+ resources: {},
144
+ iamStatements: [],
145
+ environment: {},
146
+ functions: {},
147
+ layers: {},
148
+ plugins: [],
149
+ custom: {},
150
+ vpcConfig: null,
151
+ };
152
+
153
+ // Merge results from each builder
154
+ for (const [builderName, result] of Object.entries(buildResults)) {
155
+ // Merge resources
156
+ if (result.resources) {
157
+ Object.assign(merged.resources, result.resources);
158
+ }
159
+
160
+ // Merge IAM statements
161
+ if (result.iamStatements) {
162
+ merged.iamStatements.push(...result.iamStatements);
163
+ }
164
+
165
+ // Merge environment variables
166
+ if (result.environment) {
167
+ Object.assign(merged.environment, result.environment);
168
+ }
169
+
170
+ // Merge functions
171
+ if (result.functions) {
172
+ Object.assign(merged.functions, result.functions);
173
+ }
174
+
175
+ // Merge layers
176
+ if (result.layers) {
177
+ Object.assign(merged.layers, result.layers);
178
+ }
179
+
180
+ // Merge plugins
181
+ if (result.plugins) {
182
+ merged.plugins.push(...result.plugins);
183
+ }
184
+
185
+ // Merge custom configuration
186
+ if (result.pluginConfig) {
187
+ Object.assign(merged.custom, result.pluginConfig);
188
+ }
189
+ if (result.custom) {
190
+ Object.assign(merged.custom, result.custom);
191
+ }
192
+
193
+ // Capture VPC config (from VpcBuilder)
194
+ if (result.vpcConfig) {
195
+ merged.vpcConfig = result.vpcConfig;
196
+ }
197
+
198
+ console.log(` ✓ Merged ${builderName} results`);
199
+ }
200
+
201
+ console.log('✅ Infrastructure build completed successfully\n');
202
+
203
+ return {
204
+ merged,
205
+ discoveredResources,
206
+ appEnvironmentVars: getAppEnvironmentVars(appDefinition),
207
+ };
208
+ }
209
+ }
210
+
211
+ module.exports = { BuilderOrchestrator };
212
+