@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.
Files changed (127) hide show
  1. package/frigg-cli/README.md +1 -1
  2. package/frigg-cli/__tests__/application/use-cases/AddApiModuleToIntegrationUseCase.test.js +326 -0
  3. package/frigg-cli/__tests__/application/use-cases/CreateApiModuleUseCase.test.js +337 -0
  4. package/frigg-cli/__tests__/domain/entities/ApiModule.test.js +373 -0
  5. package/frigg-cli/__tests__/domain/entities/AppDefinition.test.js +313 -0
  6. package/frigg-cli/__tests__/domain/services/IntegrationValidator.test.js +269 -0
  7. package/frigg-cli/__tests__/domain/value-objects/IntegrationName.test.js +82 -0
  8. package/frigg-cli/__tests__/infrastructure/adapters/IntegrationJsUpdater.test.js +408 -0
  9. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemApiModuleRepository.test.js +583 -0
  10. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemAppDefinitionRepository.test.js +314 -0
  11. package/frigg-cli/__tests__/infrastructure/repositories/FileSystemIntegrationRepository.test.js +383 -0
  12. package/frigg-cli/__tests__/unit/commands/build.test.js +1 -1
  13. package/frigg-cli/__tests__/unit/commands/doctor.test.js +0 -2
  14. package/frigg-cli/__tests__/unit/commands/init.test.js +406 -0
  15. package/frigg-cli/__tests__/unit/commands/install.test.js +23 -19
  16. package/frigg-cli/__tests__/unit/commands/provider-dispatch.test.js +383 -0
  17. package/frigg-cli/__tests__/unit/commands/repair.test.js +275 -0
  18. package/frigg-cli/__tests__/unit/dependencies.test.js +2 -2
  19. package/frigg-cli/__tests__/unit/start-command/application/RunPreflightChecksUseCase.test.js +411 -0
  20. package/frigg-cli/__tests__/unit/start-command/infrastructure/DatabaseAdapter.test.js +405 -0
  21. package/frigg-cli/__tests__/unit/start-command/infrastructure/DockerAdapter.test.js +496 -0
  22. package/frigg-cli/__tests__/unit/start-command/presentation/InteractivePromptAdapter.test.js +474 -0
  23. package/frigg-cli/__tests__/unit/utils/output.test.js +196 -0
  24. package/frigg-cli/application/use-cases/AddApiModuleToIntegrationUseCase.js +93 -0
  25. package/frigg-cli/application/use-cases/CreateApiModuleUseCase.js +93 -0
  26. package/frigg-cli/application/use-cases/CreateIntegrationUseCase.js +103 -0
  27. package/frigg-cli/build-command/index.js +123 -11
  28. package/frigg-cli/container.js +172 -0
  29. package/frigg-cli/deploy-command/index.js +83 -1
  30. package/frigg-cli/docs/OUTPUT_MIGRATION_GUIDE.md +286 -0
  31. package/frigg-cli/doctor-command/index.js +37 -16
  32. package/frigg-cli/domain/entities/ApiModule.js +272 -0
  33. package/frigg-cli/domain/entities/AppDefinition.js +227 -0
  34. package/frigg-cli/domain/entities/Integration.js +198 -0
  35. package/frigg-cli/domain/exceptions/DomainException.js +24 -0
  36. package/frigg-cli/domain/ports/IApiModuleRepository.js +53 -0
  37. package/frigg-cli/domain/ports/IAppDefinitionRepository.js +43 -0
  38. package/frigg-cli/domain/ports/IIntegrationRepository.js +61 -0
  39. package/frigg-cli/domain/services/IntegrationValidator.js +185 -0
  40. package/frigg-cli/domain/value-objects/IntegrationId.js +42 -0
  41. package/frigg-cli/domain/value-objects/IntegrationName.js +60 -0
  42. package/frigg-cli/domain/value-objects/SemanticVersion.js +70 -0
  43. package/frigg-cli/generate-iam-command.js +21 -1
  44. package/frigg-cli/index.js +21 -6
  45. package/frigg-cli/index.test.js +7 -2
  46. package/frigg-cli/infrastructure/UnitOfWork.js +46 -0
  47. package/frigg-cli/infrastructure/adapters/BackendJsUpdater.js +197 -0
  48. package/frigg-cli/infrastructure/adapters/FileSystemAdapter.js +224 -0
  49. package/frigg-cli/infrastructure/adapters/IntegrationJsUpdater.js +249 -0
  50. package/frigg-cli/infrastructure/adapters/SchemaValidator.js +92 -0
  51. package/frigg-cli/infrastructure/repositories/FileSystemApiModuleRepository.js +373 -0
  52. package/frigg-cli/infrastructure/repositories/FileSystemAppDefinitionRepository.js +116 -0
  53. package/frigg-cli/infrastructure/repositories/FileSystemIntegrationRepository.js +277 -0
  54. package/frigg-cli/init-command/backend-first-handler.js +124 -42
  55. package/frigg-cli/init-command/index.js +2 -1
  56. package/frigg-cli/init-command/template-handler.js +13 -3
  57. package/frigg-cli/install-command/backend-js.js +3 -3
  58. package/frigg-cli/install-command/environment-variables.js +16 -19
  59. package/frigg-cli/install-command/environment-variables.test.js +12 -13
  60. package/frigg-cli/install-command/index.js +14 -9
  61. package/frigg-cli/install-command/integration-file.js +3 -3
  62. package/frigg-cli/install-command/validate-package.js +5 -9
  63. package/frigg-cli/jest.config.js +4 -1
  64. package/frigg-cli/package-lock.json +16226 -0
  65. package/frigg-cli/repair-command/index.js +121 -128
  66. package/frigg-cli/start-command/application/RunPreflightChecksUseCase.js +376 -0
  67. package/frigg-cli/start-command/index.js +324 -2
  68. package/frigg-cli/start-command/infrastructure/DatabaseAdapter.js +591 -0
  69. package/frigg-cli/start-command/infrastructure/DockerAdapter.js +306 -0
  70. package/frigg-cli/start-command/presentation/InteractivePromptAdapter.js +329 -0
  71. package/frigg-cli/templates/backend/.env.example +62 -0
  72. package/frigg-cli/templates/backend/.eslintrc.json +12 -0
  73. package/frigg-cli/templates/backend/.prettierrc +6 -0
  74. package/frigg-cli/templates/backend/docker-compose.yml +22 -0
  75. package/frigg-cli/templates/backend/index.js +96 -0
  76. package/frigg-cli/templates/backend/infrastructure.js +12 -0
  77. package/frigg-cli/templates/backend/jest.config.js +17 -0
  78. package/frigg-cli/templates/backend/package.json +50 -0
  79. package/frigg-cli/templates/backend/src/api-modules/.gitkeep +10 -0
  80. package/frigg-cli/templates/backend/src/base/.gitkeep +7 -0
  81. package/frigg-cli/templates/backend/src/integrations/.gitkeep +10 -0
  82. package/frigg-cli/templates/backend/src/integrations/ExampleIntegration.js +65 -0
  83. package/frigg-cli/templates/backend/src/utils/.gitkeep +7 -0
  84. package/frigg-cli/templates/backend/test/setup.js +30 -0
  85. package/frigg-cli/templates/backend/ui-extensions/.gitkeep +0 -0
  86. package/frigg-cli/templates/backend/ui-extensions/README.md +77 -0
  87. package/frigg-cli/ui-command/index.js +58 -36
  88. package/frigg-cli/utils/__tests__/provider-helper.test.js +55 -0
  89. package/frigg-cli/utils/__tests__/repo-detection.test.js +436 -0
  90. package/frigg-cli/utils/output.js +382 -0
  91. package/frigg-cli/utils/provider-helper.js +75 -0
  92. package/frigg-cli/utils/repo-detection.js +85 -37
  93. package/frigg-cli/validate-command/__tests__/adapters/validate-command.test.js +205 -0
  94. package/frigg-cli/validate-command/__tests__/application/validate-app-use-case.test.js +104 -0
  95. package/frigg-cli/validate-command/__tests__/domain/fix-suggestion.test.js +153 -0
  96. package/frigg-cli/validate-command/__tests__/domain/validation-error.test.js +162 -0
  97. package/frigg-cli/validate-command/__tests__/domain/validation-result.test.js +152 -0
  98. package/frigg-cli/validate-command/__tests__/infrastructure/api-module-validator.test.js +332 -0
  99. package/frigg-cli/validate-command/__tests__/infrastructure/app-definition-validator.test.js +191 -0
  100. package/frigg-cli/validate-command/__tests__/infrastructure/integration-class-validator.test.js +146 -0
  101. package/frigg-cli/validate-command/__tests__/infrastructure/template-validation.test.js +155 -0
  102. package/frigg-cli/validate-command/adapters/cli/validate-command.js +199 -0
  103. package/frigg-cli/validate-command/application/use-cases/validate-app-use-case.js +35 -0
  104. package/frigg-cli/validate-command/domain/entities/validation-result.js +74 -0
  105. package/frigg-cli/validate-command/domain/value-objects/fix-suggestion.js +74 -0
  106. package/frigg-cli/validate-command/domain/value-objects/validation-error.js +68 -0
  107. package/frigg-cli/validate-command/infrastructure/validators/api-module-validator.js +181 -0
  108. package/frigg-cli/validate-command/infrastructure/validators/app-definition-validator.js +145 -0
  109. package/frigg-cli/validate-command/infrastructure/validators/integration-class-validator.js +113 -0
  110. package/infrastructure/create-frigg-infrastructure.js +93 -0
  111. package/infrastructure/docs/iam-policy-templates.md +1 -1
  112. package/infrastructure/domains/admin-scripts/admin-script-builder.js +200 -0
  113. package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +499 -0
  114. package/infrastructure/domains/admin-scripts/index.js +5 -0
  115. package/infrastructure/domains/networking/vpc-builder.test.js +2 -4
  116. package/infrastructure/domains/networking/vpc-resolver.test.js +1 -1
  117. package/infrastructure/domains/shared/resource-discovery.js +5 -5
  118. package/infrastructure/domains/shared/types/app-definition.js +35 -0
  119. package/infrastructure/domains/shared/types/discovery-result.test.js +1 -1
  120. package/infrastructure/domains/shared/utilities/base-definition-factory.js +10 -1
  121. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +2 -2
  122. package/infrastructure/infrastructure-composer.js +2 -0
  123. package/infrastructure/infrastructure-composer.test.js +2 -2
  124. package/infrastructure/jest.config.js +16 -0
  125. package/management-ui/README.md +245 -109
  126. package/package.json +8 -7
  127. 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 readline = require('readline');
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
- console.log('\nāœ“ No orphaned resources to import');
47
+ output.success(' No orphaned resources to import');
75
48
  return { imported: 0, failed: 0 };
