infrawise 0.5.0 → 0.7.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 +26 -14
- package/dist/adapters/aws.js +152 -72
- package/dist/adapters/dynamodb.js +17 -27
- package/dist/adapters/logs.js +12 -21
- 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 +34 -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 +131 -158
- package/dist/cli/commands/auth.js +24 -30
- package/dist/cli/commands/dev.js +46 -84
- package/dist/cli/commands/doctor.js +51 -74
- package/dist/cli/commands/init.js +48 -73
- package/dist/cli/index.js +21 -23
- 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 +50 -83
- 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 +73 -33
- package/dist/server/index.js +116 -93
- package/dist/types.js +1 -2
- package/package.json +32 -30
|
@@ -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,50 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
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.runDev = runDev;
|
|
40
|
-
const fs = __importStar(require("fs"));
|
|
41
|
-
const path = __importStar(require("path"));
|
|
42
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
-
const ora_1 = __importDefault(require("ora"));
|
|
44
|
-
const core_1 = require("../../core");
|
|
45
|
-
const server_1 = require("../../server");
|
|
46
|
-
const utils_1 = require("../utils");
|
|
47
|
-
const analyze_1 = require("./analyze");
|
|
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';
|
|
48
9
|
const BOX_W = 52;
|
|
49
10
|
const TOOL_MAP = [
|
|
50
11
|
{ name: 'get_infra_overview' },
|
|
@@ -59,6 +20,7 @@ const TOOL_MAP = [
|
|
|
59
20
|
{ name: 'get_secrets_overview', service: 'secretsManager' },
|
|
60
21
|
{ name: 'get_parameter_overview', service: 'ssm' },
|
|
61
22
|
{ name: 'get_lambda_overview', service: 'lambda' },
|
|
23
|
+
{ name: 'get_eventbridge_details', service: 'eventbridge' },
|
|
62
24
|
{ name: 'get_log_errors', service: 'cloudwatchLogs' },
|
|
63
25
|
];
|
|
64
26
|
function isEnabled(cfg, service) {
|
|
@@ -69,10 +31,10 @@ function isEnabled(cfg, service) {
|
|
|
69
31
|
}
|
|
70
32
|
function boxLine(visibleContent, coloredContent) {
|
|
71
33
|
const padding = ' '.repeat(Math.max(0, BOX_W - visibleContent.length));
|
|
72
|
-
console.log(
|
|
34
|
+
console.log(chalk.dim(' │') + coloredContent + padding + chalk.dim('│'));
|
|
73
35
|
}
|
|
74
36
|
function boxDivider() {
|
|
75
|
-
console.log(
|
|
37
|
+
console.log(chalk.dim(' ├────────────────────────────────────────────────────┤'));
|
|
76
38
|
}
|
|
77
39
|
function groupTools(tools) {
|
|
78
40
|
const lines = [];
|
|
@@ -91,40 +53,40 @@ function groupTools(tools) {
|
|
|
91
53
|
}
|
|
92
54
|
return lines;
|
|
93
55
|
}
|
|
94
|
-
async function runDev(options = {}) {
|
|
56
|
+
export async function runDev(options = {}) {
|
|
95
57
|
const port = options.port ?? 3000;
|
|
96
|
-
|
|
58
|
+
printHeader('MCP Server');
|
|
97
59
|
let config;
|
|
98
60
|
try {
|
|
99
|
-
config =
|
|
100
|
-
|
|
61
|
+
config = loadConfig(options.config);
|
|
62
|
+
log.success('Config loaded', options.config ?? 'infrawise.yaml');
|
|
101
63
|
}
|
|
102
64
|
catch (err) {
|
|
103
|
-
console.error(
|
|
65
|
+
console.error(formatError(err));
|
|
104
66
|
process.exit(1);
|
|
105
67
|
}
|
|
106
68
|
const repoPath = process.cwd();
|
|
107
69
|
// Auto-analyze if no cache
|
|
108
|
-
const cachedGraph =
|
|
109
|
-
const cachedFindings =
|
|
70
|
+
const cachedGraph = readCache('graph');
|
|
71
|
+
const cachedFindings = readCache('findings');
|
|
110
72
|
if (cachedGraph && cachedFindings) {
|
|
111
|
-
|
|
112
|
-
|
|
73
|
+
log.success('Cached analysis loaded', `${cachedGraph.nodes.length} nodes · ${cachedGraph.edges.length} edges · ${cachedFindings.length} finding(s)`);
|
|
74
|
+
setGraphState(cachedGraph, cachedFindings, config);
|
|
113
75
|
}
|
|
114
76
|
else {
|
|
115
|
-
|
|
77
|
+
log.warn('No cache found — running analysis now...');
|
|
116
78
|
console.log('');
|
|
117
|
-
await
|
|
118
|
-
const freshGraph =
|
|
119
|
-
const freshFindings =
|
|
120
|
-
|
|
79
|
+
await runAnalyze({ repo: repoPath, config: options.config });
|
|
80
|
+
const freshGraph = readCache('graph') ?? { nodes: [], edges: [] };
|
|
81
|
+
const freshFindings = readCache('findings') ?? [];
|
|
82
|
+
setGraphState(freshGraph, freshFindings, config);
|
|
121
83
|
}
|
|
122
84
|
console.log('');
|
|
123
85
|
// Start server
|
|
124
|
-
const spin = (
|
|
125
|
-
const { start } =
|
|
86
|
+
const spin = ora({ text: chalk.dim('Starting server...'), color: 'cyan' }).start();
|
|
87
|
+
const { start } = createServer(port);
|
|
126
88
|
await start();
|
|
127
|
-
spin.succeed(
|
|
89
|
+
spin.succeed(chalk.green('Server running'));
|
|
128
90
|
// Compute active/inactive tools from config
|
|
129
91
|
const activeTools = TOOL_MAP.filter((t) => isEnabled(config, t.service)).map((t) => t.name);
|
|
130
92
|
const inactiveTools = TOOL_MAP.filter((t) => !isEnabled(config, t.service)).map((t) => t.name);
|
|
@@ -133,30 +95,30 @@ async function runDev(options = {}) {
|
|
|
133
95
|
const healthUrl = `http://localhost:${port}/health`;
|
|
134
96
|
// Print box
|
|
135
97
|
console.log('');
|
|
136
|
-
console.log(
|
|
137
|
-
boxLine(' MCP Server',
|
|
98
|
+
console.log(chalk.dim(' ┌────────────────────────────────────────────────────┐'));
|
|
99
|
+
boxLine(' MCP Server', chalk.bold(' MCP Server'));
|
|
138
100
|
boxDivider();
|
|
139
|
-
boxLine(` POST ${mcpUrl}`, ` ${
|
|
140
|
-
boxLine(` GET ${healthUrl}`, ` ${
|
|
101
|
+
boxLine(` POST ${mcpUrl}`, ` ${chalk.dim('POST')} ${chalk.cyan(mcpUrl)}`);
|
|
102
|
+
boxLine(` GET ${healthUrl}`, ` ${chalk.dim('GET')} ${chalk.cyan(healthUrl)}`);
|
|
141
103
|
boxDivider();
|
|
142
104
|
const activeLabel = ` Tools (${activeTools.length} active${inactiveTools.length > 0 ? ` · ${inactiveTools.length} off` : ''})`;
|
|
143
|
-
boxLine(activeLabel,
|
|
105
|
+
boxLine(activeLabel, chalk.dim(activeLabel));
|
|
144
106
|
for (const line of groupTools(activeTools)) {
|
|
145
107
|
boxLine(` ${line}`, ` ${line}`);
|
|
146
108
|
}
|
|
147
109
|
if (inactiveTools.length > 0) {
|
|
148
110
|
boxDivider();
|
|
149
|
-
boxLine(' Off (enable in infrawise.yaml):',
|
|
111
|
+
boxLine(' Off (enable in infrawise.yaml):', chalk.dim(' Off (enable in infrawise.yaml):'));
|
|
150
112
|
for (const line of groupTools(inactiveTools)) {
|
|
151
|
-
boxLine(` ${line}`,
|
|
113
|
+
boxLine(` ${line}`, chalk.dim(` ${line}`));
|
|
152
114
|
}
|
|
153
115
|
}
|
|
154
|
-
console.log(
|
|
116
|
+
console.log(chalk.dim(' └────────────────────────────────────────────────────┘'));
|
|
155
117
|
console.log('');
|
|
156
|
-
console.log(
|
|
157
|
-
console.log(
|
|
118
|
+
console.log(chalk.dim(' Add via CLI:'));
|
|
119
|
+
console.log(chalk.dim(` claude mcp add --transport http infrawise ${mcpUrl}`));
|
|
158
120
|
console.log('');
|
|
159
|
-
console.log(
|
|
121
|
+
console.log(chalk.dim(' Watching for file changes... Press Ctrl+C to stop\n'));
|
|
160
122
|
// File watch — re-run code analysis on save, skip slow AWS/DB extraction
|
|
161
123
|
let debounceTimer = null;
|
|
162
124
|
let refreshing = false;
|
|
@@ -167,7 +129,7 @@ async function runDev(options = {}) {
|
|
|
167
129
|
return;
|
|
168
130
|
const abs = path.join(repoPath, filename);
|
|
169
131
|
if (abs === configFile) {
|
|
170
|
-
console.log(
|
|
132
|
+
console.log(chalk.dim('\n infrawise.yaml changed — restart to apply config changes\n'));
|
|
171
133
|
return;
|
|
172
134
|
}
|
|
173
135
|
const ext = path.extname(filename);
|
|
@@ -181,14 +143,14 @@ async function runDev(options = {}) {
|
|
|
181
143
|
if (refreshing)
|
|
182
144
|
return;
|
|
183
145
|
refreshing = true;
|
|
184
|
-
const spin = (
|
|
146
|
+
const spin = ora({ text: chalk.dim('Refreshing code analysis...'), color: 'cyan' }).start();
|
|
185
147
|
try {
|
|
186
|
-
const { graph, findings } = await
|
|
187
|
-
|
|
188
|
-
spin.succeed(
|
|
148
|
+
const { graph, findings } = await runCodeRefresh(repoPath, config);
|
|
149
|
+
setGraphState(graph, findings, config);
|
|
150
|
+
spin.succeed(chalk.green('Analysis refreshed') + chalk.dim(` ${graph.nodes.length} nodes · ${findings.length} finding(s)`));
|
|
189
151
|
}
|
|
190
152
|
catch (err) {
|
|
191
|
-
spin.warn(
|
|
153
|
+
spin.warn(chalk.yellow('Refresh failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
192
154
|
}
|
|
193
155
|
finally {
|
|
194
156
|
refreshing = false;
|
|
@@ -200,7 +162,7 @@ async function runDev(options = {}) {
|
|
|
200
162
|
// fs.watch may not support recursive on all platforms — silently skip
|
|
201
163
|
}
|
|
202
164
|
process.on('SIGINT', () => {
|
|
203
|
-
console.log(
|
|
165
|
+
console.log(chalk.dim('\n Shutting down...\n'));
|
|
204
166
|
process.exit(0);
|
|
205
167
|
});
|
|
206
168
|
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, validateEventBridgeAccess, } 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) {
|
|
@@ -212,12 +173,28 @@ async function runDoctor(options = {}) {
|
|
|
212
173
|
};
|
|
213
174
|
}
|
|
214
175
|
}));
|
|
176
|
+
// EventBridge
|
|
177
|
+
results.push(await runCheck('Testing EventBridge access...', async () => {
|
|
178
|
+
if (config?.eventbridge?.enabled !== true)
|
|
179
|
+
return { name: 'EventBridge', status: 'skip', message: 'Disabled in config' };
|
|
180
|
+
try {
|
|
181
|
+
await validateEventBridgeAccess(awsCfg);
|
|
182
|
+
return { name: 'EventBridge', status: 'pass', message: 'Connected' };
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
return {
|
|
186
|
+
name: 'EventBridge', status: 'warn',
|
|
187
|
+
message: err instanceof Error ? err.message : String(err),
|
|
188
|
+
detail: 'Check IAM: events:ListRules, events:ListTargetsByRule',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}));
|
|
215
192
|
// CloudWatch Logs
|
|
216
193
|
results.push(await runCheck('Testing CloudWatch Logs access...', async () => {
|
|
217
194
|
if (!config?.cloudwatchLogs?.enabled)
|
|
218
195
|
return { name: 'CloudWatch Logs', status: 'skip', message: 'Not enabled in config' };
|
|
219
196
|
try {
|
|
220
|
-
await
|
|
197
|
+
await validateLogsAccess(awsCfg);
|
|
221
198
|
return { name: 'CloudWatch Logs', status: 'pass', message: 'Connected' };
|
|
222
199
|
}
|
|
223
200
|
catch (err) {
|
|
@@ -234,7 +211,7 @@ async function runDoctor(options = {}) {
|
|
|
234
211
|
return { name: 'PostgreSQL', status: 'skip', message: 'Not configured' };
|
|
235
212
|
}
|
|
236
213
|
try {
|
|
237
|
-
const ok = await
|
|
214
|
+
const ok = await validatePostgresAccess(config.postgres.connectionString);
|
|
238
215
|
return {
|
|
239
216
|
name: 'PostgreSQL', status: ok ? 'pass' : 'fail',
|
|
240
217
|
message: ok ? 'Connected' : 'Cannot connect',
|
|
@@ -251,7 +228,7 @@ async function runDoctor(options = {}) {
|
|
|
251
228
|
return { name: 'MySQL', status: 'skip', message: 'Not configured' };
|
|
252
229
|
}
|
|
253
230
|
try {
|
|
254
|
-
const ok = await
|
|
231
|
+
const ok = await validateMySQLAccess(config.mysql.connectionString);
|
|
255
232
|
return {
|
|
256
233
|
name: 'MySQL', status: ok ? 'pass' : 'fail',
|
|
257
234
|
message: ok ? 'Connected' : 'Cannot connect',
|
|
@@ -268,7 +245,7 @@ async function runDoctor(options = {}) {
|
|
|
268
245
|
return { name: 'MongoDB', status: 'skip', message: 'Not configured' };
|
|
269
246
|
}
|
|
270
247
|
try {
|
|
271
|
-
const ok = await
|
|
248
|
+
const ok = await validateMongoAccess(config.mongodb.connectionString);
|
|
272
249
|
return {
|
|
273
250
|
name: 'MongoDB', status: ok ? 'pass' : 'fail',
|
|
274
251
|
message: ok ? 'Connected' : 'Cannot connect',
|
|
@@ -329,12 +306,12 @@ async function runDoctor(options = {}) {
|
|
|
329
306
|
const failed = results.filter((r) => r.status === 'fail').length;
|
|
330
307
|
const warned = results.filter((r) => r.status === 'warn').length;
|
|
331
308
|
console.log('');
|
|
332
|
-
console.log(
|
|
309
|
+
console.log(chalk.dim(' ' + '─'.repeat(40)));
|
|
333
310
|
if (failed === 0) {
|
|
334
|
-
console.log(` ${
|
|
311
|
+
console.log(` ${chalk.green.bold('All checks passed')} ${chalk.dim(`${passed} passed, ${warned} warning(s)`)}`);
|
|
335
312
|
}
|
|
336
313
|
else {
|
|
337
|
-
console.log(` ${
|
|
314
|
+
console.log(` ${chalk.red.bold(`${failed} check(s) failed`)} ${chalk.dim(`${passed} passed, ${warned} warning(s)`)}`);
|
|
338
315
|
}
|
|
339
316
|
console.log('');
|
|
340
317
|
if (failed > 0)
|