@friggframework/devtools 2.0.0-next.6 → 2.0.0-next.60

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 (357) hide show
  1. package/frigg-cli/README.md +1289 -0
  2. package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
  3. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +649 -0
  4. package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
  5. package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
  6. package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
  7. package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
  8. package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
  9. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +397 -0
  10. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +345 -0
  11. package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
  12. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  13. package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
  14. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  15. package/frigg-cli/__tests__/utils/test-setup.js +287 -0
  16. package/frigg-cli/build-command/index.js +53 -14
  17. package/frigg-cli/db-setup-command/index.js +246 -0
  18. package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
  19. package/frigg-cli/deploy-command/index.js +295 -17
  20. package/frigg-cli/doctor-command/index.js +335 -0
  21. package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
  22. package/frigg-cli/generate-command/azure-generator.js +43 -0
  23. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  24. package/frigg-cli/generate-command/index.js +332 -0
  25. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  26. package/frigg-cli/generate-iam-command.js +118 -0
  27. package/frigg-cli/index.js +142 -1
  28. package/frigg-cli/index.test.js +1 -4
  29. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  30. package/frigg-cli/init-command/index.js +93 -0
  31. package/frigg-cli/init-command/template-handler.js +143 -0
  32. package/frigg-cli/install-command/index.js +1 -4
  33. package/frigg-cli/jest.config.js +124 -0
  34. package/frigg-cli/package.json +63 -0
  35. package/frigg-cli/repair-command/index.js +564 -0
  36. package/frigg-cli/start-command/index.js +125 -6
  37. package/frigg-cli/start-command/start-command.test.js +297 -0
  38. package/frigg-cli/test/init-command.test.js +180 -0
  39. package/frigg-cli/test/npm-registry.test.js +319 -0
  40. package/frigg-cli/ui-command/index.js +154 -0
  41. package/frigg-cli/utils/app-resolver.js +319 -0
  42. package/frigg-cli/utils/backend-path.js +16 -17
  43. package/frigg-cli/utils/database-validator.js +167 -0
  44. package/frigg-cli/utils/error-messages.js +329 -0
  45. package/frigg-cli/utils/npm-registry.js +167 -0
  46. package/frigg-cli/utils/process-manager.js +199 -0
  47. package/frigg-cli/utils/repo-detection.js +405 -0
  48. package/infrastructure/ARCHITECTURE.md +487 -0
  49. package/infrastructure/CLAUDE.md +481 -0
  50. package/infrastructure/HEALTH.md +468 -0
  51. package/infrastructure/README.md +522 -0
  52. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  53. package/infrastructure/__tests__/helpers/test-utils.js +277 -0
  54. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  55. package/infrastructure/__tests__/template-generation.test.js +687 -0
  56. package/infrastructure/create-frigg-infrastructure.js +129 -20
  57. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  58. package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
  59. package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +105 -0
  60. package/infrastructure/docs/deployment-instructions.md +268 -0
  61. package/infrastructure/docs/generate-iam-command.md +278 -0
  62. package/infrastructure/docs/iam-policy-templates.md +193 -0
  63. package/infrastructure/domains/database/aurora-builder.js +809 -0
  64. package/infrastructure/domains/database/aurora-builder.test.js +950 -0
  65. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  66. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  67. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  68. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  69. package/infrastructure/domains/database/migration-builder.js +701 -0
  70. package/infrastructure/domains/database/migration-builder.test.js +321 -0
  71. package/infrastructure/domains/database/migration-resolver.js +163 -0
  72. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  73. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  74. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  75. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  76. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  77. package/infrastructure/domains/health/application/ports/index.js +26 -0
  78. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  79. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  80. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  81. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  82. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  83. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  84. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  85. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  86. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  87. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  88. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  89. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  90. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  91. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  92. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  93. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  94. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  95. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  96. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  97. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  98. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  99. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  100. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  101. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  102. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  103. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  104. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  105. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  106. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  107. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  108. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  109. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  110. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  111. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  112. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  113. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  114. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  115. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  116. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  117. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  118. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  119. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  120. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  121. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  122. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  123. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  124. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  125. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  126. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  127. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  128. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  129. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  130. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  131. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  132. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  133. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  134. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  135. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  136. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  137. package/infrastructure/domains/integration/integration-builder.js +404 -0
  138. package/infrastructure/domains/integration/integration-builder.test.js +690 -0
  139. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  140. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  141. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  142. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  143. package/infrastructure/domains/networking/vpc-builder.js +2051 -0
  144. package/infrastructure/domains/networking/vpc-builder.test.js +1960 -0
  145. package/infrastructure/domains/networking/vpc-discovery.js +177 -0
  146. package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
  147. package/infrastructure/domains/networking/vpc-resolver.js +505 -0
  148. package/infrastructure/domains/networking/vpc-resolver.test.js +801 -0
  149. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  150. package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
  151. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  152. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  153. package/infrastructure/domains/security/iam-generator.js +816 -0
  154. package/infrastructure/domains/security/iam-generator.test.js +204 -0
  155. package/infrastructure/domains/security/kms-builder.js +415 -0
  156. package/infrastructure/domains/security/kms-builder.test.js +392 -0
  157. package/infrastructure/domains/security/kms-discovery.js +80 -0
  158. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  159. package/infrastructure/domains/security/kms-resolver.js +96 -0
  160. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  161. package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +401 -0
  162. package/infrastructure/domains/security/templates/iam-policy-basic.json +218 -0
  163. package/infrastructure/domains/security/templates/iam-policy-full.json +288 -0
  164. package/infrastructure/domains/shared/base-builder.js +112 -0
  165. package/infrastructure/domains/shared/base-resolver.js +186 -0
  166. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  167. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  168. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  169. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  170. package/infrastructure/domains/shared/cloudformation-discovery.js +672 -0
  171. package/infrastructure/domains/shared/cloudformation-discovery.test.js +985 -0
  172. package/infrastructure/domains/shared/environment-builder.js +119 -0
  173. package/infrastructure/domains/shared/environment-builder.test.js +247 -0
  174. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +579 -0
  175. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +416 -0
  176. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  177. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  178. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  179. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  180. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  181. package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
  182. package/infrastructure/domains/shared/resource-discovery.js +233 -0
  183. package/infrastructure/domains/shared/resource-discovery.test.js +588 -0
  184. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  185. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  186. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  187. package/infrastructure/domains/shared/types/index.js +46 -0
  188. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  189. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  190. package/infrastructure/domains/shared/utilities/base-definition-factory.js +394 -0
  191. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  192. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +291 -0
  193. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  194. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  195. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +159 -0
  196. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +444 -0
  197. package/infrastructure/domains/shared/validation/env-validator.js +78 -0
  198. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  199. package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
  200. package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
  201. package/infrastructure/esbuild.config.js +53 -0
  202. package/infrastructure/infrastructure-composer.js +117 -0
  203. package/infrastructure/infrastructure-composer.test.js +1895 -0
  204. package/infrastructure/integration.test.js +383 -0
  205. package/infrastructure/scripts/build-prisma-layer.js +701 -0
  206. package/infrastructure/scripts/build-prisma-layer.test.js +170 -0
  207. package/infrastructure/scripts/build-time-discovery.js +238 -0
  208. package/infrastructure/scripts/build-time-discovery.test.js +379 -0
  209. package/infrastructure/scripts/run-discovery.js +110 -0
  210. package/infrastructure/scripts/verify-prisma-layer.js +72 -0
  211. package/layers/prisma/.build-complete +3 -0
  212. package/layers/prisma/nodejs/package.json +8 -0
  213. package/management-ui/.eslintrc.js +22 -0
  214. package/management-ui/README.md +203 -0
  215. package/management-ui/components.json +21 -0
  216. package/management-ui/docs/phase2-integration-guide.md +320 -0
  217. package/management-ui/index.html +13 -0
  218. package/management-ui/package.json +76 -0
  219. package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
  220. package/management-ui/postcss.config.js +6 -0
  221. package/management-ui/server/api/backend.js +256 -0
  222. package/management-ui/server/api/cli.js +315 -0
  223. package/management-ui/server/api/codegen.js +663 -0
  224. package/management-ui/server/api/connections.js +857 -0
  225. package/management-ui/server/api/discovery.js +185 -0
  226. package/management-ui/server/api/environment/index.js +1 -0
  227. package/management-ui/server/api/environment/router.js +378 -0
  228. package/management-ui/server/api/environment.js +328 -0
  229. package/management-ui/server/api/integrations.js +876 -0
  230. package/management-ui/server/api/logs.js +248 -0
  231. package/management-ui/server/api/monitoring.js +282 -0
  232. package/management-ui/server/api/open-ide.js +31 -0
  233. package/management-ui/server/api/project.js +1029 -0
  234. package/management-ui/server/api/users/sessions.js +371 -0
  235. package/management-ui/server/api/users/simulation.js +254 -0
  236. package/management-ui/server/api/users.js +362 -0
  237. package/management-ui/server/api-contract.md +275 -0
  238. package/management-ui/server/index.js +873 -0
  239. package/management-ui/server/middleware/errorHandler.js +93 -0
  240. package/management-ui/server/middleware/security.js +32 -0
  241. package/management-ui/server/processManager.js +296 -0
  242. package/management-ui/server/server.js +346 -0
  243. package/management-ui/server/services/aws-monitor.js +413 -0
  244. package/management-ui/server/services/npm-registry.js +347 -0
  245. package/management-ui/server/services/template-engine.js +538 -0
  246. package/management-ui/server/utils/cliIntegration.js +220 -0
  247. package/management-ui/server/utils/environment/auditLogger.js +471 -0
  248. package/management-ui/server/utils/environment/awsParameterStore.js +275 -0
  249. package/management-ui/server/utils/environment/encryption.js +278 -0
  250. package/management-ui/server/utils/environment/envFileManager.js +286 -0
  251. package/management-ui/server/utils/import-commonjs.js +28 -0
  252. package/management-ui/server/utils/response.js +83 -0
  253. package/management-ui/server/websocket/handler.js +325 -0
  254. package/management-ui/src/App.jsx +25 -0
  255. package/management-ui/src/assets/FriggLogo.svg +1 -0
  256. package/management-ui/src/components/AppRouter.jsx +65 -0
  257. package/management-ui/src/components/Button.jsx +70 -0
  258. package/management-ui/src/components/Card.jsx +97 -0
  259. package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
  260. package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
  261. package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
  262. package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
  263. package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
  264. package/management-ui/src/components/ErrorBoundary.jsx +73 -0
  265. package/management-ui/src/components/IntegrationCard.jsx +481 -0
  266. package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
  267. package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
  268. package/management-ui/src/components/IntegrationStatus.jsx +336 -0
  269. package/management-ui/src/components/Layout.jsx +716 -0
  270. package/management-ui/src/components/LoadingSpinner.jsx +113 -0
  271. package/management-ui/src/components/RepositoryPicker.jsx +248 -0
  272. package/management-ui/src/components/SessionMonitor.jsx +350 -0
  273. package/management-ui/src/components/StatusBadge.jsx +208 -0
  274. package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
  275. package/management-ui/src/components/UserSimulation.jsx +327 -0
  276. package/management-ui/src/components/Welcome.jsx +434 -0
  277. package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
  278. package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
  279. package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
  280. package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
  281. package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
  282. package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
  283. package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
  284. package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
  285. package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
  286. package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
  287. package/management-ui/src/components/codegen/index.js +10 -0
  288. package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
  289. package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
  290. package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
  291. package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
  292. package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
  293. package/management-ui/src/components/connections/index.js +5 -0
  294. package/management-ui/src/components/index.js +21 -0
  295. package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
  296. package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
  297. package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
  298. package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
  299. package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
  300. package/management-ui/src/components/monitoring/index.js +6 -0
  301. package/management-ui/src/components/monitoring/monitoring.css +218 -0
  302. package/management-ui/src/components/theme-provider.jsx +52 -0
  303. package/management-ui/src/components/theme-toggle.jsx +39 -0
  304. package/management-ui/src/components/ui/badge.tsx +36 -0
  305. package/management-ui/src/components/ui/button.test.jsx +56 -0
  306. package/management-ui/src/components/ui/button.tsx +57 -0
  307. package/management-ui/src/components/ui/card.tsx +76 -0
  308. package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
  309. package/management-ui/src/components/ui/select.tsx +157 -0
  310. package/management-ui/src/components/ui/skeleton.jsx +15 -0
  311. package/management-ui/src/hooks/useFrigg.jsx +387 -0
  312. package/management-ui/src/hooks/useSocket.jsx +58 -0
  313. package/management-ui/src/index.css +193 -0
  314. package/management-ui/src/lib/utils.ts +6 -0
  315. package/management-ui/src/main.jsx +10 -0
  316. package/management-ui/src/pages/CodeGeneration.jsx +14 -0
  317. package/management-ui/src/pages/Connections.jsx +252 -0
  318. package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
  319. package/management-ui/src/pages/Dashboard.jsx +311 -0
  320. package/management-ui/src/pages/Environment.jsx +314 -0
  321. package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
  322. package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
  323. package/management-ui/src/pages/IntegrationTest.jsx +742 -0
  324. package/management-ui/src/pages/Integrations.jsx +253 -0
  325. package/management-ui/src/pages/Monitoring.jsx +17 -0
  326. package/management-ui/src/pages/Simulation.jsx +155 -0
  327. package/management-ui/src/pages/Users.jsx +492 -0
  328. package/management-ui/src/services/api.js +41 -0
  329. package/management-ui/src/services/apiModuleService.js +193 -0
  330. package/management-ui/src/services/websocket-handlers.js +120 -0
  331. package/management-ui/src/test/api/project.test.js +273 -0
  332. package/management-ui/src/test/components/Welcome.test.jsx +378 -0
  333. package/management-ui/src/test/mocks/server.js +178 -0
  334. package/management-ui/src/test/setup.js +61 -0
  335. package/management-ui/src/test/utils/test-utils.jsx +134 -0
  336. package/management-ui/src/utils/repository.js +98 -0
  337. package/management-ui/src/utils/repository.test.js +118 -0
  338. package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
  339. package/management-ui/tailwind.config.js +63 -0
  340. package/management-ui/tsconfig.json +37 -0
  341. package/management-ui/tsconfig.node.json +10 -0
  342. package/management-ui/vite.config.js +26 -0
  343. package/management-ui/vitest.config.js +38 -0
  344. package/package.json +35 -14
  345. package/test/index.js +2 -4
  346. package/test/mock-integration.js +4 -14
  347. package/infrastructure/app-handler-helpers.js +0 -57
  348. package/infrastructure/backend-utils.js +0 -87
  349. package/infrastructure/routers/auth.js +0 -26
  350. package/infrastructure/routers/integration-defined-routers.js +0 -42
  351. package/infrastructure/routers/middleware/loadUser.js +0 -15
  352. package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
  353. package/infrastructure/routers/user.js +0 -41
  354. package/infrastructure/routers/websocket.js +0 -55
  355. package/infrastructure/serverless-template.js +0 -291
  356. package/infrastructure/workers/integration-defined-workers.js +0 -24
  357. package/test/auther-definition-tester.js +0 -125
