infrawise 0.1.2 → 0.2.1

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/dist/index.js DELETED
@@ -1,4800 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __commonJS = (cb, mod) => function __require() {
10
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
-
29
- // ../core/dist/config.js
30
- var require_config = __commonJS({
31
- "../core/dist/config.js"(exports2) {
32
- "use strict";
33
- var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
34
- if (k2 === void 0) k2 = k;
35
- var desc = Object.getOwnPropertyDescriptor(m, k);
36
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
37
- desc = { enumerable: true, get: function() {
38
- return m[k];
39
- } };
40
- }
41
- Object.defineProperty(o, k2, desc);
42
- }) : (function(o, m, k, k2) {
43
- if (k2 === void 0) k2 = k;
44
- o[k2] = m[k];
45
- }));
46
- var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
47
- Object.defineProperty(o, "default", { enumerable: true, value: v });
48
- }) : function(o, v) {
49
- o["default"] = v;
50
- });
51
- var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
52
- var ownKeys = function(o) {
53
- ownKeys = Object.getOwnPropertyNames || function(o2) {
54
- var ar = [];
55
- for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
56
- return ar;
57
- };
58
- return ownKeys(o);
59
- };
60
- return function(mod) {
61
- if (mod && mod.__esModule) return mod;
62
- var result = {};
63
- if (mod != null) {
64
- for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
65
- }
66
- __setModuleDefault(result, mod);
67
- return result;
68
- };
69
- })();
70
- Object.defineProperty(exports2, "__esModule", { value: true });
71
- exports2.ConfigError = exports2.InfrawiseConfigSchema = void 0;
72
- exports2.loadConfig = loadConfig4;
73
- exports2.generateDefaultConfig = generateDefaultConfig2;
74
- var zod_1 = require("zod");
75
- var fs4 = __importStar(require("fs"));
76
- var path5 = __importStar(require("path"));
77
- var yaml = __importStar(require("js-yaml"));
78
- exports2.InfrawiseConfigSchema = zod_1.z.object({
79
- project: zod_1.z.string().min(1, "Project name is required"),
80
- aws: zod_1.z.object({
81
- profile: zod_1.z.string().optional().default("default"),
82
- region: zod_1.z.string().optional().default("us-east-1")
83
- }).optional().default({}),
84
- dynamodb: zod_1.z.object({ includeTables: zod_1.z.array(zod_1.z.string()).optional() }).optional(),
85
- postgres: zod_1.z.object({
86
- enabled: zod_1.z.boolean().optional().default(false),
87
- connectionString: zod_1.z.string().optional()
88
- }).optional(),
89
- mysql: zod_1.z.object({
90
- enabled: zod_1.z.boolean().optional().default(false),
91
- connectionString: zod_1.z.string().optional()
92
- }).optional(),
93
- mongodb: zod_1.z.object({
94
- enabled: zod_1.z.boolean().optional().default(false),
95
- connectionString: zod_1.z.string().optional(),
96
- databases: zod_1.z.array(zod_1.z.string()).optional()
97
- }).optional(),
98
- terraform: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
99
- sqs: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
100
- sns: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
101
- ssm: zod_1.z.object({
102
- enabled: zod_1.z.boolean().optional().default(true),
103
- paths: zod_1.z.array(zod_1.z.string()).optional()
104
- }).optional(),
105
- secretsManager: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
106
- lambda: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
107
- cloudwatchLogs: zod_1.z.object({
108
- enabled: zod_1.z.boolean().optional().default(false),
109
- logGroupPrefixes: zod_1.z.array(zod_1.z.string()).optional(),
110
- windowHours: zod_1.z.number().int().positive().optional().default(24)
111
- }).optional(),
112
- analysis: zod_1.z.object({
113
- sampleSize: zod_1.z.number().int().positive().optional().default(100)
114
- }).optional()
115
- });
116
- var ConfigError = class extends Error {
117
- details;
118
- constructor(message, details) {
119
- super(message);
120
- this.details = details;
121
- this.name = "ConfigError";
122
- }
123
- };
124
- exports2.ConfigError = ConfigError;
125
- function loadConfig4(configPath) {
126
- const resolvedPath = configPath ? path5.resolve(configPath) : path5.resolve(process.cwd(), "infrawise.yaml");
127
- if (!fs4.existsSync(resolvedPath)) {
128
- throw new ConfigError(`Configuration file not found at: ${resolvedPath}`, [
129
- "Run `infrawise init` to generate a configuration file",
130
- `Or specify a path with --config <path>`
131
- ]);
132
- }
133
- let rawContent;
134
- try {
135
- rawContent = fs4.readFileSync(resolvedPath, "utf-8");
136
- } catch (err) {
137
- throw new ConfigError(`Unable to read configuration file: ${resolvedPath}`, [String(err)]);
138
- }
139
- let parsedYaml;
140
- try {
141
- parsedYaml = yaml.load(rawContent);
142
- } catch (err) {
143
- throw new ConfigError(`Invalid YAML in configuration file: ${resolvedPath}`, [String(err)]);
144
- }
145
- const result = exports2.InfrawiseConfigSchema.safeParse(parsedYaml);
146
- if (!result.success) {
147
- const details = result.error.errors.map((e) => ` - ${e.path.join(".")}: ${e.message}`);
148
- throw new ConfigError("Configuration validation failed", details);
149
- }
150
- return result.data;
151
- }
152
- function generateDefaultConfig2(projectName, options) {
153
- const config = {
154
- project: projectName,
155
- aws: {
156
- profile: options?.aws?.profile ?? "default",
157
- region: options?.aws?.region ?? "us-east-1"
158
- },
159
- dynamodb: { includeTables: options?.dynamodb?.includeTables ?? [] },
160
- postgres: {
161
- enabled: options?.postgres?.enabled ?? false,
162
- connectionString: options?.postgres?.connectionString ?? ""
163
- },
164
- mysql: {
165
- enabled: options?.mysql?.enabled ?? false,
166
- connectionString: options?.mysql?.connectionString ?? ""
167
- },
168
- mongodb: {
169
- enabled: options?.mongodb?.enabled ?? false,
170
- connectionString: options?.mongodb?.connectionString ?? "",
171
- databases: options?.mongodb?.databases ?? []
172
- },
173
- terraform: { enabled: options?.terraform?.enabled ?? true },
174
- sqs: { enabled: options?.sqs?.enabled ?? true },
175
- sns: { enabled: options?.sns?.enabled ?? true },
176
- ssm: {
177
- enabled: options?.ssm?.enabled ?? true,
178
- paths: options?.ssm?.paths ?? []
179
- },
180
- secretsManager: { enabled: options?.secretsManager?.enabled ?? true },
181
- lambda: { enabled: options?.lambda?.enabled ?? true },
182
- cloudwatchLogs: {
183
- enabled: options?.cloudwatchLogs?.enabled ?? false,
184
- logGroupPrefixes: options?.cloudwatchLogs?.logGroupPrefixes ?? [],
185
- windowHours: options?.cloudwatchLogs?.windowHours ?? 24
186
- },
187
- analysis: { sampleSize: options?.analysis?.sampleSize ?? 100 }
188
- };
189
- return yaml.dump(config, { lineWidth: 120 });
190
- }
191
- }
192
- });
193
-
194
- // ../core/dist/logger.js
195
- var require_logger = __commonJS({
196
- "../core/dist/logger.js"(exports2) {
197
- "use strict";
198
- var __importDefault = exports2 && exports2.__importDefault || function(mod) {
199
- return mod && mod.__esModule ? mod : { "default": mod };
200
- };
201
- Object.defineProperty(exports2, "__esModule", { value: true });
202
- exports2.logger = void 0;
203
- var pino_1 = __importDefault(require("pino"));
204
- function createLogger() {
205
- const isDevelopment = process.env.NODE_ENV !== "production";
206
- if (isDevelopment) {
207
- return (0, pino_1.default)({
208
- level: process.env.LOG_LEVEL ?? "info",
209
- transport: {
210
- target: "pino-pretty",
211
- options: {
212
- colorize: true,
213
- translateTime: "HH:MM:ss",
214
- ignore: "pid,hostname",
215
- messageFormat: "{msg}"
216
- }
217
- }
218
- });
219
- }
220
- return (0, pino_1.default)({
221
- level: process.env.LOG_LEVEL ?? "info"
222
- });
223
- }
224
- exports2.logger = createLogger();
225
- }
226
- });
227
-
228
- // ../core/dist/errors.js
229
- var require_errors = __commonJS({
230
- "../core/dist/errors.js"(exports2) {
231
- "use strict";
232
- Object.defineProperty(exports2, "__esModule", { value: true });
233
- exports2.ConfigError = exports2.RepositoryScanError = exports2.PostgresConnectionError = exports2.DynamoDBError = exports2.AWSConnectionError = exports2.InfrawiseError = void 0;
234
- exports2.formatError = formatError3;
235
- var InfrawiseError = class extends Error {
236
- reasons;
237
- remediation;
238
- constructor(message, reasons, remediation) {
239
- super(message);
240
- this.reasons = reasons;
241
- this.remediation = remediation;
242
- this.name = "InfrawiseError";
243
- }
244
- format() {
245
- const lines = [`
246
- ${this.message}
247
- `];
248
- if (this.reasons && this.reasons.length > 0) {
249
- lines.push("Possible reasons:");
250
- for (const reason of this.reasons) {
251
- lines.push(` - ${reason}`);
252
- }
253
- lines.push("");
254
- }
255
- if (this.remediation) {
256
- lines.push(`Run: ${this.remediation}`);
257
- }
258
- return lines.join("\n");
259
- }
260
- };
261
- exports2.InfrawiseError = InfrawiseError;
262
- var AWSConnectionError = class extends InfrawiseError {
263
- constructor(details) {
264
- super("Unable to connect to AWS.", [
265
- "Invalid or missing AWS credentials",
266
- "Incorrect AWS profile specified",
267
- "Network connectivity issues",
268
- details ?? "Unexpected AWS error"
269
- ], "infrawise doctor");
270
- this.name = "AWSConnectionError";
271
- }
272
- };
273
- exports2.AWSConnectionError = AWSConnectionError;
274
- var DynamoDBError = class extends InfrawiseError {
275
- constructor(details) {
276
- super("Unable to access DynamoDB.", [
277
- "Insufficient IAM permissions (need dynamodb:ListTables, dynamodb:DescribeTable)",
278
- "Wrong AWS region configured",
279
- "DynamoDB endpoint not reachable",
280
- details ?? "Unexpected DynamoDB error"
281
- ], "infrawise doctor");
282
- this.name = "DynamoDBError";
283
- }
284
- };
285
- exports2.DynamoDBError = DynamoDBError;
286
- var PostgresConnectionError = class extends InfrawiseError {
287
- constructor(details) {
288
- super("Unable to connect to PostgreSQL.", [
289
- "Invalid connection string",
290
- "Security group restrictions",
291
- "Expired credentials",
292
- details ?? "Unexpected PostgreSQL error"
293
- ], "infrawise doctor");
294
- this.name = "PostgresConnectionError";
295
- }
296
- };
297
- exports2.PostgresConnectionError = PostgresConnectionError;
298
- var RepositoryScanError = class extends InfrawiseError {
299
- constructor(details) {
300
- super("Unable to scan repository.", [
301
- "Path does not exist or is not accessible",
302
- "Not a valid TypeScript project",
303
- "tsconfig.json not found",
304
- details ?? "Unexpected scan error"
305
- ], "infrawise doctor");
306
- this.name = "RepositoryScanError";
307
- }
308
- };
309
- exports2.RepositoryScanError = RepositoryScanError;
310
- var ConfigError = class extends InfrawiseError {
311
- constructor(details) {
312
- super("Invalid or missing configuration.", [
313
- "infrawise.yaml not found in current directory",
314
- "Missing required fields in configuration",
315
- details ?? "Unexpected config error"
316
- ], "infrawise init");
317
- this.name = "ConfigError";
318
- }
319
- };
320
- exports2.ConfigError = ConfigError;
321
- function formatError3(err) {
322
- if (err instanceof InfrawiseError) {
323
- return err.format();
324
- }
325
- if (err instanceof Error) {
326
- return `
327
- Unexpected error: ${err.message}
328
- `;
329
- }
330
- return `
331
- Unexpected error: ${String(err)}
332
- `;
333
- }
334
- }
335
- });
336
-
337
- // ../core/dist/cache.js
338
- var require_cache = __commonJS({
339
- "../core/dist/cache.js"(exports2) {
340
- "use strict";
341
- var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
342
- if (k2 === void 0) k2 = k;
343
- var desc = Object.getOwnPropertyDescriptor(m, k);
344
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
345
- desc = { enumerable: true, get: function() {
346
- return m[k];
347
- } };
348
- }
349
- Object.defineProperty(o, k2, desc);
350
- }) : (function(o, m, k, k2) {
351
- if (k2 === void 0) k2 = k;
352
- o[k2] = m[k];
353
- }));
354
- var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
355
- Object.defineProperty(o, "default", { enumerable: true, value: v });
356
- }) : function(o, v) {
357
- o["default"] = v;
358
- });
359
- var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
360
- var ownKeys = function(o) {
361
- ownKeys = Object.getOwnPropertyNames || function(o2) {
362
- var ar = [];
363
- for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
364
- return ar;
365
- };
366
- return ownKeys(o);
367
- };
368
- return function(mod) {
369
- if (mod && mod.__esModule) return mod;
370
- var result = {};
371
- if (mod != null) {
372
- for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
373
- }
374
- __setModuleDefault(result, mod);
375
- return result;
376
- };
377
- })();
378
- Object.defineProperty(exports2, "__esModule", { value: true });
379
- exports2.writeCache = writeCache2;
380
- exports2.readCache = readCache2;
381
- exports2.clearCache = clearCache;
382
- var fs4 = __importStar(require("fs"));
383
- var path5 = __importStar(require("path"));
384
- var CACHE_VERSION = "1.0.0";
385
- var CACHE_DIR = path5.join(process.cwd(), ".infrawise", "cache");
386
- function ensureCacheDir() {
387
- if (!fs4.existsSync(CACHE_DIR)) {
388
- fs4.mkdirSync(CACHE_DIR, { recursive: true });
389
- }
390
- }
391
- function writeCache2(key, data) {
392
- ensureCacheDir();
393
- const entry = {
394
- timestamp: Date.now(),
395
- data,
396
- version: CACHE_VERSION
397
- };
398
- const filePath = path5.join(CACHE_DIR, `${key}.json`);
399
- fs4.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
400
- }
401
- function readCache2(key, maxAgeMs = 36e5) {
402
- const filePath = path5.join(CACHE_DIR, `${key}.json`);
403
- if (!fs4.existsSync(filePath))
404
- return null;
405
- try {
406
- const raw = fs4.readFileSync(filePath, "utf-8");
407
- const entry = JSON.parse(raw);
408
- if (entry.version !== CACHE_VERSION)
409
- return null;
410
- if (Date.now() - entry.timestamp > maxAgeMs)
411
- return null;
412
- return entry.data;
413
- } catch {
414
- return null;
415
- }
416
- }
417
- function clearCache(key) {
418
- if (key) {
419
- const filePath = path5.join(CACHE_DIR, `${key}.json`);
420
- if (fs4.existsSync(filePath))
421
- fs4.unlinkSync(filePath);
422
- } else {
423
- if (fs4.existsSync(CACHE_DIR)) {
424
- const files = fs4.readdirSync(CACHE_DIR);
425
- for (const file of files) {
426
- fs4.unlinkSync(path5.join(CACHE_DIR, file));
427
- }
428
- }
429
- }
430
- }
431
- }
432
- });
433
-
434
- // ../core/dist/index.js
435
- var require_dist = __commonJS({
436
- "../core/dist/index.js"(exports2) {
437
- "use strict";
438
- Object.defineProperty(exports2, "__esModule", { value: true });
439
- exports2.clearCache = exports2.readCache = exports2.writeCache = exports2.formatError = exports2.ConfigError = exports2.RepositoryScanError = exports2.PostgresConnectionError = exports2.DynamoDBError = exports2.AWSConnectionError = exports2.InfrawiseError = exports2.logger = exports2.ConfigValidationError = exports2.InfrawiseConfigSchema = exports2.generateDefaultConfig = exports2.loadConfig = void 0;
440
- var config_1 = require_config();
441
- Object.defineProperty(exports2, "loadConfig", { enumerable: true, get: function() {
442
- return config_1.loadConfig;
443
- } });
444
- Object.defineProperty(exports2, "generateDefaultConfig", { enumerable: true, get: function() {
445
- return config_1.generateDefaultConfig;
446
- } });
447
- Object.defineProperty(exports2, "InfrawiseConfigSchema", { enumerable: true, get: function() {
448
- return config_1.InfrawiseConfigSchema;
449
- } });
450
- Object.defineProperty(exports2, "ConfigValidationError", { enumerable: true, get: function() {
451
- return config_1.ConfigError;
452
- } });
453
- var logger_1 = require_logger();
454
- Object.defineProperty(exports2, "logger", { enumerable: true, get: function() {
455
- return logger_1.logger;
456
- } });
457
- var errors_1 = require_errors();
458
- Object.defineProperty(exports2, "InfrawiseError", { enumerable: true, get: function() {
459
- return errors_1.InfrawiseError;
460
- } });
461
- Object.defineProperty(exports2, "AWSConnectionError", { enumerable: true, get: function() {
462
- return errors_1.AWSConnectionError;
463
- } });
464
- Object.defineProperty(exports2, "DynamoDBError", { enumerable: true, get: function() {
465
- return errors_1.DynamoDBError;
466
- } });
467
- Object.defineProperty(exports2, "PostgresConnectionError", { enumerable: true, get: function() {
468
- return errors_1.PostgresConnectionError;
469
- } });
470
- Object.defineProperty(exports2, "RepositoryScanError", { enumerable: true, get: function() {
471
- return errors_1.RepositoryScanError;
472
- } });
473
- Object.defineProperty(exports2, "ConfigError", { enumerable: true, get: function() {
474
- return errors_1.ConfigError;
475
- } });
476
- Object.defineProperty(exports2, "formatError", { enumerable: true, get: function() {
477
- return errors_1.formatError;
478
- } });
479
- var cache_1 = require_cache();
480
- Object.defineProperty(exports2, "writeCache", { enumerable: true, get: function() {
481
- return cache_1.writeCache;
482
- } });
483
- Object.defineProperty(exports2, "readCache", { enumerable: true, get: function() {
484
- return cache_1.readCache;
485
- } });
486
- Object.defineProperty(exports2, "clearCache", { enumerable: true, get: function() {
487
- return cache_1.clearCache;
488
- } });
489
- }
490
- });
491
-
492
- // ../adapters/dynamodb/dist/index.js
493
- var require_dist2 = __commonJS({
494
- "../adapters/dynamodb/dist/index.js"(exports2) {
495
- "use strict";
496
- Object.defineProperty(exports2, "__esModule", { value: true });
497
- exports2.extractDynamoMetadata = extractDynamoMetadata2;
498
- exports2.validateDynamoAccess = validateDynamoAccess3;
499
- var client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
500
- var credential_providers_1 = require("@aws-sdk/credential-providers");
501
- var core_1 = require_dist();
502
- function createDynamoClient(config) {
503
- const region = config.aws?.region ?? "us-east-1";
504
- const profile = config.aws?.profile;
505
- const clientConfig = { region };
506
- if (profile) {
507
- clientConfig.credentials = (0, credential_providers_1.fromIni)({ profile });
508
- }
509
- return new client_dynamodb_1.DynamoDBClient(clientConfig);
510
- }
511
- function parseTableDescription(desc) {
512
- const tableName = desc.TableName ?? "unknown";
513
- const partitionKey = desc.KeySchema?.find((k) => k.KeyType === "HASH")?.AttributeName;
514
- const sortKey = desc.KeySchema?.find((k) => k.KeyType === "RANGE")?.AttributeName;
515
- const indexes = [];
516
- if (desc.GlobalSecondaryIndexes) {
517
- for (const gsi of desc.GlobalSecondaryIndexes) {
518
- if (gsi.IndexName)
519
- indexes.push(gsi.IndexName);
520
- }
521
- }
522
- if (desc.LocalSecondaryIndexes) {
523
- for (const lsi of desc.LocalSecondaryIndexes) {
524
- if (lsi.IndexName)
525
- indexes.push(lsi.IndexName);
526
- }
527
- }
528
- return { tableName, partitionKey, sortKey, indexes };
529
- }
530
- async function listAllTables(client) {
531
- const tableNames = [];
532
- let lastEvaluatedTableName;
533
- do {
534
- const command = new client_dynamodb_1.ListTablesCommand({
535
- ExclusiveStartTableName: lastEvaluatedTableName,
536
- Limit: 100
537
- });
538
- const response = await client.send(command);
539
- if (response.TableNames) {
540
- tableNames.push(...response.TableNames);
541
- }
542
- lastEvaluatedTableName = response.LastEvaluatedTableName;
543
- } while (lastEvaluatedTableName);
544
- return tableNames;
545
- }
546
- async function extractDynamoMetadata2(config) {
547
- const client = createDynamoClient(config);
548
- const includeTables = config.dynamodb?.includeTables;
549
- let tableNames;
550
- try {
551
- const allTables = await listAllTables(client);
552
- if (includeTables && includeTables.length > 0) {
553
- tableNames = allTables.filter((name) => includeTables.includes(name));
554
- core_1.logger.debug(`Filtered to ${tableNames.length} tables from config`);
555
- } else {
556
- tableNames = allTables;
557
- }
558
- core_1.logger.info(`Found ${tableNames.length} DynamoDB table(s)`);
559
- } catch (err) {
560
- throw new core_1.DynamoDBError(err instanceof Error ? err.message : "Failed to list DynamoDB tables");
561
- }
562
- const results = [];
563
- for (const tableName of tableNames) {
564
- try {
565
- const command = new client_dynamodb_1.DescribeTableCommand({ TableName: tableName });
566
- const response = await client.send(command);
567
- if (response.Table) {
568
- results.push(parseTableDescription(response.Table));
569
- core_1.logger.debug(`Described table: ${tableName}`);
570
- }
571
- } catch (err) {
572
- core_1.logger.warn(`Failed to describe table ${tableName}: ${err instanceof Error ? err.message : String(err)}`);
573
- }
574
- }
575
- return results;
576
- }
577
- async function validateDynamoAccess3(config) {
578
- const client = createDynamoClient(config);
579
- try {
580
- await client.send(new client_dynamodb_1.ListTablesCommand({ Limit: 1 }));
581
- return true;
582
- } catch {
583
- return false;
584
- }
585
- }
586
- }
587
- });
588
-
589
- // ../adapters/postgres/dist/index.js
590
- var require_dist3 = __commonJS({
591
- "../adapters/postgres/dist/index.js"(exports2) {
592
- "use strict";
593
- Object.defineProperty(exports2, "__esModule", { value: true });
594
- exports2.extractPostgresMetadata = extractPostgresMetadata2;
595
- exports2.validatePostgresAccess = validatePostgresAccess2;
596
- var pg_1 = require("pg");
597
- var core_1 = require_dist();
598
- async function extractPostgresMetadata2(connectionString) {
599
- const pool = new pg_1.Pool({
600
- connectionString,
601
- max: 1,
602
- idleTimeoutMillis: 1e4,
603
- connectionTimeoutMillis: 5e3
604
- });
605
- try {
606
- const client = await pool.connect();
607
- try {
608
- const tablesResult = await client.query(`
609
- SELECT table_schema, table_name
610
- FROM information_schema.tables
611
- WHERE table_type = 'BASE TABLE'
612
- AND table_schema NOT IN ('pg_catalog', 'information_schema')
613
- ORDER BY table_schema, table_name
614
- `);
615
- core_1.logger.info(`Found ${tablesResult.rows.length} PostgreSQL table(s)`);
616
- const columnsResult = await client.query(`
617
- SELECT table_schema, table_name, column_name
618
- FROM information_schema.columns
619
- WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
620
- ORDER BY table_schema, table_name, ordinal_position
621
- `);
622
- const indexesResult = await client.query(`
623
- SELECT schemaname, tablename, indexname
624
- FROM pg_indexes
625
- WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
626
- ORDER BY schemaname, tablename, indexname
627
- `);
628
- const primaryKeysResult = await client.query(`
629
- SELECT
630
- tc.table_schema,
631
- tc.table_name,
632
- kcu.column_name
633
- FROM information_schema.table_constraints tc
634
- JOIN information_schema.key_column_usage kcu
635
- ON tc.constraint_name = kcu.constraint_name
636
- AND tc.table_schema = kcu.table_schema
637
- WHERE tc.constraint_type = 'PRIMARY KEY'
638
- AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
639
- ORDER BY tc.table_schema, tc.table_name
640
- `);
641
- const columnMap = /* @__PURE__ */ new Map();
642
- for (const row of columnsResult.rows) {
643
- const key = `${row.table_schema}.${row.table_name}`;
644
- if (!columnMap.has(key))
645
- columnMap.set(key, []);
646
- columnMap.get(key).push(row.column_name);
647
- }
648
- const indexMap = /* @__PURE__ */ new Map();
649
- for (const row of indexesResult.rows) {
650
- const key = `${row.schemaname}.${row.tablename}`;
651
- if (!indexMap.has(key))
652
- indexMap.set(key, []);
653
- indexMap.get(key).push(row.indexname);
654
- }
655
- const pkMap = /* @__PURE__ */ new Map();
656
- for (const row of primaryKeysResult.rows) {
657
- const key = `${row.table_schema}.${row.table_name}`;
658
- if (!pkMap.has(key))
659
- pkMap.set(key, []);
660
- pkMap.get(key).push(row.column_name);
661
- }
662
- const results = [];
663
- for (const table of tablesResult.rows) {
664
- const key = `${table.table_schema}.${table.table_name}`;
665
- results.push({
666
- schema: table.table_schema,
667
- table: table.table_name,
668
- columns: columnMap.get(key) ?? [],
669
- indexes: indexMap.get(key) ?? [],
670
- primaryKeys: pkMap.get(key) ?? []
671
- });
672
- }
673
- return results;
674
- } finally {
675
- client.release();
676
- }
677
- } catch (err) {
678
- if (err instanceof core_1.PostgresConnectionError)
679
- throw err;
680
- throw new core_1.PostgresConnectionError(err instanceof Error ? err.message : "Unknown error connecting to PostgreSQL");
681
- } finally {
682
- await pool.end();
683
- }
684
- }
685
- async function validatePostgresAccess2(connectionString) {
686
- const pool = new pg_1.Pool({
687
- connectionString,
688
- max: 1,
689
- connectionTimeoutMillis: 5e3
690
- });
691
- try {
692
- const client = await pool.connect();
693
- await client.query("SELECT 1");
694
- client.release();
695
- return true;
696
- } catch {
697
- return false;
698
- } finally {
699
- await pool.end();
700
- }
701
- }
702
- }
703
- });
704
-
705
- // ../adapters/mysql/dist/index.js
706
- var require_dist4 = __commonJS({
707
- "../adapters/mysql/dist/index.js"(exports2) {
708
- "use strict";
709
- var __importDefault = exports2 && exports2.__importDefault || function(mod) {
710
- return mod && mod.__esModule ? mod : { "default": mod };
711
- };
712
- Object.defineProperty(exports2, "__esModule", { value: true });
713
- exports2.MySQLConnectionError = void 0;
714
- exports2.extractMySQLMetadata = extractMySQLMetadata2;
715
- exports2.validateMySQLAccess = validateMySQLAccess2;
716
- var promise_1 = __importDefault(require("mysql2/promise"));
717
- var core_1 = require_dist();
718
- var SYSTEM_SCHEMAS = /* @__PURE__ */ new Set(["information_schema", "performance_schema", "mysql", "sys"]);
719
- var MySQLConnectionError = class extends core_1.InfrawiseError {
720
- constructor(details) {
721
- super("Unable to connect to MySQL.\n\nPossible reasons:\n- invalid connection string\n- port 3306 not accessible\n- wrong credentials\n\nRun: infrawise doctor", void 0, void 0);
722
- this.name = "MySQLConnectionError";
723
- if (details) {
724
- this.message = `Unable to connect to MySQL.
725
-
726
- Possible reasons:
727
- - invalid connection string
728
- - port 3306 not accessible
729
- - wrong credentials
730
-
731
- Run: infrawise doctor
732
-
733
- Detail: ${details}`;
734
- }
735
- }
736
- };
737
- exports2.MySQLConnectionError = MySQLConnectionError;
738
- async function extractMySQLMetadata2(connectionString) {
739
- let connection;
740
- try {
741
- connection = await promise_1.default.createConnection(connectionString);
742
- const [tableRows] = await connection.execute(`
743
- SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE
744
- FROM information_schema.tables
745
- WHERE TABLE_TYPE = 'BASE TABLE'
746
- AND TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => "?").join(", ")})
747
- ORDER BY TABLE_SCHEMA, TABLE_NAME
748
- `, [...SYSTEM_SCHEMAS]);
749
- core_1.logger.info(`Found ${tableRows.length} MySQL table(s)`);
750
- const [columnRows] = await connection.execute(`
751
- SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
752
- FROM information_schema.columns
753
- WHERE TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => "?").join(", ")})
754
- ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
755
- `, [...SYSTEM_SCHEMAS]);
756
- const [indexRows] = await connection.execute(`
757
- SELECT TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
758
- FROM information_schema.statistics
759
- WHERE TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => "?").join(", ")})
760
- GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
761
- ORDER BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
762
- `, [...SYSTEM_SCHEMAS]);
763
- const [pkRows] = await connection.execute(`
764
- SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
765
- FROM information_schema.key_column_usage
766
- WHERE CONSTRAINT_NAME = 'PRIMARY'
767
- AND TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => "?").join(", ")})
768
- ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
769
- `, [...SYSTEM_SCHEMAS]);
770
- const columnMap = /* @__PURE__ */ new Map();
771
- for (const row of columnRows) {
772
- const key = `${row["TABLE_SCHEMA"]}.${row["TABLE_NAME"]}`;
773
- if (!columnMap.has(key))
774
- columnMap.set(key, []);
775
- columnMap.get(key).push(row["COLUMN_NAME"]);
776
- }
777
- const indexMap = /* @__PURE__ */ new Map();
778
- for (const row of indexRows) {
779
- const key = `${row["TABLE_SCHEMA"]}.${row["TABLE_NAME"]}`;
780
- if (!indexMap.has(key))
781
- indexMap.set(key, []);
782
- const idxName = row["INDEX_NAME"];
783
- if (!indexMap.get(key).includes(idxName)) {
784
- indexMap.get(key).push(idxName);
785
- }
786
- }
787
- const pkMap = /* @__PURE__ */ new Map();
788
- for (const row of pkRows) {
789
- const key = `${row["TABLE_SCHEMA"]}.${row["TABLE_NAME"]}`;
790
- if (!pkMap.has(key))
791
- pkMap.set(key, []);
792
- pkMap.get(key).push(row["COLUMN_NAME"]);
793
- }
794
- const results = [];
795
- for (const row of tableRows) {
796
- const schema = row["TABLE_SCHEMA"];
797
- const table = row["TABLE_NAME"];
798
- const engine = row["ENGINE"] ?? "InnoDB";
799
- const key = `${schema}.${table}`;
800
- results.push({
801
- schema,
802
- table,
803
- columns: columnMap.get(key) ?? [],
804
- indexes: indexMap.get(key) ?? [],
805
- primaryKeys: pkMap.get(key) ?? [],
806
- engine
807
- });
808
- }
809
- return results;
810
- } catch (err) {
811
- if (err instanceof MySQLConnectionError)
812
- throw err;
813
- throw new MySQLConnectionError(err instanceof Error ? err.message : String(err));
814
- } finally {
815
- if (connection) {
816
- await connection.end().catch(() => void 0);
817
- }
818
- }
819
- }
820
- async function validateMySQLAccess2(connectionString) {
821
- let connection;
822
- try {
823
- connection = await promise_1.default.createConnection(connectionString);
824
- await connection.execute("SELECT 1");
825
- return true;
826
- } catch {
827
- return false;
828
- } finally {
829
- if (connection) {
830
- await connection.end().catch(() => void 0);
831
- }
832
- }
833
- }
834
- }
835
- });
836
-
837
- // ../adapters/mongodb/dist/index.js
838
- var require_dist5 = __commonJS({
839
- "../adapters/mongodb/dist/index.js"(exports2) {
840
- "use strict";
841
- Object.defineProperty(exports2, "__esModule", { value: true });
842
- exports2.MongoConnectionError = void 0;
843
- exports2.extractMongoMetadata = extractMongoMetadata2;
844
- exports2.validateMongoAccess = validateMongoAccess2;
845
- var mongodb_1 = require("mongodb");
846
- var core_1 = require_dist();
847
- var SYSTEM_DATABASES = /* @__PURE__ */ new Set(["admin", "local", "config"]);
848
- var MongoConnectionError = class extends core_1.InfrawiseError {
849
- constructor(details) {
850
- super("Unable to connect to MongoDB.\n\nPossible reasons:\n- invalid connection string\n- port 27017 not accessible\n- wrong credentials\n\nRun: infrawise doctor", void 0, void 0);
851
- this.name = "MongoConnectionError";
852
- if (details) {
853
- this.message = `Unable to connect to MongoDB.
854
-
855
- Possible reasons:
856
- - invalid connection string
857
- - port 27017 not accessible
858
- - wrong credentials
859
-
860
- Run: infrawise doctor
861
-
862
- Detail: ${details}`;
863
- }
864
- }
865
- };
866
- exports2.MongoConnectionError = MongoConnectionError;
867
- async function extractMongoMetadata2(connectionString, databases) {
868
- const client = new mongodb_1.MongoClient(connectionString, {
869
- serverSelectionTimeoutMS: 5e3,
870
- connectTimeoutMS: 5e3
871
- });
872
- try {
873
- await client.connect();
874
- let dbNames;
875
- if (databases && databases.length > 0) {
876
- dbNames = databases;
877
- } else {
878
- const adminDb = client.db("admin");
879
- const dbList = await adminDb.admin().listDatabases();
880
- dbNames = dbList.databases.map((d) => d.name).filter((name) => !SYSTEM_DATABASES.has(name));
881
- }
882
- core_1.logger.info(`Introspecting ${dbNames.length} MongoDB database(s)`);
883
- const results = [];
884
- for (const dbName of dbNames) {
885
- const db = client.db(dbName);
886
- let collectionNames;
887
- try {
888
- const collections = await db.listCollections().toArray();
889
- collectionNames = collections.map((c) => c.name);
890
- } catch (err) {
891
- core_1.logger.warn(`Skipping database "${dbName}": ${err instanceof Error ? err.message : String(err)}`);
892
- continue;
893
- }
894
- for (const collName of collectionNames) {
895
- const collection = db.collection(collName);
896
- let rawIndexes = [];
897
- try {
898
- rawIndexes = await collection.indexes();
899
- } catch {
900
- rawIndexes = [];
901
- }
902
- let estimatedCount = 0;
903
- try {
904
- estimatedCount = await collection.estimatedDocumentCount();
905
- } catch {
906
- estimatedCount = 0;
907
- }
908
- const indexes = rawIndexes.map((idx) => ({
909
- name: String(idx["name"] ?? ""),
910
- keys: idx["key"] ?? {},
911
- unique: Boolean(idx["unique"]),
912
- sparse: Boolean(idx["sparse"])
913
- }));
914
- results.push({
915
- database: dbName,
916
- collection: collName,
917
- indexes,
918
- estimatedCount
919
- });
920
- }
921
- }
922
- core_1.logger.info(`Found ${results.length} MongoDB collection(s)`);
923
- return results;
924
- } catch (err) {
925
- if (err instanceof MongoConnectionError)
926
- throw err;
927
- throw new MongoConnectionError(err instanceof Error ? err.message : String(err));
928
- } finally {
929
- await client.close().catch(() => void 0);
930
- }
931
- }
932
- async function validateMongoAccess2(connectionString) {
933
- const client = new mongodb_1.MongoClient(connectionString, {
934
- serverSelectionTimeoutMS: 5e3,
935
- connectTimeoutMS: 5e3
936
- });
937
- try {
938
- await client.connect();
939
- await client.db("admin").command({ ping: 1 });
940
- return true;
941
- } catch {
942
- return false;
943
- } finally {
944
- await client.close().catch(() => void 0);
945
- }
946
- }
947
- }
948
- });
949
-
950
- // ../adapters/terraform/dist/index.js
951
- var require_dist6 = __commonJS({
952
- "../adapters/terraform/dist/index.js"(exports2) {
953
- "use strict";
954
- var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
955
- if (k2 === void 0) k2 = k;
956
- var desc = Object.getOwnPropertyDescriptor(m, k);
957
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
958
- desc = { enumerable: true, get: function() {
959
- return m[k];
960
- } };
961
- }
962
- Object.defineProperty(o, k2, desc);
963
- }) : (function(o, m, k, k2) {
964
- if (k2 === void 0) k2 = k;
965
- o[k2] = m[k];
966
- }));
967
- var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
968
- Object.defineProperty(o, "default", { enumerable: true, value: v });
969
- }) : function(o, v) {
970
- o["default"] = v;
971
- });
972
- var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
973
- var ownKeys = function(o) {
974
- ownKeys = Object.getOwnPropertyNames || function(o2) {
975
- var ar = [];
976
- for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
977
- return ar;
978
- };
979
- return ownKeys(o);
980
- };
981
- return function(mod) {
982
- if (mod && mod.__esModule) return mod;
983
- var result = {};
984
- if (mod != null) {
985
- for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
986
- }
987
- __setModuleDefault(result, mod);
988
- return result;
989
- };
990
- })();
991
- var __importDefault = exports2 && exports2.__importDefault || function(mod) {
992
- return mod && mod.__esModule ? mod : { "default": mod };
993
- };
994
- Object.defineProperty(exports2, "__esModule", { value: true });
995
- exports2.extractTerraformSchema = extractTerraformSchema;
996
- exports2.extractCloudFormationSchema = extractCloudFormationSchema;
997
- exports2.extractCDKSchema = extractCDKSchema;
998
- exports2.extractIaCSchema = extractIaCSchema2;
999
- var fs4 = __importStar(require("fs"));
1000
- var path5 = __importStar(require("path"));
1001
- var js_yaml_1 = __importDefault(require("js-yaml"));
1002
- var core_1 = require_dist();
1003
- function emptySchema() {
1004
- return {
1005
- dynamoTables: [],
1006
- rdsInstances: [],
1007
- mongoClusters: [],
1008
- queues: [],
1009
- topics: [],
1010
- lambdas: [],
1011
- buckets: [],
1012
- parameters: [],
1013
- secrets: [],
1014
- apiGateways: []
1015
- };
1016
- }
1017
- function findFilesRecursively(dir, extensions, skipDirs = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".infrawise"])) {
1018
- const results = [];
1019
- if (!fs4.existsSync(dir))
1020
- return results;
1021
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
1022
- for (const entry of entries) {
1023
- if (skipDirs.has(entry.name))
1024
- continue;
1025
- const fullPath = path5.join(dir, entry.name);
1026
- if (entry.isDirectory()) {
1027
- results.push(...findFilesRecursively(fullPath, extensions, skipDirs));
1028
- } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
1029
- results.push(fullPath);
1030
- }
1031
- }
1032
- return results;
1033
- }
1034
- function extractTerraformResourceBlocks(content) {
1035
- const results = [];
1036
- const resourcePattern = /resource\s+"([^"]+)"\s+"([^"]+)"\s*\{/g;
1037
- let match;
1038
- while ((match = resourcePattern.exec(content)) !== null) {
1039
- const resourceType = match[1] ?? "";
1040
- const resourceName = match[2] ?? "";
1041
- const startBrace = match.index + match[0].length - 1;
1042
- let depth = 1;
1043
- let i = startBrace + 1;
1044
- while (i < content.length && depth > 0) {
1045
- if (content[i] === "{")
1046
- depth++;
1047
- else if (content[i] === "}")
1048
- depth--;
1049
- i++;
1050
- }
1051
- results.push({ resourceType, resourceName, body: content.slice(startBrace + 1, i - 1) });
1052
- }
1053
- return results;
1054
- }
1055
- function tfStr(body, attr) {
1056
- const m = body.match(new RegExp(`${attr}\\s*=\\s*"([^"]*)"`, "i"));
1057
- return m?.[1];
1058
- }
1059
- function tfBool(body, attr) {
1060
- const m = body.match(new RegExp(`${attr}\\s*=\\s*(true|false)`, "i"));
1061
- return m?.[1] === "true";
1062
- }
1063
- function tfGSINames(body) {
1064
- const names = [];
1065
- const pat = /global_secondary_index\s*\{([^}]*)\}/g;
1066
- let m;
1067
- while ((m = pat.exec(body)) !== null) {
1068
- const nameMatch = m[1].match(/name\s*=\s*"([^"]*)"/);
1069
- if (nameMatch?.[1])
1070
- names.push(nameMatch[1]);
1071
- }
1072
- return names;
1073
- }
1074
- async function extractTerraformSchema(repoPath) {
1075
- const schema = emptySchema();
1076
- const tfFiles = findFilesRecursively(repoPath, [".tf"]);
1077
- core_1.logger.info(`Found ${tfFiles.length} Terraform file(s)`);
1078
- for (const filePath of tfFiles) {
1079
- let content;
1080
- try {
1081
- content = fs4.readFileSync(filePath, "utf-8");
1082
- } catch {
1083
- continue;
1084
- }
1085
- for (const { resourceType, resourceName, body } of extractTerraformResourceBlocks(content)) {
1086
- switch (resourceType) {
1087
- case "aws_dynamodb_table":
1088
- schema.dynamoTables.push({
1089
- name: tfStr(body, "name") ?? resourceName,
1090
- partitionKey: tfStr(body, "hash_key"),
1091
- sortKey: tfStr(body, "range_key"),
1092
- gsiNames: tfGSINames(body),
1093
- source: "terraform",
1094
- filePath
1095
- });
1096
- break;
1097
- case "aws_db_instance":
1098
- case "aws_rds_cluster":
1099
- schema.rdsInstances.push({
1100
- identifier: tfStr(body, "identifier") ?? tfStr(body, "cluster_identifier") ?? resourceName,
1101
- engine: tfStr(body, "engine") ?? "unknown",
1102
- source: "terraform",
1103
- filePath
1104
- });
1105
- break;
1106
- case "aws_docdb_cluster":
1107
- schema.mongoClusters.push({
1108
- identifier: tfStr(body, "cluster_identifier") ?? resourceName,
1109
- source: "terraform",
1110
- filePath
1111
- });
1112
- break;
1113
- case "aws_sqs_queue": {
1114
- const name = tfStr(body, "name") ?? resourceName;
1115
- const hasDLQ = body.includes("redrive_policy");
1116
- const encrypted = body.includes("kms_master_key_id") || tfBool(body, "sqs_managed_sse_enabled");
1117
- schema.queues.push({ name, hasDLQ, encrypted, source: "terraform", filePath });
1118
- break;
1119
- }
1120
- case "aws_sns_topic":
1121
- schema.topics.push({
1122
- name: tfStr(body, "name") ?? resourceName,
1123
- encrypted: body.includes("kms_master_key_id"),
1124
- source: "terraform",
1125
- filePath
1126
- });
1127
- break;
1128
- case "aws_lambda_function":
1129
- schema.lambdas.push({
1130
- name: tfStr(body, "function_name") ?? resourceName,
1131
- runtime: tfStr(body, "runtime"),
1132
- source: "terraform",
1133
- filePath
1134
- });
1135
- break;
1136
- case "aws_s3_bucket":
1137
- schema.buckets.push({
1138
- name: tfStr(body, "bucket") ?? tfStr(body, "bucket_prefix") ?? resourceName,
1139
- versioned: body.includes("versioning") && body.includes("enabled = true"),
1140
- source: "terraform",
1141
- filePath
1142
- });
1143
- break;
1144
- case "aws_ssm_parameter":
1145
- schema.parameters.push({
1146
- name: tfStr(body, "name") ?? resourceName,
1147
- type: tfStr(body, "type") ?? "String",
1148
- source: "terraform",
1149
- filePath
1150
- });
1151
- break;
1152
- case "aws_secretsmanager_secret":
1153
- schema.secrets.push({
1154
- name: tfStr(body, "name") ?? resourceName,
1155
- source: "terraform",
1156
- filePath
1157
- });
1158
- break;
1159
- case "aws_api_gateway_rest_api":
1160
- case "aws_apigatewayv2_api":
1161
- schema.apiGateways.push({
1162
- name: tfStr(body, "name") ?? resourceName,
1163
- source: "terraform",
1164
- filePath
1165
- });
1166
- break;
1167
- }
1168
- }
1169
- }
1170
- return schema;
1171
- }
1172
- function isCloudFormationTemplate(parsed) {
1173
- if (typeof parsed !== "object" || parsed === null)
1174
- return false;
1175
- const obj = parsed;
1176
- return "AWSTemplateFormatVersion" in obj || "Resources" in obj && typeof obj["Resources"] === "object";
1177
- }
1178
- function parseCFNFile(filePath) {
1179
- let content;
1180
- try {
1181
- content = fs4.readFileSync(filePath, "utf-8");
1182
- } catch {
1183
- return null;
1184
- }
1185
- if (!content.includes("AWSTemplateFormatVersion") && !content.includes("Resources"))
1186
- return null;
1187
- let parsed;
1188
- try {
1189
- parsed = filePath.endsWith(".json") ? JSON.parse(content) : js_yaml_1.default.load(content);
1190
- } catch {
1191
- return null;
1192
- }
1193
- if (!isCloudFormationTemplate(parsed))
1194
- return null;
1195
- return parsed;
1196
- }
1197
- function cfnStr(props, ...keys) {
1198
- for (const key of keys) {
1199
- if (typeof props[key] === "string")
1200
- return props[key];
1201
- }
1202
- return void 0;
1203
- }
1204
- function cfnBool(props, key) {
1205
- return props[key] === true || props[key] === "true" || props[key] === "Enabled";
1206
- }
1207
- function processCFNResources(resources, schema, filePath, source) {
1208
- for (const [logicalId, rawResource] of Object.entries(resources)) {
1209
- if (typeof rawResource !== "object" || rawResource === null)
1210
- continue;
1211
- const resource = rawResource;
1212
- const resourceType = resource["Type"];
1213
- const props = resource["Properties"] ?? {};
1214
- switch (resourceType) {
1215
- case "AWS::DynamoDB::Table": {
1216
- let pk, sk;
1217
- const keySchema = props["KeySchema"];
1218
- if (Array.isArray(keySchema)) {
1219
- for (const kd of keySchema) {
1220
- if (typeof kd !== "object" || kd === null)
1221
- continue;
1222
- const k = kd;
1223
- if (k["KeyType"] === "HASH")
1224
- pk = k["AttributeName"];
1225
- if (k["KeyType"] === "RANGE")
1226
- sk = k["AttributeName"];
1227
- }
1228
- }
1229
- const gsiNames = [];
1230
- const gsis = props["GlobalSecondaryIndexes"];
1231
- if (Array.isArray(gsis)) {
1232
- for (const g of gsis) {
1233
- if (typeof g === "object" && g !== null) {
1234
- const gi = g;
1235
- if (typeof gi["IndexName"] === "string")
1236
- gsiNames.push(gi["IndexName"]);
1237
- }
1238
- }
1239
- }
1240
- schema.dynamoTables.push({
1241
- name: cfnStr(props, "TableName") ?? logicalId,
1242
- partitionKey: pk,
1243
- sortKey: sk,
1244
- gsiNames,
1245
- source,
1246
- filePath
1247
- });
1248
- break;
1249
- }
1250
- case "AWS::RDS::DBInstance":
1251
- schema.rdsInstances.push({
1252
- identifier: cfnStr(props, "DBInstanceIdentifier") ?? logicalId,
1253
- engine: cfnStr(props, "Engine") ?? "unknown",
1254
- source,
1255
- filePath
1256
- });
1257
- break;
1258
- case "AWS::RDS::DBCluster":
1259
- schema.rdsInstances.push({
1260
- identifier: cfnStr(props, "DBClusterIdentifier") ?? logicalId,
1261
- engine: cfnStr(props, "Engine") ?? "aurora",
1262
- source,
1263
- filePath
1264
- });
1265
- break;
1266
- case "AWS::DocDB::DBCluster":
1267
- schema.mongoClusters.push({
1268
- identifier: cfnStr(props, "DBClusterIdentifier") ?? logicalId,
1269
- source,
1270
- filePath
1271
- });
1272
- break;
1273
- case "AWS::SQS::Queue": {
1274
- const name = cfnStr(props, "QueueName") ?? logicalId;
1275
- const hasDLQ = !!props["RedrivePolicy"];
1276
- const encrypted = !!(props["KmsMasterKeyId"] || cfnBool(props, "SqsManagedSseEnabled"));
1277
- schema.queues.push({ name, hasDLQ, encrypted, source, filePath });
1278
- break;
1279
- }
1280
- case "AWS::SNS::Topic":
1281
- schema.topics.push({
1282
- name: cfnStr(props, "TopicName") ?? logicalId,
1283
- encrypted: !!props["KmsMasterKeyId"],
1284
- source,
1285
- filePath
1286
- });
1287
- break;
1288
- case "AWS::Lambda::Function":
1289
- schema.lambdas.push({
1290
- name: cfnStr(props, "FunctionName") ?? logicalId,
1291
- runtime: cfnStr(props, "Runtime"),
1292
- source,
1293
- filePath
1294
- });
1295
- break;
1296
- case "AWS::S3::Bucket": {
1297
- const versioningConfig = props["VersioningConfiguration"];
1298
- schema.buckets.push({
1299
- name: cfnStr(props, "BucketName") ?? logicalId,
1300
- versioned: versioningConfig?.["Status"] === "Enabled",
1301
- source,
1302
- filePath
1303
- });
1304
- break;
1305
- }
1306
- case "AWS::SSM::Parameter":
1307
- schema.parameters.push({
1308
- name: cfnStr(props, "Name") ?? logicalId,
1309
- type: cfnStr(props, "Type") ?? "String",
1310
- source,
1311
- filePath
1312
- });
1313
- break;
1314
- case "AWS::SecretsManager::Secret":
1315
- schema.secrets.push({
1316
- name: cfnStr(props, "Name") ?? logicalId,
1317
- source,
1318
- filePath
1319
- });
1320
- break;
1321
- case "AWS::ApiGateway::RestApi":
1322
- case "AWS::ApiGatewayV2::Api":
1323
- schema.apiGateways.push({
1324
- name: cfnStr(props, "Name") ?? logicalId,
1325
- source,
1326
- filePath
1327
- });
1328
- break;
1329
- }
1330
- }
1331
- }
1332
- async function extractCloudFormationSchema(repoPath) {
1333
- const schema = emptySchema();
1334
- const cfnFiles = findFilesRecursively(repoPath, [".yaml", ".yml", ".json"]);
1335
- core_1.logger.info(`Scanning ${cfnFiles.length} potential CloudFormation file(s)`);
1336
- for (const filePath of cfnFiles) {
1337
- const parsed = parseCFNFile(filePath);
1338
- if (!parsed)
1339
- continue;
1340
- const resources = parsed["Resources"];
1341
- if (!resources)
1342
- continue;
1343
- processCFNResources(resources, schema, filePath, "cloudformation");
1344
- }
1345
- return schema;
1346
- }
1347
- async function extractCDKSchema(repoPath) {
1348
- const schema = emptySchema();
1349
- const cdkOutDir = path5.join(repoPath, "cdk.out");
1350
- if (fs4.existsSync(cdkOutDir)) {
1351
- const templateFiles = fs4.readdirSync(cdkOutDir).filter((f) => f.endsWith(".template.json")).map((f) => path5.join(cdkOutDir, f));
1352
- core_1.logger.info(`Found ${templateFiles.length} CDK synthesized template(s) in cdk.out/`);
1353
- for (const filePath of templateFiles) {
1354
- const parsed = parseCFNFile(filePath);
1355
- if (!parsed)
1356
- continue;
1357
- const resources = parsed["Resources"];
1358
- if (!resources)
1359
- continue;
1360
- processCFNResources(resources, schema, filePath, "cdk");
1361
- }
1362
- }
1363
- if (schema.queues.length === 0 && schema.topics.length === 0 && schema.lambdas.length === 0) {
1364
- const cdkJsonPath = path5.join(repoPath, "cdk.json");
1365
- if (fs4.existsSync(cdkJsonPath)) {
1366
- core_1.logger.info("CDK project detected (cdk.json found) \u2014 run `cdk synth` for full IaC analysis");
1367
- }
1368
- }
1369
- return schema;
1370
- }
1371
- function mergeSchemas(...schemas) {
1372
- const merged = emptySchema();
1373
- const seen = {
1374
- dynamo: /* @__PURE__ */ new Set(),
1375
- rds: /* @__PURE__ */ new Set(),
1376
- mongo: /* @__PURE__ */ new Set(),
1377
- queue: /* @__PURE__ */ new Set(),
1378
- topic: /* @__PURE__ */ new Set(),
1379
- lambda: /* @__PURE__ */ new Set(),
1380
- bucket: /* @__PURE__ */ new Set(),
1381
- param: /* @__PURE__ */ new Set(),
1382
- secret: /* @__PURE__ */ new Set(),
1383
- api: /* @__PURE__ */ new Set()
1384
- };
1385
- for (const schema of schemas) {
1386
- for (const t of schema.dynamoTables) {
1387
- const k = `${t.source}::${t.name}`;
1388
- if (!seen.dynamo.has(k)) {
1389
- seen.dynamo.add(k);
1390
- merged.dynamoTables.push(t);
1391
- }
1392
- }
1393
- for (const r of schema.rdsInstances) {
1394
- const k = `${r.source}::${r.identifier}`;
1395
- if (!seen.rds.has(k)) {
1396
- seen.rds.add(k);
1397
- merged.rdsInstances.push(r);
1398
- }
1399
- }
1400
- for (const m of schema.mongoClusters) {
1401
- const k = `${m.source}::${m.identifier}`;
1402
- if (!seen.mongo.has(k)) {
1403
- seen.mongo.add(k);
1404
- merged.mongoClusters.push(m);
1405
- }
1406
- }
1407
- for (const q of schema.queues) {
1408
- const k = `${q.source}::${q.name}`;
1409
- if (!seen.queue.has(k)) {
1410
- seen.queue.add(k);
1411
- merged.queues.push(q);
1412
- }
1413
- }
1414
- for (const t of schema.topics) {
1415
- const k = `${t.source}::${t.name}`;
1416
- if (!seen.topic.has(k)) {
1417
- seen.topic.add(k);
1418
- merged.topics.push(t);
1419
- }
1420
- }
1421
- for (const l of schema.lambdas) {
1422
- const k = `${l.source}::${l.name}`;
1423
- if (!seen.lambda.has(k)) {
1424
- seen.lambda.add(k);
1425
- merged.lambdas.push(l);
1426
- }
1427
- }
1428
- for (const b of schema.buckets) {
1429
- const k = `${b.source}::${b.name}`;
1430
- if (!seen.bucket.has(k)) {
1431
- seen.bucket.add(k);
1432
- merged.buckets.push(b);
1433
- }
1434
- }
1435
- for (const p of schema.parameters) {
1436
- const k = `${p.source}::${p.name}`;
1437
- if (!seen.param.has(k)) {
1438
- seen.param.add(k);
1439
- merged.parameters.push(p);
1440
- }
1441
- }
1442
- for (const s of schema.secrets) {
1443
- const k = `${s.source}::${s.name}`;
1444
- if (!seen.secret.has(k)) {
1445
- seen.secret.add(k);
1446
- merged.secrets.push(s);
1447
- }
1448
- }
1449
- for (const a of schema.apiGateways) {
1450
- const k = `${a.source}::${a.name}`;
1451
- if (!seen.api.has(k)) {
1452
- seen.api.add(k);
1453
- merged.apiGateways.push(a);
1454
- }
1455
- }
1456
- }
1457
- return merged;
1458
- }
1459
- async function extractIaCSchema2(repoPath) {
1460
- const [tfSchema, cfnSchema, cdkSchema] = await Promise.all([
1461
- extractTerraformSchema(repoPath),
1462
- extractCloudFormationSchema(repoPath),
1463
- extractCDKSchema(repoPath)
1464
- ]);
1465
- const merged = mergeSchemas(tfSchema, cfnSchema, cdkSchema);
1466
- const total = merged.dynamoTables.length + merged.rdsInstances.length + merged.mongoClusters.length + merged.queues.length + merged.topics.length + merged.lambdas.length + merged.buckets.length + merged.parameters.length + merged.secrets.length + merged.apiGateways.length;
1467
- core_1.logger.info(`IaC schema total: ${total} resource(s) across TF/CFN/CDK`);
1468
- return merged;
1469
- }
1470
- }
1471
- });
1472
-
1473
- // ../adapters/aws-services/dist/index.js
1474
- var require_dist7 = __commonJS({
1475
- "../adapters/aws-services/dist/index.js"(exports2) {
1476
- "use strict";
1477
- Object.defineProperty(exports2, "__esModule", { value: true });
1478
- exports2.extractSQSMetadata = extractSQSMetadata2;
1479
- exports2.validateSQSAccess = validateSQSAccess2;
1480
- exports2.extractSNSMetadata = extractSNSMetadata2;
1481
- exports2.validateSNSAccess = validateSNSAccess2;
1482
- exports2.extractSSMMetadata = extractSSMMetadata2;
1483
- exports2.validateSSMAccess = validateSSMAccess2;
1484
- exports2.extractSecretsMetadata = extractSecretsMetadata2;
1485
- exports2.validateSecretsAccess = validateSecretsAccess2;
1486
- exports2.extractLambdaMetadata = extractLambdaMetadata2;
1487
- exports2.validateLambdaAccess = validateLambdaAccess2;
1488
- var client_sqs_1 = require("@aws-sdk/client-sqs");
1489
- var client_sns_1 = require("@aws-sdk/client-sns");
1490
- var client_ssm_1 = require("@aws-sdk/client-ssm");
1491
- var client_secrets_manager_1 = require("@aws-sdk/client-secrets-manager");
1492
- var client_lambda_1 = require("@aws-sdk/client-lambda");
1493
- var credential_providers_1 = require("@aws-sdk/credential-providers");
1494
- var core_1 = require_dist();
1495
- function clientConfig(cfg) {
1496
- const region = cfg.region ?? "us-east-1";
1497
- return cfg.profile ? { region, credentials: (0, credential_providers_1.fromIni)({ profile: cfg.profile }) } : { region };
1498
- }
1499
- async function extractSQSMetadata2(cfg = {}) {
1500
- const client = new client_sqs_1.SQSClient(clientConfig(cfg));
1501
- const queues = [];
1502
- try {
1503
- let nextToken;
1504
- const queueUrls = [];
1505
- do {
1506
- const res = await client.send(new client_sqs_1.ListQueuesCommand({ NextToken: nextToken, MaxResults: 1e3 }));
1507
- queueUrls.push(...res.QueueUrls ?? []);
1508
- nextToken = res.NextToken;
1509
- } while (nextToken);
1510
- for (const url of queueUrls) {
1511
- try {
1512
- const attrs = await client.send(new client_sqs_1.GetQueueAttributesCommand({
1513
- QueueUrl: url,
1514
- AttributeNames: [
1515
- "QueueArn",
1516
- "VisibilityTimeout",
1517
- "MessageRetentionPeriod",
1518
- "RedrivePolicy",
1519
- "KmsMasterKeyId",
1520
- "SqsManagedSseEnabled",
1521
- "ApproximateNumberOfMessages",
1522
- "ApproximateNumberOfMessagesNotVisible"
1523
- ]
1524
- }));
1525
- const a = attrs.Attributes ?? {};
1526
- const arn = a["QueueArn"] ?? "";
1527
- const name = arn.split(":").pop() ?? url.split("/").pop() ?? url;
1528
- const redrivePolicy = a["RedrivePolicy"];
1529
- const dlqArn = redrivePolicy ? JSON.parse(redrivePolicy).deadLetterTargetArn : void 0;
1530
- const encrypted = !!(a["KmsMasterKeyId"] || a["SqsManagedSseEnabled"] === "true");
1531
- const retentionSeconds = parseInt(a["MessageRetentionPeriod"] ?? "345600", 10);
1532
- queues.push({
1533
- name,
1534
- url,
1535
- arn,
1536
- hasDLQ: !!dlqArn,
1537
- dlqArn,
1538
- encrypted,
1539
- visibilityTimeoutSec: parseInt(a["VisibilityTimeout"] ?? "30", 10),
1540
- retentionDays: Math.round(retentionSeconds / 86400),
1541
- approximateMessages: parseInt(a["ApproximateNumberOfMessages"] ?? "0", 10),
1542
- approximateInflight: parseInt(a["ApproximateNumberOfMessagesNotVisible"] ?? "0", 10)
1543
- });
1544
- } catch (err) {
1545
- core_1.logger.warn(`SQS attrs failed for ${url}: ${err instanceof Error ? err.message : String(err)}`);
1546
- }
1547
- }
1548
- } catch (err) {
1549
- core_1.logger.warn(`SQS list failed: ${err instanceof Error ? err.message : String(err)}`);
1550
- }
1551
- return queues;
1552
- }
1553
- async function validateSQSAccess2(cfg = {}) {
1554
- await new client_sqs_1.SQSClient(clientConfig(cfg)).send(new client_sqs_1.ListQueuesCommand({ MaxResults: 1 }));
1555
- }
1556
- async function extractSNSMetadata2(cfg = {}) {
1557
- const client = new client_sns_1.SNSClient(clientConfig(cfg));
1558
- const topics = [];
1559
- try {
1560
- let nextToken;
1561
- const topicArns = [];
1562
- do {
1563
- const res = await client.send(new client_sns_1.ListTopicsCommand({ NextToken: nextToken }));
1564
- topicArns.push(...(res.Topics ?? []).map((t) => t.TopicArn ?? "").filter(Boolean));
1565
- nextToken = res.NextToken;
1566
- } while (nextToken);
1567
- for (const arn of topicArns) {
1568
- try {
1569
- const [attrsRes, subsRes] = await Promise.all([
1570
- client.send(new client_sns_1.GetTopicAttributesCommand({ TopicArn: arn })),
1571
- client.send(new client_sns_1.ListSubscriptionsByTopicCommand({ TopicArn: arn }))
1572
- ]);
1573
- const attrs = attrsRes.Attributes ?? {};
1574
- const subs = subsRes.Subscriptions ?? [];
1575
- topics.push({
1576
- name: arn.split(":").pop() ?? arn,
1577
- arn,
1578
- encrypted: !!attrs["KmsMasterKeyId"],
1579
- subscriptionCount: subs.length,
1580
- subscriptionProtocols: [...new Set(subs.map((s) => s.Protocol ?? "unknown"))]
1581
- });
1582
- } catch (err) {
1583
- core_1.logger.warn(`SNS attrs failed for ${arn}: ${err instanceof Error ? err.message : String(err)}`);
1584
- }
1585
- }
1586
- } catch (err) {
1587
- core_1.logger.warn(`SNS list failed: ${err instanceof Error ? err.message : String(err)}`);
1588
- }
1589
- return topics;
1590
- }
1591
- async function validateSNSAccess2(cfg = {}) {
1592
- await new client_sns_1.SNSClient(clientConfig(cfg)).send(new client_sns_1.ListTopicsCommand({}));
1593
- }
1594
- async function extractSSMMetadata2(cfg = {}) {
1595
- const client = new client_ssm_1.SSMClient(clientConfig(cfg));
1596
- const parameters = [];
1597
- try {
1598
- let nextToken;
1599
- do {
1600
- const res = await client.send(new client_ssm_1.DescribeParametersCommand({
1601
- NextToken: nextToken,
1602
- MaxResults: 50
1603
- // Only metadata — GetParameter/GetParameters would return values
1604
- }));
1605
- for (const p of res.Parameters ?? []) {
1606
- parameters.push({
1607
- name: p.Name ?? "",
1608
- type: p.Type ?? "String",
1609
- tier: p.Tier ?? "Standard",
1610
- lastModified: p.LastModifiedDate?.toISOString(),
1611
- description: p.Description,
1612
- keyId: p.KeyId
1613
- });
1614
- }
1615
- nextToken = res.NextToken;
1616
- } while (nextToken && parameters.length < 500);
1617
- } catch (err) {
1618
- core_1.logger.warn(`SSM list failed: ${err instanceof Error ? err.message : String(err)}`);
1619
- }
1620
- return parameters;
1621
- }
1622
- async function validateSSMAccess2(cfg = {}) {
1623
- await new client_ssm_1.SSMClient(clientConfig(cfg)).send(new client_ssm_1.DescribeParametersCommand({ MaxResults: 1 }));
1624
- }
1625
- async function extractSecretsMetadata2(cfg = {}) {
1626
- const client = new client_secrets_manager_1.SecretsManagerClient(clientConfig(cfg));
1627
- const secrets = [];
1628
- try {
1629
- let nextToken;
1630
- do {
1631
- const res = await client.send(new client_secrets_manager_1.ListSecretsCommand({ NextToken: nextToken, MaxResults: 100 }));
1632
- for (const s of res.SecretList ?? []) {
1633
- secrets.push({
1634
- name: s.Name ?? "",
1635
- arn: s.ARN ?? "",
1636
- rotationEnabled: s.RotationEnabled ?? false,
1637
- rotationDays: s.RotationRules?.AutomaticallyAfterDays,
1638
- lastRotated: s.LastRotatedDate?.toISOString(),
1639
- lastAccessed: s.LastAccessedDate?.toISOString(),
1640
- description: s.Description
1641
- });
1642
- }
1643
- nextToken = res.NextToken;
1644
- } while (nextToken && secrets.length < 200);
1645
- } catch (err) {
1646
- core_1.logger.warn(`Secrets Manager list failed: ${err instanceof Error ? err.message : String(err)}`);
1647
- }
1648
- return secrets;
1649
- }
1650
- async function validateSecretsAccess2(cfg = {}) {
1651
- await new client_secrets_manager_1.SecretsManagerClient(clientConfig(cfg)).send(new client_secrets_manager_1.ListSecretsCommand({ MaxResults: 1 }));
1652
- }
1653
- async function extractLambdaMetadata2(cfg = {}) {
1654
- const client = new client_lambda_1.LambdaClient(clientConfig(cfg));
1655
- const functions = [];
1656
- try {
1657
- let marker;
1658
- do {
1659
- const res = await client.send(new client_lambda_1.ListFunctionsCommand({ Marker: marker, MaxItems: 50 }));
1660
- for (const fn of res.Functions ?? []) {
1661
- functions.push({
1662
- name: fn.FunctionName ?? "",
1663
- arn: fn.FunctionArn ?? "",
1664
- runtime: fn.Runtime,
1665
- handler: fn.Handler,
1666
- memoryMB: fn.MemorySize,
1667
- timeoutSec: fn.Timeout,
1668
- lastModified: fn.LastModified,
1669
- envVarKeys: Object.keys(fn.Environment?.Variables ?? {}),
1670
- layers: (fn.Layers ?? []).map((l) => l.Arn ?? "").filter(Boolean)
1671
- });
1672
- }
1673
- marker = res.NextMarker;
1674
- } while (marker && functions.length < 200);
1675
- } catch (err) {
1676
- core_1.logger.warn(`Lambda list failed: ${err instanceof Error ? err.message : String(err)}`);
1677
- }
1678
- return functions;
1679
- }
1680
- async function validateLambdaAccess2(cfg = {}) {
1681
- await new client_lambda_1.LambdaClient(clientConfig(cfg)).send(new client_lambda_1.ListFunctionsCommand({ MaxItems: 1 }));
1682
- }
1683
- }
1684
- });
1685
-
1686
- // ../adapters/logs/dist/index.js
1687
- var require_dist8 = __commonJS({
1688
- "../adapters/logs/dist/index.js"(exports2) {
1689
- "use strict";
1690
- Object.defineProperty(exports2, "__esModule", { value: true });
1691
- exports2.extractLogsSummary = extractLogsSummary2;
1692
- exports2.validateLogsAccess = validateLogsAccess2;
1693
- var client_cloudwatch_logs_1 = require("@aws-sdk/client-cloudwatch-logs");
1694
- var credential_providers_1 = require("@aws-sdk/credential-providers");
1695
- var core_1 = require_dist();
1696
- var MAX_LOG_GROUPS = 50;
1697
- var MAX_EVENTS_PER_GROUP = 50;
1698
- function clientConfig(cfg) {
1699
- const region = cfg.region ?? "us-east-1";
1700
- return cfg.profile ? { region, credentials: (0, credential_providers_1.fromIni)({ profile: cfg.profile }) } : { region };
1701
- }
1702
- function toPattern(message) {
1703
- return message.slice(0, 200).replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<UUID>").replace(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?Z?/g, "<TIMESTAMP>").replace(/\b\d{5,}\b/g, "<NUM>").replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, "<IP>").replace(/Bearer\s+\S+/gi, "Bearer <TOKEN>").trim();
1704
- }
1705
- function topPatterns(messages, limit = 5) {
1706
- const counts = {};
1707
- for (const msg of messages) {
1708
- const p = toPattern(msg);
1709
- counts[p] = (counts[p] ?? 0) + 1;
1710
- }
1711
- return Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([pattern, count]) => ({ pattern, count }));
1712
- }
1713
- async function extractLogsSummary2(cfg = {}) {
1714
- const client = new client_cloudwatch_logs_1.CloudWatchLogsClient(clientConfig(cfg));
1715
- const windowMs = (cfg.windowHours ?? 24) * 60 * 60 * 1e3;
1716
- const startTime = Date.now() - windowMs;
1717
- const summaries = [];
1718
- const logGroups = [];
1719
- try {
1720
- const prefixes = cfg.logGroupPrefixes?.length ? cfg.logGroupPrefixes : [void 0];
1721
- for (const prefix of prefixes) {
1722
- let nextToken;
1723
- do {
1724
- const res = await client.send(new client_cloudwatch_logs_1.DescribeLogGroupsCommand({
1725
- nextToken,
1726
- limit: Math.min(50, MAX_LOG_GROUPS - logGroups.length),
1727
- ...prefix ? { logGroupNamePrefix: prefix } : {}
1728
- }));
1729
- for (const lg of res.logGroups ?? []) {
1730
- logGroups.push({ name: lg.logGroupName ?? "", retentionDays: lg.retentionInDays });
1731
- }
1732
- nextToken = res.nextToken;
1733
- if (logGroups.length >= MAX_LOG_GROUPS)
1734
- break;
1735
- } while (nextToken);
1736
- if (logGroups.length >= MAX_LOG_GROUPS)
1737
- break;
1738
- }
1739
- } catch (err) {
1740
- core_1.logger.warn(`CloudWatch Logs discovery failed: ${err instanceof Error ? err.message : String(err)}`);
1741
- return summaries;
1742
- }
1743
- for (const lg of logGroups) {
1744
- const errorMessages = [];
1745
- const warnMessages = [];
1746
- let lastErrorTime;
1747
- for (const filterPattern of ["ERROR", "Exception", "WARN"]) {
1748
- if (errorMessages.length + warnMessages.length >= MAX_EVENTS_PER_GROUP)
1749
- break;
1750
- try {
1751
- const res = await client.send(new client_cloudwatch_logs_1.FilterLogEventsCommand({
1752
- logGroupName: lg.name,
1753
- filterPattern,
1754
- startTime,
1755
- limit: 25
1756
- }));
1757
- for (const event of res.events ?? []) {
1758
- const msg = event.message ?? "";
1759
- if (filterPattern === "WARN") {
1760
- warnMessages.push(msg);
1761
- } else {
1762
- errorMessages.push(msg);
1763
- if (event.timestamp) {
1764
- const ts = new Date(event.timestamp).toISOString();
1765
- if (!lastErrorTime || ts > lastErrorTime)
1766
- lastErrorTime = ts;
1767
- }
1768
- }
1769
- }
1770
- } catch {
1771
- }
1772
- }
1773
- summaries.push({
1774
- logGroupName: lg.name,
1775
- retentionDays: lg.retentionDays,
1776
- errorCount: errorMessages.length,
1777
- warnCount: warnMessages.length,
1778
- topErrorPatterns: topPatterns(errorMessages),
1779
- lastErrorTime
1780
- });
1781
- }
1782
- core_1.logger.info(`CloudWatch Logs: sampled ${summaries.length} log group(s)`);
1783
- return summaries;
1784
- }
1785
- async function validateLogsAccess2(cfg = {}) {
1786
- await new client_cloudwatch_logs_1.CloudWatchLogsClient(clientConfig(cfg)).send(new client_cloudwatch_logs_1.DescribeLogGroupsCommand({ limit: 1 }));
1787
- }
1788
- }
1789
- });
1790
-
1791
- // ../context/dist/index.js
1792
- var require_dist9 = __commonJS({
1793
- "../context/dist/index.js"(exports2) {
1794
- "use strict";
1795
- var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
1796
- if (k2 === void 0) k2 = k;
1797
- var desc = Object.getOwnPropertyDescriptor(m, k);
1798
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1799
- desc = { enumerable: true, get: function() {
1800
- return m[k];
1801
- } };
1802
- }
1803
- Object.defineProperty(o, k2, desc);
1804
- }) : (function(o, m, k, k2) {
1805
- if (k2 === void 0) k2 = k;
1806
- o[k2] = m[k];
1807
- }));
1808
- var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
1809
- Object.defineProperty(o, "default", { enumerable: true, value: v });
1810
- }) : function(o, v) {
1811
- o["default"] = v;
1812
- });
1813
- var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
1814
- var ownKeys = function(o) {
1815
- ownKeys = Object.getOwnPropertyNames || function(o2) {
1816
- var ar = [];
1817
- for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
1818
- return ar;
1819
- };
1820
- return ownKeys(o);
1821
- };
1822
- return function(mod) {
1823
- if (mod && mod.__esModule) return mod;
1824
- var result = {};
1825
- if (mod != null) {
1826
- for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
1827
- }
1828
- __setModuleDefault(result, mod);
1829
- return result;
1830
- };
1831
- })();
1832
- Object.defineProperty(exports2, "__esModule", { value: true });
1833
- exports2.scanRepository = scanRepository2;
1834
- var path5 = __importStar(require("path"));
1835
- var fs4 = __importStar(require("fs"));
1836
- var ts_morph_1 = require("ts-morph");
1837
- var core_1 = require_dist();
1838
- var DYNAMO_OPERATIONS = /* @__PURE__ */ new Set([
1839
- "query",
1840
- "scan",
1841
- "getItem",
1842
- "putItem",
1843
- "updateItem",
1844
- "deleteItem",
1845
- "batchGetItem",
1846
- "batchWriteItem",
1847
- "transactGetItems",
1848
- "transactWriteItems",
1849
- // Also handle command class names (AWS SDK v3)
1850
- "QueryCommand",
1851
- "ScanCommand",
1852
- "GetItemCommand",
1853
- "PutItemCommand",
1854
- "UpdateItemCommand",
1855
- "DeleteItemCommand"
1856
- ]);
1857
- var DYNAMO_CLIENT_PATTERNS = ["DynamoDBClient", "DynamoDB", "dynamoDB", "dynamo", "ddb"];
1858
- var POSTGRES_QUERY_METHODS = /* @__PURE__ */ new Set(["query", "execute", "exec"]);
1859
- var KNEX_METHODS = /* @__PURE__ */ new Set(["select", "where", "join", "from", "insert", "update", "delete", "del"]);
1860
- var MYSQL_QUERY_METHODS = /* @__PURE__ */ new Set(["query", "execute", "exec"]);
1861
- var MYSQL_CLIENT_PATTERNS = ["mysql", "mysql2", "connection", "pool", "conn"];
1862
- var MONGO_READ_METHODS = /* @__PURE__ */ new Set([
1863
- "find",
1864
- "findOne",
1865
- "findById",
1866
- "insertOne",
1867
- "insertMany",
1868
- "updateOne",
1869
- "updateMany",
1870
- "deleteOne",
1871
- "deleteMany",
1872
- "aggregate",
1873
- "countDocuments",
1874
- "estimatedDocumentCount"
1875
- ]);
1876
- var MONGO_COLLECTION_METHODS = /* @__PURE__ */ new Set(["collection"]);
1877
- var SQS_COMMANDS = /* @__PURE__ */ new Set(["SendMessageCommand", "SendMessageBatchCommand", "ReceiveMessageCommand", "DeleteMessageCommand", "sendMessage", "sendMessageBatch", "receiveMessage"]);
1878
- var SNS_COMMANDS = /* @__PURE__ */ new Set(["PublishCommand", "PublishBatchCommand", "publish", "publishBatch"]);
1879
- var SSM_COMMANDS = /* @__PURE__ */ new Set(["GetParameterCommand", "GetParametersCommand", "GetParametersByPathCommand", "getParameter", "getParameters", "getParametersByPath"]);
1880
- var SECRETS_COMMANDS = /* @__PURE__ */ new Set(["GetSecretValueCommand", "getSecretValue"]);
1881
- var LAMBDA_COMMANDS = /* @__PURE__ */ new Set(["InvokeCommand", "InvokeAsyncCommand", "invoke", "invokeAsync"]);
1882
- var SQS_ARG_KEYS = ["QueueUrl", "QueueName"];
1883
- var SNS_ARG_KEYS = ["TopicArn", "TargetArn"];
1884
- var SSM_ARG_KEYS = ["Name", "Path"];
1885
- var SECRETS_ARG_KEYS = ["SecretId", "SecretName"];
1886
- var LAMBDA_ARG_KEYS = ["FunctionName"];
1887
- var PRISMA_METHODS = /* @__PURE__ */ new Set([
1888
- "findMany",
1889
- "findFirst",
1890
- "findUnique",
1891
- "create",
1892
- "update",
1893
- "upsert",
1894
- "delete",
1895
- "deleteMany",
1896
- "updateMany"
1897
- ]);
1898
- function getEnclosingFunctionName(node) {
1899
- let current = node.getParent();
1900
- while (current) {
1901
- if (ts_morph_1.Node.isFunctionDeclaration(current) || ts_morph_1.Node.isFunctionExpression(current) || ts_morph_1.Node.isArrowFunction(current) || ts_morph_1.Node.isMethodDeclaration(current)) {
1902
- if (ts_morph_1.Node.isFunctionDeclaration(current) || ts_morph_1.Node.isMethodDeclaration(current)) {
1903
- return current.getName() ?? "<anonymous>";
1904
- }
1905
- const parent = current.getParent();
1906
- if (parent && ts_morph_1.Node.isVariableDeclaration(parent)) {
1907
- return parent.getName();
1908
- }
1909
- if (parent && ts_morph_1.Node.isPropertyAssignment(parent)) {
1910
- return parent.getName();
1911
- }
1912
- return "<anonymous>";
1913
- }
1914
- current = current.getParent();
1915
- }
1916
- return "<module>";
1917
- }
1918
- function extractTableNameFromArg(arg) {
1919
- if (ts_morph_1.Node.isStringLiteral(arg)) {
1920
- return arg.getLiteralValue();
1921
- }
1922
- if (ts_morph_1.Node.isObjectLiteralExpression(arg)) {
1923
- for (const prop of arg.getProperties()) {
1924
- if (ts_morph_1.Node.isPropertyAssignment(prop) && prop.getName() === "TableName") {
1925
- const init = prop.getInitializer();
1926
- if (init && ts_morph_1.Node.isStringLiteral(init)) {
1927
- return init.getLiteralValue();
1928
- }
1929
- }
1930
- }
1931
- }
1932
- return "unknown";
1933
- }
1934
- function detectDynamoOperations(callExpr, filePath) {
1935
- const expr = callExpr.getExpression();
1936
- const args = callExpr.getArguments();
1937
- if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
1938
- const methodName = expr.getName();
1939
- if (methodName === "send" && args.length > 0) {
1940
- const firstArg = args[0];
1941
- if (ts_morph_1.Node.isNewExpression(firstArg)) {
1942
- const className = firstArg.getExpression().getText();
1943
- if (DYNAMO_OPERATIONS.has(className)) {
1944
- const cmdArgs = firstArg.getArguments();
1945
- const tableName = cmdArgs.length > 0 ? extractTableNameFromArg(cmdArgs[0]) : "unknown";
1946
- return {
1947
- functionName: getEnclosingFunctionName(callExpr),
1948
- operationType: className,
1949
- databaseType: "dynamodb",
1950
- target: tableName,
1951
- filePath
1952
- };
1953
- }
1954
- }
1955
- }
1956
- if (DYNAMO_OPERATIONS.has(methodName)) {
1957
- const objText = expr.getExpression().getText().toLowerCase();
1958
- if (DYNAMO_CLIENT_PATTERNS.some((p) => objText.includes(p.toLowerCase()))) {
1959
- const tableName = args.length > 0 ? extractTableNameFromArg(args[0]) : "unknown";
1960
- return {
1961
- functionName: getEnclosingFunctionName(callExpr),
1962
- operationType: methodName,
1963
- databaseType: "dynamodb",
1964
- target: tableName,
1965
- filePath
1966
- };
1967
- }
1968
- }
1969
- }
1970
- return null;
1971
- }
1972
- function extractSqlTableName(sql) {
1973
- const patterns = [
1974
- /FROM\s+["']?(\w+)["']?/i,
1975
- /INTO\s+["']?(\w+)["']?/i,
1976
- /UPDATE\s+["']?(\w+)["']?/i,
1977
- /JOIN\s+["']?(\w+)["']?/i
1978
- ];
1979
- for (const pattern of patterns) {
1980
- const match = sql.match(pattern);
1981
- if (match?.[1])
1982
- return match[1];
1983
- }
1984
- return "unknown";
1985
- }
1986
- function detectPostgresOperations(callExpr, filePath) {
1987
- const expr = callExpr.getExpression();
1988
- const args = callExpr.getArguments();
1989
- if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
1990
- return null;
1991
- const methodName = expr.getName();
1992
- const objExpr = expr.getExpression();
1993
- if (POSTGRES_QUERY_METHODS.has(methodName)) {
1994
- const objText = objExpr.getText().toLowerCase();
1995
- if (objText.includes("pool") || objText.includes("client") || objText.includes("db") || objText.includes("pg") || objText.includes("conn")) {
1996
- let tableName = "unknown";
1997
- if (args.length > 0) {
1998
- const firstArg = args[0];
1999
- if (ts_morph_1.Node.isStringLiteral(firstArg)) {
2000
- tableName = extractSqlTableName(firstArg.getLiteralValue());
2001
- } else if (ts_morph_1.Node.isNoSubstitutionTemplateLiteral(firstArg)) {
2002
- tableName = extractSqlTableName(firstArg.getLiteralText());
2003
- } else if (ts_morph_1.Node.isTemplateExpression(firstArg)) {
2004
- tableName = extractSqlTableName(firstArg.getHead().getLiteralText());
2005
- }
2006
- }
2007
- return {
2008
- functionName: getEnclosingFunctionName(callExpr),
2009
- operationType: "query",
2010
- databaseType: "postgres",
2011
- target: tableName,
2012
- filePath
2013
- };
2014
- }
2015
- }
2016
- if (PRISMA_METHODS.has(methodName)) {
2017
- const accessChain = objExpr;
2018
- if (ts_morph_1.Node.isPropertyAccessExpression(accessChain)) {
2019
- const modelName = accessChain.getName();
2020
- const rootObj = accessChain.getExpression().getText().toLowerCase();
2021
- if (rootObj.includes("prisma")) {
2022
- return {
2023
- functionName: getEnclosingFunctionName(callExpr),
2024
- operationType: methodName,
2025
- databaseType: "postgres",
2026
- target: modelName,
2027
- filePath
2028
- };
2029
- }
2030
- }
2031
- }
2032
- if (KNEX_METHODS.has(methodName)) {
2033
- const calleeText = objExpr.getText();
2034
- if (calleeText.includes("knex") || calleeText.includes("db(") || calleeText.includes("trx(")) {
2035
- return {
2036
- functionName: getEnclosingFunctionName(callExpr),
2037
- operationType: methodName,
2038
- databaseType: "postgres",
2039
- target: "unknown",
2040
- filePath
2041
- };
2042
- }
2043
- if (ts_morph_1.Node.isCallExpression(objExpr)) {
2044
- const innerExpr = objExpr.getExpression();
2045
- if (innerExpr.getText().toLowerCase().match(/knex|db|trx/)) {
2046
- const innerArgs = objExpr.getArguments();
2047
- const tableName = innerArgs.length > 0 && ts_morph_1.Node.isStringLiteral(innerArgs[0]) ? innerArgs[0].getLiteralValue() : "unknown";
2048
- return {
2049
- functionName: getEnclosingFunctionName(callExpr),
2050
- operationType: methodName,
2051
- databaseType: "postgres",
2052
- target: tableName,
2053
- filePath
2054
- };
2055
- }
2056
- }
2057
- }
2058
- return null;
2059
- }
2060
- function detectMySQLOperations(callExpr, filePath) {
2061
- const expr = callExpr.getExpression();
2062
- const args = callExpr.getArguments();
2063
- if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
2064
- return null;
2065
- const methodName = expr.getName();
2066
- const objExpr = expr.getExpression();
2067
- const objText = objExpr.getText().toLowerCase();
2068
- if (MYSQL_QUERY_METHODS.has(methodName)) {
2069
- const isMysqlClient = MYSQL_CLIENT_PATTERNS.some((p) => objText.includes(p.toLowerCase()));
2070
- if (isMysqlClient) {
2071
- let tableName = "unknown";
2072
- if (args.length > 0) {
2073
- const firstArg = args[0];
2074
- if (ts_morph_1.Node.isStringLiteral(firstArg)) {
2075
- tableName = extractSqlTableName(firstArg.getLiteralValue());
2076
- } else if (ts_morph_1.Node.isNoSubstitutionTemplateLiteral(firstArg)) {
2077
- tableName = extractSqlTableName(firstArg.getLiteralText());
2078
- } else if (ts_morph_1.Node.isTemplateExpression(firstArg)) {
2079
- tableName = extractSqlTableName(firstArg.getHead().getLiteralText());
2080
- }
2081
- }
2082
- return {
2083
- functionName: getEnclosingFunctionName(callExpr),
2084
- operationType: "query",
2085
- databaseType: "mysql",
2086
- target: tableName,
2087
- filePath
2088
- };
2089
- }
2090
- }
2091
- if (KNEX_METHODS.has(methodName)) {
2092
- if (objText.includes("mysql") || objText.includes("knex")) {
2093
- let tableName = "unknown";
2094
- if (ts_morph_1.Node.isCallExpression(objExpr)) {
2095
- const innerArgs = objExpr.getArguments();
2096
- if (innerArgs.length > 0 && ts_morph_1.Node.isStringLiteral(innerArgs[0])) {
2097
- tableName = innerArgs[0].getLiteralValue();
2098
- }
2099
- }
2100
- return {
2101
- functionName: getEnclosingFunctionName(callExpr),
2102
- operationType: methodName,
2103
- databaseType: "mysql",
2104
- target: tableName,
2105
- filePath
2106
- };
2107
- }
2108
- }
2109
- return null;
2110
- }
2111
- function detectMongoOperations(callExpr, filePath) {
2112
- const expr = callExpr.getExpression();
2113
- if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
2114
- return null;
2115
- const methodName = expr.getName();
2116
- const objExpr = expr.getExpression();
2117
- if (MONGO_READ_METHODS.has(methodName)) {
2118
- const objText = objExpr.getText().toLowerCase();
2119
- if (objText.includes("collection") || objText.includes("col") || objText.includes("db.") || objText.includes("model") || // Mongoose: User.find(), Order.findOne()
2120
- /^[A-Z][a-zA-Z]+$/.test(objExpr.getText())) {
2121
- let collectionName = "unknown";
2122
- if (ts_morph_1.Node.isCallExpression(objExpr)) {
2123
- const innerExpr = objExpr.getExpression();
2124
- if (ts_morph_1.Node.isPropertyAccessExpression(innerExpr) && MONGO_COLLECTION_METHODS.has(innerExpr.getName())) {
2125
- const innerArgs = objExpr.getArguments();
2126
- if (innerArgs.length > 0 && ts_morph_1.Node.isStringLiteral(innerArgs[0])) {
2127
- collectionName = innerArgs[0].getLiteralValue();
2128
- }
2129
- }
2130
- } else if (ts_morph_1.Node.isPropertyAccessExpression(objExpr)) {
2131
- collectionName = objExpr.getName();
2132
- } else {
2133
- collectionName = objExpr.getText();
2134
- }
2135
- const opType = methodName === "find" || methodName === "aggregate" ? "scan" : "query";
2136
- return {
2137
- functionName: getEnclosingFunctionName(callExpr),
2138
- operationType: opType,
2139
- databaseType: "mongodb",
2140
- target: collectionName,
2141
- filePath
2142
- };
2143
- }
2144
- }
2145
- if (MONGO_COLLECTION_METHODS.has(methodName)) {
2146
- const args = callExpr.getArguments();
2147
- const objText = objExpr.getText().toLowerCase();
2148
- if (objText.includes("db") || objText.includes("mongo")) {
2149
- const collectionName = args.length > 0 && ts_morph_1.Node.isStringLiteral(args[0]) ? args[0].getLiteralValue() : "unknown";
2150
- return {
2151
- functionName: getEnclosingFunctionName(callExpr),
2152
- operationType: "query",
2153
- databaseType: "mongodb",
2154
- target: collectionName,
2155
- filePath
2156
- };
2157
- }
2158
- }
2159
- return null;
2160
- }
2161
- function extractArgValue(arg, ...keys) {
2162
- if (ts_morph_1.Node.isObjectLiteralExpression(arg)) {
2163
- for (const prop of arg.getProperties()) {
2164
- if (ts_morph_1.Node.isPropertyAssignment(prop) && keys.includes(prop.getName())) {
2165
- const init = prop.getInitializer();
2166
- if (init && ts_morph_1.Node.isStringLiteral(init)) {
2167
- const val = init.getLiteralValue();
2168
- return val.includes(":") ? val.split(":").pop() ?? val : val;
2169
- }
2170
- }
2171
- }
2172
- }
2173
- return "unknown";
2174
- }
2175
- function detectAWSServiceOperations(callExpr, filePath) {
2176
- const expr = callExpr.getExpression();
2177
- const args = callExpr.getArguments();
2178
- if (ts_morph_1.Node.isPropertyAccessExpression(expr) && expr.getName() === "send" && args.length > 0) {
2179
- const firstArg = args[0];
2180
- if (ts_morph_1.Node.isNewExpression(firstArg)) {
2181
- const className = firstArg.getExpression().getText();
2182
- const cmdArgs = firstArg.getArguments();
2183
- if (SQS_COMMANDS.has(className)) {
2184
- return {
2185
- functionName: getEnclosingFunctionName(callExpr),
2186
- operationType: className,
2187
- databaseType: "sqs",
2188
- target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...SQS_ARG_KEYS) : "unknown",
2189
- filePath
2190
- };
2191
- }
2192
- if (SNS_COMMANDS.has(className)) {
2193
- return {
2194
- functionName: getEnclosingFunctionName(callExpr),
2195
- operationType: className,
2196
- databaseType: "sns",
2197
- target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...SNS_ARG_KEYS) : "unknown",
2198
- filePath
2199
- };
2200
- }
2201
- if (SSM_COMMANDS.has(className)) {
2202
- return {
2203
- functionName: getEnclosingFunctionName(callExpr),
2204
- operationType: className,
2205
- databaseType: "ssm",
2206
- target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...SSM_ARG_KEYS) : "unknown",
2207
- filePath
2208
- };
2209
- }
2210
- if (SECRETS_COMMANDS.has(className)) {
2211
- return {
2212
- functionName: getEnclosingFunctionName(callExpr),
2213
- operationType: className,
2214
- databaseType: "secretsmanager",
2215
- target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...SECRETS_ARG_KEYS) : "unknown",
2216
- filePath
2217
- };
2218
- }
2219
- if (LAMBDA_COMMANDS.has(className)) {
2220
- return {
2221
- functionName: getEnclosingFunctionName(callExpr),
2222
- operationType: className,
2223
- databaseType: "lambda",
2224
- target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...LAMBDA_ARG_KEYS) : "unknown",
2225
- filePath
2226
- };
2227
- }
2228
- }
2229
- }
2230
- if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
2231
- const methodName = expr.getName();
2232
- const objText = expr.getExpression().getText().toLowerCase();
2233
- if (SQS_COMMANDS.has(methodName) && (objText.includes("sqs") || objText.includes("queue"))) {
2234
- return {
2235
- functionName: getEnclosingFunctionName(callExpr),
2236
- operationType: methodName,
2237
- databaseType: "sqs",
2238
- target: args.length > 0 ? extractArgValue(args[0], ...SQS_ARG_KEYS) : "unknown",
2239
- filePath
2240
- };
2241
- }
2242
- if (SNS_COMMANDS.has(methodName) && (objText.includes("sns") || objText.includes("topic"))) {
2243
- return {
2244
- functionName: getEnclosingFunctionName(callExpr),
2245
- operationType: methodName,
2246
- databaseType: "sns",
2247
- target: args.length > 0 ? extractArgValue(args[0], ...SNS_ARG_KEYS) : "unknown",
2248
- filePath
2249
- };
2250
- }
2251
- if (SSM_COMMANDS.has(methodName) && (objText.includes("ssm") || objText.includes("parameter"))) {
2252
- return {
2253
- functionName: getEnclosingFunctionName(callExpr),
2254
- operationType: methodName,
2255
- databaseType: "ssm",
2256
- target: args.length > 0 ? extractArgValue(args[0], ...SSM_ARG_KEYS) : "unknown",
2257
- filePath
2258
- };
2259
- }
2260
- if (SECRETS_COMMANDS.has(methodName) && (objText.includes("secret") || objText.includes("secrets"))) {
2261
- return {
2262
- functionName: getEnclosingFunctionName(callExpr),
2263
- operationType: methodName,
2264
- databaseType: "secretsmanager",
2265
- target: args.length > 0 ? extractArgValue(args[0], ...SECRETS_ARG_KEYS) : "unknown",
2266
- filePath
2267
- };
2268
- }
2269
- if (LAMBDA_COMMANDS.has(methodName) && (objText.includes("lambda") || objText.includes("fn"))) {
2270
- return {
2271
- functionName: getEnclosingFunctionName(callExpr),
2272
- operationType: methodName,
2273
- databaseType: "lambda",
2274
- target: args.length > 0 ? extractArgValue(args[0], ...LAMBDA_ARG_KEYS) : "unknown",
2275
- filePath
2276
- };
2277
- }
2278
- }
2279
- return null;
2280
- }
2281
- async function scanRepository2(repoPath) {
2282
- const resolvedPath = path5.resolve(repoPath);
2283
- if (!fs4.existsSync(resolvedPath)) {
2284
- throw new core_1.RepositoryScanError(`Path does not exist: ${resolvedPath}`);
2285
- }
2286
- const tsconfigPath = path5.join(resolvedPath, "tsconfig.json");
2287
- const hasTsConfig = fs4.existsSync(tsconfigPath);
2288
- const project = new ts_morph_1.Project({
2289
- tsConfigFilePath: hasTsConfig ? tsconfigPath : void 0,
2290
- compilerOptions: hasTsConfig ? void 0 : {
2291
- target: 99,
2292
- allowJs: true
2293
- },
2294
- skipAddingFilesFromTsConfig: !hasTsConfig
2295
- });
2296
- if (!hasTsConfig) {
2297
- project.addSourceFilesAtPaths([
2298
- path5.join(resolvedPath, "**/*.ts"),
2299
- path5.join(resolvedPath, "**/*.tsx"),
2300
- `!${path5.join(resolvedPath, "**/node_modules/**")}`,
2301
- `!${path5.join(resolvedPath, "**/dist/**")}`
2302
- ]);
2303
- }
2304
- const sourceFiles = project.getSourceFiles();
2305
- core_1.logger.info(`Scanning ${sourceFiles.length} TypeScript file(s) in ${resolvedPath}`);
2306
- const operations = [];
2307
- for (const sourceFile of sourceFiles) {
2308
- const filePath = sourceFile.getFilePath();
2309
- if (filePath.includes("node_modules") || filePath.includes("/dist/"))
2310
- continue;
2311
- const callExpressions = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression);
2312
- for (const callExpr of callExpressions) {
2313
- const dynamoOp = detectDynamoOperations(callExpr, filePath);
2314
- if (dynamoOp) {
2315
- operations.push(dynamoOp);
2316
- continue;
2317
- }
2318
- const postgresOp = detectPostgresOperations(callExpr, filePath);
2319
- if (postgresOp) {
2320
- operations.push(postgresOp);
2321
- continue;
2322
- }
2323
- const mysqlOp = detectMySQLOperations(callExpr, filePath);
2324
- if (mysqlOp) {
2325
- operations.push(mysqlOp);
2326
- continue;
2327
- }
2328
- const mongoOp = detectMongoOperations(callExpr, filePath);
2329
- if (mongoOp) {
2330
- operations.push(mongoOp);
2331
- continue;
2332
- }
2333
- const awsOp = detectAWSServiceOperations(callExpr, filePath);
2334
- if (awsOp) {
2335
- operations.push(awsOp);
2336
- }
2337
- }
2338
- }
2339
- core_1.logger.info(`Extracted ${operations.length} database operation(s)`);
2340
- return operations;
2341
- }
2342
- }
2343
- });
2344
-
2345
- // ../graph/dist/index.js
2346
- var require_dist10 = __commonJS({
2347
- "../graph/dist/index.js"(exports2) {
2348
- "use strict";
2349
- Object.defineProperty(exports2, "__esModule", { value: true });
2350
- exports2.buildGraph = buildGraph2;
2351
- exports2.getTableNodes = getTableNodes;
2352
- exports2.getFunctionNodes = getFunctionNodes;
2353
- exports2.getIndexNodes = getIndexNodes;
2354
- exports2.getQueueNodes = getQueueNodes;
2355
- exports2.getTopicNodes = getTopicNodes;
2356
- exports2.getSecretNodes = getSecretNodes;
2357
- exports2.getParameterNodes = getParameterNodes;
2358
- exports2.getLogGroupNodes = getLogGroupNodes;
2359
- exports2.getLambdaNodes = getLambdaNodes;
2360
- exports2.getEdgesForNode = getEdgesForNode;
2361
- exports2.getOutgoingEdges = getOutgoingEdges;
2362
- exports2.getIncomingEdges = getIncomingEdges;
2363
- exports2.getScanEdges = getScanEdges;
2364
- exports2.getEdgeFrequency = getEdgeFrequency;
2365
- function buildGraph2(operations, dynamoMeta, postgresMeta, mysqlMeta = [], mongoMeta = [], servicesMeta = {}) {
2366
- const nodes = [];
2367
- const edges = [];
2368
- const nodeIds = /* @__PURE__ */ new Set();
2369
- function addNode(node) {
2370
- if (!nodeIds.has(node.id)) {
2371
- nodes.push(node);
2372
- nodeIds.add(node.id);
2373
- }
2374
- }
2375
- for (const table of dynamoMeta) {
2376
- const nodeId = `table:dynamo:${table.tableName}`;
2377
- addNode({ id: nodeId, type: "table", name: table.tableName, databaseType: "dynamodb" });
2378
- for (const indexName of table.indexes) {
2379
- const indexNodeId = `index:${table.tableName}:${indexName}`;
2380
- addNode({ id: indexNodeId, type: "index", name: indexName });
2381
- edges.push({ from: nodeId, to: indexNodeId, type: "uses_index" });
2382
- }
2383
- }
2384
- for (const table of postgresMeta) {
2385
- const nodeId = `table:postgres:${table.schema}.${table.table}`;
2386
- addNode({ id: nodeId, type: "table", name: `${table.schema}.${table.table}`, databaseType: "postgres" });
2387
- for (const indexName of table.indexes) {
2388
- const indexNodeId = `index:${table.schema}.${table.table}:${indexName}`;
2389
- addNode({ id: indexNodeId, type: "index", name: indexName });
2390
- edges.push({ from: nodeId, to: indexNodeId, type: "uses_index" });
2391
- }
2392
- }
2393
- for (const table of mysqlMeta) {
2394
- const nodeId = `table:mysql:${table.schema}.${table.table}`;
2395
- addNode({ id: nodeId, type: "table", name: `${table.schema}.${table.table}`, databaseType: "mysql" });
2396
- for (const indexName of table.indexes) {
2397
- const indexNodeId = `index:${table.schema}.${table.table}:${indexName}`;
2398
- addNode({ id: indexNodeId, type: "index", name: indexName });
2399
- edges.push({ from: nodeId, to: indexNodeId, type: "uses_index" });
2400
- }
2401
- }
2402
- for (const coll of mongoMeta) {
2403
- const nodeId = `table:mongodb:${coll.database}.${coll.collection}`;
2404
- addNode({ id: nodeId, type: "table", name: `${coll.database}.${coll.collection}`, databaseType: "mongodb" });
2405
- for (const idx of coll.indexes) {
2406
- if (idx.name === "_id_")
2407
- continue;
2408
- const indexNodeId = `index:${coll.database}.${coll.collection}:${idx.name}`;
2409
- addNode({ id: indexNodeId, type: "index", name: idx.name });
2410
- edges.push({ from: nodeId, to: indexNodeId, type: "uses_index" });
2411
- }
2412
- }
2413
- for (const q of servicesMeta.sqs ?? []) {
2414
- addNode({
2415
- id: `queue:aws:${q.name}`,
2416
- type: "queue",
2417
- name: q.name,
2418
- provider: "aws",
2419
- hasDLQ: q.hasDLQ,
2420
- encrypted: q.encrypted,
2421
- approximateMessages: q.approximateMessages,
2422
- retentionDays: q.retentionDays
2423
- });
2424
- }
2425
- for (const t of servicesMeta.sns ?? []) {
2426
- addNode({
2427
- id: `topic:aws:${t.name}`,
2428
- type: "topic",
2429
- name: t.name,
2430
- provider: "aws",
2431
- subscriptionCount: t.subscriptionCount,
2432
- encrypted: t.encrypted
2433
- });
2434
- }
2435
- for (const s of servicesMeta.secrets ?? []) {
2436
- addNode({
2437
- id: `secret:aws:${s.name}`,
2438
- type: "secret",
2439
- name: s.name,
2440
- provider: "aws",
2441
- rotationEnabled: s.rotationEnabled,
2442
- rotationDays: s.rotationDays
2443
- });
2444
- }
2445
- for (const p of servicesMeta.ssm ?? []) {
2446
- addNode({
2447
- id: `parameter:aws:${p.name}`,
2448
- type: "parameter",
2449
- name: p.name,
2450
- provider: "aws",
2451
- paramType: p.type,
2452
- tier: p.tier
2453
- });
2454
- }
2455
- for (const lg of servicesMeta.logs ?? []) {
2456
- addNode({
2457
- id: `log_group:aws:${lg.logGroupName}`,
2458
- type: "log_group",
2459
- name: lg.logGroupName,
2460
- provider: "aws",
2461
- retentionDays: lg.retentionDays,
2462
- errorCount: lg.errorCount,
2463
- topErrorPatterns: lg.topErrorPatterns
2464
- });
2465
- }
2466
- for (const fn of servicesMeta.lambda ?? []) {
2467
- addNode({
2468
- id: `lambda:aws:${fn.name}`,
2469
- type: "lambda",
2470
- name: fn.name,
2471
- runtime: fn.runtime,
2472
- memoryMB: fn.memoryMB,
2473
- timeoutSec: fn.timeoutSec,
2474
- envVarKeys: fn.envVarKeys
2475
- });
2476
- }
2477
- for (const op of operations) {
2478
- const funcNodeId = `function:${op.filePath}:${op.functionName}`;
2479
- if (!nodeIds.has(funcNodeId)) {
2480
- nodes.push({ id: funcNodeId, type: "function", name: op.functionName, file: op.filePath });
2481
- nodeIds.add(funcNodeId);
2482
- }
2483
- if (op.databaseType === "sqs") {
2484
- const queueId = `queue:aws:${op.target}`;
2485
- if (!nodeIds.has(queueId)) {
2486
- addNode({ id: queueId, type: "queue", name: op.target, provider: "aws", hasDLQ: false, encrypted: false });
2487
- }
2488
- edges.push({ from: funcNodeId, to: queueId, type: "publishes_to" });
2489
- continue;
2490
- }
2491
- if (op.databaseType === "sns") {
2492
- const topicId = `topic:aws:${op.target}`;
2493
- if (!nodeIds.has(topicId)) {
2494
- addNode({ id: topicId, type: "topic", name: op.target, provider: "aws", encrypted: false });
2495
- }
2496
- edges.push({ from: funcNodeId, to: topicId, type: "publishes_to" });
2497
- continue;
2498
- }
2499
- if (op.databaseType === "ssm") {
2500
- const paramId = `parameter:aws:${op.target}`;
2501
- if (!nodeIds.has(paramId)) {
2502
- addNode({ id: paramId, type: "parameter", name: op.target, provider: "aws", paramType: "String", tier: "Standard" });
2503
- }
2504
- edges.push({ from: funcNodeId, to: paramId, type: "reads_parameter" });
2505
- continue;
2506
- }
2507
- if (op.databaseType === "secretsmanager") {
2508
- const secretId = `secret:aws:${op.target}`;
2509
- if (!nodeIds.has(secretId)) {
2510
- addNode({ id: secretId, type: "secret", name: op.target, provider: "aws", rotationEnabled: false });
2511
- }
2512
- edges.push({ from: funcNodeId, to: secretId, type: "reads_secret" });
2513
- continue;
2514
- }
2515
- if (op.databaseType === "lambda") {
2516
- const lambdaId = `lambda:aws:${op.target}`;
2517
- if (!nodeIds.has(lambdaId)) {
2518
- addNode({ id: lambdaId, type: "lambda", name: op.target });
2519
- }
2520
- edges.push({ from: funcNodeId, to: lambdaId, type: "triggers" });
2521
- continue;
2522
- }
2523
- let tableNodeId;
2524
- if (op.databaseType === "dynamodb") {
2525
- tableNodeId = `table:dynamo:${op.target}`;
2526
- if (!nodeIds.has(tableNodeId)) {
2527
- addNode({ id: tableNodeId, type: "table", name: op.target, databaseType: "dynamodb" });
2528
- }
2529
- } else if (op.databaseType === "mysql") {
2530
- const q = op.target.includes(".") ? op.target : `default.${op.target}`;
2531
- tableNodeId = `table:mysql:${q}`;
2532
- if (!nodeIds.has(tableNodeId)) {
2533
- addNode({ id: tableNodeId, type: "table", name: q, databaseType: "mysql" });
2534
- }
2535
- } else if (op.databaseType === "mongodb") {
2536
- const q = op.target.includes(".") ? op.target : `default.${op.target}`;
2537
- tableNodeId = `table:mongodb:${q}`;
2538
- if (!nodeIds.has(tableNodeId)) {
2539
- addNode({ id: tableNodeId, type: "table", name: q, databaseType: "mongodb" });
2540
- }
2541
- } else {
2542
- const q = op.target.includes(".") ? op.target : `public.${op.target}`;
2543
- tableNodeId = `table:postgres:${q}`;
2544
- if (!nodeIds.has(tableNodeId)) {
2545
- const parts = q.split(".");
2546
- addNode({ id: tableNodeId, type: "table", name: q, databaseType: "postgres" });
2547
- if (!postgresMeta.find((t) => `${t.schema}.${t.table}` === q)) {
2548
- postgresMeta.push({
2549
- schema: parts[0] ?? "public",
2550
- table: parts[1] ?? op.target,
2551
- columns: [],
2552
- indexes: [],
2553
- primaryKeys: []
2554
- });
2555
- }
2556
- }
2557
- }
2558
- const edgeType = resolveEdgeType(op.operationType);
2559
- edges.push({ from: funcNodeId, to: tableNodeId, type: edgeType });
2560
- }
2561
- return { nodes, edges };
2562
- }
2563
- function resolveEdgeType(operationType) {
2564
- const op = operationType.toLowerCase();
2565
- if (op === "scan" || op === "scancommand")
2566
- return "scan";
2567
- if (op === "join" || op === "joins")
2568
- return "joins";
2569
- return "query";
2570
- }
2571
- function getTableNodes(graph) {
2572
- return graph.nodes.filter((n) => n.type === "table");
2573
- }
2574
- function getFunctionNodes(graph) {
2575
- return graph.nodes.filter((n) => n.type === "function");
2576
- }
2577
- function getIndexNodes(graph) {
2578
- return graph.nodes.filter((n) => n.type === "index");
2579
- }
2580
- function getQueueNodes(graph) {
2581
- return graph.nodes.filter((n) => n.type === "queue");
2582
- }
2583
- function getTopicNodes(graph) {
2584
- return graph.nodes.filter((n) => n.type === "topic");
2585
- }
2586
- function getSecretNodes(graph) {
2587
- return graph.nodes.filter((n) => n.type === "secret");
2588
- }
2589
- function getParameterNodes(graph) {
2590
- return graph.nodes.filter((n) => n.type === "parameter");
2591
- }
2592
- function getLogGroupNodes(graph) {
2593
- return graph.nodes.filter((n) => n.type === "log_group");
2594
- }
2595
- function getLambdaNodes(graph) {
2596
- return graph.nodes.filter((n) => n.type === "lambda");
2597
- }
2598
- function getEdgesForNode(graph, nodeId) {
2599
- return graph.edges.filter((e) => e.from === nodeId || e.to === nodeId);
2600
- }
2601
- function getOutgoingEdges(graph, nodeId) {
2602
- return graph.edges.filter((e) => e.from === nodeId);
2603
- }
2604
- function getIncomingEdges(graph, nodeId) {
2605
- return graph.edges.filter((e) => e.to === nodeId);
2606
- }
2607
- function getScanEdges(graph) {
2608
- return graph.edges.filter((e) => e.type === "scan");
2609
- }
2610
- function getEdgeFrequency(graph) {
2611
- const freq = /* @__PURE__ */ new Map();
2612
- for (const edge of graph.edges) {
2613
- const key = `${edge.from}->${edge.to}`;
2614
- freq.set(key, (freq.get(key) ?? 0) + 1);
2615
- }
2616
- return freq;
2617
- }
2618
- }
2619
- });
2620
-
2621
- // ../analyzers/dist/dynamodb.js
2622
- var require_dynamodb = __commonJS({
2623
- "../analyzers/dist/dynamodb.js"(exports2) {
2624
- "use strict";
2625
- Object.defineProperty(exports2, "__esModule", { value: true });
2626
- exports2.HotPartitionAnalyzer = exports2.MissingGSIAnalyzer = exports2.FullTableScanAnalyzer = void 0;
2627
- var graph_1 = require_dist10();
2628
- var FullTableScanAnalyzer = class {
2629
- name = "FullTableScanAnalyzer";
2630
- async analyze(graph) {
2631
- const findings = [];
2632
- const scanEdges = (0, graph_1.getScanEdges)(graph);
2633
- const scannedTableIds = /* @__PURE__ */ new Set();
2634
- for (const edge of scanEdges) {
2635
- const targetNode = graph.nodes.find((n) => n.id === edge.to);
2636
- if (targetNode?.type === "table" && targetNode.databaseType === "dynamodb") {
2637
- scannedTableIds.add(targetNode.id);
2638
- }
2639
- }
2640
- for (const tableId of scannedTableIds) {
2641
- const tableNode = graph.nodes.find((n) => n.id === tableId);
2642
- if (!tableNode)
2643
- continue;
2644
- const tableScanEdges = scanEdges.filter((e) => e.to === tableId);
2645
- const callerFunctions = tableScanEdges.map((e) => {
2646
- const node = graph.nodes.find((n) => n.id === e.from);
2647
- return node?.type === "function" ? node.name : e.from;
2648
- }).join(", ");
2649
- findings.push({
2650
- severity: "high",
2651
- issue: `Full table scan detected on DynamoDB table "${tableNode.name}"`,
2652
- description: `The table "${tableNode.name}" is being scanned without any filter, which reads every item. This is expensive and slow for large tables. Called from: ${callerFunctions || "unknown"}`,
2653
- recommendation: "Replace Scan with a Query operation using a partition key or GSI. If filtering is required on non-key attributes, add a Global Secondary Index (GSI).",
2654
- metadata: {
2655
- tableName: tableNode.name,
2656
- scanCount: tableScanEdges.length,
2657
- callerFunctions
2658
- }
2659
- });
2660
- }
2661
- return findings;
2662
- }
2663
- };
2664
- exports2.FullTableScanAnalyzer = FullTableScanAnalyzer;
2665
- var MissingGSIAnalyzer = class {
2666
- name = "MissingGSIAnalyzer";
2667
- async analyze(graph) {
2668
- const findings = [];
2669
- const dynamoTables = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "dynamodb");
2670
- for (const table of dynamoTables) {
2671
- const incomingEdges = graph.edges.filter((e) => e.to === table.id);
2672
- const queryEdges = incomingEdges.filter((e) => e.type === "query");
2673
- const hasGSI = graph.edges.some((e) => e.from === table.id && e.type === "uses_index");
2674
- if (queryEdges.length > 0 && !hasGSI) {
2675
- const callerFunctions = queryEdges.map((e) => {
2676
- const node = graph.nodes.find((n) => n.id === e.from);
2677
- return node?.type === "function" ? node.name : e.from;
2678
- }).join(", ");
2679
- findings.push({
2680
- severity: "medium",
2681
- issue: `DynamoDB table "${table.name}" has no GSIs but is queried by multiple functions`,
2682
- description: `Table "${table.name}" is accessed by ${queryEdges.length} function(s) (${callerFunctions}) but has no Global Secondary Indexes defined. If queries filter on non-partition-key attributes, this will degrade to full scans.`,
2683
- recommendation: "Analyze query access patterns and add GSIs for frequently filtered attributes. Consider using single-table design patterns with composite sort keys.",
2684
- metadata: {
2685
- tableName: table.name,
2686
- queryCount: queryEdges.length,
2687
- callerFunctions
2688
- }
2689
- });
2690
- }
2691
- }
2692
- return findings;
2693
- }
2694
- };
2695
- exports2.MissingGSIAnalyzer = MissingGSIAnalyzer;
2696
- var HotPartitionAnalyzer = class {
2697
- name = "HotPartitionAnalyzer";
2698
- hotThreshold;
2699
- constructor(hotThreshold = 5) {
2700
- this.hotThreshold = hotThreshold;
2701
- }
2702
- async analyze(graph) {
2703
- const findings = [];
2704
- const edgeFrequency = (0, graph_1.getEdgeFrequency)(graph);
2705
- const tableAccessCount = /* @__PURE__ */ new Map();
2706
- for (const edge of graph.edges) {
2707
- const targetNode = graph.nodes.find((n) => n.id === edge.to);
2708
- if (targetNode?.type !== "table" || targetNode.databaseType !== "dynamodb")
2709
- continue;
2710
- if (!tableAccessCount.has(edge.to)) {
2711
- tableAccessCount.set(edge.to, /* @__PURE__ */ new Set());
2712
- }
2713
- tableAccessCount.get(edge.to).add(edge.from);
2714
- }
2715
- for (const [tableId, accessors] of tableAccessCount) {
2716
- if (accessors.size >= this.hotThreshold) {
2717
- const tableNode = graph.nodes.find((n) => n.id === tableId);
2718
- if (!tableNode)
2719
- continue;
2720
- let maxFreq = 0;
2721
- for (const [key, freq] of edgeFrequency) {
2722
- if (key.includes(tableId))
2723
- maxFreq = Math.max(maxFreq, freq);
2724
- }
2725
- findings.push({
2726
- severity: "medium",
2727
- issue: `Potential hot partition detected on DynamoDB table "${tableNode.name}"`,
2728
- description: `Table "${tableNode.name}" is accessed by ${accessors.size} distinct code paths, which may create hot partition issues at scale. High access concentration on the same partition key can throttle requests.`,
2729
- recommendation: "Consider adding a random suffix or timestamp to partition keys (write sharding). Use DynamoDB DAX for read-heavy workloads. Distribute access patterns across multiple partition key values.",
2730
- metadata: {
2731
- tableName: tableNode.name,
2732
- accessorCount: accessors.size,
2733
- maxEdgeFrequency: maxFreq
2734
- }
2735
- });
2736
- }
2737
- }
2738
- return findings;
2739
- }
2740
- };
2741
- exports2.HotPartitionAnalyzer = HotPartitionAnalyzer;
2742
- }
2743
- });
2744
-
2745
- // ../analyzers/dist/postgres.js
2746
- var require_postgres = __commonJS({
2747
- "../analyzers/dist/postgres.js"(exports2) {
2748
- "use strict";
2749
- Object.defineProperty(exports2, "__esModule", { value: true });
2750
- exports2.LargeSelectAnalyzer = exports2.NplusOneAnalyzer = exports2.MissingIndexAnalyzer = void 0;
2751
- var MissingIndexAnalyzer = class {
2752
- name = "MissingIndexAnalyzer";
2753
- async analyze(graph) {
2754
- const findings = [];
2755
- const postgresTables = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "postgres");
2756
- for (const table of postgresTables) {
2757
- const indexEdges = graph.edges.filter((e) => e.from === table.id && e.type === "uses_index");
2758
- const hasIndexes = indexEdges.length > 0;
2759
- const queryEdges = graph.edges.filter((e) => e.to === table.id && (e.type === "query" || e.type === "scan"));
2760
- if (queryEdges.length > 0 && !hasIndexes) {
2761
- const callerFunctions = queryEdges.map((e) => {
2762
- const node = graph.nodes.find((n) => n.id === e.from);
2763
- return node?.type === "function" ? node.name : e.from;
2764
- }).join(", ");
2765
- findings.push({
2766
- severity: "medium",
2767
- issue: `PostgreSQL table "${table.name}" has no indexes but is queried by ${queryEdges.length} function(s)`,
2768
- description: `Table "${table.name}" is accessed by functions (${callerFunctions}) but has no secondary indexes defined. Queries on non-primary-key columns will result in sequential scans.`,
2769
- recommendation: "Add indexes on columns used in WHERE clauses. Use EXPLAIN ANALYZE to identify slow queries. Consider composite indexes for multi-column filters.",
2770
- metadata: {
2771
- tableName: table.name,
2772
- queryCount: queryEdges.length,
2773
- callerFunctions
2774
- }
2775
- });
2776
- }
2777
- }
2778
- return findings;
2779
- }
2780
- };
2781
- exports2.MissingIndexAnalyzer = MissingIndexAnalyzer;
2782
- var NplusOneAnalyzer = class {
2783
- name = "NplusOneAnalyzer";
2784
- async analyze(graph) {
2785
- const findings = [];
2786
- const functionTableAccess = /* @__PURE__ */ new Map();
2787
- for (const edge of graph.edges) {
2788
- if (edge.type !== "query")
2789
- continue;
2790
- const fromNode = graph.nodes.find((n) => n.id === edge.from);
2791
- const toNode = graph.nodes.find((n) => n.id === edge.to);
2792
- if (fromNode?.type !== "function" || toNode?.type !== "table")
2793
- continue;
2794
- if (toNode.databaseType !== "postgres")
2795
- continue;
2796
- if (!functionTableAccess.has(edge.from)) {
2797
- functionTableAccess.set(edge.from, /* @__PURE__ */ new Map());
2798
- }
2799
- const tableAccess = functionTableAccess.get(edge.from);
2800
- tableAccess.set(edge.to, (tableAccess.get(edge.to) ?? 0) + 1);
2801
- }
2802
- for (const [funcId, tableAccess] of functionTableAccess) {
2803
- for (const [tableId, count] of tableAccess) {
2804
- if (count >= 2) {
2805
- const funcNode = graph.nodes.find((n) => n.id === funcId);
2806
- const tableNode = graph.nodes.find((n) => n.id === tableId);
2807
- if (!funcNode || !tableNode)
2808
- continue;
2809
- findings.push({
2810
- severity: "high",
2811
- issue: `Potential N+1 query pattern in function "${funcNode.name}"`,
2812
- description: `Function "${funcNode.name}" in ${funcNode.file} appears to query table "${tableNode.name}" ${count} time(s), suggesting a potential N+1 query pattern. This can lead to exponential database load when called in a loop.`,
2813
- recommendation: "Batch multiple queries into a single query using IN clauses, JOIN operations, or DataLoader patterns. Consider using a bulk-fetch approach and filtering in application code.",
2814
- metadata: {
2815
- functionName: funcNode.name,
2816
- filePath: funcNode.file,
2817
- tableName: tableNode.name,
2818
- queryCount: count
2819
- }
2820
- });
2821
- }
2822
- }
2823
- }
2824
- return findings;
2825
- }
2826
- };
2827
- exports2.NplusOneAnalyzer = NplusOneAnalyzer;
2828
- var LargeSelectAnalyzer = class {
2829
- name = "LargeSelectAnalyzer";
2830
- async analyze(graph) {
2831
- const findings = [];
2832
- const queryNodes = graph.nodes.filter((n) => n.type === "query");
2833
- for (const queryNode of queryNodes) {
2834
- if (queryNode.operation.toLowerCase().includes("select *") || queryNode.operation.toLowerCase().includes("select_all")) {
2835
- findings.push({
2836
- severity: "low",
2837
- issue: `SELECT * usage detected in query "${queryNode.operation}"`,
2838
- description: "Using SELECT * reads all columns from the table, which can be expensive for wide tables. It also prevents index-only scans and increases network transfer.",
2839
- recommendation: "Specify only the columns you need. For frequently accessed subsets of columns, consider creating a covering index that includes those columns.",
2840
- metadata: {
2841
- operation: queryNode.operation
2842
- }
2843
- });
2844
- }
2845
- }
2846
- const postgresTables = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "postgres");
2847
- for (const table of postgresTables) {
2848
- const scanEdges = graph.edges.filter((e) => e.to === table.id && e.type === "scan");
2849
- if (scanEdges.length > 0) {
2850
- const callerFunctions = scanEdges.map((e) => {
2851
- const node = graph.nodes.find((n) => n.id === e.from);
2852
- return node?.type === "function" ? node.name : e.from;
2853
- }).filter((v, i, arr) => arr.indexOf(v) === i).join(", ");
2854
- findings.push({
2855
- severity: "medium",
2856
- issue: `Sequential scan detected on PostgreSQL table "${table.name}"`,
2857
- description: `Table "${table.name}" is accessed via sequential scan by ${scanEdges.length} operation(s) from: ${callerFunctions}. Sequential scans read the entire table and are very slow on large tables.`,
2858
- recommendation: "Add appropriate indexes to support the WHERE conditions in these queries. Run EXPLAIN ANALYZE to understand the query plan. Consider partial indexes for filtered queries.",
2859
- metadata: {
2860
- tableName: table.name,
2861
- scanCount: scanEdges.length,
2862
- callerFunctions
2863
- }
2864
- });
2865
- }
2866
- }
2867
- return findings;
2868
- }
2869
- };
2870
- exports2.LargeSelectAnalyzer = LargeSelectAnalyzer;
2871
- }
2872
- });
2873
-
2874
- // ../analyzers/dist/mysql.js
2875
- var require_mysql = __commonJS({
2876
- "../analyzers/dist/mysql.js"(exports2) {
2877
- "use strict";
2878
- Object.defineProperty(exports2, "__esModule", { value: true });
2879
- exports2.MySQLFullTableScanAnalyzer = exports2.MissingMySQLIndexAnalyzer = void 0;
2880
- var graph_1 = require_dist10();
2881
- var MissingMySQLIndexAnalyzer = class {
2882
- name = "MissingMySQLIndexAnalyzer";
2883
- async analyze(graph) {
2884
- const findings = [];
2885
- const mysqlTables = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "mysql");
2886
- for (const table of mysqlTables) {
2887
- const indexEdges = graph.edges.filter((e) => e.from === table.id && e.type === "uses_index");
2888
- const hasIndexes = indexEdges.length > 0;
2889
- const queryEdges = graph.edges.filter((e) => e.to === table.id && (e.type === "query" || e.type === "scan"));
2890
- if (queryEdges.length > 0 && !hasIndexes) {
2891
- const callerFunctions = queryEdges.map((e) => {
2892
- const node = graph.nodes.find((n) => n.id === e.from);
2893
- return node?.type === "function" ? node.name : e.from;
2894
- }).join(", ");
2895
- findings.push({
2896
- severity: "medium",
2897
- issue: `MySQL table "${table.name}" has no indexes but is queried by ${queryEdges.length} function(s)`,
2898
- description: `Table "${table.name}" is accessed by functions (${callerFunctions}) but has no secondary indexes defined. Queries on non-primary-key columns will result in full table scans.`,
2899
- recommendation: "Add indexes on columns used in WHERE clauses. Run EXPLAIN on slow queries. Consider composite indexes for multi-column filters.",
2900
- metadata: {
2901
- tableName: table.name,
2902
- queryCount: queryEdges.length,
2903
- callerFunctions
2904
- }
2905
- });
2906
- }
2907
- }
2908
- return findings;
2909
- }
2910
- };
2911
- exports2.MissingMySQLIndexAnalyzer = MissingMySQLIndexAnalyzer;
2912
- var MySQLFullTableScanAnalyzer = class {
2913
- name = "MySQLFullTableScanAnalyzer";
2914
- async analyze(graph) {
2915
- const findings = [];
2916
- const scanEdges = (0, graph_1.getScanEdges)(graph);
2917
- const scannedTableIds = /* @__PURE__ */ new Set();
2918
- for (const edge of scanEdges) {
2919
- const targetNode = graph.nodes.find((n) => n.id === edge.to);
2920
- if (targetNode?.type === "table" && targetNode.databaseType === "mysql") {
2921
- scannedTableIds.add(targetNode.id);
2922
- }
2923
- }
2924
- for (const tableId of scannedTableIds) {
2925
- const tableNode = graph.nodes.find((n) => n.id === tableId);
2926
- if (!tableNode)
2927
- continue;
2928
- const tableScanEdges = scanEdges.filter((e) => e.to === tableId);
2929
- const callerFunctions = tableScanEdges.map((e) => {
2930
- const node = graph.nodes.find((n) => n.id === e.from);
2931
- return node?.type === "function" ? node.name : e.from;
2932
- }).join(", ");
2933
- findings.push({
2934
- severity: "high",
2935
- issue: `Full table scan detected on MySQL table "${tableNode.name}"`,
2936
- description: `The table "${tableNode.name}" is being scanned without a usable index, reading every row. This is expensive for large tables. Called from: ${callerFunctions || "unknown"}`,
2937
- recommendation: "Add an index on the column(s) used in the WHERE clause. Use EXPLAIN to verify the query plan uses the index after adding it.",
2938
- metadata: {
2939
- tableName: tableNode.name,
2940
- scanCount: tableScanEdges.length,
2941
- callerFunctions
2942
- }
2943
- });
2944
- }
2945
- return findings;
2946
- }
2947
- };
2948
- exports2.MySQLFullTableScanAnalyzer = MySQLFullTableScanAnalyzer;
2949
- }
2950
- });
2951
-
2952
- // ../analyzers/dist/mongodb.js
2953
- var require_mongodb = __commonJS({
2954
- "../analyzers/dist/mongodb.js"(exports2) {
2955
- "use strict";
2956
- Object.defineProperty(exports2, "__esModule", { value: true });
2957
- exports2.MongoCollectionScanAnalyzer = exports2.MissingMongoIndexAnalyzer = void 0;
2958
- var graph_1 = require_dist10();
2959
- var MissingMongoIndexAnalyzer = class {
2960
- name = "MissingMongoIndexAnalyzer";
2961
- async analyze(graph) {
2962
- const findings = [];
2963
- const mongoCollections = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "mongodb");
2964
- for (const coll of mongoCollections) {
2965
- const indexEdges = graph.edges.filter((e) => e.from === coll.id && e.type === "uses_index");
2966
- const hasIndexes = indexEdges.length > 0;
2967
- const queryEdges = graph.edges.filter((e) => e.to === coll.id && (e.type === "query" || e.type === "scan"));
2968
- if (queryEdges.length > 0 && !hasIndexes) {
2969
- const callerFunctions = queryEdges.map((e) => {
2970
- const node = graph.nodes.find((n) => n.id === e.from);
2971
- return node?.type === "function" ? node.name : e.from;
2972
- }).join(", ");
2973
- findings.push({
2974
- severity: "medium",
2975
- issue: `MongoDB collection "${coll.name}" has no indexes but is queried by ${queryEdges.length} function(s)`,
2976
- description: `Collection "${coll.name}" is accessed by functions (${callerFunctions}) but has no secondary indexes defined. Queries on non-_id fields will result in full collection scans.`,
2977
- recommendation: 'Add indexes using db.collection.createIndex({ field: 1 }) for frequently queried fields. Use explain("executionStats") to verify query plan.',
2978
- metadata: {
2979
- collectionName: coll.name,
2980
- queryCount: queryEdges.length,
2981
- callerFunctions
2982
- }
2983
- });
2984
- }
2985
- }
2986
- return findings;
2987
- }
2988
- };
2989
- exports2.MissingMongoIndexAnalyzer = MissingMongoIndexAnalyzer;
2990
- var MongoCollectionScanAnalyzer = class {
2991
- name = "MongoCollectionScanAnalyzer";
2992
- async analyze(graph) {
2993
- const findings = [];
2994
- const scanEdges = (0, graph_1.getScanEdges)(graph);
2995
- const scannedCollIds = /* @__PURE__ */ new Set();
2996
- for (const edge of scanEdges) {
2997
- const targetNode = graph.nodes.find((n) => n.id === edge.to);
2998
- if (targetNode?.type === "table" && targetNode.databaseType === "mongodb") {
2999
- scannedCollIds.add(targetNode.id);
3000
- }
3001
- }
3002
- for (const collId of scannedCollIds) {
3003
- const collNode = graph.nodes.find((n) => n.id === collId);
3004
- if (!collNode)
3005
- continue;
3006
- const collScanEdges = scanEdges.filter((e) => e.to === collId);
3007
- const callerFunctions = collScanEdges.map((e) => {
3008
- const node = graph.nodes.find((n) => n.id === e.from);
3009
- return node?.type === "function" ? node.name : e.from;
3010
- }).join(", ");
3011
- findings.push({
3012
- severity: "high",
3013
- issue: `Collection scan detected on MongoDB collection "${collNode.name}"`,
3014
- description: `Collection "${collNode.name}" is being scanned without an index, reading every document. This is very expensive for large collections. Called from: ${callerFunctions || "unknown"}`,
3015
- recommendation: 'Add an index on the field(s) used as query predicates. Use db.collection.createIndex({ field: 1 }) and verify with explain("executionStats").',
3016
- metadata: {
3017
- collectionName: collNode.name,
3018
- scanCount: collScanEdges.length,
3019
- callerFunctions
3020
- }
3021
- });
3022
- }
3023
- return findings;
3024
- }
3025
- };
3026
- exports2.MongoCollectionScanAnalyzer = MongoCollectionScanAnalyzer;
3027
- }
3028
- });
3029
-
3030
- // ../analyzers/dist/terraform.js
3031
- var require_terraform = __commonJS({
3032
- "../analyzers/dist/terraform.js"(exports2) {
3033
- "use strict";
3034
- Object.defineProperty(exports2, "__esModule", { value: true });
3035
- exports2.IaCDriftAnalyzer = void 0;
3036
- var IaCDriftAnalyzer2 = class {
3037
- name = "IaCDriftAnalyzer";
3038
- iacSchema = null;
3039
- setIaCSchema(schema) {
3040
- this.iacSchema = schema;
3041
- }
3042
- async analyze(graph) {
3043
- const findings = [];
3044
- if (!this.iacSchema)
3045
- return findings;
3046
- const iac = this.iacSchema;
3047
- const deployedDynamo = new Set(graph.nodes.filter((n) => n.type === "table" && n.databaseType === "dynamodb").map((n) => n.name));
3048
- const iacDynamo = new Map(iac.dynamoTables.map((t) => [t.name, t.filePath]));
3049
- for (const [name, fp] of iacDynamo) {
3050
- if (!deployedDynamo.has(name)) {
3051
- findings.push({
3052
- severity: "medium",
3053
- issue: `IaC drift: DynamoDB table "${name}" defined in IaC but not deployed`,
3054
- description: `"${name}" is in ${fp} but not found in AWS. It may be undeployed or deleted manually.`,
3055
- recommendation: "Run `terraform apply` / deploy your stack, or remove the definition from IaC.",
3056
- metadata: { resourceType: "dynamodb_table", name, filePath: fp, driftType: "defined_not_deployed" }
3057
- });
3058
- }
3059
- }
3060
- for (const name of deployedDynamo) {
3061
- if (!iacDynamo.has(name)) {
3062
- findings.push({
3063
- severity: "medium",
3064
- issue: `IaC drift: DynamoDB table "${name}" deployed but not in IaC`,
3065
- description: `"${name}" exists in AWS DynamoDB but has no IaC definition. It may have been created manually.`,
3066
- recommendation: "Import the table with `terraform import` or add a CloudFormation resource, then track all future changes through IaC.",
3067
- metadata: { resourceType: "dynamodb_table", name, driftType: "deployed_not_defined" }
3068
- });
3069
- }
3070
- }
3071
- const deployedQueues = new Set(graph.nodes.filter((n) => n.type === "queue").map((n) => n.name));
3072
- const iacQueues = new Map(iac.queues.map((q) => [q.name, q.filePath]));
3073
- for (const [name, fp] of iacQueues) {
3074
- if (!deployedQueues.has(name)) {
3075
- findings.push({
3076
- severity: "medium",
3077
- issue: `IaC drift: SQS queue "${name}" defined in IaC but not deployed`,
3078
- description: `SQS queue "${name}" is defined in ${fp} but not found in the live account.`,
3079
- recommendation: "Deploy the queue via `terraform apply` or your CFN/CDK stack.",
3080
- metadata: { resourceType: "sqs_queue", name, filePath: fp, driftType: "defined_not_deployed" }
3081
- });
3082
- }
3083
- }
3084
- for (const name of deployedQueues) {
3085
- if (!iacQueues.has(name)) {
3086
- findings.push({
3087
- severity: "low",
3088
- issue: `IaC drift: SQS queue "${name}" deployed but not in IaC`,
3089
- description: `SQS queue "${name}" exists in AWS but is not tracked in IaC. Manual resources can't be audited or reproduced reliably.`,
3090
- recommendation: "Import or define the queue in IaC to bring it under version control.",
3091
- metadata: { resourceType: "sqs_queue", name, driftType: "deployed_not_defined" }
3092
- });
3093
- }
3094
- }
3095
- const deployedLambdas = new Set(graph.nodes.filter((n) => n.type === "lambda").map((n) => n.name));
3096
- const iacLambdas = new Map(iac.lambdas.map((l) => [l.name, l.filePath]));
3097
- for (const [name, fp] of iacLambdas) {
3098
- if (!deployedLambdas.has(name)) {
3099
- findings.push({
3100
- severity: "medium",
3101
- issue: `IaC drift: Lambda "${name}" defined in IaC but not deployed`,
3102
- description: `Lambda function "${name}" is defined in ${fp} but not found in the live account.`,
3103
- recommendation: "Deploy the function via `terraform apply` or your CFN/CDK stack.",
3104
- metadata: { resourceType: "lambda_function", name, filePath: fp, driftType: "defined_not_deployed" }
3105
- });
3106
- }
3107
- }
3108
- for (const name of deployedLambdas) {
3109
- if (!iacLambdas.has(name)) {
3110
- findings.push({
3111
- severity: "low",
3112
- issue: `IaC drift: Lambda "${name}" deployed but not in IaC`,
3113
- description: `Lambda "${name}" exists in AWS but is not tracked in IaC.`,
3114
- recommendation: "Import the function into IaC or add it as a resource.",
3115
- metadata: { resourceType: "lambda_function", name, driftType: "deployed_not_defined" }
3116
- });
3117
- }
3118
- }
3119
- return findings;
3120
- }
3121
- };
3122
- exports2.IaCDriftAnalyzer = IaCDriftAnalyzer2;
3123
- }
3124
- });
3125
-
3126
- // ../analyzers/dist/aws-services.js
3127
- var require_aws_services = __commonJS({
3128
- "../analyzers/dist/aws-services.js"(exports2) {
3129
- "use strict";
3130
- Object.defineProperty(exports2, "__esModule", { value: true });
3131
- exports2.LambdaHighTimeoutAnalyzer = exports2.LambdaDefaultMemoryAnalyzer = exports2.MissingLogRetentionAnalyzer = exports2.MissingSecretRotationAnalyzer = exports2.LargeQueueBacklogAnalyzer = exports2.UnencryptedQueueAnalyzer = exports2.MissingDLQAnalyzer = void 0;
3132
- var MissingDLQAnalyzer = class {
3133
- name = "MissingDLQAnalyzer";
3134
- async analyze(graph) {
3135
- const findings = [];
3136
- for (const node of graph.nodes) {
3137
- if (node.type !== "queue")
3138
- continue;
3139
- if (!node.hasDLQ) {
3140
- findings.push({
3141
- severity: "high",
3142
- issue: `Queue "${node.name}" has no Dead Letter Queue`,
3143
- description: `SQS queue "${node.name}" has no DLQ configured. Failed messages will be discarded after maxReceiveCount retries, causing silent data loss.`,
3144
- recommendation: `Add a Dead Letter Queue to "${node.name}". Set maxReceiveCount to 3\u20135 retries before routing to DLQ. Alert on DLQ depth.`,
3145
- metadata: { queueName: node.name, provider: node.provider }
3146
- });
3147
- }
3148
- }
3149
- return findings;
3150
- }
3151
- };
3152
- exports2.MissingDLQAnalyzer = MissingDLQAnalyzer;
3153
- var UnencryptedQueueAnalyzer = class {
3154
- name = "UnencryptedQueueAnalyzer";
3155
- async analyze(graph) {
3156
- const findings = [];
3157
- for (const node of graph.nodes) {
3158
- if (node.type !== "queue")
3159
- continue;
3160
- if (!node.encrypted) {
3161
- findings.push({
3162
- severity: "low",
3163
- issue: `Queue "${node.name}" is not encrypted`,
3164
- description: `SQS queue "${node.name}" does not have server-side encryption enabled. Messages at rest are unencrypted.`,
3165
- recommendation: `Enable SQS-managed SSE (SqsManagedSseEnabled=true) or bring your own KMS key for "${node.name}".`,
3166
- metadata: { queueName: node.name }
3167
- });
3168
- }
3169
- }
3170
- return findings;
3171
- }
3172
- };
3173
- exports2.UnencryptedQueueAnalyzer = UnencryptedQueueAnalyzer;
3174
- var LargeQueueBacklogAnalyzer = class {
3175
- name = "LargeQueueBacklogAnalyzer";
3176
- threshold;
3177
- constructor(threshold = 1e3) {
3178
- this.threshold = threshold;
3179
- }
3180
- async analyze(graph) {
3181
- const findings = [];
3182
- for (const node of graph.nodes) {
3183
- if (node.type !== "queue")
3184
- continue;
3185
- const count = node.approximateMessages ?? 0;
3186
- if (count > this.threshold) {
3187
- findings.push({
3188
- severity: "medium",
3189
- issue: `Queue "${node.name}" has a large backlog (${count.toLocaleString()} messages)`,
3190
- description: `The approximate message count for "${node.name}" is ${count.toLocaleString()}, indicating consumers may be falling behind or stuck.`,
3191
- recommendation: `Check consumer health and scaling for "${node.name}". Consider auto-scaling consumers on queue depth. If messages are stale, investigate consumer errors in CloudWatch.`,
3192
- metadata: { queueName: node.name, messageCount: count }
3193
- });
3194
- }
3195
- }
3196
- return findings;
3197
- }
3198
- };
3199
- exports2.LargeQueueBacklogAnalyzer = LargeQueueBacklogAnalyzer;
3200
- var MissingSecretRotationAnalyzer = class {
3201
- name = "MissingSecretRotationAnalyzer";
3202
- async analyze(graph) {
3203
- const findings = [];
3204
- for (const node of graph.nodes) {
3205
- if (node.type !== "secret")
3206
- continue;
3207
- if (!node.rotationEnabled) {
3208
- findings.push({
3209
- severity: "medium",
3210
- issue: `Secret "${node.name}" has no automatic rotation`,
3211
- description: `Secrets Manager secret "${node.name}" does not have automatic rotation enabled. Long-lived credentials increase the blast radius of a compromise.`,
3212
- recommendation: `Enable automatic rotation for "${node.name}" using a Lambda rotation function. AWS provides pre-built rotators for RDS, Redshift, and custom secrets.`,
3213
- metadata: { secretName: node.name, provider: node.provider }
3214
- });
3215
- }
3216
- }
3217
- return findings;
3218
- }
3219
- };
3220
- exports2.MissingSecretRotationAnalyzer = MissingSecretRotationAnalyzer;
3221
- var MissingLogRetentionAnalyzer = class {
3222
- name = "MissingLogRetentionAnalyzer";
3223
- async analyze(graph) {
3224
- const findings = [];
3225
- for (const node of graph.nodes) {
3226
- if (node.type !== "log_group")
3227
- continue;
3228
- if (node.retentionDays === void 0) {
3229
- findings.push({
3230
- severity: "medium",
3231
- issue: `Log group "${node.name}" has no retention policy`,
3232
- description: `CloudWatch Log group "${node.name}" retains logs indefinitely. This increases storage costs and can expose sensitive data longer than necessary.`,
3233
- recommendation: `Set a retention policy on "${node.name}". 90 days is a common baseline; adjust based on compliance requirements (e.g., 365 days for SOC2/PCI).`,
3234
- metadata: { logGroupName: node.name }
3235
- });
3236
- } else if (node.retentionDays > 365) {
3237
- findings.push({
3238
- severity: "low",
3239
- issue: `Log group "${node.name}" retains logs for ${node.retentionDays} days`,
3240
- description: `Log group "${node.name}" has a ${node.retentionDays}-day retention period. Unless required by compliance, this may be longer than needed.`,
3241
- recommendation: `Review whether ${node.retentionDays} days of retention is required for "${node.name}". Consider archiving older logs to S3 Glacier for cost savings.`,
3242
- metadata: { logGroupName: node.name, retentionDays: node.retentionDays }
3243
- });
3244
- }
3245
- }
3246
- return findings;
3247
- }
3248
- };
3249
- exports2.MissingLogRetentionAnalyzer = MissingLogRetentionAnalyzer;
3250
- var LambdaDefaultMemoryAnalyzer = class {
3251
- name = "LambdaDefaultMemoryAnalyzer";
3252
- async analyze(graph) {
3253
- const findings = [];
3254
- for (const node of graph.nodes) {
3255
- if (node.type !== "lambda")
3256
- continue;
3257
- if (node.memoryMB === 128) {
3258
- findings.push({
3259
- severity: "low",
3260
- issue: `Lambda "${node.name}" uses the default 128 MB memory`,
3261
- description: `"${node.name}" uses the default 128 MB. Undersized memory causes throttled CPU and higher durations. AWS Lambda pricing is duration \xD7 memory, so more memory often lowers cost by reducing duration.`,
3262
- recommendation: `Run Lambda Power Tuning on "${node.name}" to find the optimal memory/cost balance. Most workloads perform better at 512 MB\u20131 GB.`,
3263
- metadata: { functionName: node.name, memoryMB: node.memoryMB }
3264
- });
3265
- }
3266
- }
3267
- return findings;
3268
- }
3269
- };
3270
- exports2.LambdaDefaultMemoryAnalyzer = LambdaDefaultMemoryAnalyzer;
3271
- var LambdaHighTimeoutAnalyzer = class {
3272
- name = "LambdaHighTimeoutAnalyzer";
3273
- async analyze(graph) {
3274
- const findings = [];
3275
- for (const node of graph.nodes) {
3276
- if (node.type !== "lambda")
3277
- continue;
3278
- if ((node.timeoutSec ?? 0) >= 300) {
3279
- findings.push({
3280
- severity: "low",
3281
- issue: `Lambda "${node.name}" has a very high timeout (${node.timeoutSec}s)`,
3282
- description: `"${node.name}" has a ${node.timeoutSec}-second timeout. High timeouts mask latency issues and increase worst-case cost when functions hang.`,
3283
- recommendation: `Review whether "${node.name}" truly needs ${node.timeoutSec}s. Add internal circuit-breakers or streaming patterns to avoid reaching the timeout. Set alarms on p99 duration.`,
3284
- metadata: { functionName: node.name, timeoutSec: node.timeoutSec }
3285
- });
3286
- }
3287
- }
3288
- return findings;
3289
- }
3290
- };
3291
- exports2.LambdaHighTimeoutAnalyzer = LambdaHighTimeoutAnalyzer;
3292
- }
3293
- });
3294
-
3295
- // ../analyzers/dist/index.js
3296
- var require_dist11 = __commonJS({
3297
- "../analyzers/dist/index.js"(exports2) {
3298
- "use strict";
3299
- Object.defineProperty(exports2, "__esModule", { value: true });
3300
- exports2.LambdaHighTimeoutAnalyzer = exports2.LambdaDefaultMemoryAnalyzer = exports2.MissingLogRetentionAnalyzer = exports2.MissingSecretRotationAnalyzer = exports2.LargeQueueBacklogAnalyzer = exports2.UnencryptedQueueAnalyzer = exports2.MissingDLQAnalyzer = exports2.IaCDriftAnalyzer = exports2.MongoCollectionScanAnalyzer = exports2.MissingMongoIndexAnalyzer = exports2.MySQLFullTableScanAnalyzer = exports2.MissingMySQLIndexAnalyzer = exports2.LargeSelectAnalyzer = exports2.NplusOneAnalyzer = exports2.MissingIndexAnalyzer = exports2.HotPartitionAnalyzer = exports2.MissingGSIAnalyzer = exports2.FullTableScanAnalyzer = void 0;
3301
- exports2.runAllAnalyzers = runAllAnalyzers2;
3302
- exports2.summarizeFindings = summarizeFindings;
3303
- var core_1 = require_dist();
3304
- var dynamodb_1 = require_dynamodb();
3305
- var postgres_1 = require_postgres();
3306
- var mysql_1 = require_mysql();
3307
- var mongodb_1 = require_mongodb();
3308
- var terraform_1 = require_terraform();
3309
- var aws_services_1 = require_aws_services();
3310
- var dynamodb_2 = require_dynamodb();
3311
- Object.defineProperty(exports2, "FullTableScanAnalyzer", { enumerable: true, get: function() {
3312
- return dynamodb_2.FullTableScanAnalyzer;
3313
- } });
3314
- Object.defineProperty(exports2, "MissingGSIAnalyzer", { enumerable: true, get: function() {
3315
- return dynamodb_2.MissingGSIAnalyzer;
3316
- } });
3317
- Object.defineProperty(exports2, "HotPartitionAnalyzer", { enumerable: true, get: function() {
3318
- return dynamodb_2.HotPartitionAnalyzer;
3319
- } });
3320
- var postgres_2 = require_postgres();
3321
- Object.defineProperty(exports2, "MissingIndexAnalyzer", { enumerable: true, get: function() {
3322
- return postgres_2.MissingIndexAnalyzer;
3323
- } });
3324
- Object.defineProperty(exports2, "NplusOneAnalyzer", { enumerable: true, get: function() {
3325
- return postgres_2.NplusOneAnalyzer;
3326
- } });
3327
- Object.defineProperty(exports2, "LargeSelectAnalyzer", { enumerable: true, get: function() {
3328
- return postgres_2.LargeSelectAnalyzer;
3329
- } });
3330
- var mysql_2 = require_mysql();
3331
- Object.defineProperty(exports2, "MissingMySQLIndexAnalyzer", { enumerable: true, get: function() {
3332
- return mysql_2.MissingMySQLIndexAnalyzer;
3333
- } });
3334
- Object.defineProperty(exports2, "MySQLFullTableScanAnalyzer", { enumerable: true, get: function() {
3335
- return mysql_2.MySQLFullTableScanAnalyzer;
3336
- } });
3337
- var mongodb_2 = require_mongodb();
3338
- Object.defineProperty(exports2, "MissingMongoIndexAnalyzer", { enumerable: true, get: function() {
3339
- return mongodb_2.MissingMongoIndexAnalyzer;
3340
- } });
3341
- Object.defineProperty(exports2, "MongoCollectionScanAnalyzer", { enumerable: true, get: function() {
3342
- return mongodb_2.MongoCollectionScanAnalyzer;
3343
- } });
3344
- var terraform_2 = require_terraform();
3345
- Object.defineProperty(exports2, "IaCDriftAnalyzer", { enumerable: true, get: function() {
3346
- return terraform_2.IaCDriftAnalyzer;
3347
- } });
3348
- var aws_services_2 = require_aws_services();
3349
- Object.defineProperty(exports2, "MissingDLQAnalyzer", { enumerable: true, get: function() {
3350
- return aws_services_2.MissingDLQAnalyzer;
3351
- } });
3352
- Object.defineProperty(exports2, "UnencryptedQueueAnalyzer", { enumerable: true, get: function() {
3353
- return aws_services_2.UnencryptedQueueAnalyzer;
3354
- } });
3355
- Object.defineProperty(exports2, "LargeQueueBacklogAnalyzer", { enumerable: true, get: function() {
3356
- return aws_services_2.LargeQueueBacklogAnalyzer;
3357
- } });
3358
- Object.defineProperty(exports2, "MissingSecretRotationAnalyzer", { enumerable: true, get: function() {
3359
- return aws_services_2.MissingSecretRotationAnalyzer;
3360
- } });
3361
- Object.defineProperty(exports2, "MissingLogRetentionAnalyzer", { enumerable: true, get: function() {
3362
- return aws_services_2.MissingLogRetentionAnalyzer;
3363
- } });
3364
- Object.defineProperty(exports2, "LambdaDefaultMemoryAnalyzer", { enumerable: true, get: function() {
3365
- return aws_services_2.LambdaDefaultMemoryAnalyzer;
3366
- } });
3367
- Object.defineProperty(exports2, "LambdaHighTimeoutAnalyzer", { enumerable: true, get: function() {
3368
- return aws_services_2.LambdaHighTimeoutAnalyzer;
3369
- } });
3370
- var DEFAULT_ANALYZERS = [
3371
- // DynamoDB
3372
- new dynamodb_1.FullTableScanAnalyzer(),
3373
- new dynamodb_1.MissingGSIAnalyzer(),
3374
- new dynamodb_1.HotPartitionAnalyzer(),
3375
- // PostgreSQL
3376
- new postgres_1.MissingIndexAnalyzer(),
3377
- new postgres_1.NplusOneAnalyzer(),
3378
- new postgres_1.LargeSelectAnalyzer(),
3379
- // MySQL
3380
- new mysql_1.MissingMySQLIndexAnalyzer(),
3381
- new mysql_1.MySQLFullTableScanAnalyzer(),
3382
- // MongoDB
3383
- new mongodb_1.MissingMongoIndexAnalyzer(),
3384
- new mongodb_1.MongoCollectionScanAnalyzer(),
3385
- // IaC drift
3386
- new terraform_1.IaCDriftAnalyzer(),
3387
- // SQS / messaging
3388
- new aws_services_1.MissingDLQAnalyzer(),
3389
- new aws_services_1.UnencryptedQueueAnalyzer(),
3390
- new aws_services_1.LargeQueueBacklogAnalyzer(),
3391
- // Secrets Manager
3392
- new aws_services_1.MissingSecretRotationAnalyzer(),
3393
- // CloudWatch Logs
3394
- new aws_services_1.MissingLogRetentionAnalyzer(),
3395
- // Lambda
3396
- new aws_services_1.LambdaDefaultMemoryAnalyzer(),
3397
- new aws_services_1.LambdaHighTimeoutAnalyzer()
3398
- ];
3399
- async function runAllAnalyzers2(graph, analyzers = DEFAULT_ANALYZERS) {
3400
- const allFindings = [];
3401
- for (const analyzer of analyzers) {
3402
- try {
3403
- core_1.logger.debug(`Running analyzer: ${analyzer.name}`);
3404
- const findings = await analyzer.analyze(graph);
3405
- core_1.logger.info(`[${analyzer.name}] found ${findings.length} issue(s)`);
3406
- allFindings.push(...findings);
3407
- } catch (err) {
3408
- core_1.logger.warn(`Analyzer "${analyzer.name}" failed: ${err instanceof Error ? err.message : String(err)}`);
3409
- }
3410
- }
3411
- const severityOrder = { high: 0, medium: 1, low: 2 };
3412
- allFindings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
3413
- return allFindings;
3414
- }
3415
- function summarizeFindings(findings) {
3416
- return {
3417
- total: findings.length,
3418
- high: findings.filter((f) => f.severity === "high").length,
3419
- medium: findings.filter((f) => f.severity === "medium").length,
3420
- low: findings.filter((f) => f.severity === "low").length
3421
- };
3422
- }
3423
- }
3424
- });
3425
-
3426
- // ../server/dist/index.js
3427
- var require_dist12 = __commonJS({
3428
- "../server/dist/index.js"(exports2) {
3429
- "use strict";
3430
- var __importDefault = exports2 && exports2.__importDefault || function(mod) {
3431
- return mod && mod.__esModule ? mod : { "default": mod };
3432
- };
3433
- Object.defineProperty(exports2, "__esModule", { value: true });
3434
- exports2.currentFindings = exports2.currentGraph = void 0;
3435
- exports2.setGraphState = setGraphState2;
3436
- exports2.createServer = createServer2;
3437
- var fastify_1 = __importDefault(require("fastify"));
3438
- var cors_1 = __importDefault(require("@fastify/cors"));
3439
- var core_1 = require_dist();
3440
- var graph_1 = require_dist10();
3441
- var currentGraph = { nodes: [], edges: [] };
3442
- exports2.currentGraph = currentGraph;
3443
- var currentFindings = [];
3444
- exports2.currentFindings = currentFindings;
3445
- function setGraphState2(graph, findings) {
3446
- exports2.currentGraph = currentGraph = graph;
3447
- exports2.currentFindings = currentFindings = findings;
3448
- }
3449
- function createServer2(port = 3e3) {
3450
- const fastify = (0, fastify_1.default)({ logger: false });
3451
- fastify.register(cors_1.default, { origin: true });
3452
- fastify.get("/health", async () => ({
3453
- status: "ok",
3454
- version: "0.1.0",
3455
- graphNodes: currentGraph.nodes.length,
3456
- graphEdges: currentGraph.edges.length,
3457
- findings: currentFindings.length
3458
- }));
3459
- fastify.post("/mcp", async (request, reply) => {
3460
- const { tool, input } = request.body;
3461
- if (!tool) {
3462
- reply.status(400);
3463
- return { success: false, error: 'Missing "tool" field' };
3464
- }
3465
- core_1.logger.info(`MCP tool call: ${tool}`);
3466
- try {
3467
- const result = await handleToolCall(tool, input ?? {});
3468
- return { success: true, data: result };
3469
- } catch (err) {
3470
- core_1.logger.error(`MCP tool "${tool}" failed: ${err instanceof Error ? err.message : String(err)}`);
3471
- reply.status(500);
3472
- return { success: false, error: err instanceof Error ? err.message : "Unknown error" };
3473
- }
3474
- });
3475
- fastify.get("/mcp/tools", async () => ({
3476
- tools: [
3477
- // ── Overview ──────────────────────────────────────────────────────────
3478
- {
3479
- name: "get_infra_overview",
3480
- description: "Returns a complete snapshot of all infrastructure: databases, queues, topics, secrets, parameters, log groups, lambdas, and all findings. Start here for a full picture.",
3481
- input: {}
3482
- },
3483
- {
3484
- name: "get_graph_summary",
3485
- description: "Returns the full infrastructure graph (all nodes and edges) plus findings summary.",
3486
- input: {}
3487
- },
3488
- // ── Code analysis ────────────────────────────────────────────────────
3489
- {
3490
- name: "analyze_function",
3491
- description: "Analyze a specific function for all infrastructure issues: DB queries, queue publishing, secret access, etc.",
3492
- input: { function: "string \u2014 function name" }
3493
- },
3494
- // ── Database helpers ─────────────────────────────────────────────────
3495
- {
3496
- name: "suggest_gsi",
3497
- description: "Get GSI suggestions for a DynamoDB table and attribute",
3498
- input: { table: "string", attribute: "string" }
3499
- },
3500
- {
3501
- name: "postgres_index_suggestions",
3502
- description: "Get PostgreSQL index suggestions for a table column",
3503
- input: { table: "string", column: "string" }
3504
- },
3505
- {
3506
- name: "suggest_mongo_index",
3507
- description: "Get index suggestions for a MongoDB collection field",
3508
- input: { collection: "string", field: "string" }
3509
- },
3510
- {
3511
- name: "mysql_index_suggestions",
3512
- description: "Get MySQL index suggestions for a table column",
3513
- input: { table: "string", column: "string" }
3514
- },
3515
- // ── Messaging ────────────────────────────────────────────────────────
3516
- {
3517
- name: "get_queue_details",
3518
- description: "Returns all SQS queues with DLQ status, encryption, message counts, and retention. Use to audit messaging infrastructure.",
3519
- input: {}
3520
- },
3521
- {
3522
- name: "get_topic_details",
3523
- description: "Returns all SNS topics with subscription counts and protocols.",
3524
- input: {}
3525
- },
3526
- // ── Secrets & config ─────────────────────────────────────────────────
3527
- {
3528
- name: "get_secrets_overview",
3529
- description: "Returns all Secrets Manager secrets: names, rotation status, last accessed. Secret VALUES are never included.",
3530
- input: {}
3531
- },
3532
- {
3533
- name: "get_parameter_overview",
3534
- description: "Returns all SSM Parameter Store parameters: names, types, tiers. Parameter VALUES are never included.",
3535
- input: {}
3536
- },
3537
- // ── Compute ──────────────────────────────────────────────────────────
3538
- {
3539
- name: "get_lambda_overview",
3540
- description: "Returns all Lambda functions: runtime, memory, timeout, env var key names (values never included).",
3541
- input: {}
3542
- },
3543
- // ── Observability ────────────────────────────────────────────────────
3544
- {
3545
- name: "get_log_errors",
3546
- description: "Returns recent error patterns from CloudWatch log groups. Returns pattern counts and frequencies \u2014 never raw log messages. Safe for context.",
3547
- input: {
3548
- logGroup: "string (optional) \u2014 filter to a specific log group name"
3549
- }
3550
- }
3551
- ]
3552
- }));
3553
- return { fastify, start: () => startServer(fastify, port) };
3554
- }
3555
- async function handleToolCall(tool, input) {
3556
- switch (tool) {
3557
- // ── Overview ─────────────────────────────────────────────────────────────
3558
- case "get_infra_overview": {
3559
- const tables = (0, graph_1.getTableNodes)(currentGraph);
3560
- const queues = (0, graph_1.getQueueNodes)(currentGraph);
3561
- const topics = (0, graph_1.getTopicNodes)(currentGraph);
3562
- const secrets = (0, graph_1.getSecretNodes)(currentGraph);
3563
- const parameters = (0, graph_1.getParameterNodes)(currentGraph);
3564
- const logGroups = (0, graph_1.getLogGroupNodes)(currentGraph);
3565
- const lambdas = (0, graph_1.getLambdaNodes)(currentGraph);
3566
- const functions = (0, graph_1.getFunctionNodes)(currentGraph);
3567
- return {
3568
- summary: {
3569
- tables: tables.length,
3570
- functions: functions.length,
3571
- queues: queues.length,
3572
- topics: topics.length,
3573
- secrets: secrets.length,
3574
- parameters: parameters.length,
3575
- logGroups: logGroups.length,
3576
- lambdas: lambdas.length,
3577
- totalNodes: currentGraph.nodes.length,
3578
- totalEdges: currentGraph.edges.length,
3579
- findings: {
3580
- total: currentFindings.length,
3581
- high: currentFindings.filter((f) => f.severity === "high").length,
3582
- medium: currentFindings.filter((f) => f.severity === "medium").length,
3583
- low: currentFindings.filter((f) => f.severity === "low").length
3584
- }
3585
- },
3586
- databases: tables.map((t) => ({ name: t.name, type: t.databaseType })),
3587
- queues: queues.map((q) => ({
3588
- name: q.name,
3589
- hasDLQ: q.hasDLQ,
3590
- encrypted: q.encrypted,
3591
- approximateMessages: q.approximateMessages
3592
- })),
3593
- topics: topics.map((t) => ({ name: t.name, subscriptions: t.subscriptionCount })),
3594
- secrets: secrets.map((s) => ({ name: s.name, rotationEnabled: s.rotationEnabled })),
3595
- parameters: parameters.map((p) => ({ name: p.name, type: p.paramType, tier: p.tier })),
3596
- lambdas: lambdas.map((l) => ({ name: l.name, runtime: l.runtime, memoryMB: l.memoryMB })),
3597
- logGroups: logGroups.map((lg) => ({
3598
- name: lg.name,
3599
- retentionDays: lg.retentionDays ?? "never",
3600
- errorCount: lg.errorCount
3601
- })),
3602
- highFindings: currentFindings.filter((f) => f.severity === "high").map((f) => ({
3603
- issue: f.issue,
3604
- recommendation: f.recommendation
3605
- }))
3606
- };
3607
- }
3608
- case "get_graph_summary": {
3609
- return {
3610
- nodes: currentGraph.nodes,
3611
- edges: currentGraph.edges,
3612
- findings: currentFindings,
3613
- summary: {
3614
- totalNodes: currentGraph.nodes.length,
3615
- totalEdges: currentGraph.edges.length,
3616
- tables: (0, graph_1.getTableNodes)(currentGraph).length,
3617
- functions: (0, graph_1.getFunctionNodes)(currentGraph).length,
3618
- queues: (0, graph_1.getQueueNodes)(currentGraph).length,
3619
- scans: (0, graph_1.getScanEdges)(currentGraph).length,
3620
- totalFindings: currentFindings.length,
3621
- highSeverity: currentFindings.filter((f) => f.severity === "high").length,
3622
- mediumSeverity: currentFindings.filter((f) => f.severity === "medium").length,
3623
- lowSeverity: currentFindings.filter((f) => f.severity === "low").length
3624
- }
3625
- };
3626
- }
3627
- // ── Code analysis ──────────────────────────────────────────────────────────
3628
- case "analyze_function": {
3629
- const functionName = String(input.function ?? "");
3630
- if (!functionName)
3631
- throw new Error("Missing input.function");
3632
- const funcNode = currentGraph.nodes.find((n) => n.type === "function" && n.name === functionName);
3633
- if (!funcNode) {
3634
- return {
3635
- function: functionName,
3636
- found: false,
3637
- issues: [],
3638
- recommendations: [`Function "${functionName}" not found in the analyzed codebase.`]
3639
- };
3640
- }
3641
- const outEdges = (0, graph_1.getOutgoingEdges)(currentGraph, funcNode.id);
3642
- const relatedFindings = currentFindings.filter((f) => {
3643
- const meta = f.metadata;
3644
- return meta?.functionName === functionName || String(meta?.callerFunctions ?? "").includes(functionName);
3645
- });
3646
- return {
3647
- function: functionName,
3648
- found: true,
3649
- file: funcNode.type === "function" ? funcNode.file : void 0,
3650
- accesses: outEdges.map((e) => {
3651
- const target = currentGraph.nodes.find((n) => n.id === e.to);
3652
- return {
3653
- targetId: e.to,
3654
- edgeType: e.type,
3655
- targetName: target && "name" in target ? target.name : e.to,
3656
- targetType: target?.type
3657
- };
3658
- }),
3659
- issues: relatedFindings.map((f) => ({
3660
- severity: f.severity,
3661
- issue: f.issue,
3662
- description: f.description
3663
- })),
3664
- recommendations: [...new Set(relatedFindings.map((f) => f.recommendation))]
3665
- };
3666
- }
3667
- // ── Database helpers ───────────────────────────────────────────────────────
3668
- case "suggest_gsi": {
3669
- const tableName = String(input.table ?? "");
3670
- const attribute = String(input.attribute ?? "");
3671
- if (!tableName || !attribute)
3672
- throw new Error("Missing input.table or input.attribute");
3673
- const tableNode = currentGraph.nodes.find((n) => n.type === "table" && n.databaseType === "dynamodb" && "name" in n && n.name === tableName);
3674
- const sanitizedAttr = attribute.replace(/[^a-zA-Z0-9_]/g, "_");
3675
- const indexName = `${tableName}-${sanitizedAttr}-index`;
3676
- return {
3677
- table: tableName,
3678
- attribute,
3679
- found: !!tableNode,
3680
- index: { name: indexName, partitionKey: attribute, projectionType: "ALL", billingMode: "PAY_PER_REQUEST" },
3681
- rationale: `A GSI on "${attribute}" allows Query instead of Scan when filtering by this attribute.`,
3682
- recommendation: `Add GSI "${indexName}" with partition key "${attribute}" to your IaC definition.`
3683
- };
3684
- }
3685
- case "postgres_index_suggestions": {
3686
- const tableName = String(input.table ?? "");
3687
- const column = String(input.column ?? "");
3688
- if (!tableName || !column)
3689
- throw new Error("Missing input.table or input.column");
3690
- const sanitizedCol = column.replace(/[^a-zA-Z0-9_]/g, "_");
3691
- const sanitizedTable = tableName.replace(/[^a-zA-Z0-9_]/g, "_");
3692
- const indexName = `idx_${sanitizedTable}_${sanitizedCol}`;
3693
- return {
3694
- table: tableName,
3695
- column,
3696
- recommendation: `CREATE INDEX CONCURRENTLY ${indexName} ON ${tableName} (${column});`,
3697
- rationale: `An index on "${column}" eliminates sequential scans when filtering on this column.`,
3698
- notes: [
3699
- "Use CONCURRENTLY to avoid locking the table",
3700
- "Run ANALYZE after creation",
3701
- `Partial index: CREATE INDEX CONCURRENTLY ${indexName}_partial ON ${tableName} (${column}) WHERE ${column} IS NOT NULL;`
3702
- ]
3703
- };
3704
- }
3705
- case "suggest_mongo_index": {
3706
- const collection = String(input.collection ?? "");
3707
- const field = String(input.field ?? "");
3708
- if (!collection || !field)
3709
- throw new Error("Missing input.collection or input.field");
3710
- return {
3711
- collection,
3712
- field,
3713
- recommendation: `db.${collection}.createIndex({ ${field}: 1 })`,
3714
- rationale: `An index on "${field}" eliminates full collection scans when filtering on this field.`,
3715
- notes: [
3716
- `Compound: db.${collection}.createIndex({ ${field}: 1, otherField: 1 })`,
3717
- `Text: db.${collection}.createIndex({ ${field}: "text" })`,
3718
- `Verify: db.${collection}.explain("executionStats").find({ ${field}: value })`
3719
- ]
3720
- };
3721
- }
3722
- case "mysql_index_suggestions": {
3723
- const tableName = String(input.table ?? "");
3724
- const column = String(input.column ?? "");
3725
- if (!tableName || !column)
3726
- throw new Error("Missing input.table or input.column");
3727
- const sanitizedCol = column.replace(/[^a-zA-Z0-9_]/g, "_");
3728
- const sanitizedTable = tableName.replace(/[^a-zA-Z0-9_]/g, "_");
3729
- const indexName = `idx_${sanitizedTable}_${sanitizedCol}`;
3730
- return {
3731
- table: tableName,
3732
- column,
3733
- recommendation: `ALTER TABLE ${tableName} ADD INDEX ${indexName} (${column});`,
3734
- rationale: `An index on "${column}" eliminates full table scans when filtering on this column.`,
3735
- notes: [
3736
- "MySQL InnoDB adds indexes online (no full lock for 5.6+)",
3737
- `EXPLAIN SELECT ... to verify after adding`,
3738
- `Composite: ALTER TABLE ${tableName} ADD INDEX idx_composite (${column}, other_column);`
3739
- ]
3740
- };
3741
- }
3742
- // ── Messaging ─────────────────────────────────────────────────────────────
3743
- case "get_queue_details": {
3744
- const queues = (0, graph_1.getQueueNodes)(currentGraph);
3745
- const queueFindings = currentFindings.filter((f) => f.metadata?.queueName);
3746
- return {
3747
- total: queues.length,
3748
- queues: queues.map((q) => ({
3749
- name: q.name,
3750
- provider: q.provider,
3751
- hasDLQ: q.hasDLQ,
3752
- encrypted: q.encrypted,
3753
- approximateMessages: q.approximateMessages,
3754
- retentionDays: q.retentionDays,
3755
- findings: queueFindings.filter((f) => f.metadata.queueName === q.name).map((f) => ({ severity: f.severity, issue: f.issue }))
3756
- }))
3757
- };
3758
- }
3759
- case "get_topic_details": {
3760
- const topics = (0, graph_1.getTopicNodes)(currentGraph);
3761
- return {
3762
- total: topics.length,
3763
- topics: topics.map((t) => ({
3764
- name: t.name,
3765
- provider: t.provider,
3766
- subscriptionCount: t.subscriptionCount,
3767
- encrypted: t.encrypted
3768
- }))
3769
- };
3770
- }
3771
- // ── Secrets & config ──────────────────────────────────────────────────────
3772
- case "get_secrets_overview": {
3773
- const secrets = (0, graph_1.getSecretNodes)(currentGraph);
3774
- const secretFindings = currentFindings.filter((f) => f.metadata?.secretName);
3775
- return {
3776
- total: secrets.length,
3777
- note: "Secret values are never included in this response.",
3778
- secrets: secrets.map((s) => ({
3779
- name: s.name,
3780
- provider: s.provider,
3781
- rotationEnabled: s.rotationEnabled,
3782
- rotationDays: s.rotationDays,
3783
- findings: secretFindings.filter((f) => f.metadata.secretName === s.name).map((f) => ({ severity: f.severity, issue: f.issue }))
3784
- }))
3785
- };
3786
- }
3787
- case "get_parameter_overview": {
3788
- const parameters = (0, graph_1.getParameterNodes)(currentGraph);
3789
- return {
3790
- total: parameters.length,
3791
- note: "Parameter values are never included in this response.",
3792
- parameters: parameters.map((p) => ({
3793
- name: p.name,
3794
- provider: p.provider,
3795
- type: p.paramType,
3796
- tier: p.tier
3797
- }))
3798
- };
3799
- }
3800
- // ── Compute ───────────────────────────────────────────────────────────────
3801
- case "get_lambda_overview": {
3802
- const lambdas = (0, graph_1.getLambdaNodes)(currentGraph);
3803
- const lambdaFindings = currentFindings.filter((f) => f.metadata?.functionName);
3804
- return {
3805
- total: lambdas.length,
3806
- note: "Environment variable values are never included.",
3807
- lambdas: lambdas.map((l) => ({
3808
- name: l.name,
3809
- runtime: l.runtime,
3810
- memoryMB: l.memoryMB,
3811
- timeoutSec: l.timeoutSec,
3812
- envVarCount: l.envVarKeys?.length ?? 0,
3813
- envVarKeys: l.envVarKeys,
3814
- findings: lambdaFindings.filter((f) => f.metadata.functionName === l.name).map((f) => ({ severity: f.severity, issue: f.issue }))
3815
- }))
3816
- };
3817
- }
3818
- // ── Observability ─────────────────────────────────────────────────────────
3819
- case "get_log_errors": {
3820
- const filterName = input.logGroup ? String(input.logGroup) : void 0;
3821
- const logGroups = (0, graph_1.getLogGroupNodes)(currentGraph).filter((lg) => !filterName || lg.name.includes(filterName));
3822
- return {
3823
- note: "Only error patterns and counts are returned \u2014 no raw log messages.",
3824
- windowHours: 24,
3825
- logGroups: logGroups.map((lg) => ({
3826
- name: lg.name,
3827
- retentionDays: lg.retentionDays ?? "never-expires",
3828
- errorCount: lg.errorCount,
3829
- topErrorPatterns: lg.topErrorPatterns
3830
- }))
3831
- };
3832
- }
3833
- default:
3834
- throw new Error(`Unknown tool: "${tool}". Call GET /mcp/tools for the list of available tools.`);
3835
- }
3836
- }
3837
- async function startServer(fastify, port) {
3838
- try {
3839
- await fastify.listen({ port, host: "0.0.0.0" });
3840
- core_1.logger.info(`Infrawise MCP server running at http://localhost:${port}`);
3841
- } catch (err) {
3842
- core_1.logger.error(`Failed to start server: ${err instanceof Error ? err.message : String(err)}`);
3843
- process.exit(1);
3844
- }
3845
- }
3846
- }
3847
- });
3848
-
3849
- // src/index.ts
3850
- var import_commander = require("commander");
3851
-
3852
- // src/utils.ts
3853
- var fs = __toESM(require("fs"));
3854
- var path = __toESM(require("path"));
3855
- var os = __toESM(require("os"));
3856
- var import_chalk = __toESM(require("chalk"));
3857
- function readAWSProfiles() {
3858
- const credentialsPath = path.join(os.homedir(), ".aws", "credentials");
3859
- const configPath = path.join(os.homedir(), ".aws", "config");
3860
- const profiles = /* @__PURE__ */ new Set();
3861
- function parseFile(filePath) {
3862
- if (!fs.existsSync(filePath)) return;
3863
- for (const line of fs.readFileSync(filePath, "utf-8").split("\n")) {
3864
- const match = line.match(/^\[(.+)\]$/);
3865
- if (match?.[1]) {
3866
- let name = match[1];
3867
- if (name.startsWith("profile ")) name = name.slice(8);
3868
- profiles.add(name);
3869
- }
3870
- }
3871
- }
3872
- parseFile(credentialsPath);
3873
- parseFile(configPath);
3874
- return profiles.size > 0 ? [...profiles] : ["default"];
3875
- }
3876
- function detectAWSRegion() {
3877
- if (process.env.AWS_DEFAULT_REGION) return process.env.AWS_DEFAULT_REGION;
3878
- if (process.env.AWS_REGION) return process.env.AWS_REGION;
3879
- const configPath = path.join(os.homedir(), ".aws", "config");
3880
- if (fs.existsSync(configPath)) {
3881
- const match = fs.readFileSync(configPath, "utf-8").match(/region\s*=\s*(.+)/);
3882
- if (match?.[1]) return match[1].trim();
3883
- }
3884
- return "us-east-1";
3885
- }
3886
- function detectRepoType(repoPath) {
3887
- if (fs.existsSync(path.join(repoPath, "tsconfig.json"))) return "typescript";
3888
- if (fs.existsSync(path.join(repoPath, "package.json"))) return "javascript";
3889
- return "unknown";
3890
- }
3891
- function printBanner() {
3892
- const line1 = import_chalk.default.bold.hex("#6366f1")(" infrawise");
3893
- const version = import_chalk.default.dim(" v0.1.0");
3894
- const tagline = import_chalk.default.dim(" Infrastructure Intelligence Platform\n");
3895
- console.log(`
3896
- ${line1}${version}`);
3897
- console.log(tagline);
3898
- }
3899
- function printHeader(title) {
3900
- console.log(import_chalk.default.bold(`
3901
- ${title}`));
3902
- console.log(import_chalk.default.dim("\u2500".repeat(title.length + 2)));
3903
- }
3904
- var log = {
3905
- success: (msg, detail) => {
3906
- console.log(` ${import_chalk.default.green("\u2713")} ${msg}${detail ? import_chalk.default.dim(` ${detail}`) : ""}`);
3907
- },
3908
- fail: (msg, detail) => {
3909
- console.log(` ${import_chalk.default.red("\u2717")} ${import_chalk.default.red(msg)}${detail ? import_chalk.default.dim(`
3910
- ${detail}`) : ""}`);
3911
- },
3912
- warn: (msg, detail) => {
3913
- console.log(` ${import_chalk.default.yellow("\u26A0")} ${import_chalk.default.yellow(msg)}${detail ? import_chalk.default.dim(`
3914
- ${detail}`) : ""}`);
3915
- },
3916
- skip: (msg, detail) => {
3917
- console.log(` ${import_chalk.default.dim("\u2212")} ${import_chalk.default.dim(msg)}${detail ? import_chalk.default.dim(` ${detail}`) : ""}`);
3918
- },
3919
- info: (msg) => {
3920
- console.log(` ${import_chalk.default.cyan("\u203A")} ${msg}`);
3921
- },
3922
- dim: (msg) => {
3923
- console.log(import_chalk.default.dim(` ${msg}`));
3924
- }
3925
- };
3926
- function severityBadge(severity) {
3927
- switch (severity) {
3928
- case "high":
3929
- return import_chalk.default.bgRed.white.bold(` HIGH `);
3930
- case "medium":
3931
- return import_chalk.default.bgYellow.black.bold(` MED `);
3932
- case "low":
3933
- return import_chalk.default.bgCyan.black.bold(` LOW `);
3934
- }
3935
- }
3936
- function printFinding(finding, index) {
3937
- const badge = severityBadge(finding.severity);
3938
- const num = import_chalk.default.dim(`${index + 1}.`);
3939
- console.log(`
3940
- ${num} ${badge} ${import_chalk.default.bold(finding.issue)}`);
3941
- console.log(import_chalk.default.dim(` ${finding.description}`));
3942
- console.log(` ${import_chalk.default.green("\u2192")} ${finding.recommendation}`);
3943
- }
3944
- function printSummaryBox(findings) {
3945
- const high = findings.filter((f) => f.severity === "high").length;
3946
- const medium = findings.filter((f) => f.severity === "medium").length;
3947
- const low = findings.filter((f) => f.severity === "low").length;
3948
- console.log("");
3949
- console.log(import_chalk.default.dim(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
3950
- console.log(import_chalk.default.dim(" \u2502") + import_chalk.default.bold(" Analysis Summary ") + import_chalk.default.dim("\u2502"));
3951
- console.log(import_chalk.default.dim(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
3952
- console.log(import_chalk.default.dim(" \u2502") + ` ${import_chalk.default.red("\u25CF")} High ${import_chalk.default.red.bold(String(high).padStart(3))} ` + import_chalk.default.dim("\u2502"));
3953
- console.log(import_chalk.default.dim(" \u2502") + ` ${import_chalk.default.yellow("\u25CF")} Medium ${import_chalk.default.yellow.bold(String(medium).padStart(3))} ` + import_chalk.default.dim("\u2502"));
3954
- console.log(import_chalk.default.dim(" \u2502") + ` ${import_chalk.default.cyan("\u25CF")} Low ${import_chalk.default.cyan.bold(String(low).padStart(3))} ` + import_chalk.default.dim("\u2502"));
3955
- console.log(import_chalk.default.dim(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
3956
- console.log(import_chalk.default.dim(" \u2502") + ` Total ${import_chalk.default.bold(String(findings.length).padStart(3))} ` + import_chalk.default.dim("\u2502"));
3957
- console.log(import_chalk.default.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
3958
- }
3959
-
3960
- // src/commands/init.ts
3961
- var fs2 = __toESM(require("fs"));
3962
- var path2 = __toESM(require("path"));
3963
- var import_chalk2 = __toESM(require("chalk"));
3964
- var import_inquirer = __toESM(require("inquirer"));
3965
- var import_core = __toESM(require_dist());
3966
- async function runInit(options = {}) {
3967
- const cwd = process.cwd();
3968
- const configPath = path2.join(cwd, "infrawise.yaml");
3969
- if (fs2.existsSync(configPath) && !options.force) {
3970
- console.log(`
3971
- ${import_chalk2.default.yellow("\u26A0")} ${import_chalk2.default.yellow("infrawise.yaml already exists.")} ${import_chalk2.default.dim("Use --force to overwrite.")}
3972
- `);
3973
- return;
3974
- }
3975
- printHeader("Initialize Infrawise");
3976
- const repoType = detectRepoType(cwd);
3977
- const repoName = path2.basename(cwd);
3978
- const profiles = readAWSProfiles();
3979
- const detectedRegion = detectAWSRegion();
3980
- log.success(`Repository detected`, repoName);
3981
- log.success(`Type`, repoType);
3982
- log.success(`AWS profiles found`, String(profiles.length));
3983
- console.log("");
3984
- const core = await import_inquirer.default.prompt([
3985
- {
3986
- type: "input",
3987
- name: "project",
3988
- message: "Project name:",
3989
- default: repoName
3990
- },
3991
- {
3992
- type: "list",
3993
- name: "awsProfile",
3994
- message: "AWS profile:",
3995
- choices: profiles,
3996
- default: profiles[0]
3997
- },
3998
- {
3999
- type: "input",
4000
- name: "region",
4001
- message: "AWS region:",
4002
- default: detectedRegion
4003
- }
4004
- ]);
4005
- console.log("\n " + import_chalk2.default.bold("Databases"));
4006
- const databases = await import_inquirer.default.prompt([
4007
- {
4008
- type: "input",
4009
- name: "dynamoTables",
4010
- message: "DynamoDB tables to include:",
4011
- default: "",
4012
- suffix: import_chalk2.default.dim(" (comma-separated, blank = all)")
4013
- },
4014
- {
4015
- type: "confirm",
4016
- name: "pgEnabled",
4017
- message: "Enable PostgreSQL analysis?",
4018
- default: false
4019
- },
4020
- {
4021
- type: "input",
4022
- name: "pgConnectionString",
4023
- message: "PostgreSQL connection string:",
4024
- default: "postgresql://localhost:5432/mydb",
4025
- when: (a) => a.pgEnabled
4026
- },
4027
- {
4028
- type: "confirm",
4029
- name: "mysqlEnabled",
4030
- message: "Enable MySQL analysis?",
4031
- default: false
4032
- },
4033
- {
4034
- type: "input",
4035
- name: "mysqlConnectionString",
4036
- message: "MySQL connection string:",
4037
- default: "mysql://localhost:3306/mydb",
4038
- when: (a) => a.mysqlEnabled
4039
- },
4040
- {
4041
- type: "confirm",
4042
- name: "mongoEnabled",
4043
- message: "Enable MongoDB analysis?",
4044
- default: false
4045
- },
4046
- {
4047
- type: "input",
4048
- name: "mongoConnectionString",
4049
- message: "MongoDB connection string:",
4050
- default: "mongodb://localhost:27017",
4051
- when: (a) => a.mongoEnabled
4052
- }
4053
- ]);
4054
- console.log("\n " + import_chalk2.default.bold("AWS Services"));
4055
- console.log(import_chalk2.default.dim(" Infrawise will introspect these services \u2014 credentials from the AWS profile above."));
4056
- const services = await import_inquirer.default.prompt([
4057
- {
4058
- type: "confirm",
4059
- name: "sqsEnabled",
4060
- message: "Introspect SQS queues?",
4061
- default: true
4062
- },
4063
- {
4064
- type: "confirm",
4065
- name: "snsEnabled",
4066
- message: "Introspect SNS topics?",
4067
- default: true
4068
- },
4069
- {
4070
- type: "confirm",
4071
- name: "ssmEnabled",
4072
- message: "Introspect SSM Parameter Store? (metadata only, no values)",
4073
- default: true
4074
- },
4075
- {
4076
- type: "input",
4077
- name: "ssmPaths",
4078
- message: "SSM path prefixes to filter:",
4079
- default: "",
4080
- suffix: import_chalk2.default.dim(" (comma-separated, blank = all e.g. /myapp/prod)"),
4081
- when: (a) => a.ssmEnabled
4082
- },
4083
- {
4084
- type: "confirm",
4085
- name: "secretsEnabled",
4086
- message: "Introspect Secrets Manager? (names & rotation only, no values)",
4087
- default: true
4088
- },
4089
- {
4090
- type: "confirm",
4091
- name: "lambdaEnabled",
4092
- message: "Introspect Lambda functions?",
4093
- default: true
4094
- },
4095
- {
4096
- type: "confirm",
4097
- name: "logsEnabled",
4098
- message: "Sample CloudWatch Logs? (error patterns only, no raw logs)",
4099
- default: false
4100
- },
4101
- {
4102
- type: "input",
4103
- name: "logGroupPrefixes",
4104
- message: "CloudWatch log group prefixes:",
4105
- default: "",
4106
- suffix: import_chalk2.default.dim(" (comma-separated, blank = all)"),
4107
- when: (a) => a.logsEnabled
4108
- }
4109
- ]);
4110
- const includeTables = databases.dynamoTables ? databases.dynamoTables.split(",").map((t) => t.trim()).filter(Boolean) : [];
4111
- const ssmPaths = services.ssmPaths ? services.ssmPaths.split(",").map((p) => p.trim()).filter(Boolean) : [];
4112
- const logGroupPrefixes = services.logGroupPrefixes ? services.logGroupPrefixes.split(",").map((p) => p.trim()).filter(Boolean) : [];
4113
- const configContent = (0, import_core.generateDefaultConfig)(core.project, {
4114
- aws: { profile: core.awsProfile, region: core.region },
4115
- dynamodb: { includeTables },
4116
- postgres: { enabled: databases.pgEnabled, connectionString: databases.pgConnectionString ?? "" },
4117
- mysql: { enabled: databases.mysqlEnabled, connectionString: databases.mysqlConnectionString ?? "" },
4118
- mongodb: { enabled: databases.mongoEnabled, connectionString: databases.mongoConnectionString ?? "" },
4119
- sqs: { enabled: services.sqsEnabled },
4120
- sns: { enabled: services.snsEnabled },
4121
- ssm: { enabled: services.ssmEnabled, paths: ssmPaths },
4122
- secretsManager: { enabled: services.secretsEnabled },
4123
- lambda: { enabled: services.lambdaEnabled },
4124
- cloudwatchLogs: {
4125
- enabled: services.logsEnabled,
4126
- logGroupPrefixes,
4127
- windowHours: 24
4128
- }
4129
- });
4130
- fs2.writeFileSync(configPath, configContent, "utf-8");
4131
- console.log("");
4132
- log.success(`Created ${import_chalk2.default.bold("infrawise.yaml")}`);
4133
- console.log("");
4134
- console.log(import_chalk2.default.bold(" Next steps:"));
4135
- log.info(`Run ${import_chalk2.default.cyan("infrawise doctor")} to validate your setup`);
4136
- log.info(`Run ${import_chalk2.default.cyan("infrawise analyze")} to scan your infrastructure`);
4137
- log.info(`Run ${import_chalk2.default.cyan("infrawise dev")} to start the MCP server`);
4138
- console.log("");
4139
- }
4140
-
4141
- // src/commands/auth.ts
4142
- var import_chalk3 = __toESM(require("chalk"));
4143
- var import_inquirer2 = __toESM(require("inquirer"));
4144
- var import_ora = __toESM(require("ora"));
4145
- var import_adapters_dynamodb = __toESM(require_dist2());
4146
- async function runAuth() {
4147
- printHeader("AWS Authentication");
4148
- const profiles = readAWSProfiles();
4149
- if (profiles.length === 0) {
4150
- log.fail("No AWS profiles found");
4151
- console.log("");
4152
- log.info("Run " + import_chalk3.default.cyan("aws configure") + " to set up credentials");
4153
- log.info("Or manually edit " + import_chalk3.default.dim("~/.aws/credentials"));
4154
- console.log("");
4155
- return;
4156
- }
4157
- log.success(`Found ${profiles.length} profile(s)`);
4158
- console.log("");
4159
- const { selectedProfile } = await import_inquirer2.default.prompt([
4160
- {
4161
- type: "list",
4162
- name: "selectedProfile",
4163
- message: "Select a profile to validate:",
4164
- choices: profiles
4165
- }
4166
- ]);
4167
- console.log("");
4168
- const spin = (0, import_ora.default)({ text: import_chalk3.default.dim(`Validating "${selectedProfile}"...`), color: "cyan" }).start();
4169
- const testConfig = {
4170
- project: "auth-test",
4171
- aws: { profile: selectedProfile, region: "us-east-1" }
4172
- };
4173
- const isValid = await (0, import_adapters_dynamodb.validateDynamoAccess)(testConfig);
4174
- if (isValid) {
4175
- spin.succeed(import_chalk3.default.green(`Profile "${import_chalk3.default.bold(selectedProfile)}" is valid`));
4176
- console.log("");
4177
- console.log(import_chalk3.default.dim(" Update your infrawise.yaml:"));
4178
- console.log(import_chalk3.default.cyan(` aws:
4179
- profile: ${selectedProfile}`));
4180
- } else {
4181
- spin.fail(import_chalk3.default.red(`Profile "${import_chalk3.default.bold(selectedProfile)}" cannot access DynamoDB`));
4182
- console.log("");
4183
- log.warn("Possible causes:");
4184
- log.dim("Missing IAM permissions \u2014 need dynamodb:ListTables, dynamodb:DescribeTable");
4185
- log.dim("Expired SSO \u2014 run: aws sso login");
4186
- log.dim("Wrong region \u2014 check your AWS config");
4187
- console.log("");
4188
- log.info(`Run ${import_chalk3.default.cyan("infrawise doctor")} for a full diagnostic`);
4189
- }
4190
- console.log("");
4191
- }
4192
-
4193
- // src/commands/analyze.ts
4194
- var path3 = __toESM(require("path"));
4195
- var import_chalk4 = __toESM(require("chalk"));
4196
- var import_ora2 = __toESM(require("ora"));
4197
- var import_core2 = __toESM(require_dist());
4198
- var import_adapters_dynamodb2 = __toESM(require_dist2());
4199
- var import_adapters_postgres = __toESM(require_dist3());
4200
- var import_adapters_mysql = __toESM(require_dist4());
4201
- var import_adapters_mongodb = __toESM(require_dist5());
4202
- var import_adapters_terraform = __toESM(require_dist6());
4203
- var import_adapters_aws_services = __toESM(require_dist7());
4204
- var import_adapters_logs = __toESM(require_dist8());
4205
- var import_context = __toESM(require_dist9());
4206
- var import_graph = __toESM(require_dist10());
4207
- var import_analyzers = __toESM(require_dist11());
4208
- async function runAnalyze(options = {}) {
4209
- printHeader("Running Analysis");
4210
- let config;
4211
- try {
4212
- config = (0, import_core2.loadConfig)(options.config);
4213
- log.success("Config loaded", options.config ?? "infrawise.yaml");
4214
- } catch (err) {
4215
- console.error((0, import_core2.formatError)(err));
4216
- process.exit(1);
4217
- }
4218
- const repoPath = options.repo ?? process.cwd();
4219
- const awsCfg = { region: config.aws?.region, profile: config.aws?.profile };
4220
- const dynamoMeta = [];
4221
- const postgresMeta = [];
4222
- const mysqlMeta = [];
4223
- const mongoMeta = [];
4224
- const servicesMeta = {};
4225
- {
4226
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting DynamoDB tables..."), color: "cyan" }).start();
4227
- try {
4228
- const result = await (0, import_adapters_dynamodb2.extractDynamoMetadata)(config);
4229
- dynamoMeta.push(...result);
4230
- spin.succeed(import_chalk4.default.green("DynamoDB") + import_chalk4.default.dim(` ${result.length} table(s)`));
4231
- } catch (err) {
4232
- spin.warn(import_chalk4.default.yellow("DynamoDB skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4233
- }
4234
- }
4235
- if (config.postgres?.enabled && config.postgres.connectionString) {
4236
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting PostgreSQL schema..."), color: "cyan" }).start();
4237
- try {
4238
- const result = await (0, import_adapters_postgres.extractPostgresMetadata)(config.postgres.connectionString);
4239
- postgresMeta.push(...result);
4240
- spin.succeed(import_chalk4.default.green("PostgreSQL") + import_chalk4.default.dim(` ${result.length} table(s)`));
4241
- } catch (err) {
4242
- spin.warn(import_chalk4.default.yellow("PostgreSQL skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4243
- }
4244
- }
4245
- if (config.mysql?.enabled && config.mysql.connectionString) {
4246
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting MySQL schema..."), color: "cyan" }).start();
4247
- try {
4248
- const result = await (0, import_adapters_mysql.extractMySQLMetadata)(config.mysql.connectionString);
4249
- mysqlMeta.push(...result);
4250
- spin.succeed(import_chalk4.default.green("MySQL") + import_chalk4.default.dim(` ${result.length} table(s)`));
4251
- } catch (err) {
4252
- spin.warn(import_chalk4.default.yellow("MySQL skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4253
- }
4254
- }
4255
- if (config.mongodb?.enabled && config.mongodb.connectionString) {
4256
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting MongoDB schema..."), color: "cyan" }).start();
4257
- try {
4258
- const result = await (0, import_adapters_mongodb.extractMongoMetadata)(config.mongodb.connectionString, config.mongodb.databases);
4259
- mongoMeta.push(...result);
4260
- spin.succeed(import_chalk4.default.green("MongoDB") + import_chalk4.default.dim(` ${result.length} collection(s)`));
4261
- } catch (err) {
4262
- spin.warn(import_chalk4.default.yellow("MongoDB skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4263
- }
4264
- }
4265
- if (config.sqs?.enabled !== false) {
4266
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting SQS queues..."), color: "cyan" }).start();
4267
- try {
4268
- const result = await (0, import_adapters_aws_services.extractSQSMetadata)(awsCfg);
4269
- servicesMeta.sqs = result;
4270
- spin.succeed(import_chalk4.default.green("SQS") + import_chalk4.default.dim(` ${result.length} queue(s)`));
4271
- } catch (err) {
4272
- spin.warn(import_chalk4.default.yellow("SQS skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4273
- }
4274
- }
4275
- if (config.sns?.enabled !== false) {
4276
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting SNS topics..."), color: "cyan" }).start();
4277
- try {
4278
- const result = await (0, import_adapters_aws_services.extractSNSMetadata)(awsCfg);
4279
- servicesMeta.sns = result;
4280
- spin.succeed(import_chalk4.default.green("SNS") + import_chalk4.default.dim(` ${result.length} topic(s)`));
4281
- } catch (err) {
4282
- spin.warn(import_chalk4.default.yellow("SNS skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4283
- }
4284
- }
4285
- if (config.ssm?.enabled !== false) {
4286
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting SSM parameters..."), color: "cyan" }).start();
4287
- try {
4288
- const result = await (0, import_adapters_aws_services.extractSSMMetadata)({ ...awsCfg, paths: config.ssm?.paths });
4289
- servicesMeta.ssm = result;
4290
- spin.succeed(import_chalk4.default.green("SSM") + import_chalk4.default.dim(` ${result.length} parameter(s) `) + import_chalk4.default.dim("(metadata only, no values)"));
4291
- } catch (err) {
4292
- spin.warn(import_chalk4.default.yellow("SSM skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4293
- }
4294
- }
4295
- if (config.secretsManager?.enabled !== false) {
4296
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting Secrets Manager metadata..."), color: "cyan" }).start();
4297
- try {
4298
- const result = await (0, import_adapters_aws_services.extractSecretsMetadata)(awsCfg);
4299
- servicesMeta.secrets = result;
4300
- spin.succeed(import_chalk4.default.green("Secrets Manager") + import_chalk4.default.dim(` ${result.length} secret(s) `) + import_chalk4.default.dim("(names/rotation only, no values)"));
4301
- } catch (err) {
4302
- spin.warn(import_chalk4.default.yellow("Secrets Manager skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4303
- }
4304
- }
4305
- if (config.lambda?.enabled !== false) {
4306
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting Lambda functions..."), color: "cyan" }).start();
4307
- try {
4308
- const result = await (0, import_adapters_aws_services.extractLambdaMetadata)(awsCfg);
4309
- servicesMeta.lambda = result;
4310
- spin.succeed(import_chalk4.default.green("Lambda") + import_chalk4.default.dim(` ${result.length} function(s)`));
4311
- } catch (err) {
4312
- spin.warn(import_chalk4.default.yellow("Lambda skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4313
- }
4314
- }
4315
- if (config.cloudwatchLogs?.enabled) {
4316
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Sampling CloudWatch Logs (errors only, max 50 groups)..."), color: "cyan" }).start();
4317
- try {
4318
- const result = await (0, import_adapters_logs.extractLogsSummary)({
4319
- ...awsCfg,
4320
- logGroupPrefixes: config.cloudwatchLogs.logGroupPrefixes,
4321
- windowHours: config.cloudwatchLogs.windowHours
4322
- });
4323
- servicesMeta.logs = result;
4324
- const errorGroups = result.filter((lg) => lg.errorCount > 0).length;
4325
- spin.succeed(import_chalk4.default.green("CloudWatch Logs") + import_chalk4.default.dim(` ${result.length} group(s), ${errorGroups} with errors`));
4326
- } catch (err) {
4327
- spin.warn(import_chalk4.default.yellow("CloudWatch Logs skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4328
- }
4329
- }
4330
- let iacDriftAnalyzer;
4331
- {
4332
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting IaC schema (Terraform / CloudFormation / CDK)..."), color: "cyan" }).start();
4333
- try {
4334
- const iacSchema = await (0, import_adapters_terraform.extractIaCSchema)(repoPath);
4335
- const total = iacSchema.dynamoTables.length + iacSchema.rdsInstances.length + iacSchema.mongoClusters.length + iacSchema.queues.length + iacSchema.topics.length + iacSchema.lambdas.length + iacSchema.buckets.length + iacSchema.parameters.length + iacSchema.secrets.length + iacSchema.apiGateways.length;
4336
- iacDriftAnalyzer = new import_analyzers.IaCDriftAnalyzer();
4337
- iacDriftAnalyzer.setIaCSchema(iacSchema);
4338
- spin.succeed(import_chalk4.default.green("IaC schema") + import_chalk4.default.dim(` ${total} resource(s) across TF/CFN/CDK`));
4339
- } catch (err) {
4340
- spin.warn(import_chalk4.default.yellow("IaC scan skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4341
- }
4342
- }
4343
- let operations;
4344
- {
4345
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim(`Scanning ${path3.basename(repoPath)} for service usage...`), color: "cyan" }).start();
4346
- try {
4347
- operations = await (0, import_context.scanRepository)(repoPath);
4348
- spin.succeed(import_chalk4.default.green("Repository scanned") + import_chalk4.default.dim(` ${operations.length} service operation(s) found`));
4349
- } catch (err) {
4350
- spin.warn(import_chalk4.default.yellow("Repository scan failed") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
4351
- operations = [];
4352
- }
4353
- }
4354
- let graph;
4355
- {
4356
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Building infrastructure graph..."), color: "cyan" }).start();
4357
- graph = (0, import_graph.buildGraph)(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
4358
- spin.succeed(import_chalk4.default.green("Graph built") + import_chalk4.default.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
4359
- }
4360
- const findings = await (async () => {
4361
- const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Running analyzers..."), color: "cyan" }).start();
4362
- const {
4363
- FullTableScanAnalyzer,
4364
- MissingGSIAnalyzer,
4365
- HotPartitionAnalyzer,
4366
- MissingIndexAnalyzer,
4367
- NplusOneAnalyzer,
4368
- LargeSelectAnalyzer,
4369
- MissingMySQLIndexAnalyzer,
4370
- MySQLFullTableScanAnalyzer,
4371
- MissingMongoIndexAnalyzer,
4372
- MongoCollectionScanAnalyzer,
4373
- MissingDLQAnalyzer,
4374
- UnencryptedQueueAnalyzer,
4375
- LargeQueueBacklogAnalyzer,
4376
- MissingSecretRotationAnalyzer,
4377
- MissingLogRetentionAnalyzer,
4378
- LambdaDefaultMemoryAnalyzer,
4379
- LambdaHighTimeoutAnalyzer
4380
- } = await Promise.resolve().then(() => __toESM(require_dist11()));
4381
- const analyzers = [
4382
- new FullTableScanAnalyzer(),
4383
- new MissingGSIAnalyzer(),
4384
- new HotPartitionAnalyzer(),
4385
- new MissingIndexAnalyzer(),
4386
- new NplusOneAnalyzer(),
4387
- new LargeSelectAnalyzer(),
4388
- new MissingMySQLIndexAnalyzer(),
4389
- new MySQLFullTableScanAnalyzer(),
4390
- new MissingMongoIndexAnalyzer(),
4391
- new MongoCollectionScanAnalyzer(),
4392
- new MissingDLQAnalyzer(),
4393
- new UnencryptedQueueAnalyzer(),
4394
- new LargeQueueBacklogAnalyzer(),
4395
- new MissingSecretRotationAnalyzer(),
4396
- new MissingLogRetentionAnalyzer(),
4397
- new LambdaDefaultMemoryAnalyzer(),
4398
- new LambdaHighTimeoutAnalyzer(),
4399
- ...iacDriftAnalyzer ? [iacDriftAnalyzer] : []
4400
- ];
4401
- const result = await (0, import_analyzers.runAllAnalyzers)(graph, analyzers);
4402
- spin.succeed(import_chalk4.default.green("Analysis complete") + import_chalk4.default.dim(` ${result.length} finding(s)`));
4403
- return result;
4404
- })();
4405
- (0, import_core2.writeCache)("graph", graph);
4406
- (0, import_core2.writeCache)("findings", findings);
4407
- (0, import_core2.writeCache)("operations", operations);
4408
- console.log("");
4409
- if (findings.length === 0) {
4410
- console.log(` ${import_chalk4.default.green.bold("\u2713 No issues found!")} ${import_chalk4.default.dim("Your infrastructure looks clean.")}`);
4411
- } else {
4412
- console.log(import_chalk4.default.bold(` Findings`) + import_chalk4.default.dim(` ${findings.length} total`));
4413
- findings.forEach((f, i) => printFinding(f, i));
4414
- printSummaryBox(findings);
4415
- if (findings.some((f) => f.severity === "high")) {
4416
- console.log(`
4417
- ${import_chalk4.default.red.bold("Action required:")} ${import_chalk4.default.red("High severity issues detected.")}`);
4418
- }
4419
- }
4420
- console.log("");
4421
- log.dim(`Results cached in .infrawise/cache/`);
4422
- log.info(`Run ${import_chalk4.default.cyan("infrawise dev")} to explore via the MCP server`);
4423
- console.log("");
4424
- }
4425
-
4426
- // src/commands/dev.ts
4427
- var import_chalk5 = __toESM(require("chalk"));
4428
- var import_ora3 = __toESM(require("ora"));
4429
- var import_core3 = __toESM(require_dist());
4430
- var import_server = __toESM(require_dist12());
4431
- async function runDev(options = {}) {
4432
- const port = options.port ?? 3e3;
4433
- printHeader("MCP Server");
4434
- try {
4435
- (0, import_core3.loadConfig)(options.config);
4436
- log.success("Config loaded", options.config ?? "infrawise.yaml");
4437
- } catch (err) {
4438
- console.error((0, import_core3.formatError)(err));
4439
- process.exit(1);
4440
- }
4441
- const cachedGraph = (0, import_core3.readCache)("graph");
4442
- const cachedFindings = (0, import_core3.readCache)("findings");
4443
- if (cachedGraph && cachedFindings) {
4444
- log.success(
4445
- "Cached analysis loaded",
4446
- `${cachedGraph.nodes.length} nodes \xB7 ${cachedGraph.edges.length} edges \xB7 ${cachedFindings.length} finding(s)`
4447
- );
4448
- (0, import_server.setGraphState)(cachedGraph, cachedFindings);
4449
- } else {
4450
- log.warn("No cached analysis found");
4451
- log.dim(`Run ${import_chalk5.default.cyan("infrawise analyze")} first for full results`);
4452
- (0, import_server.setGraphState)({ nodes: [], edges: [] }, []);
4453
- }
4454
- console.log("");
4455
- const spin = (0, import_ora3.default)({ text: import_chalk5.default.dim("Starting server..."), color: "cyan" }).start();
4456
- const { start } = (0, import_server.createServer)(port);
4457
- await start();
4458
- spin.succeed(import_chalk5.default.green("Server running"));
4459
- console.log("");
4460
- console.log(import_chalk5.default.dim(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
4461
- console.log(import_chalk5.default.dim(" \u2502") + import_chalk5.default.bold(" MCP Server ") + import_chalk5.default.dim("\u2502"));
4462
- console.log(import_chalk5.default.dim(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
4463
- console.log(import_chalk5.default.dim(" \u2502") + ` ${import_chalk5.default.dim("POST")} ${import_chalk5.default.cyan(`http://localhost:${port}/mcp`)} ` + import_chalk5.default.dim("\u2502"));
4464
- console.log(import_chalk5.default.dim(" \u2502") + ` ${import_chalk5.default.dim("GET")} ${import_chalk5.default.cyan(`http://localhost:${port}/mcp/tools`)} ` + import_chalk5.default.dim("\u2502"));
4465
- console.log(import_chalk5.default.dim(" \u2502") + ` ${import_chalk5.default.dim("GET")} ${import_chalk5.default.cyan(`http://localhost:${port}/health`)} ` + import_chalk5.default.dim("\u2502"));
4466
- console.log(import_chalk5.default.dim(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
4467
- console.log(import_chalk5.default.dim(" \u2502") + import_chalk5.default.dim(" Tools: get_graph_summary \xB7 analyze_function ") + import_chalk5.default.dim("\u2502"));
4468
- console.log(import_chalk5.default.dim(" \u2502") + import_chalk5.default.dim(" suggest_gsi \xB7 postgres_index_suggestions ") + import_chalk5.default.dim("\u2502"));
4469
- console.log(import_chalk5.default.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
4470
- console.log("");
4471
- console.log(import_chalk5.default.dim(" Add to .claude/settings.json:"));
4472
- console.log(import_chalk5.default.dim(" {"));
4473
- console.log(import_chalk5.default.dim(' "mcpServers": {'));
4474
- console.log(import_chalk5.default.dim(' "infrawise": {'));
4475
- console.log(import_chalk5.default.dim(` "url": "http://localhost:${port}/mcp"`));
4476
- console.log(import_chalk5.default.dim(" }"));
4477
- console.log(import_chalk5.default.dim(" }"));
4478
- console.log(import_chalk5.default.dim(" }"));
4479
- console.log("");
4480
- console.log(import_chalk5.default.dim(" Press Ctrl+C to stop\n"));
4481
- process.on("SIGINT", () => {
4482
- console.log(import_chalk5.default.dim("\n Shutting down...\n"));
4483
- process.exit(0);
4484
- });
4485
- await new Promise(() => {
4486
- });
4487
- }
4488
-
4489
- // src/commands/doctor.ts
4490
- var fs3 = __toESM(require("fs"));
4491
- var path4 = __toESM(require("path"));
4492
- var os2 = __toESM(require("os"));
4493
- var import_chalk6 = __toESM(require("chalk"));
4494
- var import_ora4 = __toESM(require("ora"));
4495
- var import_core4 = __toESM(require_dist());
4496
- var import_adapters_dynamodb3 = __toESM(require_dist2());
4497
- var import_adapters_postgres2 = __toESM(require_dist3());
4498
- var import_adapters_mysql2 = __toESM(require_dist4());
4499
- var import_adapters_mongodb2 = __toESM(require_dist5());
4500
- var import_adapters_aws_services2 = __toESM(require_dist7());
4501
- var import_adapters_logs2 = __toESM(require_dist8());
4502
- async function runCheck(label, fn) {
4503
- const spin = (0, import_ora4.default)({ text: import_chalk6.default.dim(label), color: "cyan" }).start();
4504
- const result = await fn();
4505
- switch (result.status) {
4506
- case "pass":
4507
- spin.succeed(import_chalk6.default.green(result.name) + import_chalk6.default.dim(` ${result.message}`));
4508
- break;
4509
- case "fail":
4510
- spin.fail(import_chalk6.default.red(result.name) + import_chalk6.default.dim(` ${result.message}`));
4511
- break;
4512
- case "warn":
4513
- spin.warn(import_chalk6.default.yellow(result.name) + import_chalk6.default.dim(` ${result.message}`));
4514
- break;
4515
- case "skip":
4516
- spin.info(import_chalk6.default.dim(`${result.name} ${result.message}`));
4517
- break;
4518
- }
4519
- if (result.detail) console.log(import_chalk6.default.dim(` ${result.detail}`));
4520
- return result;
4521
- }
4522
- async function runDoctor(options = {}) {
4523
- printHeader("Infrawise Doctor");
4524
- const configPath = path4.resolve(options.config ?? "infrawise.yaml");
4525
- const results = [];
4526
- results.push(await runCheck("Checking config file...", async () => {
4527
- const exists = fs3.existsSync(configPath);
4528
- return {
4529
- name: "Config file",
4530
- status: exists ? "pass" : "fail",
4531
- message: exists ? configPath : `Not found at ${configPath}`,
4532
- detail: exists ? void 0 : "Run: infrawise init"
4533
- };
4534
- }));
4535
- let config;
4536
- results.push(await runCheck("Validating config...", async () => {
4537
- if (!fs3.existsSync(configPath)) return { name: "Config validation", status: "skip", message: "No config file" };
4538
- try {
4539
- config = (0, import_core4.loadConfig)(options.config);
4540
- return { name: "Config validation", status: "pass", message: `project: ${config.project}` };
4541
- } catch (err) {
4542
- return {
4543
- name: "Config validation",
4544
- status: "fail",
4545
- message: "Invalid config",
4546
- detail: err instanceof Error ? err.message : String(err)
4547
- };
4548
- }
4549
- }));
4550
- results.push(await runCheck("Checking AWS credentials...", async () => {
4551
- const home = process.env.HOME ?? os2.homedir();
4552
- const hasCreds = fs3.existsSync(path4.join(home, ".aws", "credentials")) || fs3.existsSync(path4.join(home, ".aws", "config"));
4553
- return {
4554
- name: "AWS credentials",
4555
- status: hasCreds ? "pass" : "warn",
4556
- message: hasCreds ? "Found" : "Not found \u2014 may use env vars or IAM role",
4557
- detail: hasCreds ? void 0 : "Run: aws configure"
4558
- };
4559
- }));
4560
- const awsCfg = { region: config?.aws?.region, profile: config?.aws?.profile };
4561
- results.push(await runCheck("Testing DynamoDB access...", async () => {
4562
- if (!config) return { name: "DynamoDB", status: "skip", message: "No valid config" };
4563
- try {
4564
- const ok = await (0, import_adapters_dynamodb3.validateDynamoAccess)(config);
4565
- return {
4566
- name: "DynamoDB",
4567
- status: ok ? "pass" : "fail",
4568
- message: ok ? `Connected (profile: ${config.aws?.profile ?? "default"})` : "Cannot connect",
4569
- detail: ok ? void 0 : "Check IAM: dynamodb:ListTables, dynamodb:DescribeTable"
4570
- };
4571
- } catch (err) {
4572
- return { name: "DynamoDB", status: "fail", message: err instanceof Error ? err.message : String(err) };
4573
- }
4574
- }));
4575
- results.push(await runCheck("Testing SQS access...", async () => {
4576
- if (config?.sqs?.enabled === false) return { name: "SQS", status: "skip", message: "Disabled in config" };
4577
- try {
4578
- await (0, import_adapters_aws_services2.validateSQSAccess)(awsCfg);
4579
- return { name: "SQS", status: "pass", message: "Connected" };
4580
- } catch (err) {
4581
- return {
4582
- name: "SQS",
4583
- status: "warn",
4584
- message: err instanceof Error ? err.message : String(err),
4585
- detail: "Check IAM: sqs:ListQueues, sqs:GetQueueAttributes"
4586
- };
4587
- }
4588
- }));
4589
- results.push(await runCheck("Testing SNS access...", async () => {
4590
- if (config?.sns?.enabled === false) return { name: "SNS", status: "skip", message: "Disabled in config" };
4591
- try {
4592
- await (0, import_adapters_aws_services2.validateSNSAccess)(awsCfg);
4593
- return { name: "SNS", status: "pass", message: "Connected" };
4594
- } catch (err) {
4595
- return {
4596
- name: "SNS",
4597
- status: "warn",
4598
- message: err instanceof Error ? err.message : String(err),
4599
- detail: "Check IAM: sns:ListTopics, sns:GetTopicAttributes, sns:ListSubscriptionsByTopic"
4600
- };
4601
- }
4602
- }));
4603
- results.push(await runCheck("Testing SSM Parameter Store access...", async () => {
4604
- if (config?.ssm?.enabled === false) return { name: "SSM", status: "skip", message: "Disabled in config" };
4605
- try {
4606
- await (0, import_adapters_aws_services2.validateSSMAccess)(awsCfg);
4607
- return { name: "SSM", status: "pass", message: "Connected (metadata only)" };
4608
- } catch (err) {
4609
- return {
4610
- name: "SSM",
4611
- status: "warn",
4612
- message: err instanceof Error ? err.message : String(err),
4613
- detail: "Check IAM: ssm:DescribeParameters"
4614
- };
4615
- }
4616
- }));
4617
- results.push(await runCheck("Testing Secrets Manager access...", async () => {
4618
- if (config?.secretsManager?.enabled === false) return { name: "Secrets Manager", status: "skip", message: "Disabled in config" };
4619
- try {
4620
- await (0, import_adapters_aws_services2.validateSecretsAccess)(awsCfg);
4621
- return { name: "Secrets Manager", status: "pass", message: "Connected (names/rotation only)" };
4622
- } catch (err) {
4623
- return {
4624
- name: "Secrets Manager",
4625
- status: "warn",
4626
- message: err instanceof Error ? err.message : String(err),
4627
- detail: "Check IAM: secretsmanager:ListSecrets"
4628
- };
4629
- }
4630
- }));
4631
- results.push(await runCheck("Testing Lambda access...", async () => {
4632
- if (config?.lambda?.enabled === false) return { name: "Lambda", status: "skip", message: "Disabled in config" };
4633
- try {
4634
- await (0, import_adapters_aws_services2.validateLambdaAccess)(awsCfg);
4635
- return { name: "Lambda", status: "pass", message: "Connected" };
4636
- } catch (err) {
4637
- return {
4638
- name: "Lambda",
4639
- status: "warn",
4640
- message: err instanceof Error ? err.message : String(err),
4641
- detail: "Check IAM: lambda:ListFunctions"
4642
- };
4643
- }
4644
- }));
4645
- results.push(await runCheck("Testing CloudWatch Logs access...", async () => {
4646
- if (!config?.cloudwatchLogs?.enabled) return { name: "CloudWatch Logs", status: "skip", message: "Not enabled in config" };
4647
- try {
4648
- await (0, import_adapters_logs2.validateLogsAccess)(awsCfg);
4649
- return { name: "CloudWatch Logs", status: "pass", message: "Connected" };
4650
- } catch (err) {
4651
- return {
4652
- name: "CloudWatch Logs",
4653
- status: "warn",
4654
- message: err instanceof Error ? err.message : String(err),
4655
- detail: "Check IAM: logs:DescribeLogGroups, logs:FilterLogEvents"
4656
- };
4657
- }
4658
- }));
4659
- results.push(await runCheck("Testing PostgreSQL...", async () => {
4660
- if (!config?.postgres?.enabled || !config.postgres.connectionString) {
4661
- return { name: "PostgreSQL", status: "skip", message: "Not configured" };
4662
- }
4663
- try {
4664
- const ok = await (0, import_adapters_postgres2.validatePostgresAccess)(config.postgres.connectionString);
4665
- return {
4666
- name: "PostgreSQL",
4667
- status: ok ? "pass" : "fail",
4668
- message: ok ? "Connected" : "Cannot connect",
4669
- detail: ok ? void 0 : "Check connection string and security group"
4670
- };
4671
- } catch (err) {
4672
- return { name: "PostgreSQL", status: "fail", message: err instanceof Error ? err.message : String(err) };
4673
- }
4674
- }));
4675
- results.push(await runCheck("Testing MySQL...", async () => {
4676
- if (!config?.mysql?.enabled || !config.mysql.connectionString) {
4677
- return { name: "MySQL", status: "skip", message: "Not configured" };
4678
- }
4679
- try {
4680
- const ok = await (0, import_adapters_mysql2.validateMySQLAccess)(config.mysql.connectionString);
4681
- return {
4682
- name: "MySQL",
4683
- status: ok ? "pass" : "fail",
4684
- message: ok ? "Connected" : "Cannot connect",
4685
- detail: ok ? void 0 : "Check connection string, host, port 3306"
4686
- };
4687
- } catch (err) {
4688
- return { name: "MySQL", status: "fail", message: err instanceof Error ? err.message : String(err) };
4689
- }
4690
- }));
4691
- results.push(await runCheck("Testing MongoDB...", async () => {
4692
- if (!config?.mongodb?.enabled || !config.mongodb.connectionString) {
4693
- return { name: "MongoDB", status: "skip", message: "Not configured" };
4694
- }
4695
- try {
4696
- const ok = await (0, import_adapters_mongodb2.validateMongoAccess)(config.mongodb.connectionString);
4697
- return {
4698
- name: "MongoDB",
4699
- status: ok ? "pass" : "fail",
4700
- message: ok ? "Connected" : "Cannot connect",
4701
- detail: ok ? void 0 : "Check connection string and port 27017"
4702
- };
4703
- } catch (err) {
4704
- return { name: "MongoDB", status: "fail", message: err instanceof Error ? err.message : String(err) };
4705
- }
4706
- }));
4707
- results.push(await runCheck("Detecting project type...", async () => {
4708
- const hasTsConfig = fs3.existsSync(path4.join(process.cwd(), "tsconfig.json"));
4709
- const hasPkg = fs3.existsSync(path4.join(process.cwd(), "package.json"));
4710
- return {
4711
- name: "Project type",
4712
- status: hasTsConfig ? "pass" : "warn",
4713
- message: hasTsConfig ? "TypeScript" : hasPkg ? "JavaScript (no tsconfig.json)" : "Unknown",
4714
- detail: hasTsConfig ? void 0 : "Scanner works best with TypeScript projects"
4715
- };
4716
- }));
4717
- results.push(await runCheck("Detecting IaC files...", async () => {
4718
- const cwd = process.cwd();
4719
- const hasTF = fs3.existsSync(path4.join(cwd, "main.tf")) || fs3.readdirSync(cwd).some((f) => f.endsWith(".tf"));
4720
- const hasCFN = fs3.existsSync(path4.join(cwd, "template.yaml")) || fs3.existsSync(path4.join(cwd, "template.json"));
4721
- const hasCDK = fs3.existsSync(path4.join(cwd, "cdk.json"));
4722
- const hasCDKOut = fs3.existsSync(path4.join(cwd, "cdk.out"));
4723
- const found = [];
4724
- if (hasTF) found.push("Terraform");
4725
- if (hasCFN) found.push("CloudFormation");
4726
- if (hasCDK) found.push(`CDK${hasCDKOut ? " (synthesized)" : " (run cdk synth)"}`);
4727
- return {
4728
- name: "IaC files",
4729
- status: found.length > 0 ? "pass" : "warn",
4730
- message: found.length > 0 ? found.join(", ") : "No Terraform/CFN/CDK files detected",
4731
- detail: found.length === 0 ? "IaC analysis will be skipped without TF/CFN/CDK files" : void 0
4732
- };
4733
- }));
4734
- results.push(await runCheck("Checking analysis cache...", async () => {
4735
- const cached = fs3.existsSync(path4.join(process.cwd(), ".infrawise", "cache", "graph.json"));
4736
- return {
4737
- name: "Analysis cache",
4738
- status: cached ? "pass" : "warn",
4739
- message: cached ? "Cached results found" : "No cache \u2014 run infrawise analyze first"
4740
- };
4741
- }));
4742
- const passed = results.filter((r) => r.status === "pass").length;
4743
- const failed = results.filter((r) => r.status === "fail").length;
4744
- const warned = results.filter((r) => r.status === "warn").length;
4745
- console.log("");
4746
- console.log(import_chalk6.default.dim(" " + "\u2500".repeat(40)));
4747
- if (failed === 0) {
4748
- console.log(` ${import_chalk6.default.green.bold("All checks passed")} ${import_chalk6.default.dim(`${passed} passed, ${warned} warning(s)`)}`);
4749
- } else {
4750
- console.log(` ${import_chalk6.default.red.bold(`${failed} check(s) failed`)} ${import_chalk6.default.dim(`${passed} passed, ${warned} warning(s)`)}`);
4751
- }
4752
- console.log("");
4753
- if (failed > 0) process.exit(1);
4754
- }
4755
-
4756
- // src/index.ts
4757
- var program = new import_commander.Command();
4758
- program.name("infrawise").description("CLI-first infrastructure intelligence platform for DynamoDB and PostgreSQL").version("0.1.0");
4759
- program.command("init").description("Detect AWS profile/region, discover DynamoDB tables, and generate infrawise.yaml").option("--force", "Overwrite existing infrawise.yaml").action(async (options) => {
4760
- printBanner();
4761
- await runInit({ force: options.force });
4762
- });
4763
- program.command("auth").description("Validate and select AWS profile from ~/.aws/credentials").action(async () => {
4764
- printBanner();
4765
- await runAuth();
4766
- });
4767
- program.command("analyze").description("Load config, run extractors, build graph, and run all analyzers").option("-c, --config <path>", "Path to infrawise.yaml", "infrawise.yaml").option("-r, --repo <path>", "Path to repository to scan", process.cwd()).option("--no-cache", "Skip reading/writing the cache").action(async (options) => {
4768
- printBanner();
4769
- await runAnalyze({
4770
- config: options.config !== "infrawise.yaml" ? options.config : void 0,
4771
- repo: options.repo,
4772
- noCache: !options.cache
4773
- });
4774
- });
4775
- program.command("dev").description("Start Fastify MCP server on localhost:3000").option("-c, --config <path>", "Path to infrawise.yaml", "infrawise.yaml").option("-p, --port <number>", "Port to listen on", "3000").action(async (options) => {
4776
- printBanner();
4777
- await runDev({
4778
- config: options.config !== "infrawise.yaml" ? options.config : void 0,
4779
- port: parseInt(options.port, 10)
4780
- });
4781
- });
4782
- program.command("doctor").description("Validate AWS access, postgres connectivity, config, and repo scan").option("-c, --config <path>", "Path to infrawise.yaml", "infrawise.yaml").action(async (options) => {
4783
- printBanner();
4784
- await runDoctor({
4785
- config: options.config !== "infrawise.yaml" ? options.config : void 0
4786
- });
4787
- });
4788
- program.exitOverride((err) => {
4789
- if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
4790
- process.exit(0);
4791
- }
4792
- console.error(`
4793
- Error: ${err.message}`);
4794
- process.exit(1);
4795
- });
4796
- process.on("unhandledRejection", (reason) => {
4797
- console.error("\nUnhandled error:", reason instanceof Error ? reason.message : String(reason));
4798
- process.exit(1);
4799
- });
4800
- program.parse(process.argv);