@friggframework/devtools 2.0.0-next.44 → 2.0.0-next.46

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 +633 -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 -2074
  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,535 @@
1
+ /**
2
+ * RepairViaImportUseCase - Import Orphaned Resources into CloudFormation Stack
3
+ *
4
+ * Application Layer - Use Case
5
+ *
6
+ * Business logic for the "frigg repair --import" command. Orchestrates resource
7
+ * import operations to fix orphaned resources by bringing them under CloudFormation management.
8
+ *
9
+ * Responsibilities:
10
+ * - Validate resources can be imported
11
+ * - Retrieve resource details from cloud
12
+ * - Generate CloudFormation template snippets
13
+ * - Execute import operations (single or batch)
14
+ * - Track import operation status
15
+ * - Map orphaned resources to correct logical IDs using template comparison
16
+ */
17
+
18
+ const { TemplateParser } = require('../../domain/services/template-parser');
19
+ const { LogicalIdMapper } = require('../../domain/services/logical-id-mapper');
20
+
21
+ class RepairViaImportUseCase {
22
+ /**
23
+ * Create use case with required dependencies
24
+ *
25
+ * @param {Object} params
26
+ * @param {IResourceImporter} params.resourceImporter - Resource import operations
27
+ * @param {IResourceDetector} params.resourceDetector - Resource discovery and details
28
+ * @param {IStackRepository} params.stackRepository - CloudFormation stack operations
29
+ * @param {TemplateParser} params.templateParser - CloudFormation template parsing
30
+ * @param {LogicalIdMapper} params.logicalIdMapper - Logical ID mapping service
31
+ */
32
+ constructor({ resourceImporter, resourceDetector, stackRepository, templateParser, logicalIdMapper }) {
33
+ if (!resourceImporter) {
34
+ throw new Error('resourceImporter is required');
35
+ }
36
+ if (!resourceDetector) {
37
+ throw new Error('resourceDetector is required');
38
+ }
39
+
40
+ this.resourceImporter = resourceImporter;
41
+ this.resourceDetector = resourceDetector;
42
+ this.stackRepository = stackRepository;
43
+ this.templateParser = templateParser || new TemplateParser();
44
+ this.logicalIdMapper = logicalIdMapper || new LogicalIdMapper({ region: 'us-east-1' });
45
+ }
46
+
47
+ /**
48
+ * Import a single orphaned resource into a CloudFormation stack
49
+ *
50
+ * @param {Object} params
51
+ * @param {StackIdentifier} params.stackIdentifier - Target stack
52
+ * @param {string} params.logicalId - Desired logical ID for resource in template
53
+ * @param {string} params.physicalId - Physical ID of resource in cloud
54
+ * @param {string} params.resourceType - CloudFormation resource type
55
+ * @returns {Promise<Object>} Import result
56
+ */
57
+ async importSingleResource({ stackIdentifier, logicalId, physicalId, resourceType }) {
58
+ // 1. Validate resource can be imported
59
+ const validation = await this.resourceImporter.validateImport({
60
+ resourceType,
61
+ physicalId,
62
+ region: stackIdentifier.region,
63
+ });
64
+
65
+ if (!validation.canImport) {
66
+ throw new Error(validation.reason);
67
+ }
68
+
69
+ // 2. Get detailed resource properties from cloud
70
+ const resourceDetails = await this.resourceDetector.getResourceDetails({
71
+ resourceType,
72
+ physicalId,
73
+ region: stackIdentifier.region,
74
+ });
75
+
76
+ // 3. Execute import operation
77
+ const importResult = await this.resourceImporter.importResource({
78
+ stackIdentifier,
79
+ logicalId,
80
+ resourceType,
81
+ physicalId,
82
+ properties: resourceDetails.properties,
83
+ });
84
+
85
+ // 4. Return result with warnings if any
86
+ return {
87
+ success: true,
88
+ operationId: importResult.operationId,
89
+ status: importResult.status,
90
+ message: importResult.message,
91
+ warnings: validation.warnings || [],
92
+ resource: {
93
+ logicalId,
94
+ physicalId,
95
+ resourceType,
96
+ },
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Import multiple orphaned resources into a stack in batch
102
+ *
103
+ * @param {Object} params
104
+ * @param {StackIdentifier} params.stackIdentifier - Target stack
105
+ * @param {Array<Object>} params.resources - Resources to import
106
+ * @param {string} params.resources[].logicalId - Logical ID
107
+ * @param {string} params.resources[].physicalId - Physical ID
108
+ * @param {string} params.resources[].resourceType - Resource type
109
+ * @returns {Promise<Object>} Batch import result
110
+ */
111
+ async importMultipleResources({ stackIdentifier, resources }) {
112
+ const validationErrors = [];
113
+ const validResources = [];
114
+
115
+ // 1. Validate all resources first
116
+ for (const resource of resources) {
117
+ try {
118
+ const validation = await this.resourceImporter.validateImport({
119
+ resourceType: resource.resourceType,
120
+ physicalId: resource.physicalId,
121
+ region: stackIdentifier.region,
122
+ });
123
+
124
+ if (!validation.canImport) {
125
+ validationErrors.push({
126
+ logicalId: resource.logicalId,
127
+ physicalId: resource.physicalId,
128
+ reason: validation.reason,
129
+ });
130
+ continue;
131
+ }
132
+
133
+ // Get resource details
134
+ const resourceDetails = await this.resourceDetector.getResourceDetails({
135
+ resourceType: resource.resourceType,
136
+ physicalId: resource.physicalId,
137
+ region: stackIdentifier.region,
138
+ });
139
+
140
+ validResources.push({
141
+ logicalId: resource.logicalId,
142
+ resourceType: resource.resourceType,
143
+ physicalId: resource.physicalId,
144
+ properties: resourceDetails.properties,
145
+ });
146
+ } catch (error) {
147
+ validationErrors.push({
148
+ logicalId: resource.logicalId,
149
+ physicalId: resource.physicalId,
150
+ reason: error.message,
151
+ });
152
+ }
153
+ }
154
+
155
+ // 2. If ANY validation failed, fail the entire batch (all-or-nothing approach)
156
+ if (validationErrors.length > 0) {
157
+ return {
158
+ success: false,
159
+ importedCount: 0,
160
+ failedCount: validationErrors.length,
161
+ validationErrors,
162
+ message: `${validationErrors.length} resource(s) failed validation - batch import aborted`,
163
+ };
164
+ }
165
+
166
+ // 3. All validations passed - import resources in batch
167
+ if (validResources.length > 0) {
168
+ const importResult = await this.resourceImporter.importMultipleResources({
169
+ stackIdentifier,
170
+ resources: validResources,
171
+ });
172
+
173
+ return {
174
+ success: true,
175
+ importedCount: importResult.importedCount,
176
+ failedCount: importResult.failedCount,
177
+ operationId: importResult.operationId,
178
+ status: importResult.status,
179
+ message: importResult.message,
180
+ details: importResult.details,
181
+ };
182
+ }
183
+
184
+ // 4. No resources provided
185
+ return {
186
+ success: false,
187
+ importedCount: 0,
188
+ failedCount: 0,
189
+ message: 'No resources provided for import',
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Get status of an ongoing import operation
195
+ *
196
+ * @param {Object} params
197
+ * @param {string} params.operationId - CloudFormation change set ID
198
+ * @returns {Promise<Object>} Operation status
199
+ */
200
+ async getImportStatus({ operationId }) {
201
+ return await this.resourceImporter.getImportStatus(operationId);
202
+ }
203
+
204
+ /**
205
+ * Preview what template changes will be made for an import
206
+ *
207
+ * @param {Object} params
208
+ * @param {StackIdentifier} params.stackIdentifier - Target stack
209
+ * @param {string} params.logicalId - Desired logical ID
210
+ * @param {string} params.physicalId - Physical resource ID
211
+ * @param {string} params.resourceType - Resource type
212
+ * @returns {Promise<Object>} Preview with template snippet
213
+ */
214
+ async previewImport({ stackIdentifier, logicalId, physicalId, resourceType }) {
215
+ // Get resource details from cloud
216
+ const resourceDetails = await this.resourceDetector.getResourceDetails({
217
+ resourceType,
218
+ physicalId,
219
+ region: stackIdentifier.region,
220
+ });
221
+
222
+ // Generate template snippet
223
+ const templateSnippet = await this.resourceImporter.generateTemplateSnippet({
224
+ logicalId,
225
+ resourceType,
226
+ properties: resourceDetails.properties,
227
+ });
228
+
229
+ return {
230
+ logicalId,
231
+ physicalId,
232
+ resourceType,
233
+ templateSnippet,
234
+ properties: resourceDetails.properties,
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Import orphaned resources with automatic logical ID mapping
240
+ * Uses template comparison to find correct logical IDs
241
+ *
242
+ * @param {Object} params
243
+ * @param {StackIdentifier} params.stackIdentifier - Target stack
244
+ * @param {Array} params.orphanedResources - Orphaned resources to import
245
+ * @param {string} params.buildTemplatePath - Path to .serverless/cloudformation-template-update-stack.json
246
+ * @returns {Promise<Object>} Import result with mappings
247
+ */
248
+ async importWithLogicalIdMapping({ stackIdentifier, orphanedResources, buildTemplatePath }) {
249
+ // 1. Validate build template exists
250
+ if (!buildTemplatePath) {
251
+ throw new Error('buildTemplatePath is required');
252
+ }
253
+
254
+ const fs = require('fs');
255
+ if (!fs.existsSync(buildTemplatePath)) {
256
+ throw new Error(
257
+ `Build template not found at: ${buildTemplatePath}\n\n` +
258
+ `Please run one of:\n` +
259
+ ` • serverless package\n` +
260
+ ` • frigg build\n` +
261
+ ` • frigg deploy --stage dev\n\n` +
262
+ `Then try again:\n` +
263
+ ` frigg repair --import ${stackIdentifier.stackName}`
264
+ );
265
+ }
266
+
267
+ // 2. Parse build template
268
+ const buildTemplate = this.templateParser.parseTemplate(buildTemplatePath);
269
+
270
+ // 3. Get deployed template from CloudFormation
271
+ if (!this.stackRepository) {
272
+ throw new Error('stackRepository is required for template comparison');
273
+ }
274
+
275
+ const deployedTemplate = await this.stackRepository.getTemplate(stackIdentifier);
276
+
277
+ // 4. Map orphaned resources to logical IDs
278
+ const mappings = await this.logicalIdMapper.mapOrphanedResourcesToLogicalIds({
279
+ orphanedResources,
280
+ buildTemplate,
281
+ deployedTemplate,
282
+ });
283
+
284
+ // 5. Filter out unmapped resources
285
+ const mappedResources = mappings.filter((m) => m.logicalId !== null);
286
+ const unmappedResources = mappings.filter((m) => m.logicalId === null);
287
+
288
+ if (mappedResources.length === 0) {
289
+ return {
290
+ success: false,
291
+ message: 'No resources could be mapped to logical IDs',
292
+ unmappedCount: unmappedResources.length,
293
+ unmappedResources,
294
+ };
295
+ }
296
+
297
+ // 6. Deduplicate: Select ONE resource per logical ID based on deployed template
298
+ const { selectedResources, duplicates } = this._deduplicateResourcesByLogicalId(
299
+ mappedResources,
300
+ deployedTemplate
301
+ );
302
+
303
+ // 7. Check for warnings
304
+ const multiResourceWarnings = this._checkForMultipleResources(duplicates);
305
+
306
+ // 8. Generate import-resources.json format using SELECTED resources
307
+ const resourcesToImport = selectedResources.map((mapping) => ({
308
+ ResourceType: mapping.resourceType,
309
+ LogicalResourceId: mapping.logicalId,
310
+ ResourceIdentifier: this._getResourceIdentifier(mapping),
311
+ }));
312
+
313
+ // 9. Return result with deduplication info
314
+ return {
315
+ success: true,
316
+ mappedCount: selectedResources.length,
317
+ unmappedCount: unmappedResources.length,
318
+ duplicatesRemoved: duplicates.length,
319
+ mappings: selectedResources,
320
+ unmappedResources,
321
+ duplicates, // Resources that were filtered out
322
+ resourcesToImport,
323
+ warnings: multiResourceWarnings,
324
+ buildTemplatePath,
325
+ deployedTemplatePath: 'CloudFormation (deployed)',
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Deduplicate resources: Select ONE resource per logical ID
331
+ * When multiple resources have the same logical ID, pick the one that's
332
+ * actually referenced in the deployed template.
333
+ *
334
+ * @param {Array} mappedResources - Resources with logical IDs
335
+ * @param {Object} deployedTemplate - Deployed CloudFormation template
336
+ * @returns {Object} { selectedResources, duplicates }
337
+ * @private
338
+ */
339
+ _deduplicateResourcesByLogicalId(mappedResources, deployedTemplate) {
340
+ // Group resources by logical ID
341
+ const byLogicalId = {};
342
+ mappedResources.forEach((resource) => {
343
+ if (!byLogicalId[resource.logicalId]) {
344
+ byLogicalId[resource.logicalId] = [];
345
+ }
346
+ byLogicalId[resource.logicalId].push(resource);
347
+ });
348
+
349
+ // Extract all physical IDs referenced in deployed template
350
+ const referencedIds = this._extractReferencedIdsFromTemplate(deployedTemplate);
351
+
352
+ const selectedResources = [];
353
+ const duplicates = [];
354
+
355
+ // For each logical ID, select ONE resource
356
+ Object.entries(byLogicalId).forEach(([logicalId, resources]) => {
357
+ if (resources.length === 1) {
358
+ // Only one resource - select it
359
+ selectedResources.push(resources[0]);
360
+ } else {
361
+ // Multiple resources - pick the one in deployed template
362
+ let selected = null;
363
+
364
+ // Try to find resource that's actually referenced
365
+ for (const resource of resources) {
366
+ if (this._isResourceReferenced(resource, referencedIds)) {
367
+ selected = resource;
368
+ break;
369
+ }
370
+ }
371
+
372
+ // Fallback: If none are referenced, pick first one
373
+ if (!selected) {
374
+ selected = resources[0];
375
+ }
376
+
377
+ selectedResources.push(selected);
378
+
379
+ // Mark others as duplicates
380
+ resources.forEach((r) => {
381
+ if (r.physicalId !== selected.physicalId) {
382
+ duplicates.push(r);
383
+ }
384
+ });
385
+ }
386
+ });
387
+
388
+ return { selectedResources, duplicates };
389
+ }
390
+
391
+ /**
392
+ * Extract all physical resource IDs referenced in deployed template
393
+ * Looks for hardcoded IDs in Lambda VPC configs, security group rules, etc.
394
+ * @private
395
+ */
396
+ _extractReferencedIdsFromTemplate(template) {
397
+ const referenced = {
398
+ vpcIds: new Set(),
399
+ subnetIds: new Set(),
400
+ securityGroupIds: new Set(),
401
+ };
402
+
403
+ if (!template || !template.resources) {
404
+ return referenced;
405
+ }
406
+
407
+ // Traverse all resources in template
408
+ Object.values(template.resources).forEach((resource) => {
409
+ // Lambda VPC config contains hardcoded IDs
410
+ if (
411
+ resource.Type === 'AWS::Lambda::Function' &&
412
+ resource.Properties?.VpcConfig
413
+ ) {
414
+ const { SubnetIds, SecurityGroupIds } = resource.Properties.VpcConfig;
415
+
416
+ if (SubnetIds) {
417
+ SubnetIds.forEach((id) => {
418
+ if (typeof id === 'string' && id.startsWith('subnet-')) {
419
+ referenced.subnetIds.add(id);
420
+ }
421
+ });
422
+ }
423
+
424
+ if (SecurityGroupIds) {
425
+ SecurityGroupIds.forEach((id) => {
426
+ if (typeof id === 'string' && id.startsWith('sg-')) {
427
+ referenced.securityGroupIds.add(id);
428
+ }
429
+ });
430
+ }
431
+ }
432
+
433
+ // Security group rules may reference other security groups
434
+ if (resource.Type === 'AWS::EC2::SecurityGroupIngress' ||
435
+ resource.Type === 'AWS::EC2::SecurityGroupEgress') {
436
+ const groupId = resource.Properties?.GroupId;
437
+ const sourceSecurityGroupId = resource.Properties?.SourceSecurityGroupId;
438
+
439
+ if (typeof groupId === 'string' && groupId.startsWith('sg-')) {
440
+ referenced.securityGroupIds.add(groupId);
441
+ }
442
+ if (typeof sourceSecurityGroupId === 'string' && sourceSecurityGroupId.startsWith('sg-')) {
443
+ referenced.securityGroupIds.add(sourceSecurityGroupId);
444
+ }
445
+ }
446
+ });
447
+
448
+ return referenced;
449
+ }
450
+
451
+ /**
452
+ * Check if a resource is referenced in the deployed template
453
+ * @private
454
+ */
455
+ _isResourceReferenced(resource, referencedIds) {
456
+ const { resourceType, physicalId } = resource;
457
+
458
+ if (resourceType === 'AWS::EC2::VPC') {
459
+ return referencedIds.vpcIds.has(physicalId);
460
+ }
461
+
462
+ if (resourceType === 'AWS::EC2::Subnet') {
463
+ return referencedIds.subnetIds.has(physicalId);
464
+ }
465
+
466
+ if (resourceType === 'AWS::EC2::SecurityGroup') {
467
+ return referencedIds.securityGroupIds.has(physicalId);
468
+ }
469
+
470
+ // For other resource types, we can't determine
471
+ return false;
472
+ }
473
+
474
+ /**
475
+ * Check for multiple resources of same type
476
+ * Returns warnings when user needs to manually select
477
+ * @private
478
+ */
479
+ _checkForMultipleResources(mappings) {
480
+ const warnings = [];
481
+ const byType = {};
482
+
483
+ // Group by resource type
484
+ mappings.forEach((mapping) => {
485
+ if (!byType[mapping.resourceType]) {
486
+ byType[mapping.resourceType] = [];
487
+ }
488
+ byType[mapping.resourceType].push(mapping);
489
+ });
490
+
491
+ // Check for multiples
492
+ Object.entries(byType).forEach(([type, resources]) => {
493
+ if (resources.length > 1) {
494
+ const shortType = type.replace('AWS::EC2::', '');
495
+ warnings.push({
496
+ type: 'MULTIPLE_RESOURCES',
497
+ resourceType: type,
498
+ count: resources.length,
499
+ message: `Multiple ${shortType}s detected (${resources.length}). Review relationships before importing.`,
500
+ resources: resources.map((r) => ({
501
+ physicalId: r.physicalId,
502
+ logicalId: r.logicalId,
503
+ matchMethod: r.matchMethod,
504
+ confidence: r.confidence,
505
+ })),
506
+ });
507
+ }
508
+ });
509
+
510
+ return warnings;
511
+ }
512
+
513
+ /**
514
+ * Get CloudFormation resource identifier for import
515
+ * @private
516
+ */
517
+ _getResourceIdentifier(mapping) {
518
+ const { resourceType, physicalId } = mapping;
519
+
520
+ // Map resource types to their identifier format
521
+ const identifierMap = {
522
+ 'AWS::EC2::VPC': { VpcId: physicalId },
523
+ 'AWS::EC2::Subnet': { SubnetId: physicalId },
524
+ 'AWS::EC2::SecurityGroup': { GroupId: physicalId },
525
+ 'AWS::EC2::InternetGateway': { InternetGatewayId: physicalId },
526
+ 'AWS::EC2::NatGateway': { NatGatewayId: physicalId },
527
+ 'AWS::EC2::RouteTable': { RouteTableId: physicalId },
528
+ 'AWS::EC2::VPCEndpoint': { VpcEndpointId: physicalId },
529
+ };
530
+
531
+ return identifierMap[resourceType] || { Id: physicalId };
532
+ }
533
+ }
534
+
535
+ module.exports = RepairViaImportUseCase;