@friggframework/devtools 2.0.0--canary.474.2ad4ebc.0 → 2.0.0--canary.474.a0b734c.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 (56) hide show
  1. package/management-ui/server/utils/cliIntegration.js +1 -1
  2. package/package.json +6 -6
  3. package/frigg-cli/.eslintrc.js +0 -141
  4. package/frigg-cli/README.md +0 -1290
  5. package/frigg-cli/__tests__/unit/commands/build.test.js +0 -279
  6. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
  7. package/frigg-cli/__tests__/unit/commands/deploy.test.js +0 -320
  8. package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
  9. package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
  10. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
  11. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
  12. package/frigg-cli/__tests__/unit/version-detection.test.js +0 -171
  13. package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
  14. package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
  15. package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
  16. package/frigg-cli/__tests__/utils/test-setup.js +0 -287
  17. package/frigg-cli/build-command/index.js +0 -66
  18. package/frigg-cli/db-setup-command/index.js +0 -193
  19. package/frigg-cli/deploy-command/index.js +0 -302
  20. package/frigg-cli/doctor-command/index.js +0 -249
  21. package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
  22. package/frigg-cli/generate-command/azure-generator.js +0 -43
  23. package/frigg-cli/generate-command/gcp-generator.js +0 -47
  24. package/frigg-cli/generate-command/index.js +0 -332
  25. package/frigg-cli/generate-command/terraform-generator.js +0 -555
  26. package/frigg-cli/generate-iam-command.js +0 -118
  27. package/frigg-cli/index.js +0 -173
  28. package/frigg-cli/index.test.js +0 -158
  29. package/frigg-cli/init-command/backend-first-handler.js +0 -756
  30. package/frigg-cli/init-command/index.js +0 -93
  31. package/frigg-cli/init-command/template-handler.js +0 -143
  32. package/frigg-cli/install-command/backend-js.js +0 -33
  33. package/frigg-cli/install-command/commit-changes.js +0 -16
  34. package/frigg-cli/install-command/environment-variables.js +0 -127
  35. package/frigg-cli/install-command/environment-variables.test.js +0 -136
  36. package/frigg-cli/install-command/index.js +0 -54
  37. package/frigg-cli/install-command/install-package.js +0 -13
  38. package/frigg-cli/install-command/integration-file.js +0 -30
  39. package/frigg-cli/install-command/logger.js +0 -12
  40. package/frigg-cli/install-command/template.js +0 -90
  41. package/frigg-cli/install-command/validate-package.js +0 -75
  42. package/frigg-cli/jest.config.js +0 -124
  43. package/frigg-cli/package.json +0 -58
  44. package/frigg-cli/repair-command/index.js +0 -341
  45. package/frigg-cli/start-command/index.js +0 -149
  46. package/frigg-cli/start-command/start-command.test.js +0 -297
  47. package/frigg-cli/test/init-command.test.js +0 -180
  48. package/frigg-cli/test/npm-registry.test.js +0 -319
  49. package/frigg-cli/ui-command/index.js +0 -154
  50. package/frigg-cli/utils/app-resolver.js +0 -319
  51. package/frigg-cli/utils/backend-path.js +0 -25
  52. package/frigg-cli/utils/database-validator.js +0 -154
  53. package/frigg-cli/utils/error-messages.js +0 -257
  54. package/frigg-cli/utils/npm-registry.js +0 -167
  55. package/frigg-cli/utils/process-manager.js +0 -199
  56. package/frigg-cli/utils/repo-detection.js +0 -405
