@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,375 @@
1
+ /**
2
+ * CloudFormation-based Resource Discovery
3
+ *
4
+ * Domain Service - Hexagonal Architecture
5
+ *
6
+ * Discovers resources from existing CloudFormation stacks as the primary
7
+ * source of truth before falling back to direct AWS API discovery.
8
+ *
9
+ * Benefits:
10
+ * - Faster discovery (1 CF call vs multiple AWS API calls)
11
+ * - More accurate (stack is source of truth)
12
+ * - Eliminates tagging dependencies
13
+ * - Idempotent (discover mode reuses stack resources)
14
+ */
15
+
16
+ class CloudFormationDiscovery {
17
+ constructor(provider, config = {}) {
18
+ this.provider = provider;
19
+ this.serviceName = config.serviceName;
20
+ this.stage = config.stage;
21
+ }
22
+
23
+ /**
24
+ * Discover resources from an existing CloudFormation stack
25
+ *
26
+ * @param {string} stackName - Name of the CloudFormation stack
27
+ * @returns {Promise<Object|null>} Discovered resources or null if stack doesn't exist
28
+ */
29
+ async discoverFromStack(stackName) {
30
+ try {
31
+ // Try to get the stack
32
+ const stack = await this.provider.describeStack(stackName);
33
+
34
+ // Get stack resources
35
+ const resources = await this.provider.listStackResources(stackName);
36
+
37
+ // Extract discovered resources from outputs and resources
38
+ const discovered = {
39
+ // Metadata to indicate resources came from CloudFormation stack
40
+ fromCloudFormationStack: true,
41
+ stackName: stackName,
42
+ existingLogicalIds: []
43
+ };
44
+
45
+ // Extract from outputs
46
+ if (stack.Outputs && stack.Outputs.length > 0) {
47
+ this._extractFromOutputs(stack.Outputs, discovered);
48
+ }
49
+
50
+ // Extract from resources (now async to query AWS for details)
51
+ // Always call this even if resources is empty, as it may query AWS for resources
52
+ await this._extractFromResources(resources || [], discovered);
53
+
54
+ // Clean up metadata if no resources were discovered
55
+ if (discovered.existingLogicalIds.length === 0) {
56
+ delete discovered.existingLogicalIds;
57
+ }
58
+
59
+ return discovered;
60
+ } catch (error) {
61
+ // Stack doesn't exist - return null to trigger fallback discovery
62
+ if (error.message && error.message.includes('does not exist')) {
63
+ return null;
64
+ }
65
+
66
+ // Other errors - log and return null
67
+ console.warn(`⚠️ CloudFormation discovery failed: ${error.message}`);
68
+ return null;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Extract discovered resources from CloudFormation stack outputs
74
+ *
75
+ * @private
76
+ * @param {Array} outputs - CloudFormation stack outputs
77
+ * @param {Object} discovered - Object to populate with discovered resources
78
+ */
79
+ _extractFromOutputs(outputs, discovered) {
80
+ const outputMap = outputs.reduce((acc, output) => {
81
+ acc[output.OutputKey] = output.OutputValue;
82
+ return acc;
83
+ }, {});
84
+
85
+ // VPC outputs
86
+ if (outputMap.VpcId) {
87
+ discovered.defaultVpcId = outputMap.VpcId; // VpcBuilder expects 'defaultVpcId'
88
+ }
89
+
90
+ if (outputMap.PrivateSubnetIds) {
91
+ // Handle comma-separated subnet IDs
92
+ discovered.privateSubnetIds = outputMap.PrivateSubnetIds.split(',').map(id => id.trim());
93
+ }
94
+
95
+ if (outputMap.PublicSubnetId) {
96
+ discovered.publicSubnetId = outputMap.PublicSubnetId;
97
+ }
98
+
99
+ if (outputMap.SecurityGroupId) {
100
+ discovered.securityGroupId = outputMap.SecurityGroupId;
101
+ }
102
+
103
+ // KMS outputs
104
+ if (outputMap.KMS_KEY_ARN) {
105
+ discovered.defaultKmsKeyId = outputMap.KMS_KEY_ARN;
106
+ }
107
+
108
+ // Database outputs (if exposed)
109
+ if (outputMap.DatabaseEndpoint) {
110
+ discovered.databaseEndpoint = outputMap.DatabaseEndpoint;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Extract discovered resources from CloudFormation stack resources
116
+ *
117
+ * @private
118
+ * @param {Array} resources - CloudFormation stack resources
119
+ * @param {Object} discovered - Object to populate with discovered resources
120
+ */
121
+ async _extractFromResources(resources, discovered) {
122
+ console.log(` DEBUG: Processing ${resources.length} CloudFormation resources...`);
123
+
124
+ // Initialize existingLogicalIds array if not present
125
+ if (!discovered.existingLogicalIds) {
126
+ discovered.existingLogicalIds = [];
127
+ }
128
+ for (const resource of resources) {
129
+ const { LogicalResourceId, PhysicalResourceId, ResourceType } = resource;
130
+
131
+ // Track Frigg-managed resources by logical ID
132
+ if (LogicalResourceId.startsWith('Frigg') || LogicalResourceId.includes('Migration')) {
133
+ discovered.existingLogicalIds.push(LogicalResourceId);
134
+ }
135
+
136
+ // Debug Aurora detection
137
+ if (LogicalResourceId.includes('Aurora')) {
138
+ console.log(` DEBUG: Found Aurora resource: ${LogicalResourceId} (${ResourceType})`);
139
+ }
140
+
141
+ // Security Group - use to get VPC ID
142
+ if (LogicalResourceId === 'FriggLambdaSecurityGroup' && ResourceType === 'AWS::EC2::SecurityGroup') {
143
+ discovered.securityGroupId = PhysicalResourceId;
144
+ console.log(` ✓ Found security group in stack: ${PhysicalResourceId}`);
145
+
146
+ // Query security group to get VPC ID (required because SG resource doesn't include VPC ID)
147
+ if (this.provider && this.provider.getEC2Client && !discovered.defaultVpcId) {
148
+ try {
149
+ console.log(` Querying EC2 to get VPC ID from security group...`);
150
+ const { DescribeSecurityGroupsCommand } = require('@aws-sdk/client-ec2');
151
+ const ec2Client = this.provider.getEC2Client();
152
+ const sgDetails = await ec2Client.send(
153
+ new DescribeSecurityGroupsCommand({
154
+ GroupIds: [PhysicalResourceId]
155
+ })
156
+ );
157
+
158
+ if (sgDetails.SecurityGroups && sgDetails.SecurityGroups.length > 0) {
159
+ discovered.defaultVpcId = sgDetails.SecurityGroups[0].VpcId;
160
+ console.log(` ✓ Extracted VPC ID from security group: ${discovered.defaultVpcId}`);
161
+ } else {
162
+ console.warn(` ⚠️ Security group query returned no results`);
163
+ }
164
+ } catch (error) {
165
+ console.warn(` ⚠️ Could not get VPC from security group: ${error.message}`);
166
+ }
167
+ }
168
+ }
169
+
170
+ // Aurora cluster - query AWS to get endpoint details
171
+ if (LogicalResourceId === 'FriggAuroraCluster' && ResourceType === 'AWS::RDS::DBCluster') {
172
+ discovered.auroraClusterId = PhysicalResourceId;
173
+ console.log(` ✓ Found Aurora cluster in stack: ${PhysicalResourceId}`);
174
+
175
+ // Query RDS to get cluster endpoint
176
+ if (this.provider && !discovered.auroraClusterEndpoint) {
177
+ try {
178
+ console.log(` Querying RDS to get Aurora endpoint...`);
179
+ const { DescribeDBClustersCommand } = require('@aws-sdk/client-rds');
180
+ const { RDSClient } = require('@aws-sdk/client-rds');
181
+
182
+ const rdsClient = new RDSClient({ region: this.provider.region });
183
+ const clusterDetails = await rdsClient.send(
184
+ new DescribeDBClustersCommand({
185
+ DBClusterIdentifier: PhysicalResourceId
186
+ })
187
+ );
188
+
189
+ if (clusterDetails.DBClusters && clusterDetails.DBClusters.length > 0) {
190
+ const cluster = clusterDetails.DBClusters[0];
191
+ discovered.auroraClusterEndpoint = cluster.Endpoint;
192
+ discovered.auroraClusterPort = cluster.Port;
193
+ discovered.auroraClusterIdentifier = cluster.DBClusterIdentifier;
194
+ console.log(` ✓ Extracted Aurora endpoint: ${cluster.Endpoint}:${cluster.Port}`);
195
+ } else {
196
+ console.warn(` ⚠️ RDS cluster query returned no results`);
197
+ }
198
+ } catch (error) {
199
+ console.warn(` ⚠️ Could not get endpoint from Aurora cluster: ${error.message}`);
200
+ }
201
+ }
202
+ }
203
+
204
+ // Migration status bucket
205
+ if (LogicalResourceId === 'FriggMigrationStatusBucket' && ResourceType === 'AWS::S3::Bucket') {
206
+ discovered.migrationStatusBucket = PhysicalResourceId;
207
+ }
208
+
209
+ // Migration queue
210
+ if (LogicalResourceId === 'DbMigrationQueue' && ResourceType === 'AWS::SQS::Queue') {
211
+ discovered.migrationQueueUrl = PhysicalResourceId;
212
+ }
213
+
214
+ // NAT Gateway
215
+ if (LogicalResourceId === 'FriggNatGateway' && ResourceType === 'AWS::EC2::NatGateway') {
216
+ discovered.natGatewayId = PhysicalResourceId;
217
+ }
218
+
219
+ // VPC - direct extraction (primary method)
220
+ if (LogicalResourceId === 'FriggVPC' && ResourceType === 'AWS::EC2::VPC') {
221
+ discovered.defaultVpcId = PhysicalResourceId;
222
+ console.log(` ✓ Found VPC in stack: ${PhysicalResourceId}`);
223
+ }
224
+
225
+ // KMS Key (alternative to output)
226
+ if (LogicalResourceId === 'FriggKMSKey' && ResourceType === 'AWS::KMS::Key') {
227
+ // Note: For KMS, we prefer the ARN from outputs, but this is a fallback
228
+ if (!discovered.defaultKmsKeyId) {
229
+ discovered.defaultKmsKeyId = PhysicalResourceId;
230
+ }
231
+ }
232
+
233
+ // KMS Key Alias - query to get the actual key ARN
234
+ if (LogicalResourceId === 'FriggKMSKeyAlias' && ResourceType === 'AWS::KMS::Alias') {
235
+ discovered.kmsKeyAlias = PhysicalResourceId;
236
+ console.log(` ✓ Found KMS key alias in stack: ${PhysicalResourceId}`);
237
+
238
+ // Query KMS to get the key ARN that this alias points to
239
+ // Always query even if key is already set, to ensure consistency
240
+ if (this.provider && this.provider.describeKmsKey) {
241
+ try {
242
+ console.log(` Querying KMS to get key ARN from alias...`);
243
+ const keyMetadata = await this.provider.describeKmsKey(PhysicalResourceId);
244
+
245
+ if (keyMetadata) {
246
+ discovered.defaultKmsKeyId = keyMetadata.Arn;
247
+ console.log(` ✓ Extracted KMS key ARN from alias: ${discovered.defaultKmsKeyId}`);
248
+ } else {
249
+ console.warn(` ⚠️ KMS key query returned no metadata`);
250
+ }
251
+ } catch (error) {
252
+ console.warn(` ⚠️ Could not get key ARN from alias: ${error.message}`);
253
+ }
254
+ }
255
+ }
256
+
257
+ // Subnets
258
+ if (LogicalResourceId === 'FriggPrivateSubnet1' && ResourceType === 'AWS::EC2::Subnet') {
259
+ discovered.privateSubnetId1 = PhysicalResourceId;
260
+ }
261
+ if (LogicalResourceId === 'FriggPrivateSubnet2' && ResourceType === 'AWS::EC2::Subnet') {
262
+ discovered.privateSubnetId2 = PhysicalResourceId;
263
+ }
264
+ if (LogicalResourceId === 'FriggPublicSubnet' && ResourceType === 'AWS::EC2::Subnet') {
265
+ discovered.publicSubnetId1 = PhysicalResourceId;
266
+ }
267
+ if (LogicalResourceId === 'FriggPublicSubnet2' && ResourceType === 'AWS::EC2::Subnet') {
268
+ discovered.publicSubnetId2 = PhysicalResourceId;
269
+ }
270
+
271
+ // Route Tables
272
+ if (LogicalResourceId === 'FriggLambdaRouteTable' && ResourceType === 'AWS::EC2::RouteTable') {
273
+ discovered.routeTableId = PhysicalResourceId;
274
+ }
275
+
276
+ // VPC Endpoint Security Group
277
+ if (LogicalResourceId === 'FriggVPCEndpointSecurityGroup' && ResourceType === 'AWS::EC2::SecurityGroup') {
278
+ discovered.vpcEndpointSecurityGroupId = PhysicalResourceId;
279
+ }
280
+
281
+ // VPC Endpoints
282
+ if (LogicalResourceId === 'FriggS3VPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
283
+ discovered.s3VpcEndpointId = PhysicalResourceId;
284
+ }
285
+ if (LogicalResourceId === 'FriggDynamoDBVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
286
+ discovered.dynamoDbVpcEndpointId = PhysicalResourceId;
287
+ }
288
+ if (LogicalResourceId === 'FriggKMSVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
289
+ discovered.kmsVpcEndpointId = PhysicalResourceId;
290
+ }
291
+ if (LogicalResourceId === 'FriggSecretsManagerVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
292
+ discovered.secretsManagerVpcEndpointId = PhysicalResourceId;
293
+ }
294
+ if (LogicalResourceId === 'FriggSQSVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
295
+ discovered.sqsVpcEndpointId = PhysicalResourceId;
296
+ }
297
+ }
298
+
299
+ // If we have a VPC ID but no subnet IDs, query EC2 for Frigg-managed subnets
300
+ if (discovered.defaultVpcId && this.provider &&
301
+ !discovered.privateSubnetId1 && !discovered.publicSubnetId1) {
302
+ try {
303
+ console.log(' Querying EC2 for Frigg-managed subnets...');
304
+ const { DescribeSubnetsCommand } = require('@aws-sdk/client-ec2');
305
+ const subnetResponse = await this.provider.getEC2Client().send(
306
+ new DescribeSubnetsCommand({
307
+ Filters: [
308
+ { Name: 'vpc-id', Values: [discovered.defaultVpcId] },
309
+ { Name: 'tag:ManagedBy', Values: ['Frigg'] },
310
+ ],
311
+ })
312
+ );
313
+
314
+ if (subnetResponse.Subnets && subnetResponse.Subnets.length > 0) {
315
+ // Extract subnet IDs by logical ID from tags
316
+ const subnets = subnetResponse.Subnets.map(subnet => ({
317
+ subnetId: subnet.SubnetId,
318
+ logicalId: subnet.Tags?.find(t => t.Key === 'aws:cloudformation:logical-id')?.Value,
319
+ isPublic: subnet.MapPublicIpOnLaunch,
320
+ }));
321
+
322
+ // Find private subnets
323
+ const privateSubnets = subnets.filter(s => !s.isPublic).sort((a, b) =>
324
+ a.logicalId?.localeCompare(b.logicalId) || 0
325
+ );
326
+ if (privateSubnets.length >= 1) {
327
+ discovered.privateSubnetId1 = privateSubnets[0].subnetId;
328
+ }
329
+ if (privateSubnets.length >= 2) {
330
+ discovered.privateSubnetId2 = privateSubnets[1].subnetId;
331
+ }
332
+
333
+ // Find public subnets
334
+ const publicSubnets = subnets.filter(s => s.isPublic).sort((a, b) =>
335
+ a.logicalId?.localeCompare(b.logicalId) || 0
336
+ );
337
+ if (publicSubnets.length >= 1) {
338
+ discovered.publicSubnetId1 = publicSubnets[0].subnetId;
339
+ }
340
+ if (publicSubnets.length >= 2) {
341
+ discovered.publicSubnetId2 = publicSubnets[1].subnetId;
342
+ }
343
+
344
+ console.log(` ✓ Found ${subnets.length} Frigg-managed subnets via EC2 query`);
345
+ }
346
+ } catch (error) {
347
+ console.warn(` ⚠️ Could not query EC2 for subnets: ${error.message}`);
348
+ }
349
+ }
350
+
351
+ // Check for KMS key alias via AWS API if not found in stack resources
352
+ // This handles cases where the alias was created outside CloudFormation
353
+ if (!discovered.defaultKmsKeyId && !discovered.kmsKeyAlias &&
354
+ this.provider && this.provider.describeKmsKey && this.serviceName && this.stage) {
355
+ try {
356
+ const aliasName = `alias/${this.serviceName}-${this.stage}-frigg-kms`;
357
+ console.log(` Querying KMS for alias: ${aliasName}...`);
358
+
359
+ const keyMetadata = await this.provider.describeKmsKey(aliasName);
360
+
361
+ if (keyMetadata) {
362
+ discovered.defaultKmsKeyId = keyMetadata.Arn;
363
+ discovered.kmsKeyAlias = aliasName;
364
+ console.log(` ✓ Found KMS key via alias query: ${discovered.defaultKmsKeyId}`);
365
+ }
366
+ } catch (error) {
367
+ // Alias not found - this is expected if no KMS key exists yet
368
+ console.log(` ℹ No KMS key alias found via AWS API`);
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ module.exports = { CloudFormationDiscovery };
375
+