infrawise 0.4.2 → 0.6.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/README.md +21 -48
- package/dist/adapters/aws.js +50 -64
- package/dist/adapters/dynamodb.js +17 -22
- package/dist/adapters/logs.js +12 -16
- package/dist/adapters/mongodb.js +10 -16
- package/dist/adapters/mysql.js +8 -17
- package/dist/adapters/postgres.js +9 -13
- package/dist/adapters/terraform.js +14 -56
- package/dist/analyzers/aws-services.js +7 -17
- package/dist/analyzers/dynamodb.js +6 -12
- package/dist/analyzers/index.js +13 -41
- package/dist/analyzers/mongodb.js +4 -9
- package/dist/analyzers/mysql.js +4 -9
- package/dist/analyzers/postgres.js +3 -9
- package/dist/analyzers/rds.js +5 -13
- package/dist/analyzers/terraform.js +1 -5
- package/dist/cli/commands/analyze.js +178 -215
- package/dist/cli/commands/auth.js +24 -30
- package/dist/cli/commands/dev.js +84 -43
- package/dist/cli/commands/doctor.js +35 -74
- package/dist/cli/commands/init.js +33 -72
- package/dist/cli/index.js +21 -28
- package/dist/cli/utils.js +40 -86
- package/dist/context/index.js +49 -85
- package/dist/core/cache.js +5 -43
- package/dist/core/config.js +43 -81
- package/dist/core/errors.js +7 -17
- package/dist/core/index.js +4 -22
- package/dist/core/logger.js +4 -10
- package/dist/graph/index.js +15 -32
- package/dist/server/index.js +236 -302
- package/dist/types.js +1 -2
- package/package.json +34 -31
|
@@ -1,28 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const ora_1 = __importDefault(require("ora"));
|
|
10
|
-
const utils_1 = require("../utils");
|
|
11
|
-
const dynamodb_1 = require("../../adapters/dynamodb");
|
|
12
|
-
async function runAuth() {
|
|
13
|
-
(0, utils_1.printHeader)('AWS Authentication');
|
|
14
|
-
const profiles = (0, utils_1.readAWSProfiles)();
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { readAWSProfiles, log, printHeader } from '../utils.js';
|
|
5
|
+
import { validateDynamoAccess } from '../../adapters/dynamodb.js';
|
|
6
|
+
export async function runAuth() {
|
|
7
|
+
printHeader('AWS Authentication');
|
|
8
|
+
const profiles = readAWSProfiles();
|
|
15
9
|
if (profiles.length === 0) {
|
|
16
|
-
|
|
10
|
+
log.fail('No AWS profiles found');
|
|
17
11
|
console.log('');
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
log.info('Run ' + chalk.cyan('aws configure') + ' to set up credentials');
|
|
13
|
+
log.info('Or manually edit ' + chalk.dim('~/.aws/credentials'));
|
|
20
14
|
console.log('');
|
|
21
15
|
return;
|
|
22
16
|
}
|
|
23
|
-
|
|
17
|
+
log.success(`Found ${profiles.length} profile(s)`);
|
|
24
18
|
console.log('');
|
|
25
|
-
const { selectedProfile } = await
|
|
19
|
+
const { selectedProfile } = await inquirer.prompt([
|
|
26
20
|
{
|
|
27
21
|
type: 'list',
|
|
28
22
|
name: 'selectedProfile',
|
|
@@ -31,27 +25,27 @@ async function runAuth() {
|
|
|
31
25
|
},
|
|
32
26
|
]);
|
|
33
27
|
console.log('');
|
|
34
|
-
const spin = (
|
|
28
|
+
const spin = ora({ text: chalk.dim(`Validating "${selectedProfile}"...`), color: 'cyan' }).start();
|
|
35
29
|
const testConfig = {
|
|
36
30
|
project: 'auth-test',
|
|
37
31
|
aws: { profile: selectedProfile, region: 'us-east-1' },
|
|
38
32
|
};
|
|
39
|
-
const isValid = await
|
|
33
|
+
const isValid = await validateDynamoAccess(testConfig);
|
|
40
34
|
if (isValid) {
|
|
41
|
-
spin.succeed(
|
|
35
|
+
spin.succeed(chalk.green(`Profile "${chalk.bold(selectedProfile)}" is valid`));
|
|
42
36
|
console.log('');
|
|
43
|
-
console.log(
|
|
44
|
-
console.log(
|
|
37
|
+
console.log(chalk.dim(' Update your infrawise.yaml:'));
|
|
38
|
+
console.log(chalk.cyan(` aws:\n profile: ${selectedProfile}`));
|
|
45
39
|
}
|
|
46
40
|
else {
|
|
47
|
-
spin.fail(
|
|
41
|
+
spin.fail(chalk.red(`Profile "${chalk.bold(selectedProfile)}" cannot access DynamoDB`));
|
|
48
42
|
console.log('');
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
log.warn('Possible causes:');
|
|
44
|
+
log.dim('Missing IAM permissions — need dynamodb:ListTables, dynamodb:DescribeTable');
|
|
45
|
+
log.dim('Expired SSO — run: aws sso login');
|
|
46
|
+
log.dim('Wrong region — check your AWS config');
|
|
53
47
|
console.log('');
|
|
54
|
-
|
|
48
|
+
log.info(`Run ${chalk.cyan('infrawise doctor')} for a full diagnostic`);
|
|
55
49
|
}
|
|
56
50
|
console.log('');
|
|
57
51
|
}
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const core_1 = require("../../core");
|
|
10
|
-
const server_1 = require("../../server");
|
|
11
|
-
const utils_1 = require("../utils");
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { loadConfig, formatError, readCache } from '../../core/index.js';
|
|
6
|
+
import { createServer, setGraphState } from '../../server/index.js';
|
|
7
|
+
import { log, printHeader } from '../utils.js';
|
|
8
|
+
import { runAnalyze, runCodeRefresh } from './analyze.js';
|
|
12
9
|
const BOX_W = 52;
|
|
13
10
|
const TOOL_MAP = [
|
|
14
11
|
{ name: 'get_infra_overview' },
|
|
@@ -33,10 +30,10 @@ function isEnabled(cfg, service) {
|
|
|
33
30
|
}
|
|
34
31
|
function boxLine(visibleContent, coloredContent) {
|
|
35
32
|
const padding = ' '.repeat(Math.max(0, BOX_W - visibleContent.length));
|
|
36
|
-
console.log(
|
|
33
|
+
console.log(chalk.dim(' │') + coloredContent + padding + chalk.dim('│'));
|
|
37
34
|
}
|
|
38
35
|
function boxDivider() {
|
|
39
|
-
console.log(
|
|
36
|
+
console.log(chalk.dim(' ├────────────────────────────────────────────────────┤'));
|
|
40
37
|
}
|
|
41
38
|
function groupTools(tools) {
|
|
42
39
|
const lines = [];
|
|
@@ -55,72 +52,116 @@ function groupTools(tools) {
|
|
|
55
52
|
}
|
|
56
53
|
return lines;
|
|
57
54
|
}
|
|
58
|
-
async function runDev(options = {}) {
|
|
55
|
+
export async function runDev(options = {}) {
|
|
59
56
|
const port = options.port ?? 3000;
|
|
60
|
-
|
|
57
|
+
printHeader('MCP Server');
|
|
61
58
|
let config;
|
|
62
59
|
try {
|
|
63
|
-
config =
|
|
64
|
-
|
|
60
|
+
config = loadConfig(options.config);
|
|
61
|
+
log.success('Config loaded', options.config ?? 'infrawise.yaml');
|
|
65
62
|
}
|
|
66
63
|
catch (err) {
|
|
67
|
-
console.error(
|
|
64
|
+
console.error(formatError(err));
|
|
68
65
|
process.exit(1);
|
|
69
66
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
67
|
+
const repoPath = process.cwd();
|
|
68
|
+
// Auto-analyze if no cache
|
|
69
|
+
const cachedGraph = readCache('graph');
|
|
70
|
+
const cachedFindings = readCache('findings');
|
|
73
71
|
if (cachedGraph && cachedFindings) {
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
log.success('Cached analysis loaded', `${cachedGraph.nodes.length} nodes · ${cachedGraph.edges.length} edges · ${cachedFindings.length} finding(s)`);
|
|
73
|
+
setGraphState(cachedGraph, cachedFindings);
|
|
76
74
|
}
|
|
77
75
|
else {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
log.warn('No cache found — running analysis now...');
|
|
77
|
+
console.log('');
|
|
78
|
+
await runAnalyze({ repo: repoPath, config: options.config });
|
|
79
|
+
const freshGraph = readCache('graph') ?? { nodes: [], edges: [] };
|
|
80
|
+
const freshFindings = readCache('findings') ?? [];
|
|
81
|
+
setGraphState(freshGraph, freshFindings);
|
|
81
82
|
}
|
|
82
83
|
console.log('');
|
|
83
84
|
// Start server
|
|
84
|
-
const spin = (
|
|
85
|
-
const { start } =
|
|
85
|
+
const spin = ora({ text: chalk.dim('Starting server...'), color: 'cyan' }).start();
|
|
86
|
+
const { start } = createServer(port);
|
|
86
87
|
await start();
|
|
87
|
-
spin.succeed(
|
|
88
|
+
spin.succeed(chalk.green('Server running'));
|
|
88
89
|
// Compute active/inactive tools from config
|
|
89
90
|
const activeTools = TOOL_MAP.filter((t) => isEnabled(config, t.service)).map((t) => t.name);
|
|
90
91
|
const inactiveTools = TOOL_MAP.filter((t) => !isEnabled(config, t.service)).map((t) => t.name);
|
|
91
92
|
// URL rows
|
|
92
93
|
const mcpUrl = `http://localhost:${port}/mcp`;
|
|
93
|
-
const toolsUrl = `http://localhost:${port}/mcp/tools`;
|
|
94
94
|
const healthUrl = `http://localhost:${port}/health`;
|
|
95
95
|
// Print box
|
|
96
96
|
console.log('');
|
|
97
|
-
console.log(
|
|
98
|
-
boxLine(' MCP Server',
|
|
97
|
+
console.log(chalk.dim(' ┌────────────────────────────────────────────────────┐'));
|
|
98
|
+
boxLine(' MCP Server', chalk.bold(' MCP Server'));
|
|
99
99
|
boxDivider();
|
|
100
|
-
boxLine(` POST ${mcpUrl}`, ` ${
|
|
101
|
-
boxLine(` GET ${
|
|
102
|
-
boxLine(` GET ${healthUrl}`, ` ${chalk_1.default.dim('GET')} ${chalk_1.default.cyan(healthUrl)}`);
|
|
100
|
+
boxLine(` POST ${mcpUrl}`, ` ${chalk.dim('POST')} ${chalk.cyan(mcpUrl)}`);
|
|
101
|
+
boxLine(` GET ${healthUrl}`, ` ${chalk.dim('GET')} ${chalk.cyan(healthUrl)}`);
|
|
103
102
|
boxDivider();
|
|
104
103
|
const activeLabel = ` Tools (${activeTools.length} active${inactiveTools.length > 0 ? ` · ${inactiveTools.length} off` : ''})`;
|
|
105
|
-
boxLine(activeLabel,
|
|
104
|
+
boxLine(activeLabel, chalk.dim(activeLabel));
|
|
106
105
|
for (const line of groupTools(activeTools)) {
|
|
107
106
|
boxLine(` ${line}`, ` ${line}`);
|
|
108
107
|
}
|
|
109
108
|
if (inactiveTools.length > 0) {
|
|
110
109
|
boxDivider();
|
|
111
|
-
boxLine(' Off (enable in infrawise.yaml):',
|
|
110
|
+
boxLine(' Off (enable in infrawise.yaml):', chalk.dim(' Off (enable in infrawise.yaml):'));
|
|
112
111
|
for (const line of groupTools(inactiveTools)) {
|
|
113
|
-
boxLine(` ${line}`,
|
|
112
|
+
boxLine(` ${line}`, chalk.dim(` ${line}`));
|
|
114
113
|
}
|
|
115
114
|
}
|
|
116
|
-
console.log(
|
|
115
|
+
console.log(chalk.dim(' └────────────────────────────────────────────────────┘'));
|
|
117
116
|
console.log('');
|
|
118
|
-
console.log(
|
|
119
|
-
console.log(
|
|
117
|
+
console.log(chalk.dim(' Add via CLI:'));
|
|
118
|
+
console.log(chalk.dim(` claude mcp add --transport http infrawise ${mcpUrl}`));
|
|
120
119
|
console.log('');
|
|
121
|
-
console.log(
|
|
120
|
+
console.log(chalk.dim(' Watching for file changes... Press Ctrl+C to stop\n'));
|
|
121
|
+
// File watch — re-run code analysis on save, skip slow AWS/DB extraction
|
|
122
|
+
let debounceTimer = null;
|
|
123
|
+
let refreshing = false;
|
|
124
|
+
const configFile = path.resolve(options.config ?? 'infrawise.yaml');
|
|
125
|
+
try {
|
|
126
|
+
fs.watch(repoPath, { recursive: true }, (_, filename) => {
|
|
127
|
+
if (!filename)
|
|
128
|
+
return;
|
|
129
|
+
const abs = path.join(repoPath, filename);
|
|
130
|
+
if (abs === configFile) {
|
|
131
|
+
console.log(chalk.dim('\n infrawise.yaml changed — restart to apply config changes\n'));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const ext = path.extname(filename);
|
|
135
|
+
if (!['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext))
|
|
136
|
+
return;
|
|
137
|
+
if (filename.includes('node_modules') || filename.startsWith('.infrawise'))
|
|
138
|
+
return;
|
|
139
|
+
if (debounceTimer)
|
|
140
|
+
clearTimeout(debounceTimer);
|
|
141
|
+
debounceTimer = setTimeout(async () => {
|
|
142
|
+
if (refreshing)
|
|
143
|
+
return;
|
|
144
|
+
refreshing = true;
|
|
145
|
+
const spin = ora({ text: chalk.dim('Refreshing code analysis...'), color: 'cyan' }).start();
|
|
146
|
+
try {
|
|
147
|
+
const { graph, findings } = await runCodeRefresh(repoPath, config);
|
|
148
|
+
setGraphState(graph, findings);
|
|
149
|
+
spin.succeed(chalk.green('Analysis refreshed') + chalk.dim(` ${graph.nodes.length} nodes · ${findings.length} finding(s)`));
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
spin.warn(chalk.yellow('Refresh failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
refreshing = false;
|
|
156
|
+
}
|
|
157
|
+
}, 2000);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// fs.watch may not support recursive on all platforms — silently skip
|
|
162
|
+
}
|
|
122
163
|
process.on('SIGINT', () => {
|
|
123
|
-
console.log(
|
|
164
|
+
console.log(chalk.dim('\n Shutting down...\n'));
|
|
124
165
|
process.exit(0);
|
|
125
166
|
});
|
|
126
167
|
await new Promise(() => { });
|
|
@@ -1,78 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.runDoctor = runDoctor;
|
|
40
|
-
const fs = __importStar(require("fs"));
|
|
41
|
-
const path = __importStar(require("path"));
|
|
42
|
-
const os = __importStar(require("os"));
|
|
43
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
-
const ora_1 = __importDefault(require("ora"));
|
|
45
|
-
const core_1 = require("../../core");
|
|
46
|
-
const dynamodb_1 = require("../../adapters/dynamodb");
|
|
47
|
-
const postgres_1 = require("../../adapters/postgres");
|
|
48
|
-
const mysql_1 = require("../../adapters/mysql");
|
|
49
|
-
const mongodb_1 = require("../../adapters/mongodb");
|
|
50
|
-
const aws_1 = require("../../adapters/aws");
|
|
51
|
-
const logs_1 = require("../../adapters/logs");
|
|
52
|
-
const utils_1 = require("../utils");
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { loadConfig } from '../../core/index.js';
|
|
7
|
+
import { probeDynamoAccess } from '../../adapters/dynamodb.js';
|
|
8
|
+
import { validatePostgresAccess } from '../../adapters/postgres.js';
|
|
9
|
+
import { validateMySQLAccess } from '../../adapters/mysql.js';
|
|
10
|
+
import { validateMongoAccess } from '../../adapters/mongodb.js';
|
|
11
|
+
import { validateSQSAccess, validateSNSAccess, validateSSMAccess, validateSecretsAccess, validateLambdaAccess, } from '../../adapters/aws.js';
|
|
12
|
+
import { validateLogsAccess } from '../../adapters/logs.js';
|
|
13
|
+
import { printHeader } from '../utils.js';
|
|
53
14
|
async function runCheck(label, fn) {
|
|
54
|
-
const spin = (
|
|
15
|
+
const spin = ora({ text: chalk.dim(label), color: 'cyan' }).start();
|
|
55
16
|
const result = await fn();
|
|
56
17
|
switch (result.status) {
|
|
57
18
|
case 'pass':
|
|
58
|
-
spin.succeed(
|
|
19
|
+
spin.succeed(chalk.green(result.name) + chalk.dim(` ${result.message}`));
|
|
59
20
|
break;
|
|
60
21
|
case 'fail':
|
|
61
|
-
spin.fail(
|
|
22
|
+
spin.fail(chalk.red(result.name) + chalk.dim(` ${result.message}`));
|
|
62
23
|
break;
|
|
63
24
|
case 'warn':
|
|
64
|
-
spin.warn(
|
|
25
|
+
spin.warn(chalk.yellow(result.name) + chalk.dim(` ${result.message}`));
|
|
65
26
|
break;
|
|
66
27
|
case 'skip':
|
|
67
|
-
spin.info(
|
|
28
|
+
spin.info(chalk.dim(`${result.name} ${result.message}`));
|
|
68
29
|
break;
|
|
69
30
|
}
|
|
70
31
|
if (result.detail)
|
|
71
|
-
console.log(
|
|
32
|
+
console.log(chalk.dim(` ${result.detail}`));
|
|
72
33
|
return result;
|
|
73
34
|
}
|
|
74
|
-
async function runDoctor(options = {}) {
|
|
75
|
-
|
|
35
|
+
export async function runDoctor(options = {}) {
|
|
36
|
+
printHeader('Infrawise Doctor');
|
|
76
37
|
const configPath = path.resolve(options.config ?? 'infrawise.yaml');
|
|
77
38
|
const results = [];
|
|
78
39
|
// Config file
|
|
@@ -91,7 +52,7 @@ async function runDoctor(options = {}) {
|
|
|
91
52
|
if (!fs.existsSync(configPath))
|
|
92
53
|
return { name: 'Config validation', status: 'skip', message: 'No config file' };
|
|
93
54
|
try {
|
|
94
|
-
config =
|
|
55
|
+
config = loadConfig(options.config);
|
|
95
56
|
return { name: 'Config validation', status: 'pass', message: `project: ${config.project}` };
|
|
96
57
|
}
|
|
97
58
|
catch (err) {
|
|
@@ -121,7 +82,7 @@ async function runDoctor(options = {}) {
|
|
|
121
82
|
if (config.dynamodb?.enabled !== true)
|
|
122
83
|
return { name: 'DynamoDB', status: 'skip', message: 'Disabled in config' };
|
|
123
84
|
try {
|
|
124
|
-
await
|
|
85
|
+
await probeDynamoAccess(config);
|
|
125
86
|
return { name: 'DynamoDB', status: 'pass', message: `Connected (profile: ${config.aws?.profile ?? 'default'})` };
|
|
126
87
|
}
|
|
127
88
|
catch (err) {
|
|
@@ -137,7 +98,7 @@ async function runDoctor(options = {}) {
|
|
|
137
98
|
if (config?.sqs?.enabled !== true)
|
|
138
99
|
return { name: 'SQS', status: 'skip', message: 'Disabled in config' };
|
|
139
100
|
try {
|
|
140
|
-
await
|
|
101
|
+
await validateSQSAccess(awsCfg);
|
|
141
102
|
return { name: 'SQS', status: 'pass', message: 'Connected' };
|
|
142
103
|
}
|
|
143
104
|
catch (err) {
|
|
@@ -153,7 +114,7 @@ async function runDoctor(options = {}) {
|
|
|
153
114
|
if (config?.sns?.enabled !== true)
|
|
154
115
|
return { name: 'SNS', status: 'skip', message: 'Disabled in config' };
|
|
155
116
|
try {
|
|
156
|
-
await
|
|
117
|
+
await validateSNSAccess(awsCfg);
|
|
157
118
|
return { name: 'SNS', status: 'pass', message: 'Connected' };
|
|
158
119
|
}
|
|
159
120
|
catch (err) {
|
|
@@ -169,7 +130,7 @@ async function runDoctor(options = {}) {
|
|
|
169
130
|
if (config?.ssm?.enabled !== true)
|
|
170
131
|
return { name: 'SSM', status: 'skip', message: 'Disabled in config' };
|
|
171
132
|
try {
|
|
172
|
-
await
|
|
133
|
+
await validateSSMAccess(awsCfg);
|
|
173
134
|
return { name: 'SSM', status: 'pass', message: 'Connected (metadata only)' };
|
|
174
135
|
}
|
|
175
136
|
catch (err) {
|
|
@@ -185,7 +146,7 @@ async function runDoctor(options = {}) {
|
|
|
185
146
|
if (config?.secretsManager?.enabled !== true)
|
|
186
147
|
return { name: 'Secrets Manager', status: 'skip', message: 'Disabled in config' };
|
|
187
148
|
try {
|
|
188
|
-
await
|
|
149
|
+
await validateSecretsAccess(awsCfg);
|
|
189
150
|
return { name: 'Secrets Manager', status: 'pass', message: 'Connected (names/rotation only)' };
|
|
190
151
|
}
|
|
191
152
|
catch (err) {
|
|
@@ -201,7 +162,7 @@ async function runDoctor(options = {}) {
|
|
|
201
162
|
if (config?.lambda?.enabled !== true)
|
|
202
163
|
return { name: 'Lambda', status: 'skip', message: 'Disabled in config' };
|
|
203
164
|
try {
|
|
204
|
-
await
|
|
165
|
+
await validateLambdaAccess(awsCfg);
|
|
205
166
|
return { name: 'Lambda', status: 'pass', message: 'Connected' };
|
|
206
167
|
}
|
|
207
168
|
catch (err) {
|
|
@@ -217,7 +178,7 @@ async function runDoctor(options = {}) {
|
|
|
217
178
|
if (!config?.cloudwatchLogs?.enabled)
|
|
218
179
|
return { name: 'CloudWatch Logs', status: 'skip', message: 'Not enabled in config' };
|
|
219
180
|
try {
|
|
220
|
-
await
|
|
181
|
+
await validateLogsAccess(awsCfg);
|
|
221
182
|
return { name: 'CloudWatch Logs', status: 'pass', message: 'Connected' };
|
|
222
183
|
}
|
|
223
184
|
catch (err) {
|
|
@@ -234,7 +195,7 @@ async function runDoctor(options = {}) {
|
|
|
234
195
|
return { name: 'PostgreSQL', status: 'skip', message: 'Not configured' };
|
|
235
196
|
}
|
|
236
197
|
try {
|
|
237
|
-
const ok = await
|
|
198
|
+
const ok = await validatePostgresAccess(config.postgres.connectionString);
|
|
238
199
|
return {
|
|
239
200
|
name: 'PostgreSQL', status: ok ? 'pass' : 'fail',
|
|
240
201
|
message: ok ? 'Connected' : 'Cannot connect',
|
|
@@ -251,7 +212,7 @@ async function runDoctor(options = {}) {
|
|
|
251
212
|
return { name: 'MySQL', status: 'skip', message: 'Not configured' };
|
|
252
213
|
}
|
|
253
214
|
try {
|
|
254
|
-
const ok = await
|
|
215
|
+
const ok = await validateMySQLAccess(config.mysql.connectionString);
|
|
255
216
|
return {
|
|
256
217
|
name: 'MySQL', status: ok ? 'pass' : 'fail',
|
|
257
218
|
message: ok ? 'Connected' : 'Cannot connect',
|
|
@@ -268,7 +229,7 @@ async function runDoctor(options = {}) {
|
|
|
268
229
|
return { name: 'MongoDB', status: 'skip', message: 'Not configured' };
|
|
269
230
|
}
|
|
270
231
|
try {
|
|
271
|
-
const ok = await
|
|
232
|
+
const ok = await validateMongoAccess(config.mongodb.connectionString);
|
|
272
233
|
return {
|
|
273
234
|
name: 'MongoDB', status: ok ? 'pass' : 'fail',
|
|
274
235
|
message: ok ? 'Connected' : 'Cannot connect',
|
|
@@ -329,12 +290,12 @@ async function runDoctor(options = {}) {
|
|
|
329
290
|
const failed = results.filter((r) => r.status === 'fail').length;
|
|
330
291
|
const warned = results.filter((r) => r.status === 'warn').length;
|
|
331
292
|
console.log('');
|
|
332
|
-
console.log(
|
|
293
|
+
console.log(chalk.dim(' ' + '─'.repeat(40)));
|
|
333
294
|
if (failed === 0) {
|
|
334
|
-
console.log(` ${
|
|
295
|
+
console.log(` ${chalk.green.bold('All checks passed')} ${chalk.dim(`${passed} passed, ${warned} warning(s)`)}`);
|
|
335
296
|
}
|
|
336
297
|
else {
|
|
337
|
-
console.log(` ${
|
|
298
|
+
console.log(` ${chalk.red.bold(`${failed} check(s) failed`)} ${chalk.dim(`${passed} passed, ${warned} warning(s)`)}`);
|
|
338
299
|
}
|
|
339
300
|
console.log('');
|
|
340
301
|
if (failed > 0)
|