@@ -1,90 +0,0 @@
1
- const path = require('path');
2
-
3
- function getIntegrationTemplate(apiModuleName, backendPath, ApiClass) {
4
- // Find the sample data method
5
- const apiMethods = Object.getOwnPropertyNames(ApiClass.prototype);
6
- const sampleDataMethod =
7
- apiMethods.find(
8
- (method) =>
9
- method.toLowerCase().includes('search') ||
10
- method.toLowerCase().includes('list') ||
11
- method.toLowerCase().includes('get')
12
- ) || 'searchDeals';
13
-
14
- return `const { get, IntegrationBase, Options } = require('@friggframework/core');
15
- const { Definition: ${apiModuleName}Module, Config: defaultConfig } = require('@friggframework/api-module-${apiModuleName.toLowerCase()}');
16
-
17
- class ${apiModuleName}Integration extends IntegrationBase {
18
- static Config = {
19
- name: defaultConfig.name || '${apiModuleName.toLowerCase()}',
20
- version: '1.0.0',
21
- supportedVersions: ['1.0.0'],
22
- events: ['SEARCH_DEALS'],
23
- };
24
-
25
- static Options =
26
- new Options({
27
- module: ${apiModuleName}Module,
28
- integrations: [${apiModuleName}Module],
29
- display: {
30
- name: defaultConfig.displayName || '${apiModuleName}',
31
- description: defaultConfig.description || 'Sales & CRM, Marketing',
32
- category: defaultConfig.category || 'Sales & CRM, Marketing',
33
- detailsUrl: defaultConfig.detailsUrl || 'https://www.${apiModuleName.toLowerCase()}.com',
34
- icon: defaultConfig.icon || 'https://friggframework.org/assets/img/${apiModuleName.toLowerCase()}.jpeg',
35
- },
36
- hasUserConfig: true,
37
- });
38
-
39
- static modules = {
40
- ${apiModuleName.toLowerCase()}: ${apiModuleName}Module,
41
- }
42
-
43
- /**
44
- * HANDLE EVENTS
45
- */
46
- async receiveNotification(notifier, event, object = null) {
47
- if (event === 'SEARCH_DEALS') {
48
- return this.target.api.searchDeals(object);
49
- }
50
- }
51
-
52
- /**
53
- * ALL CUSTOM/OPTIONAL METHODS FOR AN INTEGRATION MANAGER
54
- */
55
- async getSampleData() {
56
- const res = await this.target.api.${sampleDataMethod}();
57
- return { data: res };
58
- }
59
-
60
- /**
61
- * ALL REQUIRED METHODS FOR AN INTEGRATION MANAGER
62
- */
63
- async onCreate(params) {
64
- // Validate that we have all of the data we need
65
- // Set integration status as makes sense. Default ENABLED
66
- // TODO turn this into a validateConfig method/function
67
- this.record.status = 'ENABLED';
68
- await this.record.save();
69
- return this.record;
70
- }
71
-
72
- async onUpdate(params) {
73
- const newConfig = get(params, 'config');
74
- const oldConfig = this.record.config;
75
- // Just save whatever
76
- this.record.markModified('config');
77
- await this.record.save();
78
- return this.validateConfig();
79
- }
80
-
81
- async getConfigOptions() {
82
- const options = {}
83
- return options;
84
- }
85
- }
86
-
87
- module.exports = ${apiModuleName}Integration;`;
88
- }
89
-
90
- module.exports = { getIntegrationTemplate };
@@ -1,75 +0,0 @@
1
- const { execSync } = require('child_process');
2
- const axios = require('axios');
3
- const { logError } = require('./logger');
4
- const { checkbox } = require('@inquirer/prompts');
5
-
6
- async function searchPackages(apiModuleName) {
7
- const searchCommand = `npm search @friggframework/api-module-${apiModuleName} --json`;
8
- const result = execSync(searchCommand, { encoding: 'utf8' });
9
- return JSON.parse(result);
10
- }
11
-
12
- async function checkPackageExists(packageName) {
13
- try {
14
- const response = await axios.get(
15
- `https://registry.npmjs.org/${packageName}`
16
- );
17
- return response.status === 200;
18
- } catch (error) {
19
- return false;
20
- }
21
- }
22
-
23
- async function validatePackageExists(packageName) {
24
- const packageExists = await checkPackageExists(packageName);
25
- if (!packageExists) {
26
- throw new Error(`Package ${packageName} does not exist on npm.`);
27
- }
28
- }
29
-
30
- const searchAndSelectPackage = async (apiModuleName) => {
31
- const searchResults = await searchPackages(apiModuleName || '');
32
-
33
- if (searchResults.length === 0) {
34
- logError(`No packages found matching ${apiModuleName}`);
35
- process.exit(1);
36
- }
37
-
38
- const filteredResults = searchResults.filter((pkg) => {
39
- const version = pkg.version ? pkg.version.split('.').map(Number) : [];
40
- return version[0] >= 1;
41
- });
42
-
43
- if (filteredResults.length === 0) {
44
- const earlierVersions = searchResults
45
- .map((pkg) => `${pkg.name} (${pkg.version})`)
46
- .join(', ');
47
- logError(
48
- `No packages found with version 1.0.0 or above for ${apiModuleName}. Found earlier versions: ${earlierVersions}`
49
- );
50
- process.exit(1);
51
- }
52
-
53
- const choices = filteredResults.map((pkg) => {
54
- return {
55
- name: `${pkg.name} (${pkg.version})`,
56
- value: pkg.name,
57
- checked: filteredResults.length === 1, // Automatically select if only one result
58
- };
59
- });
60
-
61
- const selectedPackages = await checkbox({
62
- message: 'Select the packages to install:',
63
- choices,
64
- });
65
- console.log('Selected packages:', selectedPackages);
66
-
67
- return selectedPackages.map((choice) => choice.split(' ')[0]);
68
- };
69
-
70
- module.exports = {
71
- validatePackageExists,
72
- checkPackageExists,
73
- searchPackages,
74
- searchAndSelectPackage,
75
- };
@@ -1,124 +0,0 @@
1
- module.exports = {
2
- displayName: 'Frigg CLI Tests',
3
- testMatch: [
4
- '<rootDir>/__tests__/**/*.test.js',
5
- '<rootDir>/__tests__/**/*.spec.js',
6
- '<rootDir>/**/start-command.test.js',
7
- '<rootDir>/**/__tests__/**/*.test.js'
8
- ],
9
- // Exclude utility files and config from being treated as tests
10
- testPathIgnorePatterns: [
11
- '/node_modules/',
12
- '/__tests__/utils/',
13
- '/__tests__/jest.config.js',
14
- '/test-setup.js'
15
- ],
16
- testEnvironment: 'node',
17
- collectCoverageFrom: [
18
- '**/*.js',
19
- '!**/*.test.js',
20
- '!**/*.spec.js',
21
- '!**/node_modules/**',
22
- '!**/__tests__/**',
23
- '!**/coverage/**'
24
- ],
25
- coverageDirectory: 'coverage',
26
- coverageReporters: [
27
- 'text',
28
- 'text-summary',
29
- 'html',
30
- 'lcov',
31
- 'json'
32
- ],
33
- coverageThreshold: {
34
- global: {
35
- branches: 85,
36
- functions: 85,
37
- lines: 85,
38
- statements: 85
39
- },
40
- './install-command/index.js': {
41
- branches: 90,
42
- functions: 90,
43
- lines: 90,
44
- statements: 90
45
- },
46
- './build-command/index.js': {
47
- branches: 90,
48
- functions: 90,
49
- lines: 90,
50
- statements: 90
51
- },
52
- './deploy-command/index.js': {
53
- branches: 90,
54
- functions: 90,
55
- lines: 90,
56
- statements: 90
57
- },
58
- './ui-command/index.js': {
59
- branches: 90,
60
- functions: 90,
61
- lines: 90,
62
- statements: 90
63
- },
64
- './generate-command/index.js': {
65
- branches: 90,
66
- functions: 90,
67
- lines: 90,
68
- statements: 90
69
- },
70
- './db-setup-command/index.js': {
71
- branches: 90,
72
- functions: 90,
73
- lines: 90,
74
- statements: 90
75
- },
76
- './utils/database-validator.js': {
77
- branches: 85,
78
- functions: 85,
79
- lines: 85,
80
- statements: 85
81
- },
82
- './utils/prisma-runner.js': {
83
- branches: 85,
84
- functions: 85,
85
- lines: 85,
86
- statements: 85
87
- },
88
- './utils/error-messages.js': {
89
- branches: 85,
90
- functions: 85,
91
- lines: 85,
92
- statements: 85
93
- }
94
- },
95
- setupFilesAfterEnv: [
96
- '<rootDir>/__tests__/utils/test-setup.js'
97
- ],
98
- testTimeout: 10000,
99
- maxWorkers: '50%',
100
- verbose: true,
101
- collectCoverage: true,
102
- coveragePathIgnorePatterns: [
103
- '/node_modules/',
104
- '/__tests__/',
105
- '/coverage/',
106
- '.test.js',
107
- '.spec.js'
108
- ],
109
- moduleFileExtensions: [
110
- 'js',
111
- 'json',
112
- 'node'
113
- ],
114
- transform: {},
115
- // testResultsProcessor: 'jest-sonar-reporter', // Optional dependency
116
- reporters: [
117
- 'default'
118
- // jest-junit reporter removed - optional dependency
119
- ],
120
- watchman: false,
121
- forceExit: true,
122
- detectOpenHandles: true,
123
- errorOnDeprecated: true
124
- };
@@ -1,58 +0,0 @@
1
- {
2
- "name": "@friggframework/frigg-cli",
3
- "version": "2.0.0-next.0",
4
- "description": "Frigg Framework CLI tool",
5
- "main": "index.js",
6
- "bin": {
7
- "frigg": "./index.js"
8
- },
9
- "scripts": {
10
- "test": "jest"
11
- },
12
- "dependencies": {
13
- "@babel/parser": "^7.25.3",
14
- "@babel/traverse": "^7.25.3",
15
- "@friggframework/core": "^2.0.0-next.0",
16
- "@friggframework/devtools": "^2.0.0-next.0",
17
- "@friggframework/schemas": "^2.0.0-next.0",
18
- "@inquirer/prompts": "^5.3.8",
19
- "axios": "^1.7.2",
20
- "chalk": "^4.1.2",
21
- "commander": "^12.1.0",
22
- "cross-spawn": "^7.0.3",
23
- "dotenv": "^16.4.5",
24
- "fs-extra": "^11.2.0",
25
- "js-yaml": "^4.1.0",
26
- "lodash": "4.17.21",
27
- "node-cache": "^5.1.2",
28
- "open": "^8.4.2",
29
- "semver": "^7.6.0",
30
- "validate-npm-package-name": "^5.0.0"
31
- },
32
- "devDependencies": {
33
- "jest": "^29.7.0",
34
- "jest-mock-extended": "^3.0.5"
35
- },
36
- "keywords": [
37
- "frigg",
38
- "cli",
39
- "integration",
40
- "framework"
41
- ],
42
- "author": "Frigg Framework Team",
43
- "license": "MIT",
44
- "repository": {
45
- "type": "git",
46
- "url": "git+https://github.com/friggframework/frigg.git"
47
- },
48
- "bugs": {
49
- "url": "https://github.com/friggframework/frigg/issues"
50
- },
51
- "homepage": "https://github.com/friggframework/frigg#readme",
52
- "engines": {
53
- "node": ">=18"
54
- },
55
- "publishConfig": {
56
- "access": "public"
57
- }
58
- }
@@ -1,341 +0,0 @@
1
- /**
2
- * Frigg Repair Command
3
- *
4
- * Repairs infrastructure issues detected by frigg doctor:
5
- * - Import orphaned resources into CloudFormation stack
6
- * - Reconcile property drift between template and actual resources
7
- *
8
- * Usage:
9
- * frigg repair --import <stack-name>
10
- * frigg repair --reconcile <stack-name>
11
- * frigg repair --import --reconcile <stack-name> # Fix all issues
12
- */
13
-
14
- const path = require('path');
15
- const readline = require('readline');
16
-
17
- // Domain and Application Layer
18
- const StackIdentifier = require('@friggframework/devtools/infrastructure/domains/health/domain/value-objects/stack-identifier');
19
- const RunHealthCheckUseCase = require('@friggframework/devtools/infrastructure/domains/health/application/use-cases/run-health-check-use-case');
20
- const RepairViaImportUseCase = require('@friggframework/devtools/infrastructure/domains/health/application/use-cases/repair-via-import-use-case');
21
- const ReconcilePropertiesUseCase = require('@friggframework/devtools/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case');
22
-
23
- // Infrastructure Layer - AWS Adapters
24
- const AWSStackRepository = require('@friggframework/devtools/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository');
25
- const AWSResourceDetector = require('@friggframework/devtools/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector');
26
- const AWSResourceImporter = require('@friggframework/devtools/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer');
27
- const AWSPropertyReconciler = require('@friggframework/devtools/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler');
28
-
29
- // Domain Services
30
- const MismatchAnalyzer = require('@friggframework/devtools/infrastructure/domains/health/domain/services/mismatch-analyzer');
31
- const HealthScoreCalculator = require('@friggframework/devtools/infrastructure/domains/health/domain/services/health-score-calculator');
32
-
33
- /**
34
- * Create readline interface for user prompts
35
- * @returns {readline.Interface}
36
- */
37
- function createReadlineInterface() {
38
- return readline.createInterface({
39
- input: process.stdin,
40
- output: process.stdout,
41
- });
42
- }
43
-
44
- /**
45
- * Prompt user for confirmation
46
- * @param {string} question - Question to ask
47
- * @returns {Promise<boolean>} User confirmed
48
- */
49
- function confirm(question) {
50
- const rl = createReadlineInterface();
51
-
52
- return new Promise((resolve) => {
53
- rl.question(`${question} (y/N): `, (answer) => {
54
- rl.close();
55
- resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
56
- });
57
- });
58
- }
59
-
60
- /**
61
- * Handle import repair operation
62
- * @param {StackIdentifier} stackIdentifier - Stack identifier
63
- * @param {Object} report - Health check report
64
- * @param {Object} options - Command options
65
- */
66
- async function handleImportRepair(stackIdentifier, report, options) {
67
- const orphanedResources = report.getOrphanedResources();
68
-
69
- if (orphanedResources.length === 0) {
70
- console.log('\n✓ No orphaned resources to import');
71
- return { imported: 0, failed: 0 };
72
- }
73
-
74
- console.log(`\n📦 Found ${orphanedResources.length} orphaned resource(s) to import:`);
75
- orphanedResources.forEach((resource, idx) => {
76
- console.log(` ${idx + 1}. ${resource.resourceType} - ${resource.physicalId}`);
77
- });
78
-
79
- // Confirm with user (unless --yes flag)
80
- if (!options.yes) {
81
- const confirmed = await confirm(`\nImport ${orphanedResources.length} orphaned resource(s)?`);
82
- if (!confirmed) {
83
- console.log('Import cancelled by user');
84
- return { imported: 0, failed: 0, cancelled: true };
85
- }
86
- }
87
-
88
- // Wire up use case
89
- const resourceDetector = new AWSResourceDetector({ region: stackIdentifier.region });
90
- const resourceImporter = new AWSResourceImporter({ region: stackIdentifier.region });
91
- const repairUseCase = new RepairViaImportUseCase({ resourceDetector, resourceImporter });
92
-
93
- // Prepare resources for import
94
- const resourcesToImport = orphanedResources.map((resource, idx) => ({
95
- logicalId: `ImportedResource${idx + 1}`, // Generate logical ID
96
- physicalId: resource.physicalId,
97
- resourceType: resource.resourceType,
98
- }));
99
-
100
- // Execute import
101
- console.log('\n🔧 Importing resources...');
102
- const importResult = await repairUseCase.importMultipleResources({
103
- stackIdentifier,
104
- resources: resourcesToImport,
105
- });
106
-
107
- // Report results
108
- if (importResult.success) {
109
- console.log(`\n✓ Successfully imported ${importResult.importedCount} resource(s)`);
110
- } else {
111
- console.log(`\n✗ Import failed: ${importResult.message}`);
112
- if (importResult.validationErrors && importResult.validationErrors.length > 0) {
113
- console.log('\nValidation errors:');
114
- importResult.validationErrors.forEach((error) => {
115
- console.log(` • ${error.logicalId}: ${error.reason}`);
116
- });
117
- }
118
- }
119
-
120
- return {
121
- imported: importResult.importedCount,
122
- failed: importResult.failedCount,
123
- success: importResult.success,
124
- };
125
- }
126
-
127
- /**
128
- * Handle property reconciliation repair operation
129
- * @param {StackIdentifier} stackIdentifier - Stack identifier
130
- * @param {Object} report - Health check report
131
- * @param {Object} options - Command options
132
- */
133
- async function handleReconcileRepair(stackIdentifier, report, options) {
134
- const driftedResources = report.getDriftedResources();
135
-
136
- if (driftedResources.length === 0) {
137
- console.log('\n✓ No property drift to reconcile');
138
- return { reconciled: 0, failed: 0 };
139
- }
140
-
141
- // Count total property mismatches
142
- let totalMismatches = 0;
143
- driftedResources.forEach((resource) => {
144
- const issues = report.issues.filter(
145
- (issue) => issue.type === 'PROPERTY_MISMATCH' && issue.resourceId === resource.physicalId
146
- );
147
- totalMismatches += issues.length;
148
- });
149
-
150
- console.log(`\n🔧 Found ${driftedResources.length} drifted resource(s) with ${totalMismatches} property mismatch(es):`);
151
- driftedResources.forEach((resource) => {
152
- const issues = report.issues.filter(
153
- (issue) => issue.type === 'PROPERTY_MISMATCH' && issue.resourceId === resource.physicalId
154
- );
155
- console.log(` • ${resource.logicalId} (${resource.resourceType}): ${issues.length} mismatch(es)`);
156
- });
157
-
158
- // Determine mode (template or resource)
159
- const mode = options.mode || 'template';
160
- const modeDescription = mode === 'template'
161
- ? 'Update CloudFormation template to match actual resource state'
162
- : 'Update cloud resources to match CloudFormation template';
163
-
164
- console.log(`\nReconciliation mode: ${mode}`);
165
- console.log(` ${modeDescription}`);
166
-
167
- // Confirm with user (unless --yes flag)
168
- if (!options.yes) {
169
- const confirmed = await confirm(`\nReconcile ${totalMismatches} property mismatch(es) in ${mode} mode?`);
170
- if (!confirmed) {
171
- console.log('Reconciliation cancelled by user');
172
- return { reconciled: 0, failed: 0, cancelled: true };
173
- }
174
- }
175
-
176
- // Wire up use case
177
- const propertyReconciler = new AWSPropertyReconciler({ region: stackIdentifier.region });
178
- const reconcileUseCase = new ReconcilePropertiesUseCase({ propertyReconciler });
179
-
180
- // Execute reconciliation for each drifted resource
181
- console.log('\n🔧 Reconciling property drift...');
182
- let reconciledCount = 0;
183
- let failedCount = 0;
184
-
185
- for (const resource of driftedResources) {
186
- // Get property mismatches for this resource
187
- const resourceIssues = report.issues.filter(
188
- (issue) => issue.type === 'PROPERTY_MISMATCH' && issue.resourceId === resource.physicalId
189
- );
190
-
191
- if (resourceIssues.length === 0) continue;
192
-
193
- const mismatches = resourceIssues.map((issue) => issue.propertyMismatch);
194
-
195
- try {
196
- const result = await reconcileUseCase.reconcileMultipleProperties({
197
- stackIdentifier,
198
- logicalId: resource.logicalId,
199
- mismatches,
200
- mode,
201
- });
202
-
203
- reconciledCount += result.reconciledCount;
204
- failedCount += result.failedCount;
205
-
206
- console.log(` ✓ ${resource.logicalId}: Reconciled ${result.reconciledCount} property(ies)`);
207
- if (result.skippedCount > 0) {
208
- console.log(` (Skipped ${result.skippedCount} immutable property(ies))`);
209
- }
210
- } catch (error) {
211
- failedCount++;
212
- console.log(` ✗ ${resource.logicalId}: ${error.message}`);
213
- }
214
- }
215
-
216
- // Report results
217
- if (failedCount === 0) {
218
- console.log(`\n✓ Successfully reconciled ${reconciledCount} property mismatch(es)`);
219
- } else {
220
- console.log(`\n⚠ Reconciled ${reconciledCount} property(ies), ${failedCount} failed`);
221
- }
222
-
223
- return { reconciled: reconciledCount, failed: failedCount, success: failedCount === 0 };
224
- }
225
-
226
- /**
227
- * Execute repair operations
228
- * @param {string} stackName - CloudFormation stack name
229
- * @param {Object} options - Command options
230
- */
231
- async function repairCommand(stackName, options = {}) {
232
- try {
233
- // Validate required parameter
234
- if (!stackName) {
235
- console.error('Error: Stack name is required');
236
- console.log('Usage: frigg repair [options] <stack-name>');
237
- console.log('Options:');
238
- console.log(' --import Import orphaned resources');
239
- console.log(' --reconcile Reconcile property drift');
240
- console.log(' --yes Skip confirmation prompts');
241
- process.exit(1);
242
- }
243
-
244
- // Validate at least one repair operation is selected
245
- if (!options.import && !options.reconcile) {
246
- console.error('Error: At least one repair operation must be specified (--import or --reconcile)');
247
- console.log('Usage: frigg repair [options] <stack-name>');
248
- process.exit(1);
249
- }
250
-
251
- // Extract options with defaults
252
- const region = options.region || process.env.AWS_REGION || 'us-east-1';
253
- const verbose = options.verbose || false;
254
-
255
- console.log(`\n🏥 Running Frigg Repair on stack: ${stackName} (${region})`);
256
-
257
- // 1. Create stack identifier
258
- const stackIdentifier = new StackIdentifier({ stackName, region });
259
-
260
- // 2. Run health check first to identify issues
261
- console.log('\n🔍 Running health check to identify issues...');
262
-
263
- const stackRepository = new AWSStackRepository({ region });
264
- const resourceDetector = new AWSResourceDetector({ region });
265
- const mismatchAnalyzer = new MismatchAnalyzer();
266
- const healthScoreCalculator = new HealthScoreCalculator();
267
-
268
- const runHealthCheckUseCase = new RunHealthCheckUseCase({
269
- stackRepository,
270
- resourceDetector,
271
- mismatchAnalyzer,
272
- healthScoreCalculator,
273
- });
274
-
275
- const report = await runHealthCheckUseCase.execute({ stackIdentifier });
276
-
277
- console.log(`\nHealth Score: ${report.healthScore.value}/100 (${report.healthScore.qualitativeAssessment()})`);
278
- console.log(`Issues: ${report.getIssueCount()} total (${report.getCriticalIssueCount()} critical)`);
279
-
280
- // 3. Execute requested repair operations
281
- const results = {
282
- imported: 0,
283
- reconciled: 0,
284
- failed: 0,
285
- };
286
-
287
- if (options.import) {
288
- const importResult = await handleImportRepair(stackIdentifier, report, options);
289
- if (!importResult.cancelled) {
290
- results.imported = importResult.imported || 0;
291
- results.failed += importResult.failed || 0;
292
- }
293
- }
294
-
295
- if (options.reconcile) {
296
- const reconcileResult = await handleReconcileRepair(stackIdentifier, report, options);
297
- if (!reconcileResult.cancelled) {
298
- results.reconciled = reconcileResult.reconciled || 0;
299
- results.failed += reconcileResult.failed || 0;
300
- }
301
- }
302
-
303
- // 4. Final summary
304
- console.log('\n' + '═'.repeat(80));
305
- console.log('Repair Summary:');
306
- if (options.import) {
307
- console.log(` Imported: ${results.imported} resource(s)`);
308
- }
309
- if (options.reconcile) {
310
- console.log(` Reconciled: ${results.reconciled} property(ies)`);
311
- }
312
- console.log(` Failed: ${results.failed}`);
313
- console.log('═'.repeat(80));
314
-
315
- // Run health check again to verify repairs
316
- console.log('\n🔍 Running health check to verify repairs...');
317
- const verifyReport = await runHealthCheckUseCase.execute({ stackIdentifier });
318
- console.log(`\nNew Health Score: ${verifyReport.healthScore.value}/100 (${verifyReport.healthScore.qualitativeAssessment()})`);
319
-
320
- if (verifyReport.healthScore.value > report.healthScore.value) {
321
- console.log(`\n✓ Health improved by ${verifyReport.healthScore.value - report.healthScore.value} points!`);
322
- }
323
-
324
- // 5. Exit with appropriate code
325
- if (results.failed > 0) {
326
- process.exit(1);
327
- } else {
328
- process.exit(0);
329
- }
330
- } catch (error) {
331
- console.error(`\n✗ Repair failed: ${error.message}`);
332
-
333
- if (options.verbose && error.stack) {
334
- console.error(`\nStack trace:\n${error.stack}`);
335
- }
336
-
337
- process.exit(1);
338
- }
339
- }
340
-
341
- module.exports = { repairCommand };