infrawise 0.8.2 โ†’ 0.8.7

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.
@@ -2,7 +2,7 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import ora from 'ora';
5
- import { loadConfig, formatError, writeCache, readCache } from '../../core/index.js';
5
+ import { loadConfig, formatError, writeCache, readCache, setCacheDir } from '../../core/index.js';
6
6
  import { extractDynamoMetadata } from '../../adapters/aws/dynamodb.js';
7
7
  import { extractPostgresMetadata } from '../../adapters/db/postgres.js';
8
8
  import { extractMySQLMetadata } from '../../adapters/db/mysql.js';
@@ -39,7 +39,9 @@ function buildMarkdownReport(findings, projectName) {
39
39
  renderGroup('Verify (check intent)', '๐Ÿ”ต', verify),
40
40
  '',
41
41
  '_Generated by [infrawise](https://github.com/Sidd27/infrawise) โ€” MCP server for AWS infrastructure analysis_',
42
- ].filter((l) => l !== undefined).join('\n');
42
+ ]
43
+ .filter((l) => l !== undefined)
44
+ .join('\n');
43
45
  }
44
46
  function mkSpinner(text) {
45
47
  return ora({ text: chalk.dim(text), color: 'cyan' }).start();
@@ -49,6 +51,7 @@ export async function runAnalyze(options = {}) {
49
51
  let config;
50
52
  try {
51
53
  config = loadConfig(options.config);
54
+ setCacheDir(path.dirname(path.resolve(options.config ?? 'infrawise.yaml')));
52
55
  log.success('Config loaded', options.config ?? 'infrawise.yaml');
53
56
  }
54
57
  catch (err) {
@@ -56,8 +59,12 @@ export async function runAnalyze(options = {}) {
56
59
  process.exit(1);
57
60
  }
58
61
  const repoPath = options.repo ?? process.cwd();
59
- const minSeverity = options.severity ? SEVERITY_ORDER[options.severity] ?? 1 : 0;
60
- const awsCfg = { region: config.aws?.region, profile: config.aws?.profile, endpoint: config.aws?.endpoint };
62
+ const minSeverity = options.severity ? (SEVERITY_ORDER[options.severity] ?? 1) : 0;
63
+ const awsCfg = {
64
+ region: config.aws?.region,
65
+ profile: config.aws?.profile,
66
+ endpoint: config.aws?.endpoint,
67
+ };
61
68
  const dynamoMeta = [];
62
69
  const postgresMeta = [];
63
70
  const mysqlMeta = [];
@@ -72,7 +79,8 @@ export async function runAnalyze(options = {}) {
72
79
  s.succeed(chalk.green('DynamoDB') + chalk.dim(` ${result.length} table(s)`));
73
80
  }
74
81
  catch (err) {
75
- s.warn(chalk.yellow('DynamoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
82
+ s.warn(chalk.yellow('DynamoDB skipped') +
83
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
76
84
  }
77
85
  }
78
86
  // โ”€โ”€ PostgreSQL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -84,7 +92,8 @@ export async function runAnalyze(options = {}) {
84
92
  s.succeed(chalk.green('PostgreSQL') + chalk.dim(` ${result.length} table(s)`));
85
93
  }
86
94
  catch (err) {
87
- s.warn(chalk.yellow('PostgreSQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
95
+ s.warn(chalk.yellow('PostgreSQL skipped') +
96
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
88
97
  }
89
98
  }
90
99
  // โ”€โ”€ MySQL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -96,7 +105,8 @@ export async function runAnalyze(options = {}) {
96
105
  s.succeed(chalk.green('MySQL') + chalk.dim(` ${result.length} table(s)`));
97
106
  }
98
107
  catch (err) {
99
- s.warn(chalk.yellow('MySQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
108
+ s.warn(chalk.yellow('MySQL skipped') +
109
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
100
110
  }
101
111
  }
102
112
  // โ”€โ”€ MongoDB โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -108,7 +118,8 @@ export async function runAnalyze(options = {}) {
108
118
  s.succeed(chalk.green('MongoDB') + chalk.dim(` ${result.length} collection(s)`));
109
119
  }
110
120
  catch (err) {
111
- s.warn(chalk.yellow('MongoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
121
+ s.warn(chalk.yellow('MongoDB skipped') +
122
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
112
123
  }
113
124
  }
114
125
  // โ”€โ”€ SQS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -120,7 +131,8 @@ export async function runAnalyze(options = {}) {
120
131
  s.succeed(chalk.green('SQS') + chalk.dim(` ${result.length} queue(s)`));
121
132
  }
122
133
  catch (err) {
123
- s.warn(chalk.yellow('SQS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
134
+ s.warn(chalk.yellow('SQS skipped') +
135
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
124
136
  }
125
137
  }
126
138
  // โ”€โ”€ SNS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -132,7 +144,8 @@ export async function runAnalyze(options = {}) {
132
144
  s.succeed(chalk.green('SNS') + chalk.dim(` ${result.length} topic(s)`));
133
145
  }
134
146
  catch (err) {
135
- s.warn(chalk.yellow('SNS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
147
+ s.warn(chalk.yellow('SNS skipped') +
148
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
136
149
  }
137
150
  }
138
151
  // โ”€โ”€ SSM Parameter Store โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -141,10 +154,13 @@ export async function runAnalyze(options = {}) {
141
154
  try {
142
155
  const result = await extractSSMMetadata({ ...awsCfg, paths: config.ssm?.paths });
143
156
  servicesMeta.ssm = result;
144
- s.succeed(chalk.green('SSM') + chalk.dim(` ${result.length} parameter(s) `) + chalk.dim('(metadata only, no values)'));
157
+ s.succeed(chalk.green('SSM') +
158
+ chalk.dim(` ${result.length} parameter(s) `) +
159
+ chalk.dim('(metadata only, no values)'));
145
160
  }
146
161
  catch (err) {
147
- s.warn(chalk.yellow('SSM skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
162
+ s.warn(chalk.yellow('SSM skipped') +
163
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
148
164
  }
149
165
  }
150
166
  // โ”€โ”€ Secrets Manager โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -153,10 +169,13 @@ export async function runAnalyze(options = {}) {
153
169
  try {
154
170
  const result = await extractSecretsMetadata(awsCfg);
155
171
  servicesMeta.secrets = result;
156
- s.succeed(chalk.green('Secrets Manager') + chalk.dim(` ${result.length} secret(s) `) + chalk.dim('(names/rotation only, no values)'));
172
+ s.succeed(chalk.green('Secrets Manager') +
173
+ chalk.dim(` ${result.length} secret(s) `) +
174
+ chalk.dim('(names/rotation only, no values)'));
157
175
  }
158
176
  catch (err) {
159
- s.warn(chalk.yellow('Secrets Manager skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
177
+ s.warn(chalk.yellow('Secrets Manager skipped') +
178
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
160
179
  }
161
180
  }
162
181
  // โ”€โ”€ Lambda โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -168,7 +187,8 @@ export async function runAnalyze(options = {}) {
168
187
  s.succeed(chalk.green('Lambda') + chalk.dim(` ${result.length} function(s)`));
169
188
  }
170
189
  catch (err) {
171
- s.warn(chalk.yellow('Lambda skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
190
+ s.warn(chalk.yellow('Lambda skipped') +
191
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
172
192
  }
173
193
  }
174
194
  // โ”€โ”€ EventBridge โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -180,7 +200,8 @@ export async function runAnalyze(options = {}) {
180
200
  s.succeed(chalk.green('EventBridge') + chalk.dim(` ${result.length} rule(s)`));
181
201
  }
182
202
  catch (err) {
183
- s.warn(chalk.yellow('EventBridge skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
203
+ s.warn(chalk.yellow('EventBridge skipped') +
204
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
184
205
  }
185
206
  }
186
207
  // โ”€โ”€ RDS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -192,7 +213,8 @@ export async function runAnalyze(options = {}) {
192
213
  s.succeed(chalk.green('RDS') + chalk.dim(` ${result.length} instance(s)`));
193
214
  }
194
215
  catch (err) {
195
- s.warn(chalk.yellow('RDS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
216
+ s.warn(chalk.yellow('RDS skipped') +
217
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
196
218
  }
197
219
  }
198
220
  // โ”€โ”€ S3 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -204,7 +226,8 @@ export async function runAnalyze(options = {}) {
204
226
  s.succeed(chalk.green('S3') + chalk.dim(` ${result.length} bucket(s)`));
205
227
  }
206
228
  catch (err) {
207
- s.warn(chalk.yellow('S3 skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
229
+ s.warn(chalk.yellow('S3 skipped') +
230
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
208
231
  }
209
232
  }
210
233
  // โ”€โ”€ CloudWatch Logs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -218,10 +241,12 @@ export async function runAnalyze(options = {}) {
218
241
  });
219
242
  servicesMeta.logs = result;
220
243
  const errorGroups = result.filter((lg) => lg.errorCount > 0).length;
221
- s.succeed(chalk.green('CloudWatch Logs') + chalk.dim(` ${result.length} group(s), ${errorGroups} with errors`));
244
+ s.succeed(chalk.green('CloudWatch Logs') +
245
+ chalk.dim(` ${result.length} group(s), ${errorGroups} with errors`));
222
246
  }
223
247
  catch (err) {
224
- s.warn(chalk.yellow('CloudWatch Logs skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
248
+ s.warn(chalk.yellow('CloudWatch Logs skipped') +
249
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
225
250
  }
226
251
  }
227
252
  // โ”€โ”€ IaC schema (Terraform / CloudFormation / CDK) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -230,16 +255,23 @@ export async function runAnalyze(options = {}) {
230
255
  const s = mkSpinner('Extracting IaC schema (Terraform / CloudFormation / CDK)...');
231
256
  try {
232
257
  const iacSchema = await extractIaCSchema(repoPath);
233
- const total = iacSchema.dynamoTables.length + iacSchema.rdsInstances.length +
234
- iacSchema.mongoClusters.length + iacSchema.queues.length + iacSchema.topics.length +
235
- iacSchema.lambdas.length + iacSchema.buckets.length + iacSchema.parameters.length +
236
- iacSchema.secrets.length + iacSchema.apiGateways.length;
258
+ const total = iacSchema.dynamoTables.length +
259
+ iacSchema.rdsInstances.length +
260
+ iacSchema.mongoClusters.length +
261
+ iacSchema.queues.length +
262
+ iacSchema.topics.length +
263
+ iacSchema.lambdas.length +
264
+ iacSchema.buckets.length +
265
+ iacSchema.parameters.length +
266
+ iacSchema.secrets.length +
267
+ iacSchema.apiGateways.length;
237
268
  iacDriftAnalyzer = new IaCDriftAnalyzer();
238
269
  iacDriftAnalyzer.setIaCSchema(iacSchema);
239
270
  s.succeed(chalk.green('IaC schema') + chalk.dim(` ${total} resource(s) across TF/CFN/CDK`));
240
271
  }
241
272
  catch (err) {
242
- s.warn(chalk.yellow('IaC scan skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
273
+ s.warn(chalk.yellow('IaC scan skipped') +
274
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
243
275
  }
244
276
  }
245
277
  // โ”€โ”€ Repository scan โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -248,10 +280,12 @@ export async function runAnalyze(options = {}) {
248
280
  const s = mkSpinner(`Scanning ${path.basename(repoPath)} for service usage...`);
249
281
  try {
250
282
  operations = await scanRepository(repoPath);
251
- s.succeed(chalk.green('Repository scanned') + chalk.dim(` ${operations.length} service operation(s) found`));
283
+ s.succeed(chalk.green('Repository scanned') +
284
+ chalk.dim(` ${operations.length} service operation(s) found`));
252
285
  }
253
286
  catch (err) {
254
- s.warn(chalk.yellow('Repository scan failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
287
+ s.warn(chalk.yellow('Repository scan failed') +
288
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
255
289
  operations = [];
256
290
  }
257
291
  }
@@ -260,76 +294,85 @@ export async function runAnalyze(options = {}) {
260
294
  {
261
295
  const s = mkSpinner('Building infrastructure graph...');
262
296
  graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
263
- s.succeed(chalk.green('Graph built') + chalk.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
297
+ s.succeed(chalk.green('Graph built') +
298
+ chalk.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
264
299
  }
265
300
  // โ”€โ”€ Run analyzers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
266
301
  let findings;
267
302
  {
268
303
  const s = mkSpinner('Running analyzers...');
269
304
  const analyzers = [
270
- ...(config.dynamodb?.enabled === true ? [
271
- new FullTableScanAnalyzer(),
272
- new MissingGSIAnalyzer(),
273
- new HotPartitionAnalyzer(),
274
- ] : []),
275
- ...(config.postgres?.enabled ? [
276
- new MissingIndexAnalyzer(),
277
- new NplusOneAnalyzer(),
278
- new LargeSelectAnalyzer(),
279
- ] : []),
280
- ...(config.mysql?.enabled ? [
281
- new MissingMySQLIndexAnalyzer(),
282
- new MySQLFullTableScanAnalyzer(),
283
- ] : []),
284
- ...(config.mongodb?.enabled ? [
285
- new MissingMongoIndexAnalyzer(),
286
- new MongoCollectionScanAnalyzer(),
287
- ] : []),
288
- ...(config.sqs?.enabled === true ? [
289
- new MissingDLQAnalyzer(),
290
- new UnencryptedQueueAnalyzer(),
291
- new LargeQueueBacklogAnalyzer(),
292
- ] : []),
293
- ...(config.secretsManager?.enabled === true ? [
294
- new MissingSecretRotationAnalyzer(),
295
- ] : []),
296
- ...(config.cloudwatchLogs?.enabled ? [
297
- new MissingLogRetentionAnalyzer(),
298
- ] : []),
299
- ...(config.lambda?.enabled === true ? [
300
- new LambdaDefaultMemoryAnalyzer(),
301
- new LambdaHighTimeoutAnalyzer(),
302
- new LambdaMissingTriggerDLQAnalyzer(),
303
- ] : []),
304
- ...(config.rds?.enabled === true ? [
305
- new RDSPubliclyAccessibleAnalyzer(),
306
- new RDSNoBackupAnalyzer(),
307
- new RDSUnencryptedAnalyzer(),
308
- new RDSNoDeletionProtectionAnalyzer(),
309
- new RDSNoMultiAZAnalyzer(),
310
- ] : []),
311
- ...(config.s3?.enabled === true ? [
312
- new S3PublicAccessAnalyzer(),
313
- new S3MissingVersioningAnalyzer(),
314
- new S3UnencryptedAnalyzer(),
315
- ] : []),
305
+ ...(config.dynamodb?.enabled === true
306
+ ? [new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer()]
307
+ : []),
308
+ ...(config.postgres?.enabled
309
+ ? [new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer()]
310
+ : []),
311
+ ...(config.mysql?.enabled
312
+ ? [new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer()]
313
+ : []),
314
+ ...(config.mongodb?.enabled
315
+ ? [new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer()]
316
+ : []),
317
+ ...(config.sqs?.enabled === true
318
+ ? [
319
+ new MissingDLQAnalyzer(),
320
+ new UnencryptedQueueAnalyzer(),
321
+ new LargeQueueBacklogAnalyzer(),
322
+ ]
323
+ : []),
324
+ ...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
325
+ ...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
326
+ ...(config.lambda?.enabled === true
327
+ ? [
328
+ new LambdaDefaultMemoryAnalyzer(),
329
+ new LambdaHighTimeoutAnalyzer(),
330
+ new LambdaMissingTriggerDLQAnalyzer(),
331
+ ]
332
+ : []),
333
+ ...(config.rds?.enabled === true
334
+ ? [
335
+ new RDSPubliclyAccessibleAnalyzer(),
336
+ new RDSNoBackupAnalyzer(),
337
+ new RDSUnencryptedAnalyzer(),
338
+ new RDSNoDeletionProtectionAnalyzer(),
339
+ new RDSNoMultiAZAnalyzer(),
340
+ ]
341
+ : []),
342
+ ...(config.s3?.enabled === true
343
+ ? [
344
+ new S3PublicAccessAnalyzer(),
345
+ new S3MissingVersioningAnalyzer(),
346
+ new S3UnencryptedAnalyzer(),
347
+ ]
348
+ : []),
316
349
  ...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
317
350
  ];
318
351
  findings = await runAllAnalyzers(graph, analyzers);
319
352
  s.succeed(chalk.green('Analysis complete') + chalk.dim(` ${findings.length} finding(s)`));
320
353
  }
321
354
  // โ”€โ”€ Cache โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
322
- writeCache('graph', graph);
323
- writeCache('findings', findings);
324
- writeCache('operations', operations);
325
- writeCache('meta', { dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta });
355
+ if (!options.noCache) {
356
+ writeCache('graph', graph);
357
+ writeCache('findings', findings);
358
+ writeCache('operations', operations);
359
+ writeCache('meta', {
360
+ dynamoMeta,
361
+ postgresMeta,
362
+ mysqlMeta,
363
+ mongoMeta,
364
+ servicesMeta,
365
+ });
366
+ }
326
367
  // โ”€โ”€ Output โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
327
368
  const displayFindings = minSeverity > 0
328
369
  ? findings.filter((f) => (SEVERITY_ORDER[f.severity] ?? 0) >= minSeverity)
329
370
  : findings;
330
371
  console.log('');
331
372
  if (displayFindings.length === 0) {
332
- const msg = minSeverity > 0 ? `No ${options.severity} (or higher) severity issues found.` : 'Your infrastructure looks clean.';
373
+ const msg = minSeverity > 0
374
+ ? `No ${options.severity} (or higher) severity issues found.`
375
+ : 'Your infrastructure looks clean.';
333
376
  console.log(` ${chalk.green.bold('โœ“ No issues found!')} ${chalk.dim(msg)}`);
334
377
  }
335
378
  else {
@@ -354,7 +397,7 @@ export async function runAnalyze(options = {}) {
354
397
  console.log('');
355
398
  }
356
399
  export async function runCodeRefresh(repoPath, config) {
357
- const cached = readCache('meta', Infinity);
400
+ const cached = readCache('meta', 60 * 60 * 1000);
358
401
  const dynamoMeta = cached?.dynamoMeta ?? [];
359
402
  const postgresMeta = cached?.postgresMeta ?? [];
360
403
  const mysqlMeta = cached?.mysqlMeta ?? [];
@@ -380,35 +423,46 @@ export async function runCodeRefresh(repoPath, config) {
380
423
  }
381
424
  const graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
382
425
  const analyzers = [
383
- ...(config.dynamodb?.enabled === true ? [
384
- new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer(),
385
- ] : []),
386
- ...(config.postgres?.enabled ? [
387
- new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer(),
388
- ] : []),
389
- ...(config.mysql?.enabled ? [
390
- new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer(),
391
- ] : []),
392
- ...(config.mongodb?.enabled ? [
393
- new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer(),
394
- ] : []),
395
- ...(config.sqs?.enabled === true ? [
396
- new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer(),
397
- ] : []),
426
+ ...(config.dynamodb?.enabled === true
427
+ ? [new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer()]
428
+ : []),
429
+ ...(config.postgres?.enabled
430
+ ? [new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer()]
431
+ : []),
432
+ ...(config.mysql?.enabled
433
+ ? [new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer()]
434
+ : []),
435
+ ...(config.mongodb?.enabled
436
+ ? [new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer()]
437
+ : []),
438
+ ...(config.sqs?.enabled === true
439
+ ? [new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer()]
440
+ : []),
398
441
  ...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
399
442
  ...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
400
- ...(config.lambda?.enabled === true ? [
401
- new LambdaDefaultMemoryAnalyzer(), new LambdaHighTimeoutAnalyzer(),
402
- ] : []),
403
- ...(config.rds?.enabled === true ? [
404
- new RDSPubliclyAccessibleAnalyzer(), new RDSNoBackupAnalyzer(), new RDSUnencryptedAnalyzer(),
405
- new RDSNoDeletionProtectionAnalyzer(), new RDSNoMultiAZAnalyzer(),
406
- ] : []),
407
- ...(config.s3?.enabled === true ? [
408
- new S3PublicAccessAnalyzer(),
409
- new S3MissingVersioningAnalyzer(),
410
- new S3UnencryptedAnalyzer(),
411
- ] : []),
443
+ ...(config.lambda?.enabled === true
444
+ ? [
445
+ new LambdaDefaultMemoryAnalyzer(),
446
+ new LambdaHighTimeoutAnalyzer(),
447
+ new LambdaMissingTriggerDLQAnalyzer(),
448
+ ]
449
+ : []),
450
+ ...(config.rds?.enabled === true
451
+ ? [
452
+ new RDSPubliclyAccessibleAnalyzer(),
453
+ new RDSNoBackupAnalyzer(),
454
+ new RDSUnencryptedAnalyzer(),
455
+ new RDSNoDeletionProtectionAnalyzer(),
456
+ new RDSNoMultiAZAnalyzer(),
457
+ ]
458
+ : []),
459
+ ...(config.s3?.enabled === true
460
+ ? [
461
+ new S3PublicAccessAnalyzer(),
462
+ new S3MissingVersioningAnalyzer(),
463
+ new S3UnencryptedAnalyzer(),
464
+ ]
465
+ : []),
412
466
  ...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
413
467
  ];
414
468
  const findings = await runAllAnalyzers(graph, analyzers);
@@ -25,7 +25,10 @@ export async function runAuth() {
25
25
  },
26
26
  ]);
27
27
  console.log('');
28
- const spin = ora({ text: chalk.dim(`Validating "${selectedProfile}"...`), color: 'cyan' }).start();
28
+ const spin = ora({
29
+ text: chalk.dim(`Validating "${selectedProfile}"...`),
30
+ color: 'cyan',
31
+ }).start();
29
32
  const testConfig = {
30
33
  project: 'auth-test',
31
34
  aws: { profile: selectedProfile, region: 'us-east-1' },
@@ -2,7 +2,7 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import ora from 'ora';
5
- import { loadConfig, formatError, readCache } from '../../core/index.js';
5
+ import { loadConfig, formatError, readCache, setCacheDir } from '../../core/index.js';
6
6
  import { createServer, setGraphState } from '../../server/index.js';
7
7
  import { log, printHeader } from '../utils.js';
8
8
  import { runAnalyze, runCodeRefresh } from './analyze.js';
@@ -59,6 +59,7 @@ export async function runDev(options = {}) {
59
59
  let config;
60
60
  try {
61
61
  config = loadConfig(options.config);
62
+ setCacheDir(path.dirname(path.resolve(options.config ?? 'infrawise.yaml')));
62
63
  log.success('Config loaded', options.config ?? 'infrawise.yaml');
63
64
  }
64
65
  catch (err) {
@@ -147,10 +148,12 @@ export async function runDev(options = {}) {
147
148
  try {
148
149
  const { graph, findings } = await runCodeRefresh(repoPath, config);
149
150
  setGraphState(graph, findings, config);
150
- spin.succeed(chalk.green('Analysis refreshed') + chalk.dim(` ${graph.nodes.length} nodes ยท ${findings.length} finding(s)`));
151
+ spin.succeed(chalk.green('Analysis refreshed') +
152
+ chalk.dim(` ${graph.nodes.length} nodes ยท ${findings.length} finding(s)`));
151
153
  }
152
154
  catch (err) {
153
- spin.warn(chalk.yellow('Refresh failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
155
+ spin.warn(chalk.yellow('Refresh failed') +
156
+ chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
154
157
  }
155
158
  finally {
156
159
  refreshing = false;