@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,324 @@
1
+ /**
2
+ * VPC Resource Resolver
3
+ *
4
+ * Resolves ownership for all VPC-related resources:
5
+ * - VPC
6
+ * - Security Group
7
+ * - Subnets
8
+ * - NAT Gateway
9
+ * - VPC Endpoints
10
+ *
11
+ * Concrete implementation of BaseResourceResolver for VPC domain.
12
+ */
13
+
14
+ const BaseResourceResolver = require('../shared/base-resolver');
15
+ const { ResourceOwnership } = require('../shared/types');
16
+
17
+ class VpcResourceResolver extends BaseResourceResolver {
18
+ /**
19
+ * Resolve VPC ownership
20
+ * @param {Object} appDefinition - App definition
21
+ * @param {Object} discovery - Discovery result
22
+ * @returns {Object} Resource decision
23
+ */
24
+ resolveVpc(appDefinition, discovery) {
25
+ const userIntent = appDefinition.vpc?.ownership?.vpc || 'auto';
26
+
27
+ // Explicit external
28
+ if (userIntent === 'external') {
29
+ this.requireExternalIds(appDefinition.vpc?.external?.vpcId, 'vpcId');
30
+ return this.createExternalDecision(
31
+ appDefinition.vpc.external.vpcId,
32
+ 'User specified ownership=external for VPC'
33
+ );
34
+ }
35
+
36
+ // Explicit stack
37
+ if (userIntent === 'stack') {
38
+ const inStack = this.findInStack('FriggVPC', discovery);
39
+ return this.createStackDecision(
40
+ inStack?.physicalId,
41
+ 'User specified ownership=stack for VPC'
42
+ );
43
+ }
44
+
45
+ // Auto-decide
46
+ return this.resolveResourceOwnership(
47
+ 'auto',
48
+ 'FriggVPC',
49
+ 'AWS::EC2::VPC',
50
+ discovery
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Resolve Security Group ownership
56
+ *
57
+ * Special logic: We ALWAYS create our own FriggLambdaSecurityGroup with specific
58
+ * rules unless the user explicitly provides external SG IDs. The discovered
59
+ * defaultSecurityGroupId is the VPC's default SG, but we need our own Lambda SG.
60
+ *
61
+ * @param {Object} appDefinition - App definition
62
+ * @param {Object} discovery - Discovery result
63
+ * @returns {Object} Resource decision
64
+ */
65
+ resolveSecurityGroup(appDefinition, discovery) {
66
+ const userIntent = appDefinition.vpc?.ownership?.securityGroup || 'auto';
67
+
68
+ // Explicit external - only use external SGs if user explicitly provides them
69
+ if (userIntent === 'external') {
70
+ this.requireExternalIds(
71
+ appDefinition.vpc?.external?.securityGroupIds,
72
+ 'securityGroupIds'
73
+ );
74
+ return this.createExternalDecision(
75
+ appDefinition.vpc.external.securityGroupIds,
76
+ 'User specified ownership=external for security group'
77
+ );
78
+ }
79
+
80
+ // For stack or auto: check if FriggLambdaSecurityGroup exists in stack
81
+ // If it does, reuse it. If not, create it. Never use discovered default SG.
82
+ const inStack = this.findInStack('FriggLambdaSecurityGroup', discovery);
83
+
84
+ if (inStack) {
85
+ return this.createStackDecision(
86
+ inStack.physicalId,
87
+ 'Found FriggLambdaSecurityGroup in CloudFormation stack'
88
+ );
89
+ }
90
+
91
+ // Create new FriggLambdaSecurityGroup in stack
92
+ return this.createStackDecision(
93
+ null,
94
+ 'No existing FriggLambdaSecurityGroup - will create in stack'
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Resolve Subnets ownership
100
+ * @param {Object} appDefinition - App definition
101
+ * @param {Object} discovery - Discovery result
102
+ * @returns {Object} Resource decision
103
+ */
104
+ resolveSubnets(appDefinition, discovery) {
105
+ const userIntent = appDefinition.vpc?.ownership?.subnets || 'auto';
106
+
107
+ // Explicit external
108
+ if (userIntent === 'external') {
109
+ this.requireExternalIds(
110
+ appDefinition.vpc?.external?.subnetIds,
111
+ 'subnetIds'
112
+ );
113
+ return this.createExternalDecision(
114
+ appDefinition.vpc.external.subnetIds,
115
+ 'User specified ownership=external for subnets'
116
+ );
117
+ }
118
+
119
+ // Explicit stack
120
+ if (userIntent === 'stack') {
121
+ // Check if we have subnets in stack
122
+ const subnet1 = this.findInStack('FriggPrivateSubnet1', discovery);
123
+ const subnet2 = this.findInStack('FriggPrivateSubnet2', discovery);
124
+
125
+ if (subnet1 && subnet2) {
126
+ return {
127
+ ownership: ResourceOwnership.STACK,
128
+ physicalIds: [subnet1.physicalId, subnet2.physicalId],
129
+ reason: 'User specified ownership=stack for subnets',
130
+ metadata: {
131
+ subnet1: subnet1.physicalId,
132
+ subnet2: subnet2.physicalId
133
+ }
134
+ };
135
+ }
136
+
137
+ return this.createStackDecision(
138
+ null,
139
+ 'User specified ownership=stack for subnets (will create new)'
140
+ );
141
+ }
142
+
143
+ // Auto-decide
144
+ const subnet1InStack = this.isInStack('FriggPrivateSubnet1', discovery);
145
+ const subnet2InStack = this.isInStack('FriggPrivateSubnet2', discovery);
146
+
147
+ if (subnet1InStack && subnet2InStack) {
148
+ const subnet1 = this.findInStack('FriggPrivateSubnet1', discovery);
149
+ const subnet2 = this.findInStack('FriggPrivateSubnet2', discovery);
150
+
151
+ return {
152
+ ownership: ResourceOwnership.STACK,
153
+ physicalIds: [subnet1.physicalId, subnet2.physicalId],
154
+ reason: 'Found subnets in CloudFormation stack (must keep in template to avoid deletion)',
155
+ metadata: {
156
+ subnet1: subnet1.physicalId,
157
+ subnet2: subnet2.physicalId
158
+ }
159
+ };
160
+ }
161
+
162
+ // Check for external subnets
163
+ const externalSubnets = this.findAllExternalResources(discovery, 'AWS::EC2::Subnet');
164
+ if (externalSubnets.length >= 2) {
165
+ return {
166
+ ownership: ResourceOwnership.EXTERNAL,
167
+ physicalIds: externalSubnets.slice(0, 2).map(s => s.physicalId),
168
+ reason: 'Found external subnets via discovery',
169
+ metadata: {
170
+ count: externalSubnets.length
171
+ }
172
+ };
173
+ }
174
+
175
+ // Create new
176
+ return this.createStackDecision(
177
+ null,
178
+ 'No existing subnets found - will create in stack'
179
+ );
180
+ }
181
+
182
+ /**
183
+ * Resolve NAT Gateway ownership
184
+ * @param {Object} appDefinition - App definition
185
+ * @param {Object} discovery - Discovery result
186
+ * @returns {Object} Resource decision
187
+ */
188
+ resolveNatGateway(appDefinition, discovery) {
189
+ const userIntent = appDefinition.vpc?.ownership?.natGateway || 'auto';
190
+ const natEnabled = appDefinition.vpc?.config?.natGateway?.enable !== false;
191
+
192
+ // If NAT is disabled, return null decision
193
+ if (!natEnabled) {
194
+ return {
195
+ ownership: null,
196
+ reason: 'NAT Gateway disabled in configuration',
197
+ metadata: { disabled: true }
198
+ };
199
+ }
200
+
201
+ // Explicit external
202
+ if (userIntent === 'external') {
203
+ this.requireExternalIds(
204
+ appDefinition.vpc?.external?.natGatewayId,
205
+ 'natGatewayId'
206
+ );
207
+ return this.createExternalDecision(
208
+ appDefinition.vpc.external.natGatewayId,
209
+ 'User specified ownership=external for NAT gateway'
210
+ );
211
+ }
212
+
213
+ // Explicit stack
214
+ if (userIntent === 'stack') {
215
+ const inStack = this.findInStack('FriggNatGateway', discovery);
216
+ return this.createStackDecision(
217
+ inStack?.physicalId,
218
+ 'User specified ownership=stack for NAT gateway'
219
+ );
220
+ }
221
+
222
+ // Auto-decide
223
+ return this.resolveResourceOwnership(
224
+ 'auto',
225
+ 'FriggNatGateway',
226
+ 'AWS::EC2::NatGateway',
227
+ discovery
228
+ );
229
+ }
230
+
231
+ /**
232
+ * Resolve VPC Endpoints ownership
233
+ * @param {Object} appDefinition - App definition
234
+ * @param {Object} discovery - Discovery result
235
+ * @returns {Object} Resource decision for each endpoint type
236
+ */
237
+ resolveVpcEndpoints(appDefinition, discovery) {
238
+ const userIntent = appDefinition.vpc?.ownership?.vpcEndpoints || 'auto';
239
+ const endpointsEnabled = appDefinition.vpc?.config?.enableVpcEndpoints !== false;
240
+
241
+ // If endpoints disabled, return null decision
242
+ if (!endpointsEnabled) {
243
+ return {
244
+ s3: { ownership: null, reason: 'VPC Endpoints disabled' },
245
+ dynamodb: { ownership: null, reason: 'VPC Endpoints disabled' },
246
+ kms: { ownership: null, reason: 'VPC Endpoints disabled' },
247
+ secretsManager: { ownership: null, reason: 'VPC Endpoints disabled' },
248
+ sqs: { ownership: null, reason: 'VPC Endpoints disabled' }
249
+ };
250
+ }
251
+
252
+ // KMS endpoint only needed if encryption method is KMS
253
+ const encryptionMethod = appDefinition.encryption?.fieldLevelEncryptionMethod;
254
+ const needsKms = encryptionMethod === 'kms';
255
+
256
+ const endpoints = {
257
+ s3: this._resolveEndpoint('FriggS3VPCEndpoint', 's3', userIntent, appDefinition, discovery),
258
+ dynamodb: this._resolveEndpoint('FriggDynamoDBVPCEndpoint', 'dynamodb', userIntent, appDefinition, discovery),
259
+ kms: needsKms
260
+ ? this._resolveEndpoint('FriggKMSVPCEndpoint', 'kms', userIntent, appDefinition, discovery)
261
+ : { ownership: null, reason: 'KMS endpoint not needed (encryption method is not KMS)' },
262
+ secretsManager: this._resolveEndpoint('FriggSecretsManagerVPCEndpoint', 'secretsManager', userIntent, appDefinition, discovery),
263
+ sqs: this._resolveEndpoint('FriggSQSVPCEndpoint', 'sqs', userIntent, appDefinition, discovery)
264
+ };
265
+
266
+ return endpoints;
267
+ }
268
+
269
+ /**
270
+ * Resolve individual VPC endpoint
271
+ * @private
272
+ */
273
+ _resolveEndpoint(logicalId, endpointType, userIntent, appDefinition, discovery) {
274
+ // Explicit external
275
+ if (userIntent === 'external') {
276
+ const externalId = appDefinition.vpc?.external?.vpcEndpointIds?.[endpointType];
277
+ if (externalId) {
278
+ return this.createExternalDecision(
279
+ externalId,
280
+ `User specified ownership=external for ${endpointType} endpoint`
281
+ );
282
+ }
283
+ // User said external but didn't provide ID - skip this endpoint
284
+ return { ownership: null, reason: `External ${endpointType} endpoint ID not provided` };
285
+ }
286
+
287
+ // Explicit stack
288
+ if (userIntent === 'stack') {
289
+ const inStack = this.findInStack(logicalId, discovery);
290
+ return this.createStackDecision(
291
+ inStack?.physicalId,
292
+ `User specified ownership=stack for ${endpointType} endpoint`
293
+ );
294
+ }
295
+
296
+ // Auto-decide
297
+ return this.resolveResourceOwnership(
298
+ 'auto',
299
+ logicalId,
300
+ 'AWS::EC2::VPCEndpoint',
301
+ discovery
302
+ );
303
+ }
304
+
305
+ /**
306
+ * Resolve all VPC resources at once
307
+ * Convenience method that returns decisions for all VPC resources
308
+ *
309
+ * @param {Object} appDefinition - App definition
310
+ * @param {Object} discovery - Discovery result
311
+ * @returns {Object} Decisions for all VPC resources
312
+ */
313
+ resolveAll(appDefinition, discovery) {
314
+ return {
315
+ vpc: this.resolveVpc(appDefinition, discovery),
316
+ securityGroup: this.resolveSecurityGroup(appDefinition, discovery),
317
+ subnets: this.resolveSubnets(appDefinition, discovery),
318
+ natGateway: this.resolveNatGateway(appDefinition, discovery),
319
+ vpcEndpoints: this.resolveVpcEndpoints(appDefinition, discovery)
320
+ };
321
+ }
322
+ }
323
+
324
+ module.exports = VpcResourceResolver;