76
49
  }
77
50
 
78
- console.log(`\nšŸ“¦ Found ${orphanedResources.length} orphaned resource(s) to import:`);
51
+ output.info(`šŸ“¦ Found ${orphanedResources.length} orphaned resource(s) to import:`);
79
52
  orphanedResources.forEach((resource, idx) => {
80
- console.log(` ${idx + 1}. ${resource.resourceType} - ${resource.physicalId}`);
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
- console.log('\nāš ļø Build template not found. Generating sequential logical IDs (not recommended).');
89
- console.log(` Run one of the following to generate build template:`);
90
- console.log(` • serverless package`);
91
- console.log(` • frigg build`);
92
- console.log(` • frigg deploy --stage dev`);
93
- console.log(` Then run 'frigg repair --import ${stackIdentifier.stackName}' again for correct logical IDs.\n`);
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
- console.log('Import cancelled by user');
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
- console.log('\nšŸ”§ Importing resources with sequential IDs...');
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
- console.log(`\nāœ“ Successfully imported ${importResult.importedCount} resource(s)`);
94
+ output.success(` Successfully imported ${importResult.importedCount} resource(s)`);
122
95
  } else {
123
- console.log(`\nāœ— Import failed: ${importResult.message}`);
96
+ output.log(`\nāœ— Import failed: ${importResult.message}`);
124
97
  if (importResult.validationErrors && importResult.validationErrors.length > 0) {
125
- console.log('\nValidation errors:');
98
+ output.log('\nValidation errors:');
126
99
  importResult.validationErrors.forEach((error) => {
127
- console.log(` • ${error.logicalId}: ${error.reason}`);
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
- console.log(`\nšŸ” Analyzing templates to map orphaned resources to correct logical IDs...`);
141
- console.log(` Build template: ${buildTemplatePath}`);
142
- console.log(` Deployed template: CloudFormation (via AWS API)`);
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
- console.log(`\nāœ— Mapping failed: ${mappingResult.message}`);
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
- console.log(`\nāœ… Successfully mapped ${mappingResult.mappedCount} resource(s) to logical IDs:`);
140
+ output.success(` Successfully mapped ${mappingResult.mappedCount} resource(s) to logical IDs:`);
168
141
  mappingResult.mappings.forEach((mapping) => {
169
- console.log(` • ${mapping.logicalId} ← ${mapping.physicalId} (${mapping.matchMethod}, ${mapping.confidence} confidence)`);
142
+ output.log(` • ${mapping.logicalId} ← ${mapping.physicalId} (${mapping.matchMethod}, ${mapping.confidence} confidence)`);
170
143
  });
171
144
 
172
145
  if (mappingResult.unmappedCount > 0) {
173
- console.log(`\nāš ļø Could not map ${mappingResult.unmappedCount} resource(s):`);
146
+ output.warn(`ļø Could not map ${mappingResult.unmappedCount} resource(s):`);
174
147
  mappingResult.unmappedResources.forEach((resource) => {
175
- console.log(` • ${resource.resourceType} - ${resource.physicalId}`);
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
- console.log(`\nāš ļø Warnings:`);
154
+ output.warn(`ļø Warnings:`);
182
155
  mappingResult.warnings.forEach((warning) => {
183
- console.log(` • ${warning.message}`);
156
+ output.log(` • ${warning.message}`);
184
157
  if (warning.type === 'MULTIPLE_RESOURCES') {
185
158
  warning.resources.forEach((res) => {
186
- console.log(` - ${res.logicalId} ← ${res.physicalId} (${res.matchMethod}, ${res.confidence})`);
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
- console.log(`\nšŸ“‹ The following will be imported into CloudFormation:`);
167
+ output.info(`šŸ“‹ The following will be imported into CloudFormation:`);
195
168
  mappingResult.resourcesToImport.forEach((resource) => {
196
- console.log(` • ${resource.LogicalResourceId} (${resource.ResourceType})`);
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
- console.log('Import cancelled by user');
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
- console.log(`\nšŸ”§ Preparing CloudFormation import operation...`);
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
- console.log(' • Generating import template...');
213
+ output.log(' • Generating import template...');
241
214
  } else if (progress.step === 'generate_template' && progress.status === 'complete') {
242
- console.log(' āœ“ Template generated');
215
+ output.log(' āœ“ Template generated');
243
216
  } else if (progress.step === 'create_change_set' && progress.status === 'in_progress') {
244
- console.log(' • Creating CloudFormation change set...');
217
+ output.log(' • Creating CloudFormation change set...');
245
218
  } else if (progress.step === 'create_change_set' && progress.status === 'complete') {
246
- console.log(` āœ“ Change set created: ${progress.changeSetName}`);
219
+ output.log(` āœ“ Change set created: ${progress.changeSetName}`);
247
220
  } else if (progress.step === 'wait_change_set' && progress.status === 'in_progress') {
248
- console.log(' • Waiting for change set...');
221
+ output.log(' • Waiting for change set...');
249
222
  } else if (progress.step === 'wait_change_set' && progress.status === 'complete') {
250
- console.log(' āœ“ Change set ready');
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
- console.log(` • Importing resource ${resourceProgress}/${total}: ${logicalId} (${status})`);
227
+ output.log(` • Importing resource ${resourceProgress}/${total}: ${logicalId} (${status})`);
255
228
  } else {
256
- console.log(' • Executing import operation...');
229
+ output.log(' • Executing import operation...');
257
230
  }
258
231
  } else if (progress.step === 'execute_import' && progress.status === 'complete') {
259
- console.log(' āœ“ Import operation complete');
232
+ output.log(' āœ“ Import operation complete');
260
233
  } else if (progress.step === 'verify' && progress.status === 'in_progress') {
261
- console.log(' • Verifying imported resources...');
234
+ output.log(' • Verifying imported resources...');
262
235
  } else if (progress.step === 'verify' && progress.status === 'complete') {
263
- console.log(' āœ“ Verification complete');
236
+ output.log(' āœ“ Verification complete');
264
237
  }
265
238
  },
266
239
  });
267
240
 
268
241
  if (importResult.success) {
269
- console.log(`\nāœ… Successfully imported ${importResult.importedCount} resource(s) into CloudFormation!`);
270
- console.log(` Stack status: ${importResult.stackStatus}`);
271
- console.log(` Change set: ${importResult.changeSetName}`);
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
- console.error(`\nāŒ Import operation failed: ${importResult.error}`);
280
- console.error(` Failed at step: ${importResult.step}`);
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
- console.log('\nāœ“ No property drift to reconcile');
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
- console.log(`\nšŸ”§ Found ${driftedResources.length} drifted resource(s) with ${totalMismatches} property mismatch(es):`);
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
- console.log(` • ${resource.logicalId} (${resource.resourceType}): ${issues.length} mismatch(es)`);
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
- console.log(`\nReconciliation mode: ${mode}`);
329
- console.log(` ${modeDescription}`);
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
- console.log('Reconciliation cancelled by user');
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
- console.log('\nšŸ”§ Reconciling property drift...');
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
- console.log(` āœ“ ${resource.logicalId}: Reconciled ${result.reconciledCount} property(ies)`);
367
+ output.log(` āœ“ ${resource.logicalId}: Reconciled ${result.reconciledCount} property(ies)`);
395
368
  if (result.skippedCount > 0) {
396
- console.log(` ⚠ Skipped ${result.skippedCount} immutable property(ies) - requires manual intervention`);
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
- console.log(` [DEBUG] Expected ${mismatches.length} mismatches, got result:`, JSON.stringify(result, null, 2));
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
- console.log(` āœ— ${resource.logicalId}: ${error.message}`);
379
+ output.log(` āœ— ${resource.logicalId}: ${error.message}`);
407
380
 
408
381
  // Debug: Log full error
409
382
  if (process.env.DEBUG_RECONCILE) {
410
- console.log(` [DEBUG] Error stack:`, error.stack);
383
+ output.log(` [DEBUG] Error stack:`, error.stack);
411
384
  }
412
385
  }
413
386
  }
414
387
 
415
388
  // Report results
416
- console.log(''); // Blank line before summary
389
+ output.log(''); // Blank line before summary
417
390
 
418
391
  if (reconciledCount > 0) {
419
- console.log(`āœ… Reconciled ${reconciledCount} property(ies)`);
392
+ output.log(`āœ… Reconciled ${reconciledCount} property(ies)`);
420
393
  }
421
394
 
422
395
  if (skippedImmutableCount > 0) {
423
- console.log(`\n⚠ ${skippedImmutableCount} immutable property(ies) require manual intervention:`);
396
+ output.warn(` ${skippedImmutableCount} immutable property(ies) require manual intervention:`);
424
397
  immutableProperties.forEach(prop => {
425
- console.log(` • ${prop.logicalId}.${prop.propertyPath}`);
426
- console.log(` Template: ${JSON.stringify(prop.expectedValue)}`);
427
- console.log(` Actual: ${JSON.stringify(prop.actualValue)}`);
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
- console.log(`\nšŸ’” To resolve immutable property drift:`);
431
- console.log(` 1. These properties require resource replacement (cannot be updated in place)`);
432
- console.log(` 2. Options:`);
433
- console.log(` a) Accept the drift - update your local template to match actual values`);
434
- console.log(` b) Replace the resource - delete and recreate via CloudFormation`);
435
- console.log(` c) Use import workflow - remove from stack, then re-import with correct values`);
436
- console.log(`\n For automated import workflow (coming soon):`);
437
- console.log(` frigg repair --import-drift ${stackIdentifier.stackName}`);
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
- console.log(`āœ“ Successfully reconciled all ${reconciledCount} property mismatch(es)`);
414
+ output.log(`āœ“ Successfully reconciled all ${reconciledCount} property mismatch(es)`);
442
415
  } else {
443
- console.log(`\n⚠ Reconciled ${reconciledCount} property(ies), ${failedCount} failed`);
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
- console.error('Error: Stack name is required');
459
- console.log('Usage: frigg repair [options] <stack-name>');
460
- console.log('Options:');
461
- console.log(' --import Import orphaned resources');
462
- console.log(' --reconcile Reconcile property drift');
463
- console.log(' --yes Skip confirmation prompts');
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
- console.error('Error: At least one repair operation must be specified (--import or --reconcile)');
470
- console.log('Usage: frigg repair [options] <stack-name>');
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
- console.log(`\nšŸ„ Running Frigg Repair on stack: ${stackName} (${region})`);
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
- console.log('\nšŸ” Running health check to identify issues...');
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
- console.log(`\nHealth Score: ${report.healthScore.value}/100 (${report.healthScore.qualitativeAssessment()})`);
501
- console.log(`Issues: ${report.getIssueCount()} total (${report.getCriticalIssueCount()} critical)`);
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
- console.log('\n' + '═'.repeat(80));
528
- console.log('Repair Summary:');
507
+ output.log('\n' + '═'.repeat(80));
508
+ output.log('Repair Summary:');
529
509
  if (options.import) {
530
- console.log(` Imported: ${results.imported} resource(s)`);
510
+ output.log(` Imported: ${results.imported} resource(s)`);
531
511
  }
532
512
  if (options.reconcile) {
533
- console.log(` Reconciled: ${results.reconciled} property(ies)`);
513
+ output.log(` Reconciled: ${results.reconciled} property(ies)`);
534
514
  }
535
- console.log(` Failed: ${results.failed}`);
536
- console.log('═'.repeat(80));
515
+ output.log(` Failed: ${results.failed}`);
516
+ output.log('═'.repeat(80));
537
517
 
538
518
  // Run health check again to verify repairs
539
- console.log('\nšŸ” Running health check to verify repairs...');
519
+ output.info('šŸ” Running health check to verify repairs...');
540
520
  const verifyReport = await runHealthCheckUseCase.execute({ stackIdentifier });
541
- console.log(`\nNew Health Score: ${verifyReport.healthScore.value}/100 (${verifyReport.healthScore.qualitativeAssessment()})`);
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
- console.log(`\nāœ“ Health improved by ${verifyReport.healthScore.value - report.healthScore.value} points!`);
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
- console.error(`\nāœ— Repair failed: ${error.message}`);
534
+ output.error(` Repair failed: ${error.message}`);
555
535
 
556
536
  if (options.verbose && error.stack) {
557
- console.error(`\nStack trace:\n${error.stack}`);
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 };