@friggframework/devtools 2.0.0--canary.517.179491e.0 → 2.0.0--canary.522.893db5d.0
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.
- package/frigg-cli/README.md +1 -1
- package/frigg-cli/__tests__/application/use-cases/AddApiModuleToIntegrationUseCase.test.js +326 -0
- package/frigg-cli/__tests__/application/use-cases/CreateApiModuleUseCase.test.js +337 -0
- package/frigg-cli/__tests__/domain/entities/ApiModule.test.js +373 -0
- package/frigg-cli/__tests__/domain/entities/AppDefinition.test.js +313 -0
- package/frigg-cli/__tests__/domain/services/IntegrationValidator.test.js +269 -0
- package/frigg-cli/__tests__/domain/value-objects/IntegrationName.test.js +82 -0
- package/frigg-cli/__tests__/infrastructure/adapters/IntegrationJsUpdater.test.js +408 -0
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemApiModuleRepository.test.js +583 -0
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemAppDefinitionRepository.test.js +314 -0
- package/frigg-cli/__tests__/infrastructure/repositories/FileSystemIntegrationRepository.test.js +383 -0
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +0 -2
- package/frigg-cli/__tests__/unit/commands/init.test.js +406 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +23 -19
- package/frigg-cli/__tests__/unit/commands/repair.test.js +275 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +2 -2
- package/frigg-cli/__tests__/unit/start-command/application/RunPreflightChecksUseCase.test.js +411 -0
- package/frigg-cli/__tests__/unit/start-command/infrastructure/DatabaseAdapter.test.js +405 -0
- package/frigg-cli/__tests__/unit/start-command/infrastructure/DockerAdapter.test.js +496 -0
- package/frigg-cli/__tests__/unit/start-command/presentation/InteractivePromptAdapter.test.js +474 -0
- package/frigg-cli/__tests__/unit/utils/output.test.js +196 -0
- package/frigg-cli/application/use-cases/AddApiModuleToIntegrationUseCase.js +93 -0
- package/frigg-cli/application/use-cases/CreateApiModuleUseCase.js +93 -0
- package/frigg-cli/application/use-cases/CreateIntegrationUseCase.js +103 -0
- package/frigg-cli/container.js +172 -0
- package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +286 -0
- package/frigg-cli/doctor-command/index.js +17 -16
- package/frigg-cli/domain/entities/ApiModule.js +272 -0
- package/frigg-cli/domain/entities/AppDefinition.js +227 -0
- package/frigg-cli/domain/entities/Integration.js +198 -0
- package/frigg-cli/domain/exceptions/DomainException.js +24 -0
- package/frigg-cli/domain/ports/IApiModuleRepository.js +53 -0
- package/frigg-cli/domain/ports/IAppDefinitionRepository.js +43 -0
- package/frigg-cli/domain/ports/IIntegrationRepository.js +61 -0
- package/frigg-cli/domain/services/IntegrationValidator.js +185 -0
- package/frigg-cli/domain/value-objects/IntegrationId.js +42 -0
- package/frigg-cli/domain/value-objects/IntegrationName.js +60 -0
- package/frigg-cli/domain/value-objects/SemanticVersion.js +70 -0
- package/frigg-cli/index.js +21 -6
- package/frigg-cli/index.test.js +7 -2
- package/frigg-cli/infrastructure/UnitOfWork.js +46 -0
- package/frigg-cli/infrastructure/adapters/BackendJsUpdater.js +197 -0
- package/frigg-cli/infrastructure/adapters/FileSystemAdapter.js +224 -0
- package/frigg-cli/infrastructure/adapters/IntegrationJsUpdater.js +249 -0
- package/frigg-cli/infrastructure/adapters/SchemaValidator.js +92 -0
- package/frigg-cli/infrastructure/repositories/FileSystemApiModuleRepository.js +373 -0
- package/frigg-cli/infrastructure/repositories/FileSystemAppDefinitionRepository.js +116 -0
- package/frigg-cli/infrastructure/repositories/FileSystemIntegrationRepository.js +277 -0
- package/frigg-cli/init-command/backend-first-handler.js +124 -42
- package/frigg-cli/init-command/index.js +2 -1
- package/frigg-cli/init-command/template-handler.js +13 -3
- package/frigg-cli/install-command/backend-js.js +3 -3
- package/frigg-cli/install-command/environment-variables.js +16 -19
- package/frigg-cli/install-command/environment-variables.test.js +12 -13
- package/frigg-cli/install-command/index.js +14 -9
- package/frigg-cli/install-command/integration-file.js +3 -3
- package/frigg-cli/install-command/validate-package.js +5 -9
- package/frigg-cli/jest.config.js +4 -1
- package/frigg-cli/package-lock.json +16226 -0
- package/frigg-cli/repair-command/index.js +101 -128
- package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +376 -0
- package/frigg-cli/start-command/index.js +246 -2
- package/frigg-cli/start-command/infrastructure/DatabaseAdapter.js +591 -0
- package/frigg-cli/start-command/infrastructure/DockerAdapter.js +306 -0
- package/frigg-cli/start-command/presentation/InteractivePromptAdapter.js +329 -0
- package/frigg-cli/templates/backend/.env.example +62 -0
- package/frigg-cli/templates/backend/.eslintrc.json +12 -0
- package/frigg-cli/templates/backend/.prettierrc +6 -0
- package/frigg-cli/templates/backend/docker-compose.yml +22 -0
- package/frigg-cli/templates/backend/index.js +96 -0
- package/frigg-cli/templates/backend/infrastructure.js +12 -0
- package/frigg-cli/templates/backend/jest.config.js +17 -0
- package/frigg-cli/templates/backend/package.json +50 -0
- package/frigg-cli/templates/backend/src/api-modules/.gitkeep +10 -0
- package/frigg-cli/templates/backend/src/base/.gitkeep +7 -0
- package/frigg-cli/templates/backend/src/integrations/.gitkeep +10 -0
- package/frigg-cli/templates/backend/src/integrations/ExampleIntegration.js +65 -0
- package/frigg-cli/templates/backend/src/utils/.gitkeep +7 -0
- package/frigg-cli/templates/backend/test/setup.js +30 -0
- package/frigg-cli/templates/backend/ui-extensions/.gitkeep +0 -0
- package/frigg-cli/templates/backend/ui-extensions/README.md +77 -0
- package/frigg-cli/ui-command/index.js +58 -36
- package/frigg-cli/utils/__tests__/repo-detection.test.js +436 -0
- package/frigg-cli/utils/output.js +382 -0
- package/frigg-cli/utils/repo-detection.js +85 -37
- package/frigg-cli/validate-command/__tests__/adapters/validate-command.test.js +205 -0
- package/frigg-cli/validate-command/__tests__/application/validate-app-use-case.test.js +104 -0
- package/frigg-cli/validate-command/__tests__/domain/fix-suggestion.test.js +153 -0
- package/frigg-cli/validate-command/__tests__/domain/validation-error.test.js +162 -0
- package/frigg-cli/validate-command/__tests__/domain/validation-result.test.js +152 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/api-module-validator.test.js +332 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/app-definition-validator.test.js +191 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/integration-class-validator.test.js +146 -0
- package/frigg-cli/validate-command/__tests__/infrastructure/template-validation.test.js +155 -0
- package/frigg-cli/validate-command/adapters/cli/validate-command.js +199 -0
- package/frigg-cli/validate-command/application/use-cases/validate-app-use-case.js +35 -0
- package/frigg-cli/validate-command/domain/entities/validation-result.js +74 -0
- package/frigg-cli/validate-command/domain/value-objects/fix-suggestion.js +74 -0
- package/frigg-cli/validate-command/domain/value-objects/validation-error.js +68 -0
- package/frigg-cli/validate-command/infrastructure/validators/api-module-validator.js +181 -0
- package/frigg-cli/validate-command/infrastructure/validators/app-definition-validator.js +128 -0
- package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +113 -0
- package/infrastructure/docs/iam-policy-templates.md +1 -1
- package/infrastructure/domains/networking/vpc-builder.test.js +2 -4
- package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +4 -7
- package/infrastructure/domains/shared/resource-discovery.js +5 -5
- package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +10 -1
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
- package/infrastructure/infrastructure-composer.test.js +2 -2
- package/infrastructure/jest.config.js +16 -0
- package/management-ui/README.md +245 -109
- package/package.json +8 -7
- package/frigg-cli/install-command/logger.js +0 -12
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const path = require('path');
|
|
15
|
-
const
|
|
15
|
+
const output = require('../utils/output');
|
|
16
16
|
|
|
17
17
|
// Domain and Application Layer
|
|
18
18
|
const StackIdentifier = require('../../infrastructure/domains/health/domain/value-objects/stack-identifier');
|
|
@@ -34,33 +34,6 @@ const { TemplateParser } = require('../../infrastructure/domains/health/domain/s
|
|
|
34
34
|
const { ImportTemplateGenerator } = require('../../infrastructure/domains/health/domain/services/import-template-generator');
|
|
35
35
|
const { ImportProgressMonitor } = require('../../infrastructure/domains/health/domain/services/import-progress-monitor');
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* Create readline interface for user prompts
|
|
39
|
-
* @returns {readline.Interface}
|
|
40
|
-
*/
|
|
41
|
-
function createReadlineInterface() {
|
|
42
|
-
return readline.createInterface({
|
|
43
|
-
input: process.stdin,
|
|
44
|
-
output: process.stdout,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Prompt user for confirmation
|
|
50
|
-
* @param {string} question - Question to ask
|
|
51
|
-
* @returns {Promise<boolean>} User confirmed
|
|
52
|
-
*/
|
|
53
|
-
function confirm(question) {
|
|
54
|
-
const rl = createReadlineInterface();
|
|
55
|
-
|
|
56
|
-
return new Promise((resolve) => {
|
|
57
|
-
rl.question(`${question} (y/N): `, (answer) => {
|
|
58
|
-
rl.close();
|
|
59
|
-
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
37
|
/**
|
|
65
38
|
* Handle import repair operation using template comparison
|
|
66
39
|
* @param {StackIdentifier} stackIdentifier - Stack identifier
|
|
@@ -71,13 +44,13 @@ async function handleImportRepair(stackIdentifier, report, options) {
|
|
|
71
44
|
const orphanedResources = report.getOrphanedResources();
|
|
72
45
|
|
|
73
46
|
if (orphanedResources.length === 0) {
|
|
74
|
-
|
|
47
|
+
output.success(' No orphaned resources to import');
|
|
75
48
|
return { imported: 0, failed: 0 };
|
|
76
49
|
}
|
|
77
50
|
|
|
78
|
-
|
|
51
|
+
output.info(`📦 Found ${orphanedResources.length} orphaned resource(s) to import:`);
|
|
79
52
|
orphanedResources.forEach((resource, idx) => {
|
|
80
|
-
|
|
53
|
+
output.log(` ${idx + 1}. ${resource.resourceType} - ${resource.physicalId}`);
|
|
81
54
|
});
|
|
82
55
|
|
|
83
56
|
// Check for build template
|
|
@@ -85,12 +58,12 @@ async function handleImportRepair(stackIdentifier, report, options) {
|
|
|
85
58
|
const buildTemplateExists = TemplateParser.buildTemplateExists();
|
|
86
59
|
|
|
87
60
|
if (!buildTemplateExists) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
61
|
+
output.warn('️ Build template not found. Generating sequential logical IDs (not recommended).');
|
|
62
|
+
output.log(` Run one of the following to generate build template:`);
|
|
63
|
+
output.log(` • serverless package`);
|
|
64
|
+
output.log(` • frigg build`);
|
|
65
|
+
output.log(` • frigg deploy --stage dev`);
|
|
66
|
+
output.log(` Then run 'frigg repair --import ${stackIdentifier.stackName}' again for correct logical IDs.\n`);
|
|
94
67
|
|
|
95
68
|
// Fallback to sequential IDs (old behavior)
|
|
96
69
|
const resourcesToImport = orphanedResources.map((resource, idx) => ({
|
|
@@ -100,9 +73,9 @@ async function handleImportRepair(stackIdentifier, report, options) {
|
|
|
100
73
|
}));
|
|
101
74
|
|
|
102
75
|
if (!options.yes) {
|
|
103
|
-
const confirmed = await confirm(`\nImport ${orphanedResources.length} orphaned resource(s) with sequential IDs?`);
|
|
76
|
+
const confirmed = await output.confirm(`\nImport ${orphanedResources.length} orphaned resource(s) with sequential IDs?`);
|
|
104
77
|
if (!confirmed) {
|
|
105
|
-
|
|
78
|
+
output.log('Import cancelled');
|
|
106
79
|
return { imported: 0, failed: 0, cancelled: true };
|
|
107
80
|
}
|
|
108
81
|
}
|
|
@@ -111,20 +84,20 @@ async function handleImportRepair(stackIdentifier, report, options) {
|
|
|
111
84
|
const resourceImporter = new AWSResourceImporter({ region: stackIdentifier.region });
|
|
112
85
|
const repairUseCase = new RepairViaImportUseCase({ resourceDetector, resourceImporter });
|
|
113
86
|
|
|
114
|
-
|
|
87
|
+
output.info('🔧 Importing resources with sequential IDs...');
|
|
115
88
|
const importResult = await repairUseCase.importMultipleResources({
|
|
116
89
|
stackIdentifier,
|
|
117
90
|
resources: resourcesToImport,
|
|
118
91
|
});
|
|
119
92
|
|
|
120
93
|
if (importResult.success) {
|
|
121
|
-
|
|
94
|
+
output.success(` Successfully imported ${importResult.importedCount} resource(s)`);
|
|
122
95
|
} else {
|
|
123
|
-
|
|
96
|
+
output.log(`\n✗ Import failed: ${importResult.message}`);
|
|
124
97
|
if (importResult.validationErrors && importResult.validationErrors.length > 0) {
|
|
125
|
-
|
|
98
|
+
output.log('\nValidation errors:');
|
|
126
99
|
importResult.validationErrors.forEach((error) => {
|
|
127
|
-
|
|
100
|
+
output.log(` • ${error.logicalId}: ${error.reason}`);
|
|
128
101
|
});
|
|
129
102
|
}
|
|
130
103
|
}
|
|
@@ -137,9 +110,9 @@ async function handleImportRepair(stackIdentifier, report, options) {
|
|
|
137
110
|
}
|
|
138
111
|
|
|
139
112
|
// Use template comparison to find correct logical IDs
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
113
|
+
output.info(`🔍 Analyzing templates to map orphaned resources to correct logical IDs...`);
|
|
114
|
+
output.log(` Build template: ${buildTemplatePath}`);
|
|
115
|
+
output.log(` Deployed template: CloudFormation (via AWS API)`);
|
|
143
116
|
|
|
144
117
|
// Wire up use case with template comparison
|
|
145
118
|
const stackRepository = new AWSStackRepository({ region: stackIdentifier.region });
|
|
@@ -159,31 +132,31 @@ async function handleImportRepair(stackIdentifier, report, options) {
|
|
|
159
132
|
});
|
|
160
133
|
|
|
161
134
|
if (!mappingResult.success) {
|
|
162
|
-
|
|
135
|
+
output.log(`\n✗ Mapping failed: ${mappingResult.message}`);
|
|
163
136
|
return { imported: 0, failed: 0, success: false };
|
|
164
137
|
}
|
|
165
138
|
|
|
166
139
|
// Display mapping results
|
|
167
|
-
|
|
140
|
+
output.success(` Successfully mapped ${mappingResult.mappedCount} resource(s) to logical IDs:`);
|
|
168
141
|
mappingResult.mappings.forEach((mapping) => {
|
|
169
|
-
|
|
142
|
+
output.log(` • ${mapping.logicalId} ← ${mapping.physicalId} (${mapping.matchMethod}, ${mapping.confidence} confidence)`);
|
|
170
143
|
});
|
|
171
144
|
|
|
172
145
|
if (mappingResult.unmappedCount > 0) {
|
|
173
|
-
|
|
146
|
+
output.warn(`️ Could not map ${mappingResult.unmappedCount} resource(s):`);
|
|
174
147
|
mappingResult.unmappedResources.forEach((resource) => {
|
|
175
|
-
|
|
148
|
+
output.log(` • ${resource.resourceType} - ${resource.physicalId}`);
|
|
176
149
|
});
|
|
177
150
|
}
|
|
178
151
|
|
|
179
152
|
// Display warnings for multiple resources of same type
|
|
180
153
|
if (mappingResult.warnings && mappingResult.warnings.length > 0) {
|
|
181
|
-
|
|
154
|
+
output.warn(`️ Warnings:`);
|
|
182
155
|
mappingResult.warnings.forEach((warning) => {
|
|
183
|
-
|
|
156
|
+
output.log(` • ${warning.message}`);
|
|
184
157
|
if (warning.type === 'MULTIPLE_RESOURCES') {
|
|
185
158
|
warning.resources.forEach((res) => {
|
|
186
|
-
|
|
159
|
+
output.log(` - ${res.logicalId} ← ${res.physicalId} (${res.matchMethod}, ${res.confidence})`);
|
|
187
160
|
});
|
|
188
161
|
}
|
|
189
162
|
});
|
|
@@ -191,20 +164,20 @@ async function handleImportRepair(stackIdentifier, report, options) {
|
|
|
191
164
|
|
|
192
165
|
// Confirm with user (unless --yes flag)
|
|
193
166
|
if (!options.yes) {
|
|
194
|
-
|
|
167
|
+
output.info(`📋 The following will be imported into CloudFormation:`);
|
|
195
168
|
mappingResult.resourcesToImport.forEach((resource) => {
|
|
196
|
-
|
|
169
|
+
output.log(` • ${resource.LogicalResourceId} (${resource.ResourceType})`);
|
|
197
170
|
});
|
|
198
171
|
|
|
199
|
-
const confirmed = await confirm(`\nProceed with import of ${mappingResult.mappedCount} resource(s)?`);
|
|
172
|
+
const confirmed = await output.confirm(`\nProceed with import of ${mappingResult.mappedCount} resource(s)?`);
|
|
200
173
|
if (!confirmed) {
|
|
201
|
-
|
|
174
|
+
output.log('Import cancelled');
|
|
202
175
|
return { imported: 0, failed: 0, cancelled: true };
|
|
203
176
|
}
|
|
204
177
|
}
|
|
205
178
|
|
|
206
179
|
// Execute actual CloudFormation import operation
|
|
207
|
-
|
|
180
|
+
output.info(`🔧 Preparing CloudFormation import operation...`);
|
|
208
181
|
|
|
209
182
|
// Wire up ExecuteResourceImportUseCase
|
|
210
183
|
const templateParser = new TemplateParser();
|
|
@@ -237,38 +210,38 @@ async function handleImportRepair(stackIdentifier, report, options) {
|
|
|
237
210
|
buildTemplatePath,
|
|
238
211
|
onProgress: (progress) => {
|
|
239
212
|
if (progress.step === 'generate_template' && progress.status === 'in_progress') {
|
|
240
|
-
|
|
213
|
+
output.log(' • Generating import template...');
|
|
241
214
|
} else if (progress.step === 'generate_template' && progress.status === 'complete') {
|
|
242
|
-
|
|
215
|
+
output.log(' ✓ Template generated');
|
|
243
216
|
} else if (progress.step === 'create_change_set' && progress.status === 'in_progress') {
|
|
244
|
-
|
|
217
|
+
output.log(' • Creating CloudFormation change set...');
|
|
245
218
|
} else if (progress.step === 'create_change_set' && progress.status === 'complete') {
|
|
246
|
-
|
|
219
|
+
output.log(` ✓ Change set created: ${progress.changeSetName}`);
|
|
247
220
|
} else if (progress.step === 'wait_change_set' && progress.status === 'in_progress') {
|
|
248
|
-
|
|
221
|
+
output.log(' • Waiting for change set...');
|
|
249
222
|
} else if (progress.step === 'wait_change_set' && progress.status === 'complete') {
|
|
250
|
-
|
|
223
|
+
output.log(' ✓ Change set ready');
|
|
251
224
|
} else if (progress.step === 'execute_import' && progress.status === 'in_progress') {
|
|
252
225
|
if (progress.resourceProgress) {
|
|
253
226
|
const { logicalId, status, progress: resourceProgress, total } = progress.resourceProgress;
|
|
254
|
-
|
|
227
|
+
output.log(` • Importing resource ${resourceProgress}/${total}: ${logicalId} (${status})`);
|
|
255
228
|
} else {
|
|
256
|
-
|
|
229
|
+
output.log(' • Executing import operation...');
|
|
257
230
|
}
|
|
258
231
|
} else if (progress.step === 'execute_import' && progress.status === 'complete') {
|
|
259
|
-
|
|
232
|
+
output.log(' ✓ Import operation complete');
|
|
260
233
|
} else if (progress.step === 'verify' && progress.status === 'in_progress') {
|
|
261
|
-
|
|
234
|
+
output.log(' • Verifying imported resources...');
|
|
262
235
|
} else if (progress.step === 'verify' && progress.status === 'complete') {
|
|
263
|
-
|
|
236
|
+
output.log(' ✓ Verification complete');
|
|
264
237
|
}
|
|
265
238
|
},
|
|
266
239
|
});
|
|
267
240
|
|
|
268
241
|
if (importResult.success) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
242
|
+
output.success(` Successfully imported ${importResult.importedCount} resource(s) into CloudFormation!`);
|
|
243
|
+
output.log(` Stack status: ${importResult.stackStatus}`);
|
|
244
|
+
output.log(` Change set: ${importResult.changeSetName}`);
|
|
272
245
|
|
|
273
246
|
return {
|
|
274
247
|
imported: importResult.importedCount,
|
|
@@ -276,8 +249,8 @@ async function handleImportRepair(stackIdentifier, report, options) {
|
|
|
276
249
|
success: true,
|
|
277
250
|
};
|
|
278
251
|
} else {
|
|
279
|
-
|
|
280
|
-
|
|
252
|
+
output.error(` Import operation failed: ${importResult.error}`);
|
|
253
|
+
output.error(` Failed at step: ${importResult.step}`);
|
|
281
254
|
|
|
282
255
|
return {
|
|
283
256
|
imported: 0,
|
|
@@ -298,7 +271,7 @@ async function handleReconcileRepair(stackIdentifier, report, options) {
|
|
|
298
271
|
const driftedResources = report.getDriftedResources();
|
|
299
272
|
|
|
300
273
|
if (driftedResources.length === 0) {
|
|
301
|
-
|
|
274
|
+
output.success(' No property drift to reconcile');
|
|
302
275
|
return { reconciled: 0, failed: 0 };
|
|
303
276
|
}
|
|
304
277
|
|
|
@@ -311,12 +284,12 @@ async function handleReconcileRepair(stackIdentifier, report, options) {
|
|
|
311
284
|
totalMismatches += issues.length;
|
|
312
285
|
});
|
|
313
286
|
|
|
314
|
-
|
|
287
|
+
output.info(`🔧 Found ${driftedResources.length} drifted resource(s) with ${totalMismatches} property mismatch(es):`);
|
|
315
288
|
driftedResources.forEach((resource) => {
|
|
316
289
|
const issues = report.issues.filter(
|
|
317
290
|
(issue) => issue.type === 'PROPERTY_MISMATCH' && issue.resourceId === resource.physicalId
|
|
318
291
|
);
|
|
319
|
-
|
|
292
|
+
output.log(` • ${resource.logicalId} (${resource.resourceType}): ${issues.length} mismatch(es)`);
|
|
320
293
|
});
|
|
321
294
|
|
|
322
295
|
// Determine mode (template or resource)
|
|
@@ -325,14 +298,14 @@ async function handleReconcileRepair(stackIdentifier, report, options) {
|
|
|
325
298
|
? 'Update CloudFormation template to match actual resource state'
|
|
326
299
|
: 'Update cloud resources to match CloudFormation template';
|
|
327
300
|
|
|
328
|
-
|
|
329
|
-
|
|
301
|
+
output.log(`\nReconciliation mode: ${mode}`);
|
|
302
|
+
output.log(` ${modeDescription}`);
|
|
330
303
|
|
|
331
304
|
// Confirm with user (unless --yes flag)
|
|
332
305
|
if (!options.yes) {
|
|
333
|
-
const confirmed = await confirm(`\nReconcile ${totalMismatches} property mismatch(es) in ${mode} mode?`);
|
|
306
|
+
const confirmed = await output.confirm(`\nReconcile ${totalMismatches} property mismatch(es) in ${mode} mode?`);
|
|
334
307
|
if (!confirmed) {
|
|
335
|
-
|
|
308
|
+
output.log('Reconciliation cancelled');
|
|
336
309
|
return { reconciled: 0, failed: 0, cancelled: true };
|
|
337
310
|
}
|
|
338
311
|
}
|
|
@@ -346,7 +319,7 @@ async function handleReconcileRepair(stackIdentifier, report, options) {
|
|
|
346
319
|
const reconcileUseCase = new ReconcilePropertiesUseCase({ propertyReconciler });
|
|
347
320
|
|
|
348
321
|
// Execute reconciliation for each drifted resource
|
|
349
|
-
|
|
322
|
+
output.info('🔧 Reconciling property drift...');
|
|
350
323
|
let reconciledCount = 0;
|
|
351
324
|
let failedCount = 0;
|
|
352
325
|
let skippedImmutableCount = 0;
|
|
@@ -391,56 +364,56 @@ async function handleReconcileRepair(stackIdentifier, report, options) {
|
|
|
391
364
|
});
|
|
392
365
|
}
|
|
393
366
|
|
|
394
|
-
|
|
367
|
+
output.log(` ✓ ${resource.logicalId}: Reconciled ${result.reconciledCount} property(ies)`);
|
|
395
368
|
if (result.skippedCount > 0) {
|
|
396
|
-
|
|
369
|
+
output.log(` ⚠ Skipped ${result.skippedCount} immutable property(ies) - requires manual intervention`);
|
|
397
370
|
}
|
|
398
371
|
|
|
399
372
|
// Debug: Log full result if reconciledCount is 0 but we expected properties
|
|
400
373
|
if (process.env.DEBUG_RECONCILE && result.reconciledCount === 0 && mismatches.length > 0) {
|
|
401
|
-
|
|
374
|
+
output.log(` [DEBUG] Expected ${mismatches.length} mismatches, got result:`, JSON.stringify(result, null, 2));
|
|
402
375
|
}
|
|
403
376
|
} catch (error) {
|
|
404
377
|
// Count failed properties, not just the resource
|
|
405
378
|
failedCount += mismatches.length;
|
|
406
|
-
|
|
379
|
+
output.log(` ✗ ${resource.logicalId}: ${error.message}`);
|
|
407
380
|
|
|
408
381
|
// Debug: Log full error
|
|
409
382
|
if (process.env.DEBUG_RECONCILE) {
|
|
410
|
-
|
|
383
|
+
output.log(` [DEBUG] Error stack:`, error.stack);
|
|
411
384
|
}
|
|
412
385
|
}
|
|
413
386
|
}
|
|
414
387
|
|
|
415
388
|
// Report results
|
|
416
|
-
|
|
389
|
+
output.log(''); // Blank line before summary
|
|
417
390
|
|
|
418
391
|
if (reconciledCount > 0) {
|
|
419
|
-
|
|
392
|
+
output.log(`✅ Reconciled ${reconciledCount} property(ies)`);
|
|
420
393
|
}
|
|
421
394
|
|
|
422
395
|
if (skippedImmutableCount > 0) {
|
|
423
|
-
|
|
396
|
+
output.warn(` ${skippedImmutableCount} immutable property(ies) require manual intervention:`);
|
|
424
397
|
immutableProperties.forEach(prop => {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
398
|
+
output.log(` • ${prop.logicalId}.${prop.propertyPath}`);
|
|
399
|
+
output.log(` Template: ${JSON.stringify(prop.expectedValue)}`);
|
|
400
|
+
output.log(` Actual: ${JSON.stringify(prop.actualValue)}`);
|
|
428
401
|
});
|
|
429
402
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
403
|
+
output.info(`💡 To resolve immutable property drift:`);
|
|
404
|
+
output.log(` 1. These properties require resource replacement (cannot be updated in place)`);
|
|
405
|
+
output.log(` 2. Options:`);
|
|
406
|
+
output.log(` a) Accept the drift - update your local template to match actual values`);
|
|
407
|
+
output.log(` b) Replace the resource - delete and recreate via CloudFormation`);
|
|
408
|
+
output.log(` c) Use import workflow - remove from stack, then re-import with correct values`);
|
|
409
|
+
output.log(`\n For automated import workflow (coming soon):`);
|
|
410
|
+
output.log(` frigg repair --import-drift ${stackIdentifier.stackName}`);
|
|
438
411
|
}
|
|
439
412
|
|
|
440
413
|
if (failedCount === 0 && skippedImmutableCount === 0) {
|
|
441
|
-
|
|
414
|
+
output.log(`✓ Successfully reconciled all ${reconciledCount} property mismatch(es)`);
|
|
442
415
|
} else {
|
|
443
|
-
|
|
416
|
+
output.warn(` Reconciled ${reconciledCount} property(ies), ${failedCount} failed`);
|
|
444
417
|
}
|
|
445
418
|
|
|
446
419
|
return { reconciled: reconciledCount, failed: failedCount, success: failedCount === 0 };
|
|
@@ -455,19 +428,19 @@ async function repairCommand(stackName, options = {}) {
|
|
|
455
428
|
try {
|
|
456
429
|
// Validate required parameter
|
|
457
430
|
if (!stackName) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
431
|
+
output.error('Error: Stack name is required');
|
|
432
|
+
output.log('Usage: frigg repair [options] <stack-name>');
|
|
433
|
+
output.log('Options:');
|
|
434
|
+
output.log(' --import Import orphaned resources');
|
|
435
|
+
output.log(' --reconcile Reconcile property drift');
|
|
436
|
+
output.log(' --yes Skip confirmation prompts');
|
|
464
437
|
process.exit(1);
|
|
465
438
|
}
|
|
466
439
|
|
|
467
440
|
// Validate at least one repair operation is selected
|
|
468
441
|
if (!options.import && !options.reconcile) {
|
|
469
|
-
|
|
470
|
-
|
|
442
|
+
output.error('Error: At least one repair operation must be specified (--import or --reconcile)');
|
|
443
|
+
output.log('Usage: frigg repair [options] <stack-name>');
|
|
471
444
|
process.exit(1);
|
|
472
445
|
}
|
|
473
446
|
|
|
@@ -475,13 +448,13 @@ async function repairCommand(stackName, options = {}) {
|
|
|
475
448
|
const region = options.region || process.env.AWS_REGION || 'us-east-1';
|
|
476
449
|
const verbose = options.verbose || false;
|
|
477
450
|
|
|
478
|
-
|
|
451
|
+
output.info(`🏥 Running Frigg Repair on stack: ${stackName} (${region})`);
|
|
479
452
|
|
|
480
453
|
// 1. Create stack identifier
|
|
481
454
|
const stackIdentifier = new StackIdentifier({ stackName, region });
|
|
482
455
|
|
|
483
456
|
// 2. Run health check first to identify issues
|
|
484
|
-
|
|
457
|
+
output.info('🔍 Running health check to identify issues...');
|
|
485
458
|
|
|
486
459
|
const stackRepository = new AWSStackRepository({ region });
|
|
487
460
|
const resourceDetector = new AWSResourceDetector({ region });
|
|
@@ -497,8 +470,8 @@ async function repairCommand(stackName, options = {}) {
|
|
|
497
470
|
|
|
498
471
|
const report = await runHealthCheckUseCase.execute({ stackIdentifier });
|
|
499
472
|
|
|
500
|
-
|
|
501
|
-
|
|
473
|
+
output.log(`\nHealth Score: ${report.healthScore.value}/100 (${report.healthScore.qualitativeAssessment()})`);
|
|
474
|
+
output.log(`Issues: ${report.getIssueCount()} total (${report.getCriticalIssueCount()} critical)`);
|
|
502
475
|
|
|
503
476
|
// 3. Execute requested repair operations
|
|
504
477
|
const results = {
|
|
@@ -524,24 +497,24 @@ async function repairCommand(stackName, options = {}) {
|
|
|
524
497
|
}
|
|
525
498
|
|
|
526
499
|
// 4. Final summary
|
|
527
|
-
|
|
528
|
-
|
|
500
|
+
output.log('\n' + '═'.repeat(80));
|
|
501
|
+
output.log('Repair Summary:');
|
|
529
502
|
if (options.import) {
|
|
530
|
-
|
|
503
|
+
output.log(` Imported: ${results.imported} resource(s)`);
|
|
531
504
|
}
|
|
532
505
|
if (options.reconcile) {
|
|
533
|
-
|
|
506
|
+
output.log(` Reconciled: ${results.reconciled} property(ies)`);
|
|
534
507
|
}
|
|
535
|
-
|
|
536
|
-
|
|
508
|
+
output.log(` Failed: ${results.failed}`);
|
|
509
|
+
output.log('═'.repeat(80));
|
|
537
510
|
|
|
538
511
|
// Run health check again to verify repairs
|
|
539
|
-
|
|
512
|
+
output.info('🔍 Running health check to verify repairs...');
|
|
540
513
|
const verifyReport = await runHealthCheckUseCase.execute({ stackIdentifier });
|
|
541
|
-
|
|
514
|
+
output.log(`\nNew Health Score: ${verifyReport.healthScore.value}/100 (${verifyReport.healthScore.qualitativeAssessment()})`);
|
|
542
515
|
|
|
543
516
|
if (verifyReport.healthScore.value > report.healthScore.value) {
|
|
544
|
-
|
|
517
|
+
output.success(` Health improved by ${verifyReport.healthScore.value - report.healthScore.value} points!`);
|
|
545
518
|
}
|
|
546
519
|
|
|
547
520
|
// 5. Exit with appropriate code
|
|
@@ -551,10 +524,10 @@ async function repairCommand(stackName, options = {}) {
|
|
|
551
524
|
process.exit(0);
|
|
552
525
|
}
|
|
553
526
|
} catch (error) {
|
|
554
|
-
|
|
527
|
+
output.error(` Repair failed: ${error.message}`);
|
|
555
528
|
|
|
556
529
|
if (options.verbose && error.stack) {
|
|
557
|
-
|
|
530
|
+
output.error(`\nStack trace:\n${error.stack}`);
|
|
558
531
|
}
|
|
559
532
|
|
|
560
533
|
process.exit(1);
|