@friggframework/devtools 2.0.0--canary.549.70ef06a.0 ā 2.0.0--canary.545.ccb5010.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/build.test.js +1 -1
- 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/provider-dispatch.test.js +383 -0
- 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/build-command/index.js +123 -11
- package/frigg-cli/container.js +172 -0
- package/frigg-cli/deploy-command/index.js +83 -1
- package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +286 -0
- package/frigg-cli/doctor-command/index.js +37 -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/generate-iam-command.js +21 -1
- 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 +121 -128
- package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +376 -0
- package/frigg-cli/start-command/index.js +324 -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__/provider-helper.test.js +55 -0
- package/frigg-cli/utils/__tests__/repo-detection.test.js +436 -0
- package/frigg-cli/utils/output.js +382 -0
- package/frigg-cli/utils/provider-helper.js +75 -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 +145 -0
- package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +113 -0
- package/infrastructure/create-frigg-infrastructure.js +93 -0
- package/infrastructure/docs/iam-policy-templates.md +1 -1
- package/infrastructure/domains/admin-scripts/admin-script-builder.js +200 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +499 -0
- package/infrastructure/domains/admin-scripts/index.js +5 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +2 -4
- package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
- package/infrastructure/domains/shared/resource-discovery.js +5 -5
- package/infrastructure/domains/shared/types/app-definition.js +35 -0
- 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.js +2 -0
- 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 };
|
|
@@ -453,21 +426,28 @@ async function handleReconcileRepair(stackIdentifier, report, options) {
|
|
|
453
426
|
*/
|
|
454
427
|
async function repairCommand(stackName, options = {}) {
|
|
455
428
|
try {
|
|
429
|
+
// Guard: repair only works with AWS (CloudFormation stacks)
|
|
430
|
+
if (isNonAwsProvider()) {
|
|
431
|
+
output.error('The repair command is only available for AWS deployments.');
|
|
432
|
+
output.log('Your appDefinition uses a non-AWS provider.');
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
|
|
456
436
|
// Validate required parameter
|
|
457
437
|
if (!stackName) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
438
|
+
output.error('Error: Stack name is required');
|
|
439
|
+
output.log('Usage: frigg repair [options] <stack-name>');
|
|
440
|
+
output.log('Options:');
|
|
441
|
+
output.log(' --import Import orphaned resources');
|
|
442
|
+
output.log(' --reconcile Reconcile property drift');
|
|
443
|
+
output.log(' --yes Skip confirmation prompts');
|
|
464
444
|
process.exit(1);
|
|
465
445
|
}
|
|
466
446
|
|
|
467
447
|
// Validate at least one repair operation is selected
|
|
468
448
|
if (!options.import && !options.reconcile) {
|
|
469
|
-
|
|
470
|
-
|
|
449
|
+
output.error('Error: At least one repair operation must be specified (--import or --reconcile)');
|
|
450
|
+
output.log('Usage: frigg repair [options] <stack-name>');
|
|
471
451
|
process.exit(1);
|
|
472
452
|
}
|
|
473
453
|
|
|
@@ -475,13 +455,13 @@ async function repairCommand(stackName, options = {}) {
|
|
|
475
455
|
const region = options.region || process.env.AWS_REGION || 'us-east-1';
|
|
476
456
|
const verbose = options.verbose || false;
|
|
477
457
|
|
|
478
|
-
|
|
458
|
+
output.info(`š„ Running Frigg Repair on stack: ${stackName} (${region})`);
|
|
479
459
|
|
|
480
460
|
// 1. Create stack identifier
|
|
481
461
|
const stackIdentifier = new StackIdentifier({ stackName, region });
|
|
482
462
|
|
|
483
463
|
// 2. Run health check first to identify issues
|
|
484
|
-
|
|
464
|
+
output.info('š Running health check to identify issues...');
|
|
485
465
|
|
|
486
466
|
const stackRepository = new AWSStackRepository({ region });
|
|
487
467
|
const resourceDetector = new AWSResourceDetector({ region });
|
|
@@ -497,8 +477,8 @@ async function repairCommand(stackName, options = {}) {
|
|
|
497
477
|
|
|
498
478
|
const report = await runHealthCheckUseCase.execute({ stackIdentifier });
|
|
499
479
|
|
|
500
|
-
|
|
501
|
-
|
|
480
|
+
output.log(`\nHealth Score: ${report.healthScore.value}/100 (${report.healthScore.qualitativeAssessment()})`);
|
|
481
|
+
output.log(`Issues: ${report.getIssueCount()} total (${report.getCriticalIssueCount()} critical)`);
|
|
502
482
|
|
|
503
483
|
// 3. Execute requested repair operations
|
|
504
484
|
const results = {
|
|
@@ -524,24 +504,24 @@ async function repairCommand(stackName, options = {}) {
|
|
|
524
504
|
}
|
|
525
505
|
|
|
526
506
|
// 4. Final summary
|
|
527
|
-
|
|
528
|
-
|
|
507
|
+
output.log('\n' + 'ā'.repeat(80));
|
|
508
|
+
output.log('Repair Summary:');
|
|
529
509
|
if (options.import) {
|
|
530
|
-
|
|
510
|
+
output.log(` Imported: ${results.imported} resource(s)`);
|
|
531
511
|
}
|
|
532
512
|
if (options.reconcile) {
|
|
533
|
-
|
|
513
|
+
output.log(` Reconciled: ${results.reconciled} property(ies)`);
|
|
534
514
|
}
|
|
535
|
-
|
|
536
|
-
|
|
515
|
+
output.log(` Failed: ${results.failed}`);
|
|
516
|
+
output.log('ā'.repeat(80));
|
|
537
517
|
|
|
538
518
|
// Run health check again to verify repairs
|
|
539
|
-
|
|
519
|
+
output.info('š Running health check to verify repairs...');
|
|
540
520
|
const verifyReport = await runHealthCheckUseCase.execute({ stackIdentifier });
|
|
541
|
-
|
|
521
|
+
output.log(`\nNew Health Score: ${verifyReport.healthScore.value}/100 (${verifyReport.healthScore.qualitativeAssessment()})`);
|
|
542
522
|
|
|
543
523
|
if (verifyReport.healthScore.value > report.healthScore.value) {
|
|
544
|
-
|
|
524
|
+
output.success(` Health improved by ${verifyReport.healthScore.value - report.healthScore.value} points!`);
|
|
545
525
|
}
|
|
546
526
|
|
|
547
527
|
// 5. Exit with appropriate code
|
|
@@ -551,14 +531,27 @@ async function repairCommand(stackName, options = {}) {
|
|
|
551
531
|
process.exit(0);
|
|
552
532
|
}
|
|
553
533
|
} catch (error) {
|
|
554
|
-
|
|
534
|
+
output.error(` Repair failed: ${error.message}`);
|
|
555
535
|
|
|
556
536
|
if (options.verbose && error.stack) {
|
|
557
|
-
|
|
537
|
+
output.error(`\nStack trace:\n${error.stack}`);
|
|
558
538
|
}
|
|
559
539
|
|
|
560
540
|
process.exit(1);
|
|
561
541
|
}
|
|
562
542
|
}
|
|
563
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Check if the current appDefinition uses a non-AWS provider.
|
|
546
|
+
*/
|
|
547
|
+
function isNonAwsProvider() {
|
|
548
|
+
try {
|
|
549
|
+
const { loadProviderForCli } = require('../utils/provider-helper');
|
|
550
|
+
const result = loadProviderForCli();
|
|
551
|
+
return result && result.providerName !== 'aws';
|
|
552
|
+
} catch {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
564
557
|
module.exports = { repairCommand };
|