@@ -0,0 +1,801 @@
1
+ const VpcResourceResolver = require('./vpc-resolver');
2
+ const { ResourceOwnership } = require('../shared/types');
3
+
4
+ describe('VpcResourceResolver', () => {
5
+ let resolver;
6
+
7
+ beforeEach(() => {
8
+ resolver = new VpcResourceResolver();
9
+ });
10
+
11
+ describe('resolveVpc', () => {
12
+ it('should resolve to EXTERNAL with hardcoded vpcId', () => {
13
+ const appDefinition = {
14
+ vpc: {
15
+ ownership: { vpc: 'external' },
16
+ external: { vpcId: 'vpc-external-123' }
17
+ }
18
+ };
19
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
20
+
21
+ const decision = resolver.resolveVpc(appDefinition, discovery);
22
+
23
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
24
+ expect(decision.physicalId).toBe('vpc-external-123');
25
+ expect(decision.reason).toContain('hardcoded vpcId');
26
+ });
27
+
28
+ it('should resolve to EXTERNAL using discovered VPC when no hardcoded ID', () => {
29
+ const appDefinition = {
30
+ vpc: {
31
+ ownership: { vpc: 'external' }
32
+ // No external.vpcId provided
33
+ }
34
+ };
35
+ const discovery = {
36
+ stackManaged: [],
37
+ external: [],
38
+ fromCloudFormation: false,
39
+ defaultVpcId: 'vpc-discovered-123'
40
+ };
41
+
42
+ const decision = resolver.resolveVpc(appDefinition, discovery);
43
+
44
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
45
+ expect(decision.physicalId).toBe('vpc-discovered-123');
46
+ expect(decision.reason).toContain('discovered VPC');
47
+ });
48
+
49
+ it('should throw when external specified but no vpcId and no discovery', () => {
50
+ const appDefinition = {
51
+ vpc: {
52
+ ownership: { vpc: 'external' }
53
+ // No external.vpcId provided
54
+ }
55
+ };
56
+ const discovery = {
57
+ stackManaged: [],
58
+ external: [],
59
+ fromCloudFormation: false
60
+ // No defaultVpcId discovered
61
+ };
62
+
63
+ expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
64
+ /ownership='external' for VPC requires either/
65
+ );
66
+ });
67
+
68
+ it('should resolve to STACK when user specifies stack', () => {
69
+ const appDefinition = {
70
+ vpc: {
71
+ ownership: { vpc: 'stack' }
72
+ }
73
+ };
74
+ const discovery = {
75
+ stackManaged: [
76
+ { logicalId: 'FriggVPC', physicalId: 'vpc-stack-123', resourceType: 'AWS::EC2::VPC' }
77
+ ],
78
+ external: [],
79
+ fromCloudFormation: true
80
+ };
81
+
82
+ const decision = resolver.resolveVpc(appDefinition, discovery);
83
+
84
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
85
+ expect(decision.physicalId).toBe('vpc-stack-123');
86
+ expect(decision.reason).toContain('User specified ownership=stack');
87
+ });
88
+
89
+ it('should auto-resolve to STACK when VPC in stack (CRITICAL)', () => {
90
+ const appDefinition = {
91
+ vpc: { ownership: { vpc: 'auto' } }
92
+ };
93
+ const discovery = {
94
+ stackManaged: [
95
+ { logicalId: 'FriggVPC', physicalId: 'vpc-in-stack', resourceType: 'AWS::EC2::VPC' }
96
+ ],
97
+ external: [],
98
+ fromCloudFormation: true
99
+ };
100
+
101
+ const decision = resolver.resolveVpc(appDefinition, discovery);
102
+
103
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
104
+ expect(decision.physicalId).toBe('vpc-in-stack');
105
+ expect(decision.reason).toContain('Found in CloudFormation stack');
106
+ });
107
+
108
+ it('should auto-resolve to EXTERNAL when found externally', () => {
109
+ const appDefinition = {
110
+ vpc: { ownership: { vpc: 'auto' } }
111
+ };
112
+ const discovery = {
113
+ stackManaged: [],
114
+ external: [
115
+ { physicalId: 'vpc-external', resourceType: 'AWS::EC2::VPC', source: 'tag-search' }
116
+ ],
117
+ fromCloudFormation: false
118
+ };
119
+
120
+ const decision = resolver.resolveVpc(appDefinition, discovery);
121
+
122
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
123
+ expect(decision.physicalId).toBe('vpc-external');
124
+ });
125
+
126
+ it('should throw error when auto mode finds no VPC (changed behavior)', () => {
127
+ const appDefinition = {
128
+ vpc: { ownership: { vpc: 'auto' } }
129
+ // No management specified - defaults to discover
130
+ };
131
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
132
+
133
+ // NEW BEHAVIOR: Auto mode with no VPC found should throw error (prevent accidental VPC creation)
134
+ expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
135
+ 'VPC discovery failed: No VPC found'
136
+ );
137
+ });
138
+
139
+ it('should throw error when auto mode finds no VPC and management is not create-new', () => {
140
+ const appDefinition = {
141
+ vpc: {
142
+ enable: true,
143
+ // No management specified - defaults to discover
144
+ // No ownership specified - defaults to auto
145
+ }
146
+ };
147
+ const discovery = {
148
+ stackManaged: [],
149
+ external: [],
150
+ fromCloudFormation: false
151
+ };
152
+
153
+ // Should throw error instead of trying to create VPC
154
+ expect(() => resolver.resolveVpc(appDefinition, discovery)).toThrow(
155
+ 'VPC discovery failed: No VPC found'
156
+ );
157
+ });
158
+
159
+ it('should allow creating VPC when management is create-new', () => {
160
+ const appDefinition = {
161
+ vpc: {
162
+ enable: true,
163
+ management: 'create-new',
164
+ ownership: { vpc: 'auto' }
165
+ }
166
+ };
167
+ const discovery = {
168
+ stackManaged: [],
169
+ external: [],
170
+ fromCloudFormation: false
171
+ };
172
+
173
+ // Should NOT throw - create-new explicitly allows VPC creation
174
+ const decision = resolver.resolveVpc(appDefinition, discovery);
175
+
176
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
177
+ expect(decision.physicalId).toBeUndefined(); // resolveResourceOwnership returns undefined, not null
178
+ });
179
+ });
180
+
181
+ describe('resolveSecurityGroup', () => {
182
+ it('should resolve to EXTERNAL with user-provided hardcoded IDs', () => {
183
+ const appDefinition = {
184
+ vpc: {
185
+ ownership: { securityGroup: 'external' },
186
+ external: { securityGroupIds: ['sg-1', 'sg-2'] }
187
+ }
188
+ };
189
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
190
+
191
+ const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
192
+
193
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
194
+ expect(decision.physicalIds).toEqual(['sg-1', 'sg-2']);
195
+ expect(decision.reason).toContain('hardcoded');
196
+ });
197
+
198
+ it('should resolve to EXTERNAL using discovered default SG when no hardcoded IDs', () => {
199
+ const appDefinition = {
200
+ vpc: {
201
+ ownership: { securityGroup: 'external' }
202
+ // No external.securityGroupIds provided
203
+ }
204
+ };
205
+ const discovery = {
206
+ stackManaged: [],
207
+ external: [],
208
+ fromCloudFormation: false,
209
+ defaultSecurityGroupId: 'sg-discovered-default'
210
+ };
211
+
212
+ const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
213
+
214
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
215
+ expect(decision.physicalIds).toEqual(['sg-discovered-default']);
216
+ expect(decision.reason).toContain('discovered default security group');
217
+ });
218
+
219
+ it('should throw error when ownership=external but no IDs and no discovery', () => {
220
+ const appDefinition = {
221
+ vpc: {
222
+ ownership: { securityGroup: 'external' }
223
+ // No external.securityGroupIds provided
224
+ }
225
+ };
226
+ const discovery = {
227
+ stackManaged: [],
228
+ external: [],
229
+ fromCloudFormation: false
230
+ // No defaultSecurityGroupId discovered
231
+ };
232
+
233
+ expect(() => resolver.resolveSecurityGroup(appDefinition, discovery)).toThrow(
234
+ /ownership='external' for securityGroup requires either/
235
+ );
236
+ });
237
+
238
+ it('should prefer default SG over stack-managed SG when ownership=external and both discovered', () => {
239
+ const appDefinition = {
240
+ vpc: {
241
+ ownership: { securityGroup: 'external' }
242
+ }
243
+ };
244
+ const discovery = {
245
+ stackManaged: [
246
+ { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-stack-managed', resourceType: 'AWS::EC2::SecurityGroup' }
247
+ ],
248
+ external: [],
249
+ fromCloudFormation: true,
250
+ lambdaSecurityGroupId: 'sg-stack-managed', // Stack-managed SG
251
+ defaultSecurityGroupId: 'sg-default-vpc' // Default VPC SG
252
+ };
253
+
254
+ const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
255
+
256
+ // Should use default SG, NOT the stack-managed one
257
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
258
+ expect(decision.physicalIds).toEqual(['sg-default-vpc']);
259
+ expect(decision.reason).toContain('discovered default security group');
260
+ });
261
+
262
+ it('should auto-resolve to STACK when FriggLambdaSecurityGroup in stack', () => {
263
+ const appDefinition = { vpc: { ownership: { securityGroup: 'auto' } } };
264
+ const discovery = {
265
+ stackManaged: [
266
+ { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-069629001ade41c9a', resourceType: 'AWS::EC2::SecurityGroup' }
267
+ ],
268
+ external: [],
269
+ fromCloudFormation: true
270
+ };
271
+
272
+ const decision = resolver.resolveSecurityGroup(appDefinition, discovery);
273
+
274
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
275
+ expect(decision.physicalId).toBe('sg-069629001ade41c9a');
276
+ expect(decision.reason).toContain('Found FriggLambdaSecurityGroup in CloudFormation stack');
277
+ });
278
+ });
279
+
280
+ describe('resolveSubnets', () => {
281
+ it('should resolve to EXTERNAL with user-provided hardcoded subnet IDs', () => {
282
+ const appDefinition = {
283
+ vpc: {
284
+ ownership: { subnets: 'external' },
285
+ external: { subnetIds: ['subnet-1', 'subnet-2', 'subnet-3'] }
286
+ }
287
+ };
288
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
289
+
290
+ const decision = resolver.resolveSubnets(appDefinition, discovery);
291
+
292
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
293
+ expect(decision.physicalIds).toEqual(['subnet-1', 'subnet-2', 'subnet-3']);
294
+ expect(decision.reason).toContain('hardcoded');
295
+ });
296
+
297
+ it('should resolve to EXTERNAL using discovered subnets when no hardcoded IDs', () => {
298
+ const appDefinition = {
299
+ vpc: {
300
+ ownership: { subnets: 'external' }
301
+ // No external.subnetIds provided
302
+ }
303
+ };
304
+ const discovery = {
305
+ stackManaged: [],
306
+ external: [],
307
+ fromCloudFormation: false,
308
+ privateSubnetId1: 'subnet-discovered-1',
309
+ privateSubnetId2: 'subnet-discovered-2'
310
+ };
311
+
312
+ const decision = resolver.resolveSubnets(appDefinition, discovery);
313
+
314
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
315
+ expect(decision.physicalIds).toEqual(['subnet-discovered-1', 'subnet-discovered-2']);
316
+ expect(decision.reason).toContain('discovered subnets');
317
+ });
318
+
319
+ it('should throw error when ownership=external but no IDs and no discovery', () => {
320
+ const appDefinition = {
321
+ vpc: {
322
+ ownership: { subnets: 'external' }
323
+ // No external.subnetIds provided
324
+ }
325
+ };
326
+ const discovery = {
327
+ stackManaged: [],
328
+ external: [],
329
+ fromCloudFormation: false
330
+ // No privateSubnetId1/2 discovered
331
+ };
332
+
333
+ expect(() => resolver.resolveSubnets(appDefinition, discovery)).toThrow(
334
+ /ownership='external' for subnets requires either/
335
+ );
336
+ });
337
+
338
+ it('should resolve to STACK when subnets found in stack', () => {
339
+ const appDefinition = { vpc: { ownership: { subnets: 'auto' } } };
340
+ const discovery = {
341
+ stackManaged: [
342
+ { logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-a', resourceType: 'AWS::EC2::Subnet' },
343
+ { logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-b', resourceType: 'AWS::EC2::Subnet' }
344
+ ],
345
+ external: [],
346
+ fromCloudFormation: true
347
+ };
348
+
349
+ const decision = resolver.resolveSubnets(appDefinition, discovery);
350
+
351
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
352
+ expect(decision.physicalIds).toEqual(['subnet-a', 'subnet-b']);
353
+ expect(decision.metadata.subnet1).toBe('subnet-a');
354
+ expect(decision.metadata.subnet2).toBe('subnet-b');
355
+ });
356
+
357
+ it('should resolve to EXTERNAL when found externally', () => {
358
+ const appDefinition = { vpc: { ownership: { subnets: 'auto' } } };
359
+ const discovery = {
360
+ stackManaged: [],
361
+ external: [
362
+ { physicalId: 'subnet-ext-1', resourceType: 'AWS::EC2::Subnet', source: 'tag-search' },
363
+ { physicalId: 'subnet-ext-2', resourceType: 'AWS::EC2::Subnet', source: 'tag-search' },
364
+ { physicalId: 'subnet-ext-3', resourceType: 'AWS::EC2::Subnet', source: 'tag-search' }
365
+ ],
366
+ fromCloudFormation: false
367
+ };
368
+
369
+ const decision = resolver.resolveSubnets(appDefinition, discovery);
370
+
371
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
372
+ expect(decision.physicalIds).toHaveLength(2); // Takes first 2
373
+ expect(decision.physicalIds).toEqual(['subnet-ext-1', 'subnet-ext-2']);
374
+ });
375
+
376
+ it('should resolve to STACK when no subnets found (create new)', () => {
377
+ const appDefinition = { vpc: { ownership: { subnets: 'auto' } } };
378
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
379
+
380
+ const decision = resolver.resolveSubnets(appDefinition, discovery);
381
+
382
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
383
+ expect(decision.physicalId).toBeNull();
384
+ expect(decision.reason).toContain('No existing subnets found');
385
+ });
386
+ });
387
+
388
+ describe('resolveNatGateway', () => {
389
+ it('should return null decision when NAT disabled', () => {
390
+ const appDefinition = {
391
+ vpc: {
392
+ ownership: { natGateway: 'auto' },
393
+ config: { natGateway: { enable: false } }
394
+ }
395
+ };
396
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
397
+
398
+ const decision = resolver.resolveNatGateway(appDefinition, discovery);
399
+
400
+ expect(decision.ownership).toBeNull();
401
+ expect(decision.reason).toContain('NAT Gateway disabled');
402
+ });
403
+
404
+ it('should resolve to EXTERNAL with user-provided hardcoded ID', () => {
405
+ const appDefinition = {
406
+ vpc: {
407
+ ownership: { natGateway: 'external' },
408
+ external: { natGatewayId: 'nat-external-123' }
409
+ }
410
+ };
411
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
412
+
413
+ const decision = resolver.resolveNatGateway(appDefinition, discovery);
414
+
415
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
416
+ expect(decision.physicalId).toBe('nat-external-123');
417
+ expect(decision.reason).toContain('hardcoded');
418
+ });
419
+
420
+ it('should resolve to EXTERNAL using discovered NAT when no hardcoded ID', () => {
421
+ const appDefinition = {
422
+ vpc: {
423
+ ownership: { natGateway: 'external' }
424
+ // No external.natGatewayId provided
425
+ }
426
+ };
427
+ const discovery = {
428
+ stackManaged: [],
429
+ external: [],
430
+ fromCloudFormation: false,
431
+ natGatewayId: 'nat-discovered-123'
432
+ };
433
+
434
+ const decision = resolver.resolveNatGateway(appDefinition, discovery);
435
+
436
+ expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
437
+ expect(decision.physicalId).toBe('nat-discovered-123');
438
+ expect(decision.reason).toContain('discovered NAT gateway');
439
+ });
440
+
441
+ it('should throw error when ownership=external but no ID and no discovery', () => {
442
+ const appDefinition = {
443
+ vpc: {
444
+ ownership: { natGateway: 'external' }
445
+ // No external.natGatewayId provided
446
+ }
447
+ };
448
+ const discovery = {
449
+ stackManaged: [],
450
+ external: [],
451
+ fromCloudFormation: false
452
+ // No natGatewayId discovered
453
+ };
454
+
455
+ expect(() => resolver.resolveNatGateway(appDefinition, discovery)).toThrow(
456
+ /ownership='external' for NAT gateway requires either/
457
+ );
458
+ });
459
+
460
+ it('should auto-resolve to STACK when found in stack', () => {
461
+ const appDefinition = { vpc: { ownership: { natGateway: 'auto' } } };
462
+ const discovery = {
463
+ stackManaged: [
464
+ { logicalId: 'FriggNatGateway', physicalId: 'nat-stack-123', resourceType: 'AWS::EC2::NatGateway' }
465
+ ],
466
+ external: [],
467
+ fromCloudFormation: true
468
+ };
469
+
470
+ const decision = resolver.resolveNatGateway(appDefinition, discovery);
471
+
472
+ expect(decision.ownership).toBe(ResourceOwnership.STACK);
473
+ expect(decision.physicalId).toBe('nat-stack-123');
474
+ });
475
+ });
476
+
477
+ describe('resolveVpcEndpoints', () => {
478
+ it('should skip DynamoDB endpoint when application uses MongoDB', () => {
479
+ const appDefinition = {
480
+ vpc: { ownership: { vpcEndpoints: 'auto' } },
481
+ database: { mongoDB: { enable: true } }, // Using MongoDB, not DynamoDB
482
+ encryption: { fieldLevelEncryptionMethod: 'kms' }
483
+ };
484
+ const discovery = {
485
+ stackManaged: [],
486
+ external: [],
487
+ fromCloudFormation: false
488
+ };
489
+
490
+ const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
491
+
492
+ expect(decisions.s3.ownership).toBe('stack'); // S3 always needed
493
+ expect(decisions.dynamodb.ownership).toBeNull(); // DynamoDB NOT needed
494
+ expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
495
+ expect(decisions.kms.ownership).toBe('stack'); // KMS needed (encryption enabled)
496
+ expect(decisions.secretsManager.ownership).toBe('stack'); // SM always needed
497
+ expect(decisions.sqs.ownership).toBe('stack'); // SQS always needed
498
+ });
499
+
500
+ it('should skip DynamoDB endpoint when application uses PostgreSQL', () => {
501
+ const appDefinition = {
502
+ vpc: { ownership: { vpcEndpoints: 'auto' } },
503
+ database: { postgres: { enable: true } }, // Using PostgreSQL, not DynamoDB
504
+ };
505
+ const discovery = {
506
+ stackManaged: [],
507
+ external: [],
508
+ fromCloudFormation: false
509
+ };
510
+
511
+ const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
512
+
513
+ expect(decisions.dynamodb.ownership).toBeNull();
514
+ expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
515
+ });
516
+
517
+ it('should create DynamoDB endpoint when explicitly enabled', () => {
518
+ const appDefinition = {
519
+ vpc: { ownership: { vpcEndpoints: 'auto' } },
520
+ database: { dynamodb: { enable: true } }, // Explicitly using DynamoDB
521
+ };
522
+ const discovery = {
523
+ stackManaged: [],
524
+ external: [],
525
+ fromCloudFormation: false
526
+ };
527
+
528
+ const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
529
+
530
+ expect(decisions.dynamodb.ownership).toBe('stack'); // DynamoDB needed
531
+ });
532
+
533
+ it('should allow deletion of DynamoDB endpoint when not needed', () => {
534
+ const appDefinition = {
535
+ vpc: { ownership: { vpcEndpoints: 'auto' } },
536
+ database: { mongoDB: { enable: true } }, // Using MongoDB (DynamoDB not needed)
537
+ };
538
+ const discovery = {
539
+ stackManaged: [
540
+ { logicalId: 'FriggDynamoDBVPCEndpoint', physicalId: 'vpce-ddb-legacy', resourceType: 'AWS::EC2::VPCEndpoint' }
541
+ ],
542
+ external: [],
543
+ fromCloudFormation: true
544
+ };
545
+
546
+ const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
547
+
548
+ // Should return null (allow CloudFormation to delete it since not needed)
549
+ expect(decisions.dynamodb.ownership).toBeNull();
550
+ expect(decisions.dynamodb.reason).toContain('MongoDB/PostgreSQL');
551
+ });
552
+
553
+
554
+ it('should return null decisions when endpoints disabled', () => {
555
+ const appDefinition = {
556
+ vpc: {
557
+ ownership: { vpcEndpoints: 'auto' },
558
+ config: { enableVpcEndpoints: false }
559
+ }
560
+ };
561
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
562
+
563
+ const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
564
+
565
+ expect(decisions.s3.ownership).toBeNull();
566
+ expect(decisions.dynamodb.ownership).toBeNull();
567
+ expect(decisions.kms.ownership).toBeNull();
568
+ expect(decisions.secretsManager.ownership).toBeNull();
569
+ expect(decisions.sqs.ownership).toBeNull();
570
+ });
571
+
572
+ it('should resolve to EXTERNAL with user-provided endpoint IDs', () => {
573
+ const appDefinition = {
574
+ vpc: {
575
+ ownership: { vpcEndpoints: 'external' },
576
+ external: {
577
+ vpcEndpointIds: {
578
+ s3: 'vpce-s3-123',
579
+ dynamodb: 'vpce-ddb-456'
580
+ }
581
+ }
582
+ },
583
+ database: { dynamodb: { enable: true } } // Enable DynamoDB
584
+ };
585
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
586
+
587
+ const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
588
+
589
+ expect(decisions.s3.ownership).toBe(ResourceOwnership.EXTERNAL);
590
+ expect(decisions.s3.physicalId).toBe('vpce-s3-123');
591
+ expect(decisions.dynamodb.ownership).toBe(ResourceOwnership.EXTERNAL);
592
+ expect(decisions.dynamodb.physicalId).toBe('vpce-ddb-456');
593
+ expect(decisions.kms.ownership).toBeNull(); // Not provided
594
+ });
595
+
596
+ it('should auto-resolve to STACK when endpoints found in stack', () => {
597
+ const appDefinition = {
598
+ vpc: { ownership: { vpcEndpoints: 'auto' } },
599
+ database: { dynamodb: { enable: true } } // Enable DynamoDB
600
+ };
601
+ const discovery = {
602
+ stackManaged: [
603
+ { logicalId: 'FriggS3VPCEndpoint', physicalId: 'vpce-s3-stack', resourceType: 'AWS::EC2::VPCEndpoint' },
604
+ { logicalId: 'FriggDynamoDBVPCEndpoint', physicalId: 'vpce-ddb-stack', resourceType: 'AWS::EC2::VPCEndpoint' }
605
+ ],
606
+ external: [],
607
+ fromCloudFormation: true
608
+ };
609
+
610
+ const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
611
+
612
+ expect(decisions.s3.ownership).toBe(ResourceOwnership.STACK);
613
+ expect(decisions.s3.physicalId).toBe('vpce-s3-stack');
614
+ expect(decisions.dynamodb.ownership).toBe(ResourceOwnership.STACK);
615
+ expect(decisions.dynamodb.physicalId).toBe('vpce-ddb-stack');
616
+ });
617
+
618
+ it('should auto-resolve mixed: some in stack, some new', () => {
619
+ const appDefinition = {
620
+ vpc: { ownership: { vpcEndpoints: 'auto' } },
621
+ encryption: { fieldLevelEncryptionMethod: 'kms' }, // Enable KMS endpoint
622
+ database: { dynamodb: { enable: true } } // Enable DynamoDB
623
+ };
624
+ const discovery = {
625
+ stackManaged: [
626
+ { logicalId: 'FriggS3VPCEndpoint', physicalId: 'vpce-s3-stack', resourceType: 'AWS::EC2::VPCEndpoint' }
627
+ ],
628
+ external: [],
629
+ fromCloudFormation: true
630
+ };
631
+
632
+ const decisions = resolver.resolveVpcEndpoints(appDefinition, discovery);
633
+
634
+ expect(decisions.s3.ownership).toBe(ResourceOwnership.STACK);
635
+ expect(decisions.s3.physicalId).toBe('vpce-s3-stack');
636
+
637
+ // Others not in stack - should create new
638
+ expect(decisions.dynamodb.ownership).toBe(ResourceOwnership.STACK);
639
+ expect(decisions.dynamodb.physicalId).toBeUndefined();
640
+ expect(decisions.kms.ownership).toBe(ResourceOwnership.STACK);
641
+ expect(decisions.secretsManager.ownership).toBe(ResourceOwnership.STACK);
642
+ expect(decisions.sqs.ownership).toBe(ResourceOwnership.STACK);
643
+ });
644
+ });
645
+
646
+ describe('resolveAll', () => {
647
+ it('should resolve all VPC resources at once', () => {
648
+ const appDefinition = {
649
+ vpc: {
650
+ ownership: {
651
+ vpc: 'auto',
652
+ securityGroup: 'auto',
653
+ subnets: 'auto',
654
+ natGateway: 'auto',
655
+ vpcEndpoints: 'auto'
656
+ }
657
+ }
658
+ };
659
+ const discovery = {
660
+ stackManaged: [
661
+ { logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' },
662
+ { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-456', resourceType: 'AWS::EC2::SecurityGroup' },
663
+ { logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-1', resourceType: 'AWS::EC2::Subnet' },
664
+ { logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-2', resourceType: 'AWS::EC2::Subnet' },
665
+ { logicalId: 'FriggNatGateway', physicalId: 'nat-789', resourceType: 'AWS::EC2::NatGateway' },
666
+ { logicalId: 'FriggS3VPCEndpoint', physicalId: 'vpce-s3', resourceType: 'AWS::EC2::VPCEndpoint' }
667
+ ],
668
+ external: [],
669
+ fromCloudFormation: true
670
+ };
671
+
672
+ const decisions = resolver.resolveAll(appDefinition, discovery);
673
+
674
+ expect(decisions.vpc.ownership).toBe(ResourceOwnership.STACK);
675
+ expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
676
+ expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
677
+ expect(decisions.natGateway.ownership).toBe(ResourceOwnership.STACK);
678
+ expect(decisions.vpcEndpoints.s3.ownership).toBe(ResourceOwnership.STACK);
679
+ });
680
+
681
+ it('should handle mixed ownership scenarios', () => {
682
+ const appDefinition = {
683
+ vpc: {
684
+ ownership: {
685
+ vpc: 'external',
686
+ securityGroup: 'stack',
687
+ subnets: 'stack',
688
+ natGateway: 'auto',
689
+ vpcEndpoints: 'auto'
690
+ },
691
+ external: {
692
+ vpcId: 'vpc-shared-production'
693
+ }
694
+ }
695
+ };
696
+ const discovery = {
697
+ stackManaged: [
698
+ { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-stack', resourceType: 'AWS::EC2::SecurityGroup' },
699
+ { logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-1', resourceType: 'AWS::EC2::Subnet' },
700
+ { logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-2', resourceType: 'AWS::EC2::Subnet' }
701
+ ],
702
+ external: [],
703
+ fromCloudFormation: true
704
+ };
705
+
706
+ const decisions = resolver.resolveAll(appDefinition, discovery);
707
+
708
+ expect(decisions.vpc.ownership).toBe(ResourceOwnership.EXTERNAL);
709
+ expect(decisions.vpc.physicalId).toBe('vpc-shared-production');
710
+ expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
711
+ expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
712
+ expect(decisions.natGateway.ownership).toBe(ResourceOwnership.STACK); // Not found, create new
713
+ });
714
+ });
715
+
716
+ describe('real-world scenarios', () => {
717
+ it('scenario: fresh deploy, no resources exist', () => {
718
+ const appDefinition = {
719
+ vpc: {
720
+ enable: true,
721
+ ownership: {},
722
+ management: 'create-new' // Explicitly allow VPC creation
723
+ }
724
+ };
725
+ const discovery = { stackManaged: [], external: [], fromCloudFormation: false };
726
+
727
+ const decisions = resolver.resolveAll(appDefinition, discovery);
728
+
729
+ // All should be STACK (create new)
730
+ expect(decisions.vpc.ownership).toBe(ResourceOwnership.STACK);
731
+ expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
732
+ expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
733
+ expect(decisions.natGateway.ownership).toBe(ResourceOwnership.STACK);
734
+ });
735
+
736
+ it('scenario: redeploy existing stack (the original bug case)', () => {
737
+ const appDefinition = {
738
+ vpc: { enable: true, ownership: {} }
739
+ };
740
+ const discovery = {
741
+ stackManaged: [
742
+ { logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' },
743
+ { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-069629001ade41c9a', resourceType: 'AWS::EC2::SecurityGroup' },
744
+ { logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-1', resourceType: 'AWS::EC2::Subnet' },
745
+ { logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-2', resourceType: 'AWS::EC2::Subnet' }
746
+ ],
747
+ external: [],
748
+ fromCloudFormation: true,
749
+ stackName: 'create-frigg-app-production'
750
+ };
751
+
752
+ const decisions = resolver.resolveAll(appDefinition, discovery);
753
+
754
+ // CRITICAL: All resources in stack must get STACK ownership
755
+ expect(decisions.vpc.ownership).toBe(ResourceOwnership.STACK);
756
+ expect(decisions.vpc.physicalId).toBe('vpc-123');
757
+
758
+ expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
759
+ expect(decisions.securityGroup.physicalId).toBe('sg-069629001ade41c9a');
760
+ expect(decisions.securityGroup.reason).toContain('Found FriggLambdaSecurityGroup in CloudFormation stack');
761
+
762
+ expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
763
+ expect(decisions.subnets.physicalIds).toEqual(['subnet-1', 'subnet-2']);
764
+ });
765
+
766
+ it('scenario: use shared VPC with stack-managed resources', () => {
767
+ const appDefinition = {
768
+ vpc: {
769
+ enable: true,
770
+ ownership: {
771
+ vpc: 'external',
772
+ securityGroup: 'auto',
773
+ subnets: 'auto'
774
+ },
775
+ external: {
776
+ vpcId: 'vpc-shared-across-stages'
777
+ }
778
+ }
779
+ };
780
+ const discovery = {
781
+ stackManaged: [
782
+ { logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-stage-specific', resourceType: 'AWS::EC2::SecurityGroup' },
783
+ { logicalId: 'FriggPrivateSubnet1', physicalId: 'subnet-1', resourceType: 'AWS::EC2::Subnet' },
784
+ { logicalId: 'FriggPrivateSubnet2', physicalId: 'subnet-2', resourceType: 'AWS::EC2::Subnet' }
785
+ ],
786
+ external: [],
787
+ fromCloudFormation: true
788
+ };
789
+
790
+ const decisions = resolver.resolveAll(appDefinition, discovery);
791
+
792
+ // VPC is external
793
+ expect(decisions.vpc.ownership).toBe(ResourceOwnership.EXTERNAL);
794
+ expect(decisions.vpc.physicalId).toBe('vpc-shared-across-stages');
795
+
796
+ // But security group and subnets are stack-managed
797
+ expect(decisions.securityGroup.ownership).toBe(ResourceOwnership.STACK);
798
+ expect(decisions.subnets.ownership).toBe(ResourceOwnership.STACK);
799
+ });
800
+ });
801
+ });