@friggframework/devtools 2.0.0--canary.474.295ae1f.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.
- package/management-ui/server/utils/cliIntegration.js +1 -1
- package/package.json +6 -6
- package/frigg-cli/.eslintrc.js +0 -141
- package/frigg-cli/README.md +0 -1290
- package/frigg-cli/__tests__/unit/commands/build.test.js +0 -279
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
- package/frigg-cli/__tests__/unit/commands/deploy.test.js +0 -320
- package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
- package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
- package/frigg-cli/__tests__/unit/version-detection.test.js +0 -171
- package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
- package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
- package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
- package/frigg-cli/__tests__/utils/test-setup.js +0 -287
- package/frigg-cli/build-command/index.js +0 -66
- package/frigg-cli/db-setup-command/index.js +0 -193
- package/frigg-cli/deploy-command/index.js +0 -302
- package/frigg-cli/doctor-command/index.js +0 -249
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
- package/frigg-cli/generate-command/azure-generator.js +0 -43
- package/frigg-cli/generate-command/gcp-generator.js +0 -47
- package/frigg-cli/generate-command/index.js +0 -332
- package/frigg-cli/generate-command/terraform-generator.js +0 -555
- package/frigg-cli/generate-iam-command.js +0 -118
- package/frigg-cli/index.js +0 -173
- package/frigg-cli/index.test.js +0 -158
- package/frigg-cli/init-command/backend-first-handler.js +0 -756
- package/frigg-cli/init-command/index.js +0 -93
- package/frigg-cli/init-command/template-handler.js +0 -143
- package/frigg-cli/install-command/backend-js.js +0 -33
- package/frigg-cli/install-command/commit-changes.js +0 -16
- package/frigg-cli/install-command/environment-variables.js +0 -127
- package/frigg-cli/install-command/environment-variables.test.js +0 -136
- package/frigg-cli/install-command/index.js +0 -54
- package/frigg-cli/install-command/install-package.js +0 -13
- package/frigg-cli/install-command/integration-file.js +0 -30
- package/frigg-cli/install-command/logger.js +0 -12
- package/frigg-cli/install-command/template.js +0 -90
- package/frigg-cli/install-command/validate-package.js +0 -75
- package/frigg-cli/jest.config.js +0 -124
- package/frigg-cli/package.json +0 -58
- package/frigg-cli/repair-command/index.js +0 -341
- package/frigg-cli/start-command/index.js +0 -149
- package/frigg-cli/start-command/start-command.test.js +0 -297
- package/frigg-cli/test/init-command.test.js +0 -180
- package/frigg-cli/test/npm-registry.test.js +0 -319
- package/frigg-cli/ui-command/index.js +0 -154
- package/frigg-cli/utils/app-resolver.js +0 -319
- package/frigg-cli/utils/backend-path.js +0 -25
- package/frigg-cli/utils/database-validator.js +0 -154
- package/frigg-cli/utils/error-messages.js +0 -257
- package/frigg-cli/utils/npm-registry.js +0 -167
- package/frigg-cli/utils/process-manager.js +0 -199
- package/frigg-cli/utils/repo-detection.js +0 -405
- /package/infrastructure/{DOCTOR.md → HEALTH.md} +0 -0
|
@@ -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
|
-
};
|
package/frigg-cli/jest.config.js
DELETED
|
@@ -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
|
-
};
|
package/frigg-cli/package.json
DELETED
|
@@ -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 };
|