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.
@@ -1,124 +1,31 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
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.runAnalyze = runAnalyze;
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 dynamodb_1 = require("../../adapters/dynamodb");
46
- const postgres_1 = require("../../adapters/postgres");
47
- const mysql_1 = require("../../adapters/mysql");
48
- const mongodb_1 = require("../../adapters/mongodb");
49
- const terraform_1 = require("../../adapters/terraform");
50
- const aws_1 = require("../../adapters/aws");
51
- const logs_1 = require("../../adapters/logs");
52
- const context_1 = require("../../context");
53
- const graph_1 = require("../../graph");
54
- const analyzers_1 = require("../../analyzers");
55
- const utils_1 = require("../utils");
56
- const SEVERITY_RANK = { high: 3, medium: 2, low: 1, never: 0 };
57
- function mkSpinner(text, ci) {
58
- if (ci) {
59
- return {
60
- succeed: (msg) => console.log(` ✓ ${msg}`),
61
- warn: (msg) => console.log(` ⚠ ${msg}`),
62
- };
63
- }
64
- return (0, ora_1.default)({ text: chalk_1.default.dim(text), color: 'cyan' }).start();
65
- }
66
- function emitCIOutput(findings, failOn) {
67
- for (const f of findings) {
68
- const level = f.severity === 'high' ? 'error' : f.severity === 'medium' ? 'warning' : 'notice';
69
- console.log(`::${level} title=Infrawise [${f.severity.toUpperCase()}]::${f.issue} — ${f.description}`);
70
- }
71
- const summaryPath = process.env['GITHUB_STEP_SUMMARY'];
72
- if (summaryPath) {
73
- const high = findings.filter((f) => f.severity === 'high').length;
74
- const medium = findings.filter((f) => f.severity === 'medium').length;
75
- const low = findings.filter((f) => f.severity === 'low').length;
76
- const rows = findings.map((f) => {
77
- const icon = f.severity === 'high' ? '🔴' : f.severity === 'medium' ? '🟡' : '🔵';
78
- return `| ${icon} ${f.severity.toUpperCase()} | ${f.issue} | ${f.recommendation} |`;
79
- }).join('\n');
80
- const summary = findings.length === 0
81
- ? '## ✅ Infrawise — no issues found\n'
82
- : [
83
- '## Infrawise Analysis',
84
- '',
85
- `**${high} high · ${medium} medium · ${low} low**`,
86
- '',
87
- '| Severity | Issue | Recommendation |',
88
- '|---|---|---|',
89
- rows,
90
- ].join('\n');
91
- fs.appendFileSync(summaryPath, summary + '\n');
92
- }
93
- const threshold = SEVERITY_RANK[failOn] ?? 3;
94
- const maxFound = Math.max(0, ...findings.map((f) => SEVERITY_RANK[f.severity] ?? 0));
95
- if (maxFound >= threshold && threshold > 0) {
96
- process.exit(1);
97
- }
1
+ import * as path from 'path';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { loadConfig, formatError, writeCache, readCache } from '../../core/index.js';
5
+ import { extractDynamoMetadata } from '../../adapters/dynamodb.js';
6
+ import { extractPostgresMetadata } from '../../adapters/postgres.js';
7
+ import { extractMySQLMetadata } from '../../adapters/mysql.js';
8
+ import { extractMongoMetadata } from '../../adapters/mongodb.js';
9
+ import { extractIaCSchema } from '../../adapters/terraform.js';
10
+ import { extractSQSMetadata, extractSNSMetadata, extractSSMMetadata, extractSecretsMetadata, extractLambdaMetadata, extractRDSMetadata, } from '../../adapters/aws.js';
11
+ import { extractLogsMetadata } from '../../adapters/logs.js';
12
+ import { scanRepository } from '../../context/index.js';
13
+ import { buildGraph } from '../../graph/index.js';
14
+ import { runAllAnalyzers, IaCDriftAnalyzer, FullTableScanAnalyzer, MissingGSIAnalyzer, HotPartitionAnalyzer, MissingIndexAnalyzer, NplusOneAnalyzer, LargeSelectAnalyzer, MissingMySQLIndexAnalyzer, MySQLFullTableScanAnalyzer, MissingMongoIndexAnalyzer, MongoCollectionScanAnalyzer, MissingDLQAnalyzer, UnencryptedQueueAnalyzer, LargeQueueBacklogAnalyzer, MissingSecretRotationAnalyzer, MissingLogRetentionAnalyzer, LambdaDefaultMemoryAnalyzer, LambdaHighTimeoutAnalyzer, RDSPubliclyAccessibleAnalyzer, RDSNoBackupAnalyzer, RDSUnencryptedAnalyzer, RDSNoDeletionProtectionAnalyzer, RDSNoMultiAZAnalyzer, } from '../../analyzers/index.js';
15
+ import { printFinding, printSummaryBox, log, printHeader } from '../utils.js';
16
+ function mkSpinner(text) {
17
+ return ora({ text: chalk.dim(text), color: 'cyan' }).start();
98
18
  }
99
- async function runAnalyze(options = {}) {
100
- const ci = options.ci ?? false;
101
- const failOn = options.failOn ?? 'high';
102
- if (!ci)
103
- (0, utils_1.printHeader)('Running Analysis');
19
+ export async function runAnalyze(options = {}) {
20
+ printHeader('Running Analysis');
104
21
  let config;
105
- let isFallback = false;
106
22
  try {
107
- config = (0, core_1.loadConfig)(options.config);
108
- if (!ci)
109
- utils_1.log.success('Config loaded', options.config ?? 'infrawise.yaml');
23
+ config = loadConfig(options.config);
24
+ log.success('Config loaded', options.config ?? 'infrawise.yaml');
110
25
  }
111
26
  catch (err) {
112
- const isNotFound = err instanceof Error && err.message.includes('not found at');
113
- if (ci && isNotFound) {
114
- config = { project: path.basename(process.cwd()) };
115
- isFallback = true;
116
- console.log(' ✓ No infrawise.yaml — running code-only analysis (repo scan + IaC)');
117
- }
118
- else {
119
- console.error((0, core_1.formatError)(err));
120
- process.exit(1);
121
- }
27
+ console.error(formatError(err));
28
+ process.exit(1);
122
29
  }
123
30
  const repoPath = options.repo ?? process.cwd();
124
31
  const awsCfg = { region: config.aws?.region, profile: config.aws?.profile, endpoint: config.aws?.endpoint };
@@ -129,252 +36,308 @@ async function runAnalyze(options = {}) {
129
36
  const servicesMeta = {};
130
37
  // ── DynamoDB ────────────────────────────────────────────────────────────────
131
38
  if (config.dynamodb?.enabled === true) {
132
- const s = mkSpinner('Extracting DynamoDB tables...', ci);
39
+ const s = mkSpinner('Extracting DynamoDB tables...');
133
40
  try {
134
- const result = await (0, dynamodb_1.extractDynamoMetadata)(config);
41
+ const result = await extractDynamoMetadata(config);
135
42
  dynamoMeta.push(...result);
136
- s.succeed(chalk_1.default.green('DynamoDB') + chalk_1.default.dim(` ${result.length} table(s)`));
43
+ s.succeed(chalk.green('DynamoDB') + chalk.dim(` ${result.length} table(s)`));
137
44
  }
138
45
  catch (err) {
139
- s.warn(chalk_1.default.yellow('DynamoDB skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
46
+ s.warn(chalk.yellow('DynamoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
140
47
  }
141
48
  }
142
49
  // ── PostgreSQL ──────────────────────────────────────────────────────────────
143
50
  if (config.postgres?.enabled && config.postgres.connectionString) {
144
- const s = mkSpinner('Extracting PostgreSQL schema...', ci);
51
+ const s = mkSpinner('Extracting PostgreSQL schema...');
145
52
  try {
146
- const result = await (0, postgres_1.extractPostgresMetadata)(config.postgres.connectionString);
53
+ const result = await extractPostgresMetadata(config.postgres.connectionString);
147
54
  postgresMeta.push(...result);
148
- s.succeed(chalk_1.default.green('PostgreSQL') + chalk_1.default.dim(` ${result.length} table(s)`));
55
+ s.succeed(chalk.green('PostgreSQL') + chalk.dim(` ${result.length} table(s)`));
149
56
  }
150
57
  catch (err) {
151
- s.warn(chalk_1.default.yellow('PostgreSQL skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
58
+ s.warn(chalk.yellow('PostgreSQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
152
59
  }
153
60
  }
154
61
  // ── MySQL ───────────────────────────────────────────────────────────────────
155
62
  if (config.mysql?.enabled && config.mysql.connectionString) {
156
- const s = mkSpinner('Extracting MySQL schema...', ci);
63
+ const s = mkSpinner('Extracting MySQL schema...');
157
64
  try {
158
- const result = await (0, mysql_1.extractMySQLMetadata)(config.mysql.connectionString);
65
+ const result = await extractMySQLMetadata(config.mysql.connectionString);
159
66
  mysqlMeta.push(...result);
160
- s.succeed(chalk_1.default.green('MySQL') + chalk_1.default.dim(` ${result.length} table(s)`));
67
+ s.succeed(chalk.green('MySQL') + chalk.dim(` ${result.length} table(s)`));
161
68
  }
162
69
  catch (err) {
163
- s.warn(chalk_1.default.yellow('MySQL skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
70
+ s.warn(chalk.yellow('MySQL skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
164
71
  }
165
72
  }
166
73
  // ── MongoDB ─────────────────────────────────────────────────────────────────
167
74
  if (config.mongodb?.enabled && config.mongodb.connectionString) {
168
- const s = mkSpinner('Extracting MongoDB schema...', ci);
75
+ const s = mkSpinner('Extracting MongoDB schema...');
169
76
  try {
170
- const result = await (0, mongodb_1.extractMongoMetadata)(config.mongodb.connectionString, config.mongodb.databases);
77
+ const result = await extractMongoMetadata(config.mongodb.connectionString, config.mongodb.databases);
171
78
  mongoMeta.push(...result);
172
- s.succeed(chalk_1.default.green('MongoDB') + chalk_1.default.dim(` ${result.length} collection(s)`));
79
+ s.succeed(chalk.green('MongoDB') + chalk.dim(` ${result.length} collection(s)`));
173
80
  }
174
81
  catch (err) {
175
- s.warn(chalk_1.default.yellow('MongoDB skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
82
+ s.warn(chalk.yellow('MongoDB skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
176
83
  }
177
84
  }
178
85
  // ── SQS ─────────────────────────────────────────────────────────────────────
179
86
  if (config.sqs?.enabled === true) {
180
- const s = mkSpinner('Extracting SQS queues...', ci);
87
+ const s = mkSpinner('Extracting SQS queues...');
181
88
  try {
182
- const result = await (0, aws_1.extractSQSMetadata)(awsCfg);
89
+ const result = await extractSQSMetadata(awsCfg);
183
90
  servicesMeta.sqs = result;
184
- s.succeed(chalk_1.default.green('SQS') + chalk_1.default.dim(` ${result.length} queue(s)`));
91
+ s.succeed(chalk.green('SQS') + chalk.dim(` ${result.length} queue(s)`));
185
92
  }
186
93
  catch (err) {
187
- s.warn(chalk_1.default.yellow('SQS skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
94
+ s.warn(chalk.yellow('SQS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
188
95
  }
189
96
  }
190
97
  // ── SNS ─────────────────────────────────────────────────────────────────────
191
98
  if (config.sns?.enabled === true) {
192
- const s = mkSpinner('Extracting SNS topics...', ci);
99
+ const s = mkSpinner('Extracting SNS topics...');
193
100
  try {
194
- const result = await (0, aws_1.extractSNSMetadata)(awsCfg);
101
+ const result = await extractSNSMetadata(awsCfg);
195
102
  servicesMeta.sns = result;
196
- s.succeed(chalk_1.default.green('SNS') + chalk_1.default.dim(` ${result.length} topic(s)`));
103
+ s.succeed(chalk.green('SNS') + chalk.dim(` ${result.length} topic(s)`));
197
104
  }
198
105
  catch (err) {
199
- s.warn(chalk_1.default.yellow('SNS skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
106
+ s.warn(chalk.yellow('SNS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
200
107
  }
201
108
  }
202
109
  // ── SSM Parameter Store ──────────────────────────────────────────────────────
203
110
  if (config.ssm?.enabled === true) {
204
- const s = mkSpinner('Extracting SSM parameters...', ci);
111
+ const s = mkSpinner('Extracting SSM parameters...');
205
112
  try {
206
- const result = await (0, aws_1.extractSSMMetadata)({ ...awsCfg, paths: config.ssm?.paths });
113
+ const result = await extractSSMMetadata({ ...awsCfg, paths: config.ssm?.paths });
207
114
  servicesMeta.ssm = result;
208
- s.succeed(chalk_1.default.green('SSM') + chalk_1.default.dim(` ${result.length} parameter(s) `) + chalk_1.default.dim('(metadata only, no values)'));
115
+ s.succeed(chalk.green('SSM') + chalk.dim(` ${result.length} parameter(s) `) + chalk.dim('(metadata only, no values)'));
209
116
  }
210
117
  catch (err) {
211
- s.warn(chalk_1.default.yellow('SSM skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
118
+ s.warn(chalk.yellow('SSM skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
212
119
  }
213
120
  }
214
121
  // ── Secrets Manager ──────────────────────────────────────────────────────────
215
122
  if (config.secretsManager?.enabled === true) {
216
- const s = mkSpinner('Extracting Secrets Manager metadata...', ci);
123
+ const s = mkSpinner('Extracting Secrets Manager metadata...');
217
124
  try {
218
- const result = await (0, aws_1.extractSecretsMetadata)(awsCfg);
125
+ const result = await extractSecretsMetadata(awsCfg);
219
126
  servicesMeta.secrets = result;
220
- s.succeed(chalk_1.default.green('Secrets Manager') + chalk_1.default.dim(` ${result.length} secret(s) `) + chalk_1.default.dim('(names/rotation only, no values)'));
127
+ s.succeed(chalk.green('Secrets Manager') + chalk.dim(` ${result.length} secret(s) `) + chalk.dim('(names/rotation only, no values)'));
221
128
  }
222
129
  catch (err) {
223
- s.warn(chalk_1.default.yellow('Secrets Manager skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
130
+ s.warn(chalk.yellow('Secrets Manager skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
224
131
  }
225
132
  }
226
133
  // ── Lambda ───────────────────────────────────────────────────────────────────
227
134
  if (config.lambda?.enabled === true) {
228
- const s = mkSpinner('Extracting Lambda functions...', ci);
135
+ const s = mkSpinner('Extracting Lambda functions...');
229
136
  try {
230
- const result = await (0, aws_1.extractLambdaMetadata)(awsCfg);
137
+ const result = await extractLambdaMetadata(awsCfg);
231
138
  servicesMeta.lambda = result;
232
- s.succeed(chalk_1.default.green('Lambda') + chalk_1.default.dim(` ${result.length} function(s)`));
139
+ s.succeed(chalk.green('Lambda') + chalk.dim(` ${result.length} function(s)`));
233
140
  }
234
141
  catch (err) {
235
- s.warn(chalk_1.default.yellow('Lambda skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
142
+ s.warn(chalk.yellow('Lambda skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
236
143
  }
237
144
  }
238
145
  // ── RDS ──────────────────────────────────────────────────────────────────────
239
146
  if (config.rds?.enabled === true) {
240
- const s = mkSpinner('Extracting RDS instances...', ci);
147
+ const s = mkSpinner('Extracting RDS instances...');
241
148
  try {
242
- const result = await (0, aws_1.extractRDSMetadata)(awsCfg);
149
+ const result = await extractRDSMetadata(awsCfg);
243
150
  servicesMeta.rds = result;
244
- s.succeed(chalk_1.default.green('RDS') + chalk_1.default.dim(` ${result.length} instance(s)`));
151
+ s.succeed(chalk.green('RDS') + chalk.dim(` ${result.length} instance(s)`));
245
152
  }
246
153
  catch (err) {
247
- s.warn(chalk_1.default.yellow('RDS skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
154
+ s.warn(chalk.yellow('RDS skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
248
155
  }
249
156
  }
250
157
  // ── CloudWatch Logs ──────────────────────────────────────────────────────────
251
158
  if (config.cloudwatchLogs?.enabled) {
252
- const s = mkSpinner('Sampling CloudWatch Logs (errors only, max 50 groups)...', ci);
159
+ const s = mkSpinner('Sampling CloudWatch Logs (errors only, max 50 groups)...');
253
160
  try {
254
- const result = await (0, logs_1.extractLogsMetadata)({
161
+ const result = await extractLogsMetadata({
255
162
  ...awsCfg,
256
163
  logGroupPrefixes: config.cloudwatchLogs.logGroupPrefixes,
257
164
  windowHours: config.cloudwatchLogs.windowHours,
258
165
  });
259
166
  servicesMeta.logs = result;
260
167
  const errorGroups = result.filter((lg) => lg.errorCount > 0).length;
261
- s.succeed(chalk_1.default.green('CloudWatch Logs') + chalk_1.default.dim(` ${result.length} group(s), ${errorGroups} with errors`));
168
+ s.succeed(chalk.green('CloudWatch Logs') + chalk.dim(` ${result.length} group(s), ${errorGroups} with errors`));
262
169
  }
263
170
  catch (err) {
264
- s.warn(chalk_1.default.yellow('CloudWatch Logs skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
171
+ s.warn(chalk.yellow('CloudWatch Logs skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
265
172
  }
266
173
  }
267
174
  // ── IaC schema (Terraform / CloudFormation / CDK) ────────────────────────────
268
175
  let iacDriftAnalyzer;
269
176
  {
270
- const s = mkSpinner('Extracting IaC schema (Terraform / CloudFormation / CDK)...', ci);
177
+ const s = mkSpinner('Extracting IaC schema (Terraform / CloudFormation / CDK)...');
271
178
  try {
272
- const iacSchema = await (0, terraform_1.extractIaCSchema)(repoPath);
179
+ const iacSchema = await extractIaCSchema(repoPath);
273
180
  const total = iacSchema.dynamoTables.length + iacSchema.rdsInstances.length +
274
181
  iacSchema.mongoClusters.length + iacSchema.queues.length + iacSchema.topics.length +
275
182
  iacSchema.lambdas.length + iacSchema.buckets.length + iacSchema.parameters.length +
276
183
  iacSchema.secrets.length + iacSchema.apiGateways.length;
277
- iacDriftAnalyzer = new analyzers_1.IaCDriftAnalyzer();
184
+ iacDriftAnalyzer = new IaCDriftAnalyzer();
278
185
  iacDriftAnalyzer.setIaCSchema(iacSchema);
279
- s.succeed(chalk_1.default.green('IaC schema') + chalk_1.default.dim(` ${total} resource(s) across TF/CFN/CDK`));
186
+ s.succeed(chalk.green('IaC schema') + chalk.dim(` ${total} resource(s) across TF/CFN/CDK`));
280
187
  }
281
188
  catch (err) {
282
- s.warn(chalk_1.default.yellow('IaC scan skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
189
+ s.warn(chalk.yellow('IaC scan skipped') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
283
190
  }
284
191
  }
285
192
  // ── Repository scan ──────────────────────────────────────────────────────────
286
193
  let operations;
287
194
  {
288
- const s = mkSpinner(`Scanning ${path.basename(repoPath)} for service usage...`, ci);
195
+ const s = mkSpinner(`Scanning ${path.basename(repoPath)} for service usage...`);
289
196
  try {
290
- operations = await (0, context_1.scanRepository)(repoPath);
291
- s.succeed(chalk_1.default.green('Repository scanned') + chalk_1.default.dim(` ${operations.length} service operation(s) found`));
197
+ operations = await scanRepository(repoPath);
198
+ s.succeed(chalk.green('Repository scanned') + chalk.dim(` ${operations.length} service operation(s) found`));
292
199
  }
293
200
  catch (err) {
294
- s.warn(chalk_1.default.yellow('Repository scan failed') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
201
+ s.warn(chalk.yellow('Repository scan failed') + chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
295
202
  operations = [];
296
203
  }
297
204
  }
298
205
  // ── Build graph ──────────────────────────────────────────────────────────────
299
206
  let graph;
300
207
  {
301
- const s = mkSpinner('Building infrastructure graph...', ci);
302
- graph = (0, graph_1.buildGraph)(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
303
- s.succeed(chalk_1.default.green('Graph built') + chalk_1.default.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
208
+ const s = mkSpinner('Building infrastructure graph...');
209
+ graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
210
+ s.succeed(chalk.green('Graph built') + chalk.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
304
211
  }
305
212
  // ── Run analyzers ────────────────────────────────────────────────────────────
306
213
  let findings;
307
214
  {
308
- const s = mkSpinner('Running analyzers...', ci);
215
+ const s = mkSpinner('Running analyzers...');
309
216
  const analyzers = [
310
- ...(config.dynamodb?.enabled === true || isFallback ? [
311
- new analyzers_1.FullTableScanAnalyzer(),
312
- new analyzers_1.MissingGSIAnalyzer(),
313
- new analyzers_1.HotPartitionAnalyzer(),
217
+ ...(config.dynamodb?.enabled === true ? [
218
+ new FullTableScanAnalyzer(),
219
+ new MissingGSIAnalyzer(),
220
+ new HotPartitionAnalyzer(),
314
221
  ] : []),
315
- ...(config.postgres?.enabled || isFallback ? [
316
- new analyzers_1.MissingIndexAnalyzer(),
317
- new analyzers_1.NplusOneAnalyzer(),
318
- new analyzers_1.LargeSelectAnalyzer(),
222
+ ...(config.postgres?.enabled ? [
223
+ new MissingIndexAnalyzer(),
224
+ new NplusOneAnalyzer(),
225
+ new LargeSelectAnalyzer(),
319
226
  ] : []),
320
- ...(config.mysql?.enabled || isFallback ? [
321
- new analyzers_1.MissingMySQLIndexAnalyzer(),
322
- new analyzers_1.MySQLFullTableScanAnalyzer(),
227
+ ...(config.mysql?.enabled ? [
228
+ new MissingMySQLIndexAnalyzer(),
229
+ new MySQLFullTableScanAnalyzer(),
323
230
  ] : []),
324
- ...(config.mongodb?.enabled || isFallback ? [
325
- new analyzers_1.MissingMongoIndexAnalyzer(),
326
- new analyzers_1.MongoCollectionScanAnalyzer(),
231
+ ...(config.mongodb?.enabled ? [
232
+ new MissingMongoIndexAnalyzer(),
233
+ new MongoCollectionScanAnalyzer(),
327
234
  ] : []),
328
235
  ...(config.sqs?.enabled === true ? [
329
- new analyzers_1.MissingDLQAnalyzer(),
330
- new analyzers_1.UnencryptedQueueAnalyzer(),
331
- new analyzers_1.LargeQueueBacklogAnalyzer(),
236
+ new MissingDLQAnalyzer(),
237
+ new UnencryptedQueueAnalyzer(),
238
+ new LargeQueueBacklogAnalyzer(),
332
239
  ] : []),
333
240
  ...(config.secretsManager?.enabled === true ? [
334
- new analyzers_1.MissingSecretRotationAnalyzer(),
241
+ new MissingSecretRotationAnalyzer(),
335
242
  ] : []),
336
243
  ...(config.cloudwatchLogs?.enabled ? [
337
- new analyzers_1.MissingLogRetentionAnalyzer(),
244
+ new MissingLogRetentionAnalyzer(),
338
245
  ] : []),
339
246
  ...(config.lambda?.enabled === true ? [
340
- new analyzers_1.LambdaDefaultMemoryAnalyzer(),
341
- new analyzers_1.LambdaHighTimeoutAnalyzer(),
247
+ new LambdaDefaultMemoryAnalyzer(),
248
+ new LambdaHighTimeoutAnalyzer(),
342
249
  ] : []),
343
250
  ...(config.rds?.enabled === true ? [
344
- new analyzers_1.RDSPubliclyAccessibleAnalyzer(),
345
- new analyzers_1.RDSNoBackupAnalyzer(),
346
- new analyzers_1.RDSUnencryptedAnalyzer(),
347
- new analyzers_1.RDSNoDeletionProtectionAnalyzer(),
348
- new analyzers_1.RDSNoMultiAZAnalyzer(),
251
+ new RDSPubliclyAccessibleAnalyzer(),
252
+ new RDSNoBackupAnalyzer(),
253
+ new RDSUnencryptedAnalyzer(),
254
+ new RDSNoDeletionProtectionAnalyzer(),
255
+ new RDSNoMultiAZAnalyzer(),
349
256
  ] : []),
350
257
  ...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
351
258
  ];
352
- findings = await (0, analyzers_1.runAllAnalyzers)(graph, analyzers);
353
- s.succeed(chalk_1.default.green('Analysis complete') + chalk_1.default.dim(` ${findings.length} finding(s)`));
259
+ findings = await runAllAnalyzers(graph, analyzers);
260
+ s.succeed(chalk.green('Analysis complete') + chalk.dim(` ${findings.length} finding(s)`));
354
261
  }
355
262
  // ── Cache ─────────────────────────────────────────────────────────────────────
356
- (0, core_1.writeCache)('graph', graph);
357
- (0, core_1.writeCache)('findings', findings);
358
- (0, core_1.writeCache)('operations', operations);
263
+ writeCache('graph', graph);
264
+ writeCache('findings', findings);
265
+ writeCache('operations', operations);
266
+ writeCache('meta', { dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta });
359
267
  // ── Output ────────────────────────────────────────────────────────────────────
360
- if (ci) {
361
- emitCIOutput(findings, failOn);
362
- return;
363
- }
364
268
  console.log('');
365
269
  if (findings.length === 0) {
366
- console.log(` ${chalk_1.default.green.bold('✓ No issues found!')} ${chalk_1.default.dim('Your infrastructure looks clean.')}`);
270
+ console.log(` ${chalk.green.bold('✓ No issues found!')} ${chalk.dim('Your infrastructure looks clean.')}`);
367
271
  }
368
272
  else {
369
- console.log(chalk_1.default.bold(` Findings`) + chalk_1.default.dim(` ${findings.length} total`));
370
- findings.forEach((f, i) => (0, utils_1.printFinding)(f, i));
371
- (0, utils_1.printSummaryBox)(findings);
273
+ console.log(chalk.bold(` Findings`) + chalk.dim(` ${findings.length} total`));
274
+ findings.forEach((f, i) => printFinding(f, i));
275
+ printSummaryBox(findings);
372
276
  if (findings.some((f) => f.severity === 'high')) {
373
- console.log(`\n ${chalk_1.default.red.bold('Action required:')} ${chalk_1.default.red('High severity issues detected.')}`);
277
+ console.log(`\n ${chalk.red.bold('Action required:')} ${chalk.red('High severity issues detected.')}`);
374
278
  }
375
279
  }
376
280
  console.log('');
377
- utils_1.log.dim(`Results cached in .infrawise/cache/`);
378
- utils_1.log.info(`Run ${chalk_1.default.cyan('infrawise dev')} to explore via the MCP server`);
281
+ log.dim(`Results cached in .infrawise/cache/`);
282
+ log.info(`Run ${chalk.cyan('infrawise dev')} to explore via the MCP server`);
379
283
  console.log('');
380
284
  }
285
+ export async function runCodeRefresh(repoPath, config) {
286
+ const cached = readCache('meta', Infinity);
287
+ const dynamoMeta = cached?.dynamoMeta ?? [];
288
+ const postgresMeta = cached?.postgresMeta ?? [];
289
+ const mysqlMeta = cached?.mysqlMeta ?? [];
290
+ const mongoMeta = cached?.mongoMeta ?? [];
291
+ const servicesMeta = cached?.servicesMeta ?? {};
292
+ // Re-run IaC schema (pure file scan, no AWS calls)
293
+ let iacDriftAnalyzer;
294
+ try {
295
+ const iacSchema = await extractIaCSchema(repoPath);
296
+ iacDriftAnalyzer = new IaCDriftAnalyzer();
297
+ iacDriftAnalyzer.setIaCSchema(iacSchema);
298
+ }
299
+ catch {
300
+ // IaC scan is best-effort
301
+ }
302
+ // Re-run repo scan
303
+ let operations;
304
+ try {
305
+ operations = await scanRepository(repoPath);
306
+ }
307
+ catch {
308
+ operations = [];
309
+ }
310
+ const graph = buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
311
+ const analyzers = [
312
+ ...(config.dynamodb?.enabled === true ? [
313
+ new FullTableScanAnalyzer(), new MissingGSIAnalyzer(), new HotPartitionAnalyzer(),
314
+ ] : []),
315
+ ...(config.postgres?.enabled ? [
316
+ new MissingIndexAnalyzer(), new NplusOneAnalyzer(), new LargeSelectAnalyzer(),
317
+ ] : []),
318
+ ...(config.mysql?.enabled ? [
319
+ new MissingMySQLIndexAnalyzer(), new MySQLFullTableScanAnalyzer(),
320
+ ] : []),
321
+ ...(config.mongodb?.enabled ? [
322
+ new MissingMongoIndexAnalyzer(), new MongoCollectionScanAnalyzer(),
323
+ ] : []),
324
+ ...(config.sqs?.enabled === true ? [
325
+ new MissingDLQAnalyzer(), new UnencryptedQueueAnalyzer(), new LargeQueueBacklogAnalyzer(),
326
+ ] : []),
327
+ ...(config.secretsManager?.enabled === true ? [new MissingSecretRotationAnalyzer()] : []),
328
+ ...(config.cloudwatchLogs?.enabled ? [new MissingLogRetentionAnalyzer()] : []),
329
+ ...(config.lambda?.enabled === true ? [
330
+ new LambdaDefaultMemoryAnalyzer(), new LambdaHighTimeoutAnalyzer(),
331
+ ] : []),
332
+ ...(config.rds?.enabled === true ? [
333
+ new RDSPubliclyAccessibleAnalyzer(), new RDSNoBackupAnalyzer(), new RDSUnencryptedAnalyzer(),
334
+ new RDSNoDeletionProtectionAnalyzer(), new RDSNoMultiAZAnalyzer(),
335
+ ] : []),
336
+ ...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
337
+ ];
338
+ const findings = await runAllAnalyzers(graph, analyzers);
339
+ writeCache('graph', graph);
340
+ writeCache('findings', findings);
341
+ writeCache('operations', operations);
342
+ return { graph, findings };
343
+ }