infrawise 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +425 -0
  2. package/dist/index.js +3462 -0
  3. package/package.json +92 -0
package/dist/index.js ADDED
@@ -0,0 +1,3462 @@
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({
85
+ includeTables: zod_1.z.array(zod_1.z.string()).optional()
86
+ }).optional(),
87
+ postgres: zod_1.z.object({
88
+ enabled: zod_1.z.boolean().optional().default(false),
89
+ connectionString: zod_1.z.string().optional()
90
+ }).optional(),
91
+ mysql: zod_1.z.object({
92
+ enabled: zod_1.z.boolean().optional().default(false),
93
+ connectionString: zod_1.z.string().optional()
94
+ }).optional(),
95
+ mongodb: zod_1.z.object({
96
+ enabled: zod_1.z.boolean().optional().default(false),
97
+ connectionString: zod_1.z.string().optional(),
98
+ databases: zod_1.z.array(zod_1.z.string()).optional()
99
+ }).optional(),
100
+ terraform: zod_1.z.object({
101
+ enabled: zod_1.z.boolean().optional().default(true)
102
+ }).optional(),
103
+ analysis: zod_1.z.object({
104
+ sampleSize: zod_1.z.number().int().positive().optional().default(100)
105
+ }).optional()
106
+ });
107
+ var ConfigError = class extends Error {
108
+ details;
109
+ constructor(message, details) {
110
+ super(message);
111
+ this.details = details;
112
+ this.name = "ConfigError";
113
+ }
114
+ };
115
+ exports2.ConfigError = ConfigError;
116
+ function loadConfig4(configPath) {
117
+ const resolvedPath = configPath ? path5.resolve(configPath) : path5.resolve(process.cwd(), "infrawise.yaml");
118
+ if (!fs4.existsSync(resolvedPath)) {
119
+ throw new ConfigError(`Configuration file not found at: ${resolvedPath}`, [
120
+ "Run `infrawise init` to generate a configuration file",
121
+ `Or specify a path with --config <path>`
122
+ ]);
123
+ }
124
+ let rawContent;
125
+ try {
126
+ rawContent = fs4.readFileSync(resolvedPath, "utf-8");
127
+ } catch (err) {
128
+ throw new ConfigError(`Unable to read configuration file: ${resolvedPath}`, [
129
+ "Check file permissions",
130
+ String(err)
131
+ ]);
132
+ }
133
+ let parsedYaml;
134
+ try {
135
+ parsedYaml = yaml.load(rawContent);
136
+ } catch (err) {
137
+ throw new ConfigError(`Invalid YAML in configuration file: ${resolvedPath}`, [
138
+ "Check the YAML syntax",
139
+ String(err)
140
+ ]);
141
+ }
142
+ const result = exports2.InfrawiseConfigSchema.safeParse(parsedYaml);
143
+ if (!result.success) {
144
+ const details = result.error.errors.map((e) => ` - ${e.path.join(".")}: ${e.message}`);
145
+ throw new ConfigError("Configuration validation failed", details);
146
+ }
147
+ return result.data;
148
+ }
149
+ function generateDefaultConfig2(projectName, options) {
150
+ const config = {
151
+ project: projectName,
152
+ aws: {
153
+ profile: options?.aws?.profile ?? "default",
154
+ region: options?.aws?.region ?? "us-east-1"
155
+ },
156
+ dynamodb: {
157
+ includeTables: options?.dynamodb?.includeTables ?? []
158
+ },
159
+ postgres: {
160
+ enabled: options?.postgres?.enabled ?? false,
161
+ connectionString: options?.postgres?.connectionString ?? ""
162
+ },
163
+ mysql: {
164
+ enabled: options?.mysql?.enabled ?? false,
165
+ connectionString: options?.mysql?.connectionString ?? ""
166
+ },
167
+ mongodb: {
168
+ enabled: options?.mongodb?.enabled ?? false,
169
+ connectionString: options?.mongodb?.connectionString ?? "",
170
+ databases: options?.mongodb?.databases ?? []
171
+ },
172
+ terraform: {
173
+ enabled: options?.terraform?.enabled ?? true
174
+ },
175
+ analysis: {
176
+ sampleSize: options?.analysis?.sampleSize ?? 100
177
+ }
178
+ };
179
+ return yaml.dump(config, { lineWidth: 120 });
180
+ }
181
+ }
182
+ });
183
+
184
+ // ../core/dist/logger.js
185
+ var require_logger = __commonJS({
186
+ "../core/dist/logger.js"(exports2) {
187
+ "use strict";
188
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
189
+ return mod && mod.__esModule ? mod : { "default": mod };
190
+ };
191
+ Object.defineProperty(exports2, "__esModule", { value: true });
192
+ exports2.logger = void 0;
193
+ var pino_1 = __importDefault(require("pino"));
194
+ function createLogger() {
195
+ const isDevelopment = process.env.NODE_ENV !== "production";
196
+ if (isDevelopment) {
197
+ return (0, pino_1.default)({
198
+ level: process.env.LOG_LEVEL ?? "info",
199
+ transport: {
200
+ target: "pino-pretty",
201
+ options: {
202
+ colorize: true,
203
+ translateTime: "HH:MM:ss",
204
+ ignore: "pid,hostname",
205
+ messageFormat: "{msg}"
206
+ }
207
+ }
208
+ });
209
+ }
210
+ return (0, pino_1.default)({
211
+ level: process.env.LOG_LEVEL ?? "info"
212
+ });
213
+ }
214
+ exports2.logger = createLogger();
215
+ }
216
+ });
217
+
218
+ // ../core/dist/errors.js
219
+ var require_errors = __commonJS({
220
+ "../core/dist/errors.js"(exports2) {
221
+ "use strict";
222
+ Object.defineProperty(exports2, "__esModule", { value: true });
223
+ exports2.ConfigError = exports2.RepositoryScanError = exports2.PostgresConnectionError = exports2.DynamoDBError = exports2.AWSConnectionError = exports2.InfrawiseError = void 0;
224
+ exports2.formatError = formatError3;
225
+ var InfrawiseError = class extends Error {
226
+ reasons;
227
+ remediation;
228
+ constructor(message, reasons, remediation) {
229
+ super(message);
230
+ this.reasons = reasons;
231
+ this.remediation = remediation;
232
+ this.name = "InfrawiseError";
233
+ }
234
+ format() {
235
+ const lines = [`
236
+ ${this.message}
237
+ `];
238
+ if (this.reasons && this.reasons.length > 0) {
239
+ lines.push("Possible reasons:");
240
+ for (const reason of this.reasons) {
241
+ lines.push(` - ${reason}`);
242
+ }
243
+ lines.push("");
244
+ }
245
+ if (this.remediation) {
246
+ lines.push(`Run: ${this.remediation}`);
247
+ }
248
+ return lines.join("\n");
249
+ }
250
+ };
251
+ exports2.InfrawiseError = InfrawiseError;
252
+ var AWSConnectionError = class extends InfrawiseError {
253
+ constructor(details) {
254
+ super("Unable to connect to AWS.", [
255
+ "Invalid or missing AWS credentials",
256
+ "Incorrect AWS profile specified",
257
+ "Network connectivity issues",
258
+ details ?? "Unexpected AWS error"
259
+ ], "infrawise doctor");
260
+ this.name = "AWSConnectionError";
261
+ }
262
+ };
263
+ exports2.AWSConnectionError = AWSConnectionError;
264
+ var DynamoDBError = class extends InfrawiseError {
265
+ constructor(details) {
266
+ super("Unable to access DynamoDB.", [
267
+ "Insufficient IAM permissions (need dynamodb:ListTables, dynamodb:DescribeTable)",
268
+ "Wrong AWS region configured",
269
+ "DynamoDB endpoint not reachable",
270
+ details ?? "Unexpected DynamoDB error"
271
+ ], "infrawise doctor");
272
+ this.name = "DynamoDBError";
273
+ }
274
+ };
275
+ exports2.DynamoDBError = DynamoDBError;
276
+ var PostgresConnectionError = class extends InfrawiseError {
277
+ constructor(details) {
278
+ super("Unable to connect to PostgreSQL.", [
279
+ "Invalid connection string",
280
+ "Security group restrictions",
281
+ "Expired credentials",
282
+ details ?? "Unexpected PostgreSQL error"
283
+ ], "infrawise doctor");
284
+ this.name = "PostgresConnectionError";
285
+ }
286
+ };
287
+ exports2.PostgresConnectionError = PostgresConnectionError;
288
+ var RepositoryScanError = class extends InfrawiseError {
289
+ constructor(details) {
290
+ super("Unable to scan repository.", [
291
+ "Path does not exist or is not accessible",
292
+ "Not a valid TypeScript project",
293
+ "tsconfig.json not found",
294
+ details ?? "Unexpected scan error"
295
+ ], "infrawise doctor");
296
+ this.name = "RepositoryScanError";
297
+ }
298
+ };
299
+ exports2.RepositoryScanError = RepositoryScanError;
300
+ var ConfigError = class extends InfrawiseError {
301
+ constructor(details) {
302
+ super("Invalid or missing configuration.", [
303
+ "infrawise.yaml not found in current directory",
304
+ "Missing required fields in configuration",
305
+ details ?? "Unexpected config error"
306
+ ], "infrawise init");
307
+ this.name = "ConfigError";
308
+ }
309
+ };
310
+ exports2.ConfigError = ConfigError;
311
+ function formatError3(err) {
312
+ if (err instanceof InfrawiseError) {
313
+ return err.format();
314
+ }
315
+ if (err instanceof Error) {
316
+ return `
317
+ Unexpected error: ${err.message}
318
+ `;
319
+ }
320
+ return `
321
+ Unexpected error: ${String(err)}
322
+ `;
323
+ }
324
+ }
325
+ });
326
+
327
+ // ../core/dist/cache.js
328
+ var require_cache = __commonJS({
329
+ "../core/dist/cache.js"(exports2) {
330
+ "use strict";
331
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
332
+ if (k2 === void 0) k2 = k;
333
+ var desc = Object.getOwnPropertyDescriptor(m, k);
334
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
335
+ desc = { enumerable: true, get: function() {
336
+ return m[k];
337
+ } };
338
+ }
339
+ Object.defineProperty(o, k2, desc);
340
+ }) : (function(o, m, k, k2) {
341
+ if (k2 === void 0) k2 = k;
342
+ o[k2] = m[k];
343
+ }));
344
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
345
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
346
+ }) : function(o, v) {
347
+ o["default"] = v;
348
+ });
349
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
350
+ var ownKeys = function(o) {
351
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
352
+ var ar = [];
353
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
354
+ return ar;
355
+ };
356
+ return ownKeys(o);
357
+ };
358
+ return function(mod) {
359
+ if (mod && mod.__esModule) return mod;
360
+ var result = {};
361
+ if (mod != null) {
362
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
363
+ }
364
+ __setModuleDefault(result, mod);
365
+ return result;
366
+ };
367
+ })();
368
+ Object.defineProperty(exports2, "__esModule", { value: true });
369
+ exports2.writeCache = writeCache2;
370
+ exports2.readCache = readCache2;
371
+ exports2.clearCache = clearCache;
372
+ var fs4 = __importStar(require("fs"));
373
+ var path5 = __importStar(require("path"));
374
+ var CACHE_VERSION = "1.0.0";
375
+ var CACHE_DIR = path5.join(process.cwd(), ".infrawise", "cache");
376
+ function ensureCacheDir() {
377
+ if (!fs4.existsSync(CACHE_DIR)) {
378
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
379
+ }
380
+ }
381
+ function writeCache2(key, data) {
382
+ ensureCacheDir();
383
+ const entry = {
384
+ timestamp: Date.now(),
385
+ data,
386
+ version: CACHE_VERSION
387
+ };
388
+ const filePath = path5.join(CACHE_DIR, `${key}.json`);
389
+ fs4.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
390
+ }
391
+ function readCache2(key, maxAgeMs = 36e5) {
392
+ const filePath = path5.join(CACHE_DIR, `${key}.json`);
393
+ if (!fs4.existsSync(filePath))
394
+ return null;
395
+ try {
396
+ const raw = fs4.readFileSync(filePath, "utf-8");
397
+ const entry = JSON.parse(raw);
398
+ if (entry.version !== CACHE_VERSION)
399
+ return null;
400
+ if (Date.now() - entry.timestamp > maxAgeMs)
401
+ return null;
402
+ return entry.data;
403
+ } catch {
404
+ return null;
405
+ }
406
+ }
407
+ function clearCache(key) {
408
+ if (key) {
409
+ const filePath = path5.join(CACHE_DIR, `${key}.json`);
410
+ if (fs4.existsSync(filePath))
411
+ fs4.unlinkSync(filePath);
412
+ } else {
413
+ if (fs4.existsSync(CACHE_DIR)) {
414
+ const files = fs4.readdirSync(CACHE_DIR);
415
+ for (const file of files) {
416
+ fs4.unlinkSync(path5.join(CACHE_DIR, file));
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ });
423
+
424
+ // ../core/dist/index.js
425
+ var require_dist = __commonJS({
426
+ "../core/dist/index.js"(exports2) {
427
+ "use strict";
428
+ Object.defineProperty(exports2, "__esModule", { value: true });
429
+ 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;
430
+ var config_1 = require_config();
431
+ Object.defineProperty(exports2, "loadConfig", { enumerable: true, get: function() {
432
+ return config_1.loadConfig;
433
+ } });
434
+ Object.defineProperty(exports2, "generateDefaultConfig", { enumerable: true, get: function() {
435
+ return config_1.generateDefaultConfig;
436
+ } });
437
+ Object.defineProperty(exports2, "InfrawiseConfigSchema", { enumerable: true, get: function() {
438
+ return config_1.InfrawiseConfigSchema;
439
+ } });
440
+ Object.defineProperty(exports2, "ConfigValidationError", { enumerable: true, get: function() {
441
+ return config_1.ConfigError;
442
+ } });
443
+ var logger_1 = require_logger();
444
+ Object.defineProperty(exports2, "logger", { enumerable: true, get: function() {
445
+ return logger_1.logger;
446
+ } });
447
+ var errors_1 = require_errors();
448
+ Object.defineProperty(exports2, "InfrawiseError", { enumerable: true, get: function() {
449
+ return errors_1.InfrawiseError;
450
+ } });
451
+ Object.defineProperty(exports2, "AWSConnectionError", { enumerable: true, get: function() {
452
+ return errors_1.AWSConnectionError;
453
+ } });
454
+ Object.defineProperty(exports2, "DynamoDBError", { enumerable: true, get: function() {
455
+ return errors_1.DynamoDBError;
456
+ } });
457
+ Object.defineProperty(exports2, "PostgresConnectionError", { enumerable: true, get: function() {
458
+ return errors_1.PostgresConnectionError;
459
+ } });
460
+ Object.defineProperty(exports2, "RepositoryScanError", { enumerable: true, get: function() {
461
+ return errors_1.RepositoryScanError;
462
+ } });
463
+ Object.defineProperty(exports2, "ConfigError", { enumerable: true, get: function() {
464
+ return errors_1.ConfigError;
465
+ } });
466
+ Object.defineProperty(exports2, "formatError", { enumerable: true, get: function() {
467
+ return errors_1.formatError;
468
+ } });
469
+ var cache_1 = require_cache();
470
+ Object.defineProperty(exports2, "writeCache", { enumerable: true, get: function() {
471
+ return cache_1.writeCache;
472
+ } });
473
+ Object.defineProperty(exports2, "readCache", { enumerable: true, get: function() {
474
+ return cache_1.readCache;
475
+ } });
476
+ Object.defineProperty(exports2, "clearCache", { enumerable: true, get: function() {
477
+ return cache_1.clearCache;
478
+ } });
479
+ }
480
+ });
481
+
482
+ // ../adapters/dynamodb/dist/index.js
483
+ var require_dist2 = __commonJS({
484
+ "../adapters/dynamodb/dist/index.js"(exports2) {
485
+ "use strict";
486
+ Object.defineProperty(exports2, "__esModule", { value: true });
487
+ exports2.extractDynamoMetadata = extractDynamoMetadata2;
488
+ exports2.validateDynamoAccess = validateDynamoAccess3;
489
+ var client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
490
+ var credential_providers_1 = require("@aws-sdk/credential-providers");
491
+ var core_1 = require_dist();
492
+ function createDynamoClient(config) {
493
+ const region = config.aws?.region ?? "us-east-1";
494
+ const profile = config.aws?.profile;
495
+ const clientConfig = { region };
496
+ if (profile) {
497
+ clientConfig.credentials = (0, credential_providers_1.fromIni)({ profile });
498
+ }
499
+ return new client_dynamodb_1.DynamoDBClient(clientConfig);
500
+ }
501
+ function parseTableDescription(desc) {
502
+ const tableName = desc.TableName ?? "unknown";
503
+ const partitionKey = desc.KeySchema?.find((k) => k.KeyType === "HASH")?.AttributeName;
504
+ const sortKey = desc.KeySchema?.find((k) => k.KeyType === "RANGE")?.AttributeName;
505
+ const indexes = [];
506
+ if (desc.GlobalSecondaryIndexes) {
507
+ for (const gsi of desc.GlobalSecondaryIndexes) {
508
+ if (gsi.IndexName)
509
+ indexes.push(gsi.IndexName);
510
+ }
511
+ }
512
+ if (desc.LocalSecondaryIndexes) {
513
+ for (const lsi of desc.LocalSecondaryIndexes) {
514
+ if (lsi.IndexName)
515
+ indexes.push(lsi.IndexName);
516
+ }
517
+ }
518
+ return { tableName, partitionKey, sortKey, indexes };
519
+ }
520
+ async function listAllTables(client) {
521
+ const tableNames = [];
522
+ let lastEvaluatedTableName;
523
+ do {
524
+ const command = new client_dynamodb_1.ListTablesCommand({
525
+ ExclusiveStartTableName: lastEvaluatedTableName,
526
+ Limit: 100
527
+ });
528
+ const response = await client.send(command);
529
+ if (response.TableNames) {
530
+ tableNames.push(...response.TableNames);
531
+ }
532
+ lastEvaluatedTableName = response.LastEvaluatedTableName;
533
+ } while (lastEvaluatedTableName);
534
+ return tableNames;
535
+ }
536
+ async function extractDynamoMetadata2(config) {
537
+ const client = createDynamoClient(config);
538
+ const includeTables = config.dynamodb?.includeTables;
539
+ let tableNames;
540
+ try {
541
+ const allTables = await listAllTables(client);
542
+ if (includeTables && includeTables.length > 0) {
543
+ tableNames = allTables.filter((name) => includeTables.includes(name));
544
+ core_1.logger.debug(`Filtered to ${tableNames.length} tables from config`);
545
+ } else {
546
+ tableNames = allTables;
547
+ }
548
+ core_1.logger.info(`Found ${tableNames.length} DynamoDB table(s)`);
549
+ } catch (err) {
550
+ throw new core_1.DynamoDBError(err instanceof Error ? err.message : "Failed to list DynamoDB tables");
551
+ }
552
+ const results = [];
553
+ for (const tableName of tableNames) {
554
+ try {
555
+ const command = new client_dynamodb_1.DescribeTableCommand({ TableName: tableName });
556
+ const response = await client.send(command);
557
+ if (response.Table) {
558
+ results.push(parseTableDescription(response.Table));
559
+ core_1.logger.debug(`Described table: ${tableName}`);
560
+ }
561
+ } catch (err) {
562
+ core_1.logger.warn(`Failed to describe table ${tableName}: ${err instanceof Error ? err.message : String(err)}`);
563
+ }
564
+ }
565
+ return results;
566
+ }
567
+ async function validateDynamoAccess3(config) {
568
+ const client = createDynamoClient(config);
569
+ try {
570
+ await client.send(new client_dynamodb_1.ListTablesCommand({ Limit: 1 }));
571
+ return true;
572
+ } catch {
573
+ return false;
574
+ }
575
+ }
576
+ }
577
+ });
578
+
579
+ // ../adapters/postgres/dist/index.js
580
+ var require_dist3 = __commonJS({
581
+ "../adapters/postgres/dist/index.js"(exports2) {
582
+ "use strict";
583
+ Object.defineProperty(exports2, "__esModule", { value: true });
584
+ exports2.extractPostgresMetadata = extractPostgresMetadata2;
585
+ exports2.validatePostgresAccess = validatePostgresAccess2;
586
+ var pg_1 = require("pg");
587
+ var core_1 = require_dist();
588
+ async function extractPostgresMetadata2(connectionString) {
589
+ const pool = new pg_1.Pool({
590
+ connectionString,
591
+ max: 1,
592
+ idleTimeoutMillis: 1e4,
593
+ connectionTimeoutMillis: 5e3
594
+ });
595
+ try {
596
+ const client = await pool.connect();
597
+ try {
598
+ const tablesResult = await client.query(`
599
+ SELECT table_schema, table_name
600
+ FROM information_schema.tables
601
+ WHERE table_type = 'BASE TABLE'
602
+ AND table_schema NOT IN ('pg_catalog', 'information_schema')
603
+ ORDER BY table_schema, table_name
604
+ `);
605
+ core_1.logger.info(`Found ${tablesResult.rows.length} PostgreSQL table(s)`);
606
+ const columnsResult = await client.query(`
607
+ SELECT table_schema, table_name, column_name
608
+ FROM information_schema.columns
609
+ WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
610
+ ORDER BY table_schema, table_name, ordinal_position
611
+ `);
612
+ const indexesResult = await client.query(`
613
+ SELECT schemaname, tablename, indexname
614
+ FROM pg_indexes
615
+ WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
616
+ ORDER BY schemaname, tablename, indexname
617
+ `);
618
+ const primaryKeysResult = await client.query(`
619
+ SELECT
620
+ tc.table_schema,
621
+ tc.table_name,
622
+ kcu.column_name
623
+ FROM information_schema.table_constraints tc
624
+ JOIN information_schema.key_column_usage kcu
625
+ ON tc.constraint_name = kcu.constraint_name
626
+ AND tc.table_schema = kcu.table_schema
627
+ WHERE tc.constraint_type = 'PRIMARY KEY'
628
+ AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
629
+ ORDER BY tc.table_schema, tc.table_name
630
+ `);
631
+ const columnMap = /* @__PURE__ */ new Map();
632
+ for (const row of columnsResult.rows) {
633
+ const key = `${row.table_schema}.${row.table_name}`;
634
+ if (!columnMap.has(key))
635
+ columnMap.set(key, []);
636
+ columnMap.get(key).push(row.column_name);
637
+ }
638
+ const indexMap = /* @__PURE__ */ new Map();
639
+ for (const row of indexesResult.rows) {
640
+ const key = `${row.schemaname}.${row.tablename}`;
641
+ if (!indexMap.has(key))
642
+ indexMap.set(key, []);
643
+ indexMap.get(key).push(row.indexname);
644
+ }
645
+ const pkMap = /* @__PURE__ */ new Map();
646
+ for (const row of primaryKeysResult.rows) {
647
+ const key = `${row.table_schema}.${row.table_name}`;
648
+ if (!pkMap.has(key))
649
+ pkMap.set(key, []);
650
+ pkMap.get(key).push(row.column_name);
651
+ }
652
+ const results = [];
653
+ for (const table of tablesResult.rows) {
654
+ const key = `${table.table_schema}.${table.table_name}`;
655
+ results.push({
656
+ schema: table.table_schema,
657
+ table: table.table_name,
658
+ columns: columnMap.get(key) ?? [],
659
+ indexes: indexMap.get(key) ?? [],
660
+ primaryKeys: pkMap.get(key) ?? []
661
+ });
662
+ }
663
+ return results;
664
+ } finally {
665
+ client.release();
666
+ }
667
+ } catch (err) {
668
+ if (err instanceof core_1.PostgresConnectionError)
669
+ throw err;
670
+ throw new core_1.PostgresConnectionError(err instanceof Error ? err.message : "Unknown error connecting to PostgreSQL");
671
+ } finally {
672
+ await pool.end();
673
+ }
674
+ }
675
+ async function validatePostgresAccess2(connectionString) {
676
+ const pool = new pg_1.Pool({
677
+ connectionString,
678
+ max: 1,
679
+ connectionTimeoutMillis: 5e3
680
+ });
681
+ try {
682
+ const client = await pool.connect();
683
+ await client.query("SELECT 1");
684
+ client.release();
685
+ return true;
686
+ } catch {
687
+ return false;
688
+ } finally {
689
+ await pool.end();
690
+ }
691
+ }
692
+ }
693
+ });
694
+
695
+ // ../adapters/mysql/dist/index.js
696
+ var require_dist4 = __commonJS({
697
+ "../adapters/mysql/dist/index.js"(exports2) {
698
+ "use strict";
699
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
700
+ return mod && mod.__esModule ? mod : { "default": mod };
701
+ };
702
+ Object.defineProperty(exports2, "__esModule", { value: true });
703
+ exports2.MySQLConnectionError = void 0;
704
+ exports2.extractMySQLMetadata = extractMySQLMetadata2;
705
+ exports2.validateMySQLAccess = validateMySQLAccess2;
706
+ var promise_1 = __importDefault(require("mysql2/promise"));
707
+ var core_1 = require_dist();
708
+ var SYSTEM_SCHEMAS = /* @__PURE__ */ new Set(["information_schema", "performance_schema", "mysql", "sys"]);
709
+ var MySQLConnectionError = class extends core_1.InfrawiseError {
710
+ constructor(details) {
711
+ 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);
712
+ this.name = "MySQLConnectionError";
713
+ if (details) {
714
+ this.message = `Unable to connect to MySQL.
715
+
716
+ Possible reasons:
717
+ - invalid connection string
718
+ - port 3306 not accessible
719
+ - wrong credentials
720
+
721
+ Run: infrawise doctor
722
+
723
+ Detail: ${details}`;
724
+ }
725
+ }
726
+ };
727
+ exports2.MySQLConnectionError = MySQLConnectionError;
728
+ async function extractMySQLMetadata2(connectionString) {
729
+ let connection;
730
+ try {
731
+ connection = await promise_1.default.createConnection(connectionString);
732
+ const [tableRows] = await connection.execute(`
733
+ SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE
734
+ FROM information_schema.tables
735
+ WHERE TABLE_TYPE = 'BASE TABLE'
736
+ AND TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => "?").join(", ")})
737
+ ORDER BY TABLE_SCHEMA, TABLE_NAME
738
+ `, [...SYSTEM_SCHEMAS]);
739
+ core_1.logger.info(`Found ${tableRows.length} MySQL table(s)`);
740
+ const [columnRows] = await connection.execute(`
741
+ SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
742
+ FROM information_schema.columns
743
+ WHERE TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => "?").join(", ")})
744
+ ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
745
+ `, [...SYSTEM_SCHEMAS]);
746
+ const [indexRows] = await connection.execute(`
747
+ SELECT TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
748
+ FROM information_schema.statistics
749
+ WHERE TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => "?").join(", ")})
750
+ GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
751
+ ORDER BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
752
+ `, [...SYSTEM_SCHEMAS]);
753
+ const [pkRows] = await connection.execute(`
754
+ SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
755
+ FROM information_schema.key_column_usage
756
+ WHERE CONSTRAINT_NAME = 'PRIMARY'
757
+ AND TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => "?").join(", ")})
758
+ ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
759
+ `, [...SYSTEM_SCHEMAS]);
760
+ const columnMap = /* @__PURE__ */ new Map();
761
+ for (const row of columnRows) {
762
+ const key = `${row["TABLE_SCHEMA"]}.${row["TABLE_NAME"]}`;
763
+ if (!columnMap.has(key))
764
+ columnMap.set(key, []);
765
+ columnMap.get(key).push(row["COLUMN_NAME"]);
766
+ }
767
+ const indexMap = /* @__PURE__ */ new Map();
768
+ for (const row of indexRows) {
769
+ const key = `${row["TABLE_SCHEMA"]}.${row["TABLE_NAME"]}`;
770
+ if (!indexMap.has(key))
771
+ indexMap.set(key, []);
772
+ const idxName = row["INDEX_NAME"];
773
+ if (!indexMap.get(key).includes(idxName)) {
774
+ indexMap.get(key).push(idxName);
775
+ }
776
+ }
777
+ const pkMap = /* @__PURE__ */ new Map();
778
+ for (const row of pkRows) {
779
+ const key = `${row["TABLE_SCHEMA"]}.${row["TABLE_NAME"]}`;
780
+ if (!pkMap.has(key))
781
+ pkMap.set(key, []);
782
+ pkMap.get(key).push(row["COLUMN_NAME"]);
783
+ }
784
+ const results = [];
785
+ for (const row of tableRows) {
786
+ const schema = row["TABLE_SCHEMA"];
787
+ const table = row["TABLE_NAME"];
788
+ const engine = row["ENGINE"] ?? "InnoDB";
789
+ const key = `${schema}.${table}`;
790
+ results.push({
791
+ schema,
792
+ table,
793
+ columns: columnMap.get(key) ?? [],
794
+ indexes: indexMap.get(key) ?? [],
795
+ primaryKeys: pkMap.get(key) ?? [],
796
+ engine
797
+ });
798
+ }
799
+ return results;
800
+ } catch (err) {
801
+ if (err instanceof MySQLConnectionError)
802
+ throw err;
803
+ throw new MySQLConnectionError(err instanceof Error ? err.message : String(err));
804
+ } finally {
805
+ if (connection) {
806
+ await connection.end().catch(() => void 0);
807
+ }
808
+ }
809
+ }
810
+ async function validateMySQLAccess2(connectionString) {
811
+ let connection;
812
+ try {
813
+ connection = await promise_1.default.createConnection(connectionString);
814
+ await connection.execute("SELECT 1");
815
+ return true;
816
+ } catch {
817
+ return false;
818
+ } finally {
819
+ if (connection) {
820
+ await connection.end().catch(() => void 0);
821
+ }
822
+ }
823
+ }
824
+ }
825
+ });
826
+
827
+ // ../adapters/mongodb/dist/index.js
828
+ var require_dist5 = __commonJS({
829
+ "../adapters/mongodb/dist/index.js"(exports2) {
830
+ "use strict";
831
+ Object.defineProperty(exports2, "__esModule", { value: true });
832
+ exports2.MongoConnectionError = void 0;
833
+ exports2.extractMongoMetadata = extractMongoMetadata2;
834
+ exports2.validateMongoAccess = validateMongoAccess2;
835
+ var mongodb_1 = require("mongodb");
836
+ var core_1 = require_dist();
837
+ var SYSTEM_DATABASES = /* @__PURE__ */ new Set(["admin", "local", "config"]);
838
+ var MongoConnectionError = class extends core_1.InfrawiseError {
839
+ constructor(details) {
840
+ 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);
841
+ this.name = "MongoConnectionError";
842
+ if (details) {
843
+ this.message = `Unable to connect to MongoDB.
844
+
845
+ Possible reasons:
846
+ - invalid connection string
847
+ - port 27017 not accessible
848
+ - wrong credentials
849
+
850
+ Run: infrawise doctor
851
+
852
+ Detail: ${details}`;
853
+ }
854
+ }
855
+ };
856
+ exports2.MongoConnectionError = MongoConnectionError;
857
+ async function extractMongoMetadata2(connectionString, databases) {
858
+ const client = new mongodb_1.MongoClient(connectionString, {
859
+ serverSelectionTimeoutMS: 5e3,
860
+ connectTimeoutMS: 5e3
861
+ });
862
+ try {
863
+ await client.connect();
864
+ let dbNames;
865
+ if (databases && databases.length > 0) {
866
+ dbNames = databases;
867
+ } else {
868
+ const adminDb = client.db("admin");
869
+ const dbList = await adminDb.admin().listDatabases();
870
+ dbNames = dbList.databases.map((d) => d.name).filter((name) => !SYSTEM_DATABASES.has(name));
871
+ }
872
+ core_1.logger.info(`Introspecting ${dbNames.length} MongoDB database(s)`);
873
+ const results = [];
874
+ for (const dbName of dbNames) {
875
+ const db = client.db(dbName);
876
+ let collectionNames;
877
+ try {
878
+ const collections = await db.listCollections().toArray();
879
+ collectionNames = collections.map((c) => c.name);
880
+ } catch (err) {
881
+ core_1.logger.warn(`Skipping database "${dbName}": ${err instanceof Error ? err.message : String(err)}`);
882
+ continue;
883
+ }
884
+ for (const collName of collectionNames) {
885
+ const collection = db.collection(collName);
886
+ let rawIndexes = [];
887
+ try {
888
+ rawIndexes = await collection.indexes();
889
+ } catch {
890
+ rawIndexes = [];
891
+ }
892
+ let estimatedCount = 0;
893
+ try {
894
+ estimatedCount = await collection.estimatedDocumentCount();
895
+ } catch {
896
+ estimatedCount = 0;
897
+ }
898
+ const indexes = rawIndexes.map((idx) => ({
899
+ name: String(idx["name"] ?? ""),
900
+ keys: idx["key"] ?? {},
901
+ unique: Boolean(idx["unique"]),
902
+ sparse: Boolean(idx["sparse"])
903
+ }));
904
+ results.push({
905
+ database: dbName,
906
+ collection: collName,
907
+ indexes,
908
+ estimatedCount
909
+ });
910
+ }
911
+ }
912
+ core_1.logger.info(`Found ${results.length} MongoDB collection(s)`);
913
+ return results;
914
+ } catch (err) {
915
+ if (err instanceof MongoConnectionError)
916
+ throw err;
917
+ throw new MongoConnectionError(err instanceof Error ? err.message : String(err));
918
+ } finally {
919
+ await client.close().catch(() => void 0);
920
+ }
921
+ }
922
+ async function validateMongoAccess2(connectionString) {
923
+ const client = new mongodb_1.MongoClient(connectionString, {
924
+ serverSelectionTimeoutMS: 5e3,
925
+ connectTimeoutMS: 5e3
926
+ });
927
+ try {
928
+ await client.connect();
929
+ await client.db("admin").command({ ping: 1 });
930
+ return true;
931
+ } catch {
932
+ return false;
933
+ } finally {
934
+ await client.close().catch(() => void 0);
935
+ }
936
+ }
937
+ }
938
+ });
939
+
940
+ // ../adapters/terraform/dist/index.js
941
+ var require_dist6 = __commonJS({
942
+ "../adapters/terraform/dist/index.js"(exports2) {
943
+ "use strict";
944
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
945
+ if (k2 === void 0) k2 = k;
946
+ var desc = Object.getOwnPropertyDescriptor(m, k);
947
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
948
+ desc = { enumerable: true, get: function() {
949
+ return m[k];
950
+ } };
951
+ }
952
+ Object.defineProperty(o, k2, desc);
953
+ }) : (function(o, m, k, k2) {
954
+ if (k2 === void 0) k2 = k;
955
+ o[k2] = m[k];
956
+ }));
957
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
958
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
959
+ }) : function(o, v) {
960
+ o["default"] = v;
961
+ });
962
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
963
+ var ownKeys = function(o) {
964
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
965
+ var ar = [];
966
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
967
+ return ar;
968
+ };
969
+ return ownKeys(o);
970
+ };
971
+ return function(mod) {
972
+ if (mod && mod.__esModule) return mod;
973
+ var result = {};
974
+ if (mod != null) {
975
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
976
+ }
977
+ __setModuleDefault(result, mod);
978
+ return result;
979
+ };
980
+ })();
981
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
982
+ return mod && mod.__esModule ? mod : { "default": mod };
983
+ };
984
+ Object.defineProperty(exports2, "__esModule", { value: true });
985
+ exports2.extractTerraformSchema = extractTerraformSchema;
986
+ exports2.extractCloudFormationSchema = extractCloudFormationSchema;
987
+ exports2.extractIaCSchema = extractIaCSchema2;
988
+ var fs4 = __importStar(require("fs"));
989
+ var path5 = __importStar(require("path"));
990
+ var js_yaml_1 = __importDefault(require("js-yaml"));
991
+ var core_1 = require_dist();
992
+ function findFilesRecursively(dir, extensions) {
993
+ const results = [];
994
+ if (!fs4.existsSync(dir))
995
+ return results;
996
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
997
+ for (const entry of entries) {
998
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist")
999
+ continue;
1000
+ const fullPath = path5.join(dir, entry.name);
1001
+ if (entry.isDirectory()) {
1002
+ results.push(...findFilesRecursively(fullPath, extensions));
1003
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
1004
+ results.push(fullPath);
1005
+ }
1006
+ }
1007
+ return results;
1008
+ }
1009
+ function extractTerraformResourceBlocks(content) {
1010
+ const results = [];
1011
+ const resourcePattern = /resource\s+"([^"]+)"\s+"([^"]+)"\s*\{/g;
1012
+ let match;
1013
+ while ((match = resourcePattern.exec(content)) !== null) {
1014
+ const resourceType = match[1];
1015
+ const resourceName = match[2];
1016
+ const startBrace = match.index + match[0].length - 1;
1017
+ let depth = 1;
1018
+ let i = startBrace + 1;
1019
+ while (i < content.length && depth > 0) {
1020
+ if (content[i] === "{")
1021
+ depth++;
1022
+ else if (content[i] === "}")
1023
+ depth--;
1024
+ i++;
1025
+ }
1026
+ const body = content.slice(startBrace + 1, i - 1);
1027
+ results.push({ resourceType: resourceType ?? "", resourceName: resourceName ?? "", body });
1028
+ }
1029
+ return results;
1030
+ }
1031
+ function extractTerraformStringAttr(body, attr) {
1032
+ const pattern = new RegExp(`${attr}\\s*=\\s*"([^"]*)"`, "i");
1033
+ const m = body.match(pattern);
1034
+ return m?.[1];
1035
+ }
1036
+ function extractTerraformGSINames(body) {
1037
+ const names = [];
1038
+ const gsiPattern = /global_secondary_index\s*\{([^}]*)\}/g;
1039
+ let m;
1040
+ while ((m = gsiPattern.exec(body)) !== null) {
1041
+ const gsiBody = m[1];
1042
+ const nameMatch = gsiBody.match(/name\s*=\s*"([^"]*)"/);
1043
+ if (nameMatch?.[1])
1044
+ names.push(nameMatch[1]);
1045
+ }
1046
+ return names;
1047
+ }
1048
+ async function extractTerraformSchema(repoPath) {
1049
+ const schema = { dynamoTables: [], rdsInstances: [], mongoClusters: [] };
1050
+ const tfFiles = findFilesRecursively(repoPath, [".tf"]);
1051
+ core_1.logger.info(`Found ${tfFiles.length} Terraform file(s)`);
1052
+ for (const filePath of tfFiles) {
1053
+ let content;
1054
+ try {
1055
+ content = fs4.readFileSync(filePath, "utf-8");
1056
+ } catch {
1057
+ continue;
1058
+ }
1059
+ const blocks = extractTerraformResourceBlocks(content);
1060
+ for (const block of blocks) {
1061
+ const { resourceType, resourceName, body } = block;
1062
+ if (resourceType === "aws_dynamodb_table") {
1063
+ const partitionKey = extractTerraformStringAttr(body, "hash_key");
1064
+ const sortKey = extractTerraformStringAttr(body, "range_key");
1065
+ const gsiNames = extractTerraformGSINames(body);
1066
+ const tableName = extractTerraformStringAttr(body, "name") ?? resourceName;
1067
+ schema.dynamoTables.push({
1068
+ name: tableName,
1069
+ partitionKey,
1070
+ sortKey,
1071
+ gsiNames,
1072
+ source: "terraform",
1073
+ filePath
1074
+ });
1075
+ } else if (resourceType === "aws_db_instance") {
1076
+ const identifier = extractTerraformStringAttr(body, "identifier") ?? resourceName;
1077
+ const engine = extractTerraformStringAttr(body, "engine") ?? "unknown";
1078
+ schema.rdsInstances.push({
1079
+ identifier,
1080
+ engine,
1081
+ source: "terraform",
1082
+ filePath
1083
+ });
1084
+ } else if (resourceType === "aws_docdb_cluster") {
1085
+ const identifier = extractTerraformStringAttr(body, "cluster_identifier") ?? resourceName;
1086
+ schema.mongoClusters.push({
1087
+ identifier,
1088
+ source: "terraform",
1089
+ filePath
1090
+ });
1091
+ }
1092
+ }
1093
+ }
1094
+ return schema;
1095
+ }
1096
+ function isCloudFormationTemplate(parsed) {
1097
+ if (typeof parsed !== "object" || parsed === null)
1098
+ return false;
1099
+ const obj = parsed;
1100
+ return "AWSTemplateFormatVersion" in obj || "Resources" in obj && typeof obj["Resources"] === "object";
1101
+ }
1102
+ function parseCFNFile(filePath) {
1103
+ let content;
1104
+ try {
1105
+ content = fs4.readFileSync(filePath, "utf-8");
1106
+ } catch {
1107
+ return null;
1108
+ }
1109
+ if (!content.includes("AWSTemplateFormatVersion") && !content.includes("Resources")) {
1110
+ return null;
1111
+ }
1112
+ let parsed;
1113
+ try {
1114
+ if (filePath.endsWith(".json")) {
1115
+ parsed = JSON.parse(content);
1116
+ } else {
1117
+ parsed = js_yaml_1.default.load(content);
1118
+ }
1119
+ } catch {
1120
+ return null;
1121
+ }
1122
+ if (!isCloudFormationTemplate(parsed))
1123
+ return null;
1124
+ return parsed;
1125
+ }
1126
+ function getStringProp(obj, ...keys) {
1127
+ for (const key of keys) {
1128
+ if (typeof obj[key] === "string")
1129
+ return obj[key];
1130
+ }
1131
+ return void 0;
1132
+ }
1133
+ async function extractCloudFormationSchema(repoPath) {
1134
+ const schema = { dynamoTables: [], rdsInstances: [], mongoClusters: [] };
1135
+ const cfnFiles = findFilesRecursively(repoPath, [".yaml", ".yml", ".json"]);
1136
+ core_1.logger.info(`Scanning ${cfnFiles.length} potential CloudFormation file(s)`);
1137
+ for (const filePath of cfnFiles) {
1138
+ const parsed = parseCFNFile(filePath);
1139
+ if (!parsed)
1140
+ continue;
1141
+ const resources = parsed["Resources"];
1142
+ if (!resources || typeof resources !== "object")
1143
+ continue;
1144
+ for (const [logicalId, rawResource] of Object.entries(resources)) {
1145
+ if (typeof rawResource !== "object" || rawResource === null)
1146
+ continue;
1147
+ const resource = rawResource;
1148
+ const resourceType = resource["Type"];
1149
+ const props = resource["Properties"] ?? {};
1150
+ if (resourceType === "AWS::DynamoDB::Table") {
1151
+ let partitionKey;
1152
+ let sortKey;
1153
+ const keySchema = props["KeySchema"];
1154
+ if (Array.isArray(keySchema)) {
1155
+ for (const keyDef of keySchema) {
1156
+ if (typeof keyDef !== "object" || keyDef === null)
1157
+ continue;
1158
+ const kd = keyDef;
1159
+ if (kd["KeyType"] === "HASH")
1160
+ partitionKey = kd["AttributeName"];
1161
+ if (kd["KeyType"] === "RANGE")
1162
+ sortKey = kd["AttributeName"];
1163
+ }
1164
+ }
1165
+ const gsiNames = [];
1166
+ const gsis = props["GlobalSecondaryIndexes"];
1167
+ if (Array.isArray(gsis)) {
1168
+ for (const gsi of gsis) {
1169
+ if (typeof gsi !== "object" || gsi === null)
1170
+ continue;
1171
+ const g = gsi;
1172
+ if (typeof g["IndexName"] === "string")
1173
+ gsiNames.push(g["IndexName"]);
1174
+ }
1175
+ }
1176
+ const tableName = getStringProp(props, "TableName") ?? logicalId;
1177
+ schema.dynamoTables.push({
1178
+ name: tableName,
1179
+ partitionKey,
1180
+ sortKey,
1181
+ gsiNames,
1182
+ source: "cloudformation",
1183
+ filePath
1184
+ });
1185
+ } else if (resourceType === "AWS::RDS::DBInstance") {
1186
+ const identifier = getStringProp(props, "DBInstanceIdentifier") ?? logicalId;
1187
+ const engine = getStringProp(props, "Engine") ?? "unknown";
1188
+ schema.rdsInstances.push({
1189
+ identifier,
1190
+ engine,
1191
+ source: "cloudformation",
1192
+ filePath
1193
+ });
1194
+ } else if (resourceType === "AWS::DocDB::DBCluster") {
1195
+ const identifier = getStringProp(props, "DBClusterIdentifier") ?? logicalId;
1196
+ schema.mongoClusters.push({
1197
+ identifier,
1198
+ source: "cloudformation",
1199
+ filePath
1200
+ });
1201
+ }
1202
+ }
1203
+ }
1204
+ return schema;
1205
+ }
1206
+ async function extractIaCSchema2(repoPath) {
1207
+ const [tfSchema, cfnSchema] = await Promise.all([
1208
+ extractTerraformSchema(repoPath),
1209
+ extractCloudFormationSchema(repoPath)
1210
+ ]);
1211
+ const dynamoKey = (t) => `${t.source}::${t.name}`;
1212
+ const rdsKey = (r) => `${r.source}::${r.identifier}`;
1213
+ const mongoKey = (m) => `${m.source}::${m.identifier}`;
1214
+ const seenDynamo = /* @__PURE__ */ new Set();
1215
+ const seenRds = /* @__PURE__ */ new Set();
1216
+ const seenMongo = /* @__PURE__ */ new Set();
1217
+ const dynamoTables = [];
1218
+ const rdsInstances = [];
1219
+ const mongoClusters = [];
1220
+ for (const t of [...tfSchema.dynamoTables, ...cfnSchema.dynamoTables]) {
1221
+ const k = dynamoKey(t);
1222
+ if (!seenDynamo.has(k)) {
1223
+ seenDynamo.add(k);
1224
+ dynamoTables.push(t);
1225
+ }
1226
+ }
1227
+ for (const r of [...tfSchema.rdsInstances, ...cfnSchema.rdsInstances]) {
1228
+ const k = rdsKey(r);
1229
+ if (!seenRds.has(k)) {
1230
+ seenRds.add(k);
1231
+ rdsInstances.push(r);
1232
+ }
1233
+ }
1234
+ for (const m of [...tfSchema.mongoClusters, ...cfnSchema.mongoClusters]) {
1235
+ const k = mongoKey(m);
1236
+ if (!seenMongo.has(k)) {
1237
+ seenMongo.add(k);
1238
+ mongoClusters.push(m);
1239
+ }
1240
+ }
1241
+ return { dynamoTables, rdsInstances, mongoClusters };
1242
+ }
1243
+ }
1244
+ });
1245
+
1246
+ // ../context/dist/index.js
1247
+ var require_dist7 = __commonJS({
1248
+ "../context/dist/index.js"(exports2) {
1249
+ "use strict";
1250
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
1251
+ if (k2 === void 0) k2 = k;
1252
+ var desc = Object.getOwnPropertyDescriptor(m, k);
1253
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
1254
+ desc = { enumerable: true, get: function() {
1255
+ return m[k];
1256
+ } };
1257
+ }
1258
+ Object.defineProperty(o, k2, desc);
1259
+ }) : (function(o, m, k, k2) {
1260
+ if (k2 === void 0) k2 = k;
1261
+ o[k2] = m[k];
1262
+ }));
1263
+ var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
1264
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
1265
+ }) : function(o, v) {
1266
+ o["default"] = v;
1267
+ });
1268
+ var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
1269
+ var ownKeys = function(o) {
1270
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
1271
+ var ar = [];
1272
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
1273
+ return ar;
1274
+ };
1275
+ return ownKeys(o);
1276
+ };
1277
+ return function(mod) {
1278
+ if (mod && mod.__esModule) return mod;
1279
+ var result = {};
1280
+ if (mod != null) {
1281
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
1282
+ }
1283
+ __setModuleDefault(result, mod);
1284
+ return result;
1285
+ };
1286
+ })();
1287
+ Object.defineProperty(exports2, "__esModule", { value: true });
1288
+ exports2.scanRepository = scanRepository2;
1289
+ var path5 = __importStar(require("path"));
1290
+ var fs4 = __importStar(require("fs"));
1291
+ var ts_morph_1 = require("ts-morph");
1292
+ var core_1 = require_dist();
1293
+ var DYNAMO_OPERATIONS = /* @__PURE__ */ new Set([
1294
+ "query",
1295
+ "scan",
1296
+ "getItem",
1297
+ "putItem",
1298
+ "updateItem",
1299
+ "deleteItem",
1300
+ "batchGetItem",
1301
+ "batchWriteItem",
1302
+ "transactGetItems",
1303
+ "transactWriteItems",
1304
+ // Also handle command class names (AWS SDK v3)
1305
+ "QueryCommand",
1306
+ "ScanCommand",
1307
+ "GetItemCommand",
1308
+ "PutItemCommand",
1309
+ "UpdateItemCommand",
1310
+ "DeleteItemCommand"
1311
+ ]);
1312
+ var DYNAMO_CLIENT_PATTERNS = ["DynamoDBClient", "DynamoDB", "dynamoDB", "dynamo", "ddb"];
1313
+ var POSTGRES_QUERY_METHODS = /* @__PURE__ */ new Set(["query", "execute", "exec"]);
1314
+ var KNEX_METHODS = /* @__PURE__ */ new Set(["select", "where", "join", "from", "insert", "update", "delete", "del"]);
1315
+ var MYSQL_QUERY_METHODS = /* @__PURE__ */ new Set(["query", "execute", "exec"]);
1316
+ var MYSQL_CLIENT_PATTERNS = ["mysql", "mysql2", "connection", "pool", "conn"];
1317
+ var MONGO_READ_METHODS = /* @__PURE__ */ new Set([
1318
+ "find",
1319
+ "findOne",
1320
+ "findById",
1321
+ "insertOne",
1322
+ "insertMany",
1323
+ "updateOne",
1324
+ "updateMany",
1325
+ "deleteOne",
1326
+ "deleteMany",
1327
+ "aggregate",
1328
+ "countDocuments",
1329
+ "estimatedDocumentCount"
1330
+ ]);
1331
+ var MONGO_COLLECTION_METHODS = /* @__PURE__ */ new Set(["collection"]);
1332
+ var PRISMA_METHODS = /* @__PURE__ */ new Set([
1333
+ "findMany",
1334
+ "findFirst",
1335
+ "findUnique",
1336
+ "create",
1337
+ "update",
1338
+ "upsert",
1339
+ "delete",
1340
+ "deleteMany",
1341
+ "updateMany"
1342
+ ]);
1343
+ function getEnclosingFunctionName(node) {
1344
+ let current = node.getParent();
1345
+ while (current) {
1346
+ 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)) {
1347
+ if (ts_morph_1.Node.isFunctionDeclaration(current) || ts_morph_1.Node.isMethodDeclaration(current)) {
1348
+ return current.getName() ?? "<anonymous>";
1349
+ }
1350
+ const parent = current.getParent();
1351
+ if (parent && ts_morph_1.Node.isVariableDeclaration(parent)) {
1352
+ return parent.getName();
1353
+ }
1354
+ if (parent && ts_morph_1.Node.isPropertyAssignment(parent)) {
1355
+ return parent.getName();
1356
+ }
1357
+ return "<anonymous>";
1358
+ }
1359
+ current = current.getParent();
1360
+ }
1361
+ return "<module>";
1362
+ }
1363
+ function extractTableNameFromArg(arg) {
1364
+ if (ts_morph_1.Node.isStringLiteral(arg)) {
1365
+ return arg.getLiteralValue();
1366
+ }
1367
+ if (ts_morph_1.Node.isObjectLiteralExpression(arg)) {
1368
+ for (const prop of arg.getProperties()) {
1369
+ if (ts_morph_1.Node.isPropertyAssignment(prop) && prop.getName() === "TableName") {
1370
+ const init = prop.getInitializer();
1371
+ if (init && ts_morph_1.Node.isStringLiteral(init)) {
1372
+ return init.getLiteralValue();
1373
+ }
1374
+ }
1375
+ }
1376
+ }
1377
+ return "unknown";
1378
+ }
1379
+ function detectDynamoOperations(callExpr, filePath) {
1380
+ const expr = callExpr.getExpression();
1381
+ const args = callExpr.getArguments();
1382
+ if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
1383
+ const methodName = expr.getName();
1384
+ if (methodName === "send" && args.length > 0) {
1385
+ const firstArg = args[0];
1386
+ if (ts_morph_1.Node.isNewExpression(firstArg)) {
1387
+ const className = firstArg.getExpression().getText();
1388
+ if (DYNAMO_OPERATIONS.has(className)) {
1389
+ const cmdArgs = firstArg.getArguments();
1390
+ const tableName = cmdArgs.length > 0 ? extractTableNameFromArg(cmdArgs[0]) : "unknown";
1391
+ return {
1392
+ functionName: getEnclosingFunctionName(callExpr),
1393
+ operationType: className,
1394
+ databaseType: "dynamodb",
1395
+ target: tableName,
1396
+ filePath
1397
+ };
1398
+ }
1399
+ }
1400
+ }
1401
+ if (DYNAMO_OPERATIONS.has(methodName)) {
1402
+ const objText = expr.getExpression().getText().toLowerCase();
1403
+ if (DYNAMO_CLIENT_PATTERNS.some((p) => objText.includes(p.toLowerCase()))) {
1404
+ const tableName = args.length > 0 ? extractTableNameFromArg(args[0]) : "unknown";
1405
+ return {
1406
+ functionName: getEnclosingFunctionName(callExpr),
1407
+ operationType: methodName,
1408
+ databaseType: "dynamodb",
1409
+ target: tableName,
1410
+ filePath
1411
+ };
1412
+ }
1413
+ }
1414
+ }
1415
+ return null;
1416
+ }
1417
+ function extractSqlTableName(sql) {
1418
+ const patterns = [
1419
+ /FROM\s+["']?(\w+)["']?/i,
1420
+ /INTO\s+["']?(\w+)["']?/i,
1421
+ /UPDATE\s+["']?(\w+)["']?/i,
1422
+ /JOIN\s+["']?(\w+)["']?/i
1423
+ ];
1424
+ for (const pattern of patterns) {
1425
+ const match = sql.match(pattern);
1426
+ if (match?.[1])
1427
+ return match[1];
1428
+ }
1429
+ return "unknown";
1430
+ }
1431
+ function detectPostgresOperations(callExpr, filePath) {
1432
+ const expr = callExpr.getExpression();
1433
+ const args = callExpr.getArguments();
1434
+ if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
1435
+ return null;
1436
+ const methodName = expr.getName();
1437
+ const objExpr = expr.getExpression();
1438
+ if (POSTGRES_QUERY_METHODS.has(methodName)) {
1439
+ const objText = objExpr.getText().toLowerCase();
1440
+ if (objText.includes("pool") || objText.includes("client") || objText.includes("db") || objText.includes("pg") || objText.includes("conn")) {
1441
+ let tableName = "unknown";
1442
+ if (args.length > 0) {
1443
+ const firstArg = args[0];
1444
+ if (ts_morph_1.Node.isStringLiteral(firstArg)) {
1445
+ tableName = extractSqlTableName(firstArg.getLiteralValue());
1446
+ } else if (ts_morph_1.Node.isNoSubstitutionTemplateLiteral(firstArg)) {
1447
+ tableName = extractSqlTableName(firstArg.getLiteralText());
1448
+ } else if (ts_morph_1.Node.isTemplateExpression(firstArg)) {
1449
+ tableName = extractSqlTableName(firstArg.getHead().getLiteralText());
1450
+ }
1451
+ }
1452
+ return {
1453
+ functionName: getEnclosingFunctionName(callExpr),
1454
+ operationType: "query",
1455
+ databaseType: "postgres",
1456
+ target: tableName,
1457
+ filePath
1458
+ };
1459
+ }
1460
+ }
1461
+ if (PRISMA_METHODS.has(methodName)) {
1462
+ const accessChain = objExpr;
1463
+ if (ts_morph_1.Node.isPropertyAccessExpression(accessChain)) {
1464
+ const modelName = accessChain.getName();
1465
+ const rootObj = accessChain.getExpression().getText().toLowerCase();
1466
+ if (rootObj.includes("prisma")) {
1467
+ return {
1468
+ functionName: getEnclosingFunctionName(callExpr),
1469
+ operationType: methodName,
1470
+ databaseType: "postgres",
1471
+ target: modelName,
1472
+ filePath
1473
+ };
1474
+ }
1475
+ }
1476
+ }
1477
+ if (KNEX_METHODS.has(methodName)) {
1478
+ const calleeText = objExpr.getText();
1479
+ if (calleeText.includes("knex") || calleeText.includes("db(") || calleeText.includes("trx(")) {
1480
+ return {
1481
+ functionName: getEnclosingFunctionName(callExpr),
1482
+ operationType: methodName,
1483
+ databaseType: "postgres",
1484
+ target: "unknown",
1485
+ filePath
1486
+ };
1487
+ }
1488
+ if (ts_morph_1.Node.isCallExpression(objExpr)) {
1489
+ const innerExpr = objExpr.getExpression();
1490
+ if (innerExpr.getText().toLowerCase().match(/knex|db|trx/)) {
1491
+ const innerArgs = objExpr.getArguments();
1492
+ const tableName = innerArgs.length > 0 && ts_morph_1.Node.isStringLiteral(innerArgs[0]) ? innerArgs[0].getLiteralValue() : "unknown";
1493
+ return {
1494
+ functionName: getEnclosingFunctionName(callExpr),
1495
+ operationType: methodName,
1496
+ databaseType: "postgres",
1497
+ target: tableName,
1498
+ filePath
1499
+ };
1500
+ }
1501
+ }
1502
+ }
1503
+ return null;
1504
+ }
1505
+ function detectMySQLOperations(callExpr, filePath) {
1506
+ const expr = callExpr.getExpression();
1507
+ const args = callExpr.getArguments();
1508
+ if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
1509
+ return null;
1510
+ const methodName = expr.getName();
1511
+ const objExpr = expr.getExpression();
1512
+ const objText = objExpr.getText().toLowerCase();
1513
+ if (MYSQL_QUERY_METHODS.has(methodName)) {
1514
+ const isMysqlClient = MYSQL_CLIENT_PATTERNS.some((p) => objText.includes(p.toLowerCase()));
1515
+ if (isMysqlClient) {
1516
+ let tableName = "unknown";
1517
+ if (args.length > 0) {
1518
+ const firstArg = args[0];
1519
+ if (ts_morph_1.Node.isStringLiteral(firstArg)) {
1520
+ tableName = extractSqlTableName(firstArg.getLiteralValue());
1521
+ } else if (ts_morph_1.Node.isNoSubstitutionTemplateLiteral(firstArg)) {
1522
+ tableName = extractSqlTableName(firstArg.getLiteralText());
1523
+ } else if (ts_morph_1.Node.isTemplateExpression(firstArg)) {
1524
+ tableName = extractSqlTableName(firstArg.getHead().getLiteralText());
1525
+ }
1526
+ }
1527
+ return {
1528
+ functionName: getEnclosingFunctionName(callExpr),
1529
+ operationType: "query",
1530
+ databaseType: "mysql",
1531
+ target: tableName,
1532
+ filePath
1533
+ };
1534
+ }
1535
+ }
1536
+ if (KNEX_METHODS.has(methodName)) {
1537
+ if (objText.includes("mysql") || objText.includes("knex")) {
1538
+ let tableName = "unknown";
1539
+ if (ts_morph_1.Node.isCallExpression(objExpr)) {
1540
+ const innerArgs = objExpr.getArguments();
1541
+ if (innerArgs.length > 0 && ts_morph_1.Node.isStringLiteral(innerArgs[0])) {
1542
+ tableName = innerArgs[0].getLiteralValue();
1543
+ }
1544
+ }
1545
+ return {
1546
+ functionName: getEnclosingFunctionName(callExpr),
1547
+ operationType: methodName,
1548
+ databaseType: "mysql",
1549
+ target: tableName,
1550
+ filePath
1551
+ };
1552
+ }
1553
+ }
1554
+ return null;
1555
+ }
1556
+ function detectMongoOperations(callExpr, filePath) {
1557
+ const expr = callExpr.getExpression();
1558
+ if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
1559
+ return null;
1560
+ const methodName = expr.getName();
1561
+ const objExpr = expr.getExpression();
1562
+ if (MONGO_READ_METHODS.has(methodName)) {
1563
+ const objText = objExpr.getText().toLowerCase();
1564
+ if (objText.includes("collection") || objText.includes("col") || objText.includes("db.") || objText.includes("model") || // Mongoose: User.find(), Order.findOne()
1565
+ /^[A-Z][a-zA-Z]+$/.test(objExpr.getText())) {
1566
+ let collectionName = "unknown";
1567
+ if (ts_morph_1.Node.isCallExpression(objExpr)) {
1568
+ const innerExpr = objExpr.getExpression();
1569
+ if (ts_morph_1.Node.isPropertyAccessExpression(innerExpr) && MONGO_COLLECTION_METHODS.has(innerExpr.getName())) {
1570
+ const innerArgs = objExpr.getArguments();
1571
+ if (innerArgs.length > 0 && ts_morph_1.Node.isStringLiteral(innerArgs[0])) {
1572
+ collectionName = innerArgs[0].getLiteralValue();
1573
+ }
1574
+ }
1575
+ } else if (ts_morph_1.Node.isPropertyAccessExpression(objExpr)) {
1576
+ collectionName = objExpr.getName();
1577
+ } else {
1578
+ collectionName = objExpr.getText();
1579
+ }
1580
+ const opType = methodName === "find" || methodName === "aggregate" ? "scan" : "query";
1581
+ return {
1582
+ functionName: getEnclosingFunctionName(callExpr),
1583
+ operationType: opType,
1584
+ databaseType: "mongodb",
1585
+ target: collectionName,
1586
+ filePath
1587
+ };
1588
+ }
1589
+ }
1590
+ if (MONGO_COLLECTION_METHODS.has(methodName)) {
1591
+ const args = callExpr.getArguments();
1592
+ const objText = objExpr.getText().toLowerCase();
1593
+ if (objText.includes("db") || objText.includes("mongo")) {
1594
+ const collectionName = args.length > 0 && ts_morph_1.Node.isStringLiteral(args[0]) ? args[0].getLiteralValue() : "unknown";
1595
+ return {
1596
+ functionName: getEnclosingFunctionName(callExpr),
1597
+ operationType: "query",
1598
+ databaseType: "mongodb",
1599
+ target: collectionName,
1600
+ filePath
1601
+ };
1602
+ }
1603
+ }
1604
+ return null;
1605
+ }
1606
+ async function scanRepository2(repoPath) {
1607
+ const resolvedPath = path5.resolve(repoPath);
1608
+ if (!fs4.existsSync(resolvedPath)) {
1609
+ throw new core_1.RepositoryScanError(`Path does not exist: ${resolvedPath}`);
1610
+ }
1611
+ const tsconfigPath = path5.join(resolvedPath, "tsconfig.json");
1612
+ const hasTsConfig = fs4.existsSync(tsconfigPath);
1613
+ const project = new ts_morph_1.Project({
1614
+ tsConfigFilePath: hasTsConfig ? tsconfigPath : void 0,
1615
+ compilerOptions: hasTsConfig ? void 0 : {
1616
+ target: 99,
1617
+ allowJs: true
1618
+ },
1619
+ skipAddingFilesFromTsConfig: !hasTsConfig
1620
+ });
1621
+ if (!hasTsConfig) {
1622
+ project.addSourceFilesAtPaths([
1623
+ path5.join(resolvedPath, "**/*.ts"),
1624
+ path5.join(resolvedPath, "**/*.tsx"),
1625
+ `!${path5.join(resolvedPath, "**/node_modules/**")}`,
1626
+ `!${path5.join(resolvedPath, "**/dist/**")}`
1627
+ ]);
1628
+ }
1629
+ const sourceFiles = project.getSourceFiles();
1630
+ core_1.logger.info(`Scanning ${sourceFiles.length} TypeScript file(s) in ${resolvedPath}`);
1631
+ const operations = [];
1632
+ for (const sourceFile of sourceFiles) {
1633
+ const filePath = sourceFile.getFilePath();
1634
+ if (filePath.includes("node_modules") || filePath.includes("/dist/"))
1635
+ continue;
1636
+ const callExpressions = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression);
1637
+ for (const callExpr of callExpressions) {
1638
+ const dynamoOp = detectDynamoOperations(callExpr, filePath);
1639
+ if (dynamoOp) {
1640
+ operations.push(dynamoOp);
1641
+ continue;
1642
+ }
1643
+ const postgresOp = detectPostgresOperations(callExpr, filePath);
1644
+ if (postgresOp) {
1645
+ operations.push(postgresOp);
1646
+ continue;
1647
+ }
1648
+ const mysqlOp = detectMySQLOperations(callExpr, filePath);
1649
+ if (mysqlOp) {
1650
+ operations.push(mysqlOp);
1651
+ continue;
1652
+ }
1653
+ const mongoOp = detectMongoOperations(callExpr, filePath);
1654
+ if (mongoOp) {
1655
+ operations.push(mongoOp);
1656
+ }
1657
+ }
1658
+ }
1659
+ core_1.logger.info(`Extracted ${operations.length} database operation(s)`);
1660
+ return operations;
1661
+ }
1662
+ }
1663
+ });
1664
+
1665
+ // ../graph/dist/index.js
1666
+ var require_dist8 = __commonJS({
1667
+ "../graph/dist/index.js"(exports2) {
1668
+ "use strict";
1669
+ Object.defineProperty(exports2, "__esModule", { value: true });
1670
+ exports2.buildGraph = buildGraph2;
1671
+ exports2.getTableNodes = getTableNodes;
1672
+ exports2.getFunctionNodes = getFunctionNodes;
1673
+ exports2.getIndexNodes = getIndexNodes;
1674
+ exports2.getEdgesForNode = getEdgesForNode;
1675
+ exports2.getOutgoingEdges = getOutgoingEdges;
1676
+ exports2.getIncomingEdges = getIncomingEdges;
1677
+ exports2.getScanEdges = getScanEdges;
1678
+ exports2.getEdgeFrequency = getEdgeFrequency;
1679
+ function buildGraph2(operations, dynamoMeta, postgresMeta, mysqlMeta = [], mongoMeta = []) {
1680
+ const nodes = [];
1681
+ const edges = [];
1682
+ const nodeIds = /* @__PURE__ */ new Set();
1683
+ for (const table of dynamoMeta) {
1684
+ const nodeId = `table:dynamo:${table.tableName}`;
1685
+ if (!nodeIds.has(nodeId)) {
1686
+ nodes.push({
1687
+ id: nodeId,
1688
+ type: "table",
1689
+ name: table.tableName,
1690
+ databaseType: "dynamodb"
1691
+ });
1692
+ nodeIds.add(nodeId);
1693
+ }
1694
+ for (const indexName of table.indexes) {
1695
+ const indexNodeId = `index:${table.tableName}:${indexName}`;
1696
+ if (!nodeIds.has(indexNodeId)) {
1697
+ nodes.push({ id: indexNodeId, type: "index", name: indexName });
1698
+ nodeIds.add(indexNodeId);
1699
+ }
1700
+ edges.push({ from: nodeId, to: indexNodeId, type: "uses_index" });
1701
+ }
1702
+ }
1703
+ for (const table of postgresMeta) {
1704
+ const nodeId = `table:postgres:${table.schema}.${table.table}`;
1705
+ if (!nodeIds.has(nodeId)) {
1706
+ nodes.push({
1707
+ id: nodeId,
1708
+ type: "table",
1709
+ name: `${table.schema}.${table.table}`,
1710
+ databaseType: "postgres"
1711
+ });
1712
+ nodeIds.add(nodeId);
1713
+ }
1714
+ for (const indexName of table.indexes) {
1715
+ const indexNodeId = `index:${table.schema}.${table.table}:${indexName}`;
1716
+ if (!nodeIds.has(indexNodeId)) {
1717
+ nodes.push({ id: indexNodeId, type: "index", name: indexName });
1718
+ nodeIds.add(indexNodeId);
1719
+ }
1720
+ edges.push({ from: nodeId, to: indexNodeId, type: "uses_index" });
1721
+ }
1722
+ }
1723
+ for (const table of mysqlMeta) {
1724
+ const nodeId = `table:mysql:${table.schema}.${table.table}`;
1725
+ if (!nodeIds.has(nodeId)) {
1726
+ nodes.push({
1727
+ id: nodeId,
1728
+ type: "table",
1729
+ name: `${table.schema}.${table.table}`,
1730
+ databaseType: "mysql"
1731
+ });
1732
+ nodeIds.add(nodeId);
1733
+ }
1734
+ for (const indexName of table.indexes) {
1735
+ const indexNodeId = `index:${table.schema}.${table.table}:${indexName}`;
1736
+ if (!nodeIds.has(indexNodeId)) {
1737
+ nodes.push({ id: indexNodeId, type: "index", name: indexName });
1738
+ nodeIds.add(indexNodeId);
1739
+ }
1740
+ edges.push({ from: nodeId, to: indexNodeId, type: "uses_index" });
1741
+ }
1742
+ }
1743
+ for (const coll of mongoMeta) {
1744
+ const nodeId = `table:mongodb:${coll.database}.${coll.collection}`;
1745
+ if (!nodeIds.has(nodeId)) {
1746
+ nodes.push({
1747
+ id: nodeId,
1748
+ type: "table",
1749
+ name: `${coll.database}.${coll.collection}`,
1750
+ databaseType: "mongodb"
1751
+ });
1752
+ nodeIds.add(nodeId);
1753
+ }
1754
+ for (const idx of coll.indexes) {
1755
+ if (idx.name === "_id_")
1756
+ continue;
1757
+ const indexNodeId = `index:${coll.database}.${coll.collection}:${idx.name}`;
1758
+ if (!nodeIds.has(indexNodeId)) {
1759
+ nodes.push({ id: indexNodeId, type: "index", name: idx.name });
1760
+ nodeIds.add(indexNodeId);
1761
+ }
1762
+ edges.push({ from: nodeId, to: indexNodeId, type: "uses_index" });
1763
+ }
1764
+ }
1765
+ for (const op of operations) {
1766
+ const funcNodeId = `function:${op.filePath}:${op.functionName}`;
1767
+ if (!nodeIds.has(funcNodeId)) {
1768
+ nodes.push({
1769
+ id: funcNodeId,
1770
+ type: "function",
1771
+ name: op.functionName,
1772
+ file: op.filePath
1773
+ });
1774
+ nodeIds.add(funcNodeId);
1775
+ }
1776
+ let tableNodeId;
1777
+ if (op.databaseType === "dynamodb") {
1778
+ tableNodeId = `table:dynamo:${op.target}`;
1779
+ if (!nodeIds.has(tableNodeId)) {
1780
+ nodes.push({
1781
+ id: tableNodeId,
1782
+ type: "table",
1783
+ name: op.target,
1784
+ databaseType: "dynamodb"
1785
+ });
1786
+ nodeIds.add(tableNodeId);
1787
+ }
1788
+ } else if (op.databaseType === "mysql") {
1789
+ const qualifiedTarget = op.target.includes(".") ? op.target : `default.${op.target}`;
1790
+ tableNodeId = `table:mysql:${qualifiedTarget}`;
1791
+ if (!nodeIds.has(tableNodeId)) {
1792
+ nodes.push({
1793
+ id: tableNodeId,
1794
+ type: "table",
1795
+ name: qualifiedTarget,
1796
+ databaseType: "mysql"
1797
+ });
1798
+ nodeIds.add(tableNodeId);
1799
+ }
1800
+ } else if (op.databaseType === "mongodb") {
1801
+ const qualifiedTarget = op.target.includes(".") ? op.target : `default.${op.target}`;
1802
+ tableNodeId = `table:mongodb:${qualifiedTarget}`;
1803
+ if (!nodeIds.has(tableNodeId)) {
1804
+ nodes.push({
1805
+ id: tableNodeId,
1806
+ type: "table",
1807
+ name: qualifiedTarget,
1808
+ databaseType: "mongodb"
1809
+ });
1810
+ nodeIds.add(tableNodeId);
1811
+ }
1812
+ } else {
1813
+ const qualifiedTarget = op.target.includes(".") ? op.target : `public.${op.target}`;
1814
+ tableNodeId = `table:postgres:${qualifiedTarget}`;
1815
+ if (!nodeIds.has(tableNodeId)) {
1816
+ const parts = qualifiedTarget.split(".");
1817
+ nodes.push({
1818
+ id: tableNodeId,
1819
+ type: "table",
1820
+ name: qualifiedTarget,
1821
+ databaseType: "postgres"
1822
+ });
1823
+ nodeIds.add(tableNodeId);
1824
+ if (!postgresMeta.find((t) => `${t.schema}.${t.table}` === qualifiedTarget)) {
1825
+ postgresMeta.push({
1826
+ schema: parts[0] ?? "public",
1827
+ table: parts[1] ?? op.target,
1828
+ columns: [],
1829
+ indexes: [],
1830
+ primaryKeys: []
1831
+ });
1832
+ }
1833
+ }
1834
+ }
1835
+ const edgeType = resolveEdgeType(op.operationType);
1836
+ edges.push({ from: funcNodeId, to: tableNodeId, type: edgeType });
1837
+ }
1838
+ return { nodes, edges };
1839
+ }
1840
+ function resolveEdgeType(operationType) {
1841
+ const op = operationType.toLowerCase();
1842
+ if (op === "scan" || op === "scancommand")
1843
+ return "scan";
1844
+ if (op === "join" || op === "joins")
1845
+ return "joins";
1846
+ return "query";
1847
+ }
1848
+ function getTableNodes(graph) {
1849
+ return graph.nodes.filter((n) => n.type === "table");
1850
+ }
1851
+ function getFunctionNodes(graph) {
1852
+ return graph.nodes.filter((n) => n.type === "function");
1853
+ }
1854
+ function getIndexNodes(graph) {
1855
+ return graph.nodes.filter((n) => n.type === "index");
1856
+ }
1857
+ function getEdgesForNode(graph, nodeId) {
1858
+ return graph.edges.filter((e) => e.from === nodeId || e.to === nodeId);
1859
+ }
1860
+ function getOutgoingEdges(graph, nodeId) {
1861
+ return graph.edges.filter((e) => e.from === nodeId);
1862
+ }
1863
+ function getIncomingEdges(graph, nodeId) {
1864
+ return graph.edges.filter((e) => e.to === nodeId);
1865
+ }
1866
+ function getScanEdges(graph) {
1867
+ return graph.edges.filter((e) => e.type === "scan");
1868
+ }
1869
+ function getEdgeFrequency(graph) {
1870
+ const freq = /* @__PURE__ */ new Map();
1871
+ for (const edge of graph.edges) {
1872
+ const key = `${edge.from}->${edge.to}`;
1873
+ freq.set(key, (freq.get(key) ?? 0) + 1);
1874
+ }
1875
+ return freq;
1876
+ }
1877
+ }
1878
+ });
1879
+
1880
+ // ../analyzers/dist/dynamodb.js
1881
+ var require_dynamodb = __commonJS({
1882
+ "../analyzers/dist/dynamodb.js"(exports2) {
1883
+ "use strict";
1884
+ Object.defineProperty(exports2, "__esModule", { value: true });
1885
+ exports2.HotPartitionAnalyzer = exports2.MissingGSIAnalyzer = exports2.FullTableScanAnalyzer = void 0;
1886
+ var graph_1 = require_dist8();
1887
+ var FullTableScanAnalyzer = class {
1888
+ name = "FullTableScanAnalyzer";
1889
+ async analyze(graph) {
1890
+ const findings = [];
1891
+ const scanEdges = (0, graph_1.getScanEdges)(graph);
1892
+ const scannedTableIds = /* @__PURE__ */ new Set();
1893
+ for (const edge of scanEdges) {
1894
+ const targetNode = graph.nodes.find((n) => n.id === edge.to);
1895
+ if (targetNode?.type === "table" && targetNode.databaseType === "dynamodb") {
1896
+ scannedTableIds.add(targetNode.id);
1897
+ }
1898
+ }
1899
+ for (const tableId of scannedTableIds) {
1900
+ const tableNode = graph.nodes.find((n) => n.id === tableId);
1901
+ if (!tableNode)
1902
+ continue;
1903
+ const tableScanEdges = scanEdges.filter((e) => e.to === tableId);
1904
+ const callerFunctions = tableScanEdges.map((e) => {
1905
+ const node = graph.nodes.find((n) => n.id === e.from);
1906
+ return node?.type === "function" ? node.name : e.from;
1907
+ }).join(", ");
1908
+ findings.push({
1909
+ severity: "high",
1910
+ issue: `Full table scan detected on DynamoDB table "${tableNode.name}"`,
1911
+ 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"}`,
1912
+ 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).",
1913
+ metadata: {
1914
+ tableName: tableNode.name,
1915
+ scanCount: tableScanEdges.length,
1916
+ callerFunctions
1917
+ }
1918
+ });
1919
+ }
1920
+ return findings;
1921
+ }
1922
+ };
1923
+ exports2.FullTableScanAnalyzer = FullTableScanAnalyzer;
1924
+ var MissingGSIAnalyzer = class {
1925
+ name = "MissingGSIAnalyzer";
1926
+ async analyze(graph) {
1927
+ const findings = [];
1928
+ const dynamoTables = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "dynamodb");
1929
+ for (const table of dynamoTables) {
1930
+ const incomingEdges = graph.edges.filter((e) => e.to === table.id);
1931
+ const queryEdges = incomingEdges.filter((e) => e.type === "query");
1932
+ const hasGSI = graph.edges.some((e) => e.from === table.id && e.type === "uses_index");
1933
+ if (queryEdges.length > 0 && !hasGSI) {
1934
+ const callerFunctions = queryEdges.map((e) => {
1935
+ const node = graph.nodes.find((n) => n.id === e.from);
1936
+ return node?.type === "function" ? node.name : e.from;
1937
+ }).join(", ");
1938
+ findings.push({
1939
+ severity: "medium",
1940
+ issue: `DynamoDB table "${table.name}" has no GSIs but is queried by multiple functions`,
1941
+ 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.`,
1942
+ recommendation: "Analyze query access patterns and add GSIs for frequently filtered attributes. Consider using single-table design patterns with composite sort keys.",
1943
+ metadata: {
1944
+ tableName: table.name,
1945
+ queryCount: queryEdges.length,
1946
+ callerFunctions
1947
+ }
1948
+ });
1949
+ }
1950
+ }
1951
+ return findings;
1952
+ }
1953
+ };
1954
+ exports2.MissingGSIAnalyzer = MissingGSIAnalyzer;
1955
+ var HotPartitionAnalyzer = class {
1956
+ name = "HotPartitionAnalyzer";
1957
+ hotThreshold;
1958
+ constructor(hotThreshold = 5) {
1959
+ this.hotThreshold = hotThreshold;
1960
+ }
1961
+ async analyze(graph) {
1962
+ const findings = [];
1963
+ const edgeFrequency = (0, graph_1.getEdgeFrequency)(graph);
1964
+ const tableAccessCount = /* @__PURE__ */ new Map();
1965
+ for (const edge of graph.edges) {
1966
+ const targetNode = graph.nodes.find((n) => n.id === edge.to);
1967
+ if (targetNode?.type !== "table" || targetNode.databaseType !== "dynamodb")
1968
+ continue;
1969
+ if (!tableAccessCount.has(edge.to)) {
1970
+ tableAccessCount.set(edge.to, /* @__PURE__ */ new Set());
1971
+ }
1972
+ tableAccessCount.get(edge.to).add(edge.from);
1973
+ }
1974
+ for (const [tableId, accessors] of tableAccessCount) {
1975
+ if (accessors.size >= this.hotThreshold) {
1976
+ const tableNode = graph.nodes.find((n) => n.id === tableId);
1977
+ if (!tableNode)
1978
+ continue;
1979
+ let maxFreq = 0;
1980
+ for (const [key, freq] of edgeFrequency) {
1981
+ if (key.includes(tableId))
1982
+ maxFreq = Math.max(maxFreq, freq);
1983
+ }
1984
+ findings.push({
1985
+ severity: "medium",
1986
+ issue: `Potential hot partition detected on DynamoDB table "${tableNode.name}"`,
1987
+ 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.`,
1988
+ 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.",
1989
+ metadata: {
1990
+ tableName: tableNode.name,
1991
+ accessorCount: accessors.size,
1992
+ maxEdgeFrequency: maxFreq
1993
+ }
1994
+ });
1995
+ }
1996
+ }
1997
+ return findings;
1998
+ }
1999
+ };
2000
+ exports2.HotPartitionAnalyzer = HotPartitionAnalyzer;
2001
+ }
2002
+ });
2003
+
2004
+ // ../analyzers/dist/postgres.js
2005
+ var require_postgres = __commonJS({
2006
+ "../analyzers/dist/postgres.js"(exports2) {
2007
+ "use strict";
2008
+ Object.defineProperty(exports2, "__esModule", { value: true });
2009
+ exports2.LargeSelectAnalyzer = exports2.NplusOneAnalyzer = exports2.MissingIndexAnalyzer = void 0;
2010
+ var MissingIndexAnalyzer = class {
2011
+ name = "MissingIndexAnalyzer";
2012
+ async analyze(graph) {
2013
+ const findings = [];
2014
+ const postgresTables = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "postgres");
2015
+ for (const table of postgresTables) {
2016
+ const indexEdges = graph.edges.filter((e) => e.from === table.id && e.type === "uses_index");
2017
+ const hasIndexes = indexEdges.length > 0;
2018
+ const queryEdges = graph.edges.filter((e) => e.to === table.id && (e.type === "query" || e.type === "scan"));
2019
+ if (queryEdges.length > 0 && !hasIndexes) {
2020
+ const callerFunctions = queryEdges.map((e) => {
2021
+ const node = graph.nodes.find((n) => n.id === e.from);
2022
+ return node?.type === "function" ? node.name : e.from;
2023
+ }).join(", ");
2024
+ findings.push({
2025
+ severity: "medium",
2026
+ issue: `PostgreSQL table "${table.name}" has no indexes but is queried by ${queryEdges.length} function(s)`,
2027
+ 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.`,
2028
+ recommendation: "Add indexes on columns used in WHERE clauses. Use EXPLAIN ANALYZE to identify slow queries. Consider composite indexes for multi-column filters.",
2029
+ metadata: {
2030
+ tableName: table.name,
2031
+ queryCount: queryEdges.length,
2032
+ callerFunctions
2033
+ }
2034
+ });
2035
+ }
2036
+ }
2037
+ return findings;
2038
+ }
2039
+ };
2040
+ exports2.MissingIndexAnalyzer = MissingIndexAnalyzer;
2041
+ var NplusOneAnalyzer = class {
2042
+ name = "NplusOneAnalyzer";
2043
+ async analyze(graph) {
2044
+ const findings = [];
2045
+ const functionTableAccess = /* @__PURE__ */ new Map();
2046
+ for (const edge of graph.edges) {
2047
+ if (edge.type !== "query")
2048
+ continue;
2049
+ const fromNode = graph.nodes.find((n) => n.id === edge.from);
2050
+ const toNode = graph.nodes.find((n) => n.id === edge.to);
2051
+ if (fromNode?.type !== "function" || toNode?.type !== "table")
2052
+ continue;
2053
+ if (toNode.databaseType !== "postgres")
2054
+ continue;
2055
+ if (!functionTableAccess.has(edge.from)) {
2056
+ functionTableAccess.set(edge.from, /* @__PURE__ */ new Map());
2057
+ }
2058
+ const tableAccess = functionTableAccess.get(edge.from);
2059
+ tableAccess.set(edge.to, (tableAccess.get(edge.to) ?? 0) + 1);
2060
+ }
2061
+ for (const [funcId, tableAccess] of functionTableAccess) {
2062
+ for (const [tableId, count] of tableAccess) {
2063
+ if (count >= 2) {
2064
+ const funcNode = graph.nodes.find((n) => n.id === funcId);
2065
+ const tableNode = graph.nodes.find((n) => n.id === tableId);
2066
+ if (!funcNode || !tableNode)
2067
+ continue;
2068
+ findings.push({
2069
+ severity: "high",
2070
+ issue: `Potential N+1 query pattern in function "${funcNode.name}"`,
2071
+ 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.`,
2072
+ 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.",
2073
+ metadata: {
2074
+ functionName: funcNode.name,
2075
+ filePath: funcNode.file,
2076
+ tableName: tableNode.name,
2077
+ queryCount: count
2078
+ }
2079
+ });
2080
+ }
2081
+ }
2082
+ }
2083
+ return findings;
2084
+ }
2085
+ };
2086
+ exports2.NplusOneAnalyzer = NplusOneAnalyzer;
2087
+ var LargeSelectAnalyzer = class {
2088
+ name = "LargeSelectAnalyzer";
2089
+ async analyze(graph) {
2090
+ const findings = [];
2091
+ const queryNodes = graph.nodes.filter((n) => n.type === "query");
2092
+ for (const queryNode of queryNodes) {
2093
+ if (queryNode.operation.toLowerCase().includes("select *") || queryNode.operation.toLowerCase().includes("select_all")) {
2094
+ findings.push({
2095
+ severity: "low",
2096
+ issue: `SELECT * usage detected in query "${queryNode.operation}"`,
2097
+ 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.",
2098
+ recommendation: "Specify only the columns you need. For frequently accessed subsets of columns, consider creating a covering index that includes those columns.",
2099
+ metadata: {
2100
+ operation: queryNode.operation
2101
+ }
2102
+ });
2103
+ }
2104
+ }
2105
+ const postgresTables = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "postgres");
2106
+ for (const table of postgresTables) {
2107
+ const scanEdges = graph.edges.filter((e) => e.to === table.id && e.type === "scan");
2108
+ if (scanEdges.length > 0) {
2109
+ const callerFunctions = scanEdges.map((e) => {
2110
+ const node = graph.nodes.find((n) => n.id === e.from);
2111
+ return node?.type === "function" ? node.name : e.from;
2112
+ }).filter((v, i, arr) => arr.indexOf(v) === i).join(", ");
2113
+ findings.push({
2114
+ severity: "medium",
2115
+ issue: `Sequential scan detected on PostgreSQL table "${table.name}"`,
2116
+ 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.`,
2117
+ 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.",
2118
+ metadata: {
2119
+ tableName: table.name,
2120
+ scanCount: scanEdges.length,
2121
+ callerFunctions
2122
+ }
2123
+ });
2124
+ }
2125
+ }
2126
+ return findings;
2127
+ }
2128
+ };
2129
+ exports2.LargeSelectAnalyzer = LargeSelectAnalyzer;
2130
+ }
2131
+ });
2132
+
2133
+ // ../analyzers/dist/mysql.js
2134
+ var require_mysql = __commonJS({
2135
+ "../analyzers/dist/mysql.js"(exports2) {
2136
+ "use strict";
2137
+ Object.defineProperty(exports2, "__esModule", { value: true });
2138
+ exports2.MySQLFullTableScanAnalyzer = exports2.MissingMySQLIndexAnalyzer = void 0;
2139
+ var graph_1 = require_dist8();
2140
+ var MissingMySQLIndexAnalyzer = class {
2141
+ name = "MissingMySQLIndexAnalyzer";
2142
+ async analyze(graph) {
2143
+ const findings = [];
2144
+ const mysqlTables = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "mysql");
2145
+ for (const table of mysqlTables) {
2146
+ const indexEdges = graph.edges.filter((e) => e.from === table.id && e.type === "uses_index");
2147
+ const hasIndexes = indexEdges.length > 0;
2148
+ const queryEdges = graph.edges.filter((e) => e.to === table.id && (e.type === "query" || e.type === "scan"));
2149
+ if (queryEdges.length > 0 && !hasIndexes) {
2150
+ const callerFunctions = queryEdges.map((e) => {
2151
+ const node = graph.nodes.find((n) => n.id === e.from);
2152
+ return node?.type === "function" ? node.name : e.from;
2153
+ }).join(", ");
2154
+ findings.push({
2155
+ severity: "medium",
2156
+ issue: `MySQL table "${table.name}" has no indexes but is queried by ${queryEdges.length} function(s)`,
2157
+ 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.`,
2158
+ recommendation: "Add indexes on columns used in WHERE clauses. Run EXPLAIN on slow queries. Consider composite indexes for multi-column filters.",
2159
+ metadata: {
2160
+ tableName: table.name,
2161
+ queryCount: queryEdges.length,
2162
+ callerFunctions
2163
+ }
2164
+ });
2165
+ }
2166
+ }
2167
+ return findings;
2168
+ }
2169
+ };
2170
+ exports2.MissingMySQLIndexAnalyzer = MissingMySQLIndexAnalyzer;
2171
+ var MySQLFullTableScanAnalyzer = class {
2172
+ name = "MySQLFullTableScanAnalyzer";
2173
+ async analyze(graph) {
2174
+ const findings = [];
2175
+ const scanEdges = (0, graph_1.getScanEdges)(graph);
2176
+ const scannedTableIds = /* @__PURE__ */ new Set();
2177
+ for (const edge of scanEdges) {
2178
+ const targetNode = graph.nodes.find((n) => n.id === edge.to);
2179
+ if (targetNode?.type === "table" && targetNode.databaseType === "mysql") {
2180
+ scannedTableIds.add(targetNode.id);
2181
+ }
2182
+ }
2183
+ for (const tableId of scannedTableIds) {
2184
+ const tableNode = graph.nodes.find((n) => n.id === tableId);
2185
+ if (!tableNode)
2186
+ continue;
2187
+ const tableScanEdges = scanEdges.filter((e) => e.to === tableId);
2188
+ const callerFunctions = tableScanEdges.map((e) => {
2189
+ const node = graph.nodes.find((n) => n.id === e.from);
2190
+ return node?.type === "function" ? node.name : e.from;
2191
+ }).join(", ");
2192
+ findings.push({
2193
+ severity: "high",
2194
+ issue: `Full table scan detected on MySQL table "${tableNode.name}"`,
2195
+ 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"}`,
2196
+ 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.",
2197
+ metadata: {
2198
+ tableName: tableNode.name,
2199
+ scanCount: tableScanEdges.length,
2200
+ callerFunctions
2201
+ }
2202
+ });
2203
+ }
2204
+ return findings;
2205
+ }
2206
+ };
2207
+ exports2.MySQLFullTableScanAnalyzer = MySQLFullTableScanAnalyzer;
2208
+ }
2209
+ });
2210
+
2211
+ // ../analyzers/dist/mongodb.js
2212
+ var require_mongodb = __commonJS({
2213
+ "../analyzers/dist/mongodb.js"(exports2) {
2214
+ "use strict";
2215
+ Object.defineProperty(exports2, "__esModule", { value: true });
2216
+ exports2.MongoCollectionScanAnalyzer = exports2.MissingMongoIndexAnalyzer = void 0;
2217
+ var graph_1 = require_dist8();
2218
+ var MissingMongoIndexAnalyzer = class {
2219
+ name = "MissingMongoIndexAnalyzer";
2220
+ async analyze(graph) {
2221
+ const findings = [];
2222
+ const mongoCollections = graph.nodes.filter((n) => n.type === "table" && n.databaseType === "mongodb");
2223
+ for (const coll of mongoCollections) {
2224
+ const indexEdges = graph.edges.filter((e) => e.from === coll.id && e.type === "uses_index");
2225
+ const hasIndexes = indexEdges.length > 0;
2226
+ const queryEdges = graph.edges.filter((e) => e.to === coll.id && (e.type === "query" || e.type === "scan"));
2227
+ if (queryEdges.length > 0 && !hasIndexes) {
2228
+ const callerFunctions = queryEdges.map((e) => {
2229
+ const node = graph.nodes.find((n) => n.id === e.from);
2230
+ return node?.type === "function" ? node.name : e.from;
2231
+ }).join(", ");
2232
+ findings.push({
2233
+ severity: "medium",
2234
+ issue: `MongoDB collection "${coll.name}" has no indexes but is queried by ${queryEdges.length} function(s)`,
2235
+ 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.`,
2236
+ recommendation: 'Add indexes using db.collection.createIndex({ field: 1 }) for frequently queried fields. Use explain("executionStats") to verify query plan.',
2237
+ metadata: {
2238
+ collectionName: coll.name,
2239
+ queryCount: queryEdges.length,
2240
+ callerFunctions
2241
+ }
2242
+ });
2243
+ }
2244
+ }
2245
+ return findings;
2246
+ }
2247
+ };
2248
+ exports2.MissingMongoIndexAnalyzer = MissingMongoIndexAnalyzer;
2249
+ var MongoCollectionScanAnalyzer = class {
2250
+ name = "MongoCollectionScanAnalyzer";
2251
+ async analyze(graph) {
2252
+ const findings = [];
2253
+ const scanEdges = (0, graph_1.getScanEdges)(graph);
2254
+ const scannedCollIds = /* @__PURE__ */ new Set();
2255
+ for (const edge of scanEdges) {
2256
+ const targetNode = graph.nodes.find((n) => n.id === edge.to);
2257
+ if (targetNode?.type === "table" && targetNode.databaseType === "mongodb") {
2258
+ scannedCollIds.add(targetNode.id);
2259
+ }
2260
+ }
2261
+ for (const collId of scannedCollIds) {
2262
+ const collNode = graph.nodes.find((n) => n.id === collId);
2263
+ if (!collNode)
2264
+ continue;
2265
+ const collScanEdges = scanEdges.filter((e) => e.to === collId);
2266
+ const callerFunctions = collScanEdges.map((e) => {
2267
+ const node = graph.nodes.find((n) => n.id === e.from);
2268
+ return node?.type === "function" ? node.name : e.from;
2269
+ }).join(", ");
2270
+ findings.push({
2271
+ severity: "high",
2272
+ issue: `Collection scan detected on MongoDB collection "${collNode.name}"`,
2273
+ description: `Collection "${collNode.name}" is being scanned without an index, reading every document. This is very expensive for large collections. Called from: ${callerFunctions || "unknown"}`,
2274
+ recommendation: 'Add an index on the field(s) used as query predicates. Use db.collection.createIndex({ field: 1 }) and verify with explain("executionStats").',
2275
+ metadata: {
2276
+ collectionName: collNode.name,
2277
+ scanCount: collScanEdges.length,
2278
+ callerFunctions
2279
+ }
2280
+ });
2281
+ }
2282
+ return findings;
2283
+ }
2284
+ };
2285
+ exports2.MongoCollectionScanAnalyzer = MongoCollectionScanAnalyzer;
2286
+ }
2287
+ });
2288
+
2289
+ // ../analyzers/dist/terraform.js
2290
+ var require_terraform = __commonJS({
2291
+ "../analyzers/dist/terraform.js"(exports2) {
2292
+ "use strict";
2293
+ Object.defineProperty(exports2, "__esModule", { value: true });
2294
+ exports2.IaCDriftAnalyzer = void 0;
2295
+ var IaCDriftAnalyzer2 = class {
2296
+ name = "IaCDriftAnalyzer";
2297
+ iacSchema = null;
2298
+ setIaCSchema(schema) {
2299
+ this.iacSchema = schema;
2300
+ }
2301
+ async analyze(graph) {
2302
+ const findings = [];
2303
+ if (!this.iacSchema) {
2304
+ return findings;
2305
+ }
2306
+ const deployedDynamoTables = new Set(graph.nodes.filter((n) => n.type === "table" && n.databaseType === "dynamodb").map((n) => n.name));
2307
+ const iacDynamoTables = /* @__PURE__ */ new Map();
2308
+ for (const t of this.iacSchema.dynamoTables) {
2309
+ iacDynamoTables.set(t.name, t.filePath);
2310
+ }
2311
+ for (const [tableName, filePath] of iacDynamoTables) {
2312
+ if (!deployedDynamoTables.has(tableName)) {
2313
+ findings.push({
2314
+ severity: "medium",
2315
+ issue: `IaC drift: DynamoDB table "${tableName}" is defined in IaC but not found in AWS`,
2316
+ description: `Table "${tableName}" is defined in ${filePath} but was not found among deployed AWS DynamoDB tables. It may not have been deployed yet, or may have been deleted from AWS without updating the IaC.`,
2317
+ recommendation: "Run `terraform apply` or deploy your CloudFormation stack to create the missing table, or remove the definition from your IaC if the table is intentionally absent.",
2318
+ metadata: {
2319
+ tableName,
2320
+ filePath,
2321
+ driftType: "defined_not_deployed"
2322
+ }
2323
+ });
2324
+ }
2325
+ }
2326
+ for (const tableName of deployedDynamoTables) {
2327
+ if (!iacDynamoTables.has(tableName)) {
2328
+ findings.push({
2329
+ severity: "medium",
2330
+ issue: `IaC drift: DynamoDB table "${tableName}" is deployed in AWS but not defined in IaC`,
2331
+ description: `Table "${tableName}" exists in AWS DynamoDB but has no corresponding definition in Terraform or CloudFormation. This resource may have been created manually and is not tracked by IaC.`,
2332
+ recommendation: "Import the table into your IaC using `terraform import` or add a CloudFormation resource definition. Untracked resources can lead to configuration drift and accidental deletion.",
2333
+ metadata: {
2334
+ tableName,
2335
+ driftType: "deployed_not_defined"
2336
+ }
2337
+ });
2338
+ }
2339
+ }
2340
+ return findings;
2341
+ }
2342
+ };
2343
+ exports2.IaCDriftAnalyzer = IaCDriftAnalyzer2;
2344
+ }
2345
+ });
2346
+
2347
+ // ../analyzers/dist/index.js
2348
+ var require_dist9 = __commonJS({
2349
+ "../analyzers/dist/index.js"(exports2) {
2350
+ "use strict";
2351
+ Object.defineProperty(exports2, "__esModule", { value: true });
2352
+ exports2.IaCDriftAnalyzer = exports2.MongoCollectionScanAnalyzer = exports2.MissingMongoIndexAnalyzer = exports2.MySQLFullTableScanAnalyzer = exports2.MissingMySQLIndexAnalyzer = exports2.LargeSelectAnalyzer = exports2.NplusOneAnalyzer = exports2.MissingIndexAnalyzer = exports2.HotPartitionAnalyzer = exports2.MissingGSIAnalyzer = exports2.FullTableScanAnalyzer = void 0;
2353
+ exports2.runAllAnalyzers = runAllAnalyzers2;
2354
+ exports2.summarizeFindings = summarizeFindings;
2355
+ var core_1 = require_dist();
2356
+ var dynamodb_1 = require_dynamodb();
2357
+ var postgres_1 = require_postgres();
2358
+ var mysql_1 = require_mysql();
2359
+ var mongodb_1 = require_mongodb();
2360
+ var terraform_1 = require_terraform();
2361
+ var dynamodb_2 = require_dynamodb();
2362
+ Object.defineProperty(exports2, "FullTableScanAnalyzer", { enumerable: true, get: function() {
2363
+ return dynamodb_2.FullTableScanAnalyzer;
2364
+ } });
2365
+ Object.defineProperty(exports2, "MissingGSIAnalyzer", { enumerable: true, get: function() {
2366
+ return dynamodb_2.MissingGSIAnalyzer;
2367
+ } });
2368
+ Object.defineProperty(exports2, "HotPartitionAnalyzer", { enumerable: true, get: function() {
2369
+ return dynamodb_2.HotPartitionAnalyzer;
2370
+ } });
2371
+ var postgres_2 = require_postgres();
2372
+ Object.defineProperty(exports2, "MissingIndexAnalyzer", { enumerable: true, get: function() {
2373
+ return postgres_2.MissingIndexAnalyzer;
2374
+ } });
2375
+ Object.defineProperty(exports2, "NplusOneAnalyzer", { enumerable: true, get: function() {
2376
+ return postgres_2.NplusOneAnalyzer;
2377
+ } });
2378
+ Object.defineProperty(exports2, "LargeSelectAnalyzer", { enumerable: true, get: function() {
2379
+ return postgres_2.LargeSelectAnalyzer;
2380
+ } });
2381
+ var mysql_2 = require_mysql();
2382
+ Object.defineProperty(exports2, "MissingMySQLIndexAnalyzer", { enumerable: true, get: function() {
2383
+ return mysql_2.MissingMySQLIndexAnalyzer;
2384
+ } });
2385
+ Object.defineProperty(exports2, "MySQLFullTableScanAnalyzer", { enumerable: true, get: function() {
2386
+ return mysql_2.MySQLFullTableScanAnalyzer;
2387
+ } });
2388
+ var mongodb_2 = require_mongodb();
2389
+ Object.defineProperty(exports2, "MissingMongoIndexAnalyzer", { enumerable: true, get: function() {
2390
+ return mongodb_2.MissingMongoIndexAnalyzer;
2391
+ } });
2392
+ Object.defineProperty(exports2, "MongoCollectionScanAnalyzer", { enumerable: true, get: function() {
2393
+ return mongodb_2.MongoCollectionScanAnalyzer;
2394
+ } });
2395
+ var terraform_2 = require_terraform();
2396
+ Object.defineProperty(exports2, "IaCDriftAnalyzer", { enumerable: true, get: function() {
2397
+ return terraform_2.IaCDriftAnalyzer;
2398
+ } });
2399
+ var DEFAULT_ANALYZERS = [
2400
+ new dynamodb_1.FullTableScanAnalyzer(),
2401
+ new dynamodb_1.MissingGSIAnalyzer(),
2402
+ new dynamodb_1.HotPartitionAnalyzer(),
2403
+ new postgres_1.MissingIndexAnalyzer(),
2404
+ new postgres_1.NplusOneAnalyzer(),
2405
+ new postgres_1.LargeSelectAnalyzer(),
2406
+ new mysql_1.MissingMySQLIndexAnalyzer(),
2407
+ new mysql_1.MySQLFullTableScanAnalyzer(),
2408
+ new mongodb_1.MissingMongoIndexAnalyzer(),
2409
+ new mongodb_1.MongoCollectionScanAnalyzer(),
2410
+ new terraform_1.IaCDriftAnalyzer()
2411
+ ];
2412
+ async function runAllAnalyzers2(graph, analyzers = DEFAULT_ANALYZERS) {
2413
+ const allFindings = [];
2414
+ for (const analyzer of analyzers) {
2415
+ try {
2416
+ core_1.logger.debug(`Running analyzer: ${analyzer.name}`);
2417
+ const findings = await analyzer.analyze(graph);
2418
+ core_1.logger.info(`[${analyzer.name}] found ${findings.length} issue(s)`);
2419
+ allFindings.push(...findings);
2420
+ } catch (err) {
2421
+ core_1.logger.warn(`Analyzer "${analyzer.name}" failed: ${err instanceof Error ? err.message : String(err)}`);
2422
+ }
2423
+ }
2424
+ const severityOrder = { high: 0, medium: 1, low: 2 };
2425
+ allFindings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
2426
+ return allFindings;
2427
+ }
2428
+ function summarizeFindings(findings) {
2429
+ return {
2430
+ total: findings.length,
2431
+ high: findings.filter((f) => f.severity === "high").length,
2432
+ medium: findings.filter((f) => f.severity === "medium").length,
2433
+ low: findings.filter((f) => f.severity === "low").length
2434
+ };
2435
+ }
2436
+ }
2437
+ });
2438
+
2439
+ // ../server/dist/index.js
2440
+ var require_dist10 = __commonJS({
2441
+ "../server/dist/index.js"(exports2) {
2442
+ "use strict";
2443
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
2444
+ return mod && mod.__esModule ? mod : { "default": mod };
2445
+ };
2446
+ Object.defineProperty(exports2, "__esModule", { value: true });
2447
+ exports2.currentFindings = exports2.currentGraph = void 0;
2448
+ exports2.setGraphState = setGraphState2;
2449
+ exports2.createServer = createServer2;
2450
+ var fastify_1 = __importDefault(require("fastify"));
2451
+ var cors_1 = __importDefault(require("@fastify/cors"));
2452
+ var core_1 = require_dist();
2453
+ var graph_1 = require_dist8();
2454
+ var currentGraph = { nodes: [], edges: [] };
2455
+ exports2.currentGraph = currentGraph;
2456
+ var currentFindings = [];
2457
+ exports2.currentFindings = currentFindings;
2458
+ function setGraphState2(graph, findings) {
2459
+ exports2.currentGraph = currentGraph = graph;
2460
+ exports2.currentFindings = currentFindings = findings;
2461
+ }
2462
+ function createServer2(port = 3e3) {
2463
+ const fastify = (0, fastify_1.default)({
2464
+ logger: false
2465
+ // We use pino directly
2466
+ });
2467
+ fastify.register(cors_1.default, { origin: true });
2468
+ fastify.get("/health", async () => {
2469
+ return {
2470
+ status: "ok",
2471
+ version: "0.1.0",
2472
+ graphNodes: currentGraph.nodes.length,
2473
+ graphEdges: currentGraph.edges.length,
2474
+ findings: currentFindings.length
2475
+ };
2476
+ });
2477
+ fastify.post("/mcp", async (request, reply) => {
2478
+ const { tool, input } = request.body;
2479
+ if (!tool) {
2480
+ reply.status(400);
2481
+ return { success: false, error: 'Missing "tool" field in request body' };
2482
+ }
2483
+ core_1.logger.info(`MCP tool call: ${tool}`);
2484
+ try {
2485
+ const result = await handleToolCall(tool, input ?? {});
2486
+ return { success: true, data: result };
2487
+ } catch (err) {
2488
+ core_1.logger.error(`MCP tool "${tool}" failed: ${err instanceof Error ? err.message : String(err)}`);
2489
+ reply.status(500);
2490
+ return {
2491
+ success: false,
2492
+ error: err instanceof Error ? err.message : "Unknown error"
2493
+ };
2494
+ }
2495
+ });
2496
+ fastify.get("/mcp/tools", async () => {
2497
+ return {
2498
+ tools: [
2499
+ {
2500
+ name: "get_graph_summary",
2501
+ description: "Returns the full infrastructure graph with all findings",
2502
+ input: {}
2503
+ },
2504
+ {
2505
+ name: "analyze_function",
2506
+ description: "Analyze a specific function for database issues",
2507
+ input: { function: "string \u2014 function name to analyze" }
2508
+ },
2509
+ {
2510
+ name: "suggest_gsi",
2511
+ description: "Get GSI suggestions for a DynamoDB table and attribute",
2512
+ input: {
2513
+ table: "string \u2014 DynamoDB table name",
2514
+ attribute: "string \u2014 attribute to index"
2515
+ }
2516
+ },
2517
+ {
2518
+ name: "postgres_index_suggestions",
2519
+ description: "Get PostgreSQL index suggestions for a table column",
2520
+ input: {
2521
+ table: "string \u2014 PostgreSQL table name",
2522
+ column: "string \u2014 column to analyze"
2523
+ }
2524
+ },
2525
+ {
2526
+ name: "suggest_mongo_index",
2527
+ description: "Get index suggestions for a MongoDB collection field",
2528
+ input: {
2529
+ collection: "string \u2014 MongoDB collection name",
2530
+ field: "string \u2014 field to index"
2531
+ }
2532
+ },
2533
+ {
2534
+ name: "mysql_index_suggestions",
2535
+ description: "Get MySQL index suggestions for a table column",
2536
+ input: {
2537
+ table: "string \u2014 MySQL table name",
2538
+ column: "string \u2014 column to analyze"
2539
+ }
2540
+ }
2541
+ ]
2542
+ };
2543
+ });
2544
+ return { fastify, start: () => startServer(fastify, port) };
2545
+ }
2546
+ async function handleToolCall(tool, input) {
2547
+ switch (tool) {
2548
+ case "get_graph_summary": {
2549
+ return {
2550
+ nodes: currentGraph.nodes,
2551
+ edges: currentGraph.edges,
2552
+ findings: currentFindings,
2553
+ summary: {
2554
+ totalNodes: currentGraph.nodes.length,
2555
+ totalEdges: currentGraph.edges.length,
2556
+ tables: (0, graph_1.getTableNodes)(currentGraph).length,
2557
+ functions: (0, graph_1.getFunctionNodes)(currentGraph).length,
2558
+ scans: (0, graph_1.getScanEdges)(currentGraph).length,
2559
+ totalFindings: currentFindings.length,
2560
+ highSeverity: currentFindings.filter((f) => f.severity === "high").length,
2561
+ mediumSeverity: currentFindings.filter((f) => f.severity === "medium").length,
2562
+ lowSeverity: currentFindings.filter((f) => f.severity === "low").length
2563
+ }
2564
+ };
2565
+ }
2566
+ case "analyze_function": {
2567
+ const functionName = String(input.function ?? "");
2568
+ if (!functionName) {
2569
+ throw new Error("Missing input.function");
2570
+ }
2571
+ const funcNode = currentGraph.nodes.find((n) => n.type === "function" && n.name === functionName);
2572
+ if (!funcNode) {
2573
+ return {
2574
+ function: functionName,
2575
+ found: false,
2576
+ issues: [],
2577
+ recommendations: [`Function "${functionName}" not found in the analyzed codebase.`]
2578
+ };
2579
+ }
2580
+ const outEdges = (0, graph_1.getOutgoingEdges)(currentGraph, funcNode.id);
2581
+ const relatedFindings = currentFindings.filter((f) => {
2582
+ const meta = f.metadata;
2583
+ return meta?.functionName === functionName || meta?.callerFunctions?.toString().includes(functionName);
2584
+ });
2585
+ const issues = relatedFindings.map((f) => ({
2586
+ severity: f.severity,
2587
+ issue: f.issue,
2588
+ description: f.description
2589
+ }));
2590
+ const recommendations = relatedFindings.map((f) => f.recommendation);
2591
+ const uniqueRecs = [...new Set(recommendations)];
2592
+ return {
2593
+ function: functionName,
2594
+ found: true,
2595
+ file: funcNode.type === "function" ? funcNode.file : void 0,
2596
+ accessedTables: outEdges.map((e) => {
2597
+ const target = currentGraph.nodes.find((n) => n.id === e.to);
2598
+ return { tableId: e.to, edgeType: e.type, tableName: target && "name" in target ? target.name : e.to };
2599
+ }),
2600
+ issues,
2601
+ recommendations: uniqueRecs
2602
+ };
2603
+ }
2604
+ case "suggest_gsi": {
2605
+ const tableName = String(input.table ?? "");
2606
+ const attribute = String(input.attribute ?? "");
2607
+ if (!tableName || !attribute) {
2608
+ throw new Error("Missing input.table or input.attribute");
2609
+ }
2610
+ const tableNode = currentGraph.nodes.find((n) => n.type === "table" && n.databaseType === "dynamodb" && "name" in n && n.name === tableName);
2611
+ if (!tableNode) {
2612
+ return {
2613
+ table: tableName,
2614
+ attribute,
2615
+ found: false,
2616
+ message: `DynamoDB table "${tableName}" not found in analyzed graph`
2617
+ };
2618
+ }
2619
+ const sanitizedAttr = attribute.replace(/[^a-zA-Z0-9_]/g, "_");
2620
+ const indexName = `${tableName}-${sanitizedAttr}-index`;
2621
+ return {
2622
+ table: tableName,
2623
+ attribute,
2624
+ index: {
2625
+ name: indexName,
2626
+ partitionKey: attribute,
2627
+ projectionType: "ALL",
2628
+ billingMode: "PAY_PER_REQUEST"
2629
+ },
2630
+ rationale: `Adding a GSI with partition key "${attribute}" will allow efficient Query operations instead of full table Scans when filtering by this attribute.`,
2631
+ estimatedCost: "Approximately double the storage cost for the projected attributes.",
2632
+ recommendation: `Add the following GSI to your CloudFormation/Terraform definition:
2633
+
2634
+ GSI Name: ${indexName}
2635
+ Partition Key: ${attribute}
2636
+ Sort Key: (optional, add for range queries)
2637
+ Projection: ALL`
2638
+ };
2639
+ }
2640
+ case "postgres_index_suggestions": {
2641
+ const tableName = String(input.table ?? "");
2642
+ const column = String(input.column ?? "");
2643
+ if (!tableName || !column) {
2644
+ throw new Error("Missing input.table or input.column");
2645
+ }
2646
+ const tableNode = currentGraph.nodes.find((n) => n.type === "table" && n.databaseType === "postgres" && "name" in n && (n.name === tableName || n.name.endsWith(`.${tableName}`)));
2647
+ const sanitizedCol = column.replace(/[^a-zA-Z0-9_]/g, "_");
2648
+ const sanitizedTable = tableName.replace(/[^a-zA-Z0-9_]/g, "_");
2649
+ const indexName = `idx_${sanitizedTable}_${sanitizedCol}`;
2650
+ return {
2651
+ table: tableName,
2652
+ column,
2653
+ found: !!tableNode,
2654
+ recommendation: `CREATE INDEX CONCURRENTLY ${indexName} ON ${tableName} (${column});`,
2655
+ rationale: `An index on column "${column}" of table "${tableName}" will eliminate sequential scans when filtering on this column.`,
2656
+ notes: [
2657
+ "Use CONCURRENTLY to avoid locking the table during index creation",
2658
+ "Consider a partial index if the column has high cardinality and you commonly filter on specific values",
2659
+ "Run ANALYZE after creating the index to update statistics",
2660
+ `Example partial index: CREATE INDEX CONCURRENTLY ${indexName}_partial ON ${tableName} (${column}) WHERE ${column} IS NOT NULL;`
2661
+ ]
2662
+ };
2663
+ }
2664
+ case "suggest_mongo_index": {
2665
+ const collection = String(input.collection ?? "");
2666
+ const field = String(input.field ?? "");
2667
+ if (!collection || !field) {
2668
+ throw new Error("Missing input.collection or input.field");
2669
+ }
2670
+ const sanitizedField = field.replace(/[^a-zA-Z0-9_.]/g, "_");
2671
+ return {
2672
+ collection,
2673
+ field,
2674
+ recommendation: `db.${collection}.createIndex({ ${field}: 1 })`,
2675
+ rationale: `An index on field "${field}" of collection "${collection}" will eliminate full collection scans when filtering on this field.`,
2676
+ notes: [
2677
+ "Use { background: true } in MongoDB < 4.2 to avoid blocking reads during index creation",
2678
+ `For compound queries, consider a compound index: db.${collection}.createIndex({ ${field}: 1, otherField: 1 })`,
2679
+ `For text search, use a text index: db.${collection}.createIndex({ ${sanitizedField}: "text" })`,
2680
+ `Run db.${collection}.explain("executionStats").find({ ${field}: value }) to verify the index is used`
2681
+ ]
2682
+ };
2683
+ }
2684
+ case "mysql_index_suggestions": {
2685
+ const tableName = String(input.table ?? "");
2686
+ const column = String(input.column ?? "");
2687
+ if (!tableName || !column) {
2688
+ throw new Error("Missing input.table or input.column");
2689
+ }
2690
+ const sanitizedCol = column.replace(/[^a-zA-Z0-9_]/g, "_");
2691
+ const sanitizedTable = tableName.replace(/[^a-zA-Z0-9_]/g, "_");
2692
+ const indexName = `idx_${sanitizedTable}_${sanitizedCol}`;
2693
+ const tableNode = currentGraph.nodes.find((n) => n.type === "table" && n.databaseType === "mysql" && "name" in n && (n.name === tableName || n.name.endsWith(`.${tableName}`)));
2694
+ return {
2695
+ table: tableName,
2696
+ column,
2697
+ found: !!tableNode,
2698
+ recommendation: `ALTER TABLE ${tableName} ADD INDEX ${indexName} (${column});`,
2699
+ rationale: `An index on column "${column}" of table "${tableName}" will eliminate full table scans when filtering on this column.`,
2700
+ notes: [
2701
+ "MySQL adds indexes online (no full table lock for InnoDB with MySQL 5.6+)",
2702
+ `Consider a composite index if you filter on multiple columns together`,
2703
+ `Use EXPLAIN SELECT ... to verify the index is used after adding it`,
2704
+ `Example composite: ALTER TABLE ${tableName} ADD INDEX idx_${sanitizedTable}_composite (${column}, other_column);`
2705
+ ]
2706
+ };
2707
+ }
2708
+ default:
2709
+ throw new Error(`Unknown tool: "${tool}". Available tools: get_graph_summary, analyze_function, suggest_gsi, postgres_index_suggestions, suggest_mongo_index, mysql_index_suggestions`);
2710
+ }
2711
+ }
2712
+ async function startServer(fastify, port) {
2713
+ try {
2714
+ await fastify.listen({ port, host: "0.0.0.0" });
2715
+ core_1.logger.info(`Infrawise MCP server running at http://localhost:${port}`);
2716
+ core_1.logger.info(`MCP endpoint: http://localhost:${port}/mcp`);
2717
+ core_1.logger.info(`Available tools: http://localhost:${port}/mcp/tools`);
2718
+ } catch (err) {
2719
+ core_1.logger.error(`Failed to start server: ${err instanceof Error ? err.message : String(err)}`);
2720
+ process.exit(1);
2721
+ }
2722
+ }
2723
+ }
2724
+ });
2725
+
2726
+ // src/index.ts
2727
+ var import_commander = require("commander");
2728
+
2729
+ // src/utils.ts
2730
+ var fs = __toESM(require("fs"));
2731
+ var path = __toESM(require("path"));
2732
+ var os = __toESM(require("os"));
2733
+ var import_chalk = __toESM(require("chalk"));
2734
+ function readAWSProfiles() {
2735
+ const credentialsPath = path.join(os.homedir(), ".aws", "credentials");
2736
+ const configPath = path.join(os.homedir(), ".aws", "config");
2737
+ const profiles = /* @__PURE__ */ new Set();
2738
+ function parseFile(filePath) {
2739
+ if (!fs.existsSync(filePath)) return;
2740
+ for (const line of fs.readFileSync(filePath, "utf-8").split("\n")) {
2741
+ const match = line.match(/^\[(.+)\]$/);
2742
+ if (match?.[1]) {
2743
+ let name = match[1];
2744
+ if (name.startsWith("profile ")) name = name.slice(8);
2745
+ profiles.add(name);
2746
+ }
2747
+ }
2748
+ }
2749
+ parseFile(credentialsPath);
2750
+ parseFile(configPath);
2751
+ return profiles.size > 0 ? [...profiles] : ["default"];
2752
+ }
2753
+ function detectAWSRegion() {
2754
+ if (process.env.AWS_DEFAULT_REGION) return process.env.AWS_DEFAULT_REGION;
2755
+ if (process.env.AWS_REGION) return process.env.AWS_REGION;
2756
+ const configPath = path.join(os.homedir(), ".aws", "config");
2757
+ if (fs.existsSync(configPath)) {
2758
+ const match = fs.readFileSync(configPath, "utf-8").match(/region\s*=\s*(.+)/);
2759
+ if (match?.[1]) return match[1].trim();
2760
+ }
2761
+ return "us-east-1";
2762
+ }
2763
+ function detectRepoType(repoPath) {
2764
+ if (fs.existsSync(path.join(repoPath, "tsconfig.json"))) return "typescript";
2765
+ if (fs.existsSync(path.join(repoPath, "package.json"))) return "javascript";
2766
+ return "unknown";
2767
+ }
2768
+ function printBanner() {
2769
+ const line1 = import_chalk.default.bold.hex("#6366f1")(" infrawise");
2770
+ const version = import_chalk.default.dim(" v0.1.0");
2771
+ const tagline = import_chalk.default.dim(" Infrastructure Intelligence Platform\n");
2772
+ console.log(`
2773
+ ${line1}${version}`);
2774
+ console.log(tagline);
2775
+ }
2776
+ function printHeader(title) {
2777
+ console.log(import_chalk.default.bold(`
2778
+ ${title}`));
2779
+ console.log(import_chalk.default.dim("\u2500".repeat(title.length + 2)));
2780
+ }
2781
+ var log = {
2782
+ success: (msg, detail) => {
2783
+ console.log(` ${import_chalk.default.green("\u2713")} ${msg}${detail ? import_chalk.default.dim(` ${detail}`) : ""}`);
2784
+ },
2785
+ fail: (msg, detail) => {
2786
+ console.log(` ${import_chalk.default.red("\u2717")} ${import_chalk.default.red(msg)}${detail ? import_chalk.default.dim(`
2787
+ ${detail}`) : ""}`);
2788
+ },
2789
+ warn: (msg, detail) => {
2790
+ console.log(` ${import_chalk.default.yellow("\u26A0")} ${import_chalk.default.yellow(msg)}${detail ? import_chalk.default.dim(`
2791
+ ${detail}`) : ""}`);
2792
+ },
2793
+ skip: (msg, detail) => {
2794
+ console.log(` ${import_chalk.default.dim("\u2212")} ${import_chalk.default.dim(msg)}${detail ? import_chalk.default.dim(` ${detail}`) : ""}`);
2795
+ },
2796
+ info: (msg) => {
2797
+ console.log(` ${import_chalk.default.cyan("\u203A")} ${msg}`);
2798
+ },
2799
+ dim: (msg) => {
2800
+ console.log(import_chalk.default.dim(` ${msg}`));
2801
+ }
2802
+ };
2803
+ function severityBadge(severity) {
2804
+ switch (severity) {
2805
+ case "high":
2806
+ return import_chalk.default.bgRed.white.bold(` HIGH `);
2807
+ case "medium":
2808
+ return import_chalk.default.bgYellow.black.bold(` MED `);
2809
+ case "low":
2810
+ return import_chalk.default.bgCyan.black.bold(` LOW `);
2811
+ }
2812
+ }
2813
+ function severityColor(severity) {
2814
+ switch (severity) {
2815
+ case "high":
2816
+ return import_chalk.default.red;
2817
+ case "medium":
2818
+ return import_chalk.default.yellow;
2819
+ case "low":
2820
+ return import_chalk.default.cyan;
2821
+ }
2822
+ }
2823
+ function printFinding(finding, index) {
2824
+ const color = severityColor(finding.severity);
2825
+ const badge = severityBadge(finding.severity);
2826
+ const num = import_chalk.default.dim(`${index + 1}.`);
2827
+ console.log(`
2828
+ ${num} ${badge} ${import_chalk.default.bold(finding.issue)}`);
2829
+ console.log(import_chalk.default.dim(` ${finding.description}`));
2830
+ console.log(` ${import_chalk.default.green("\u2192")} ${finding.recommendation}`);
2831
+ }
2832
+ function printSummaryBox(findings) {
2833
+ const high = findings.filter((f) => f.severity === "high").length;
2834
+ const medium = findings.filter((f) => f.severity === "medium").length;
2835
+ const low = findings.filter((f) => f.severity === "low").length;
2836
+ console.log("");
2837
+ 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"));
2838
+ console.log(import_chalk.default.dim(" \u2502") + import_chalk.default.bold(" Analysis Summary ") + import_chalk.default.dim("\u2502"));
2839
+ 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"));
2840
+ 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"));
2841
+ 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"));
2842
+ 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"));
2843
+ 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"));
2844
+ console.log(import_chalk.default.dim(" \u2502") + ` Total ${import_chalk.default.bold(String(findings.length).padStart(3))} ` + import_chalk.default.dim("\u2502"));
2845
+ 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"));
2846
+ }
2847
+
2848
+ // src/commands/init.ts
2849
+ var fs2 = __toESM(require("fs"));
2850
+ var path2 = __toESM(require("path"));
2851
+ var import_chalk2 = __toESM(require("chalk"));
2852
+ var import_inquirer = __toESM(require("inquirer"));
2853
+ var import_core = __toESM(require_dist());
2854
+ async function runInit(options = {}) {
2855
+ const cwd = process.cwd();
2856
+ const configPath = path2.join(cwd, "infrawise.yaml");
2857
+ if (fs2.existsSync(configPath) && !options.force) {
2858
+ console.log(`
2859
+ ${import_chalk2.default.yellow("\u26A0")} ${import_chalk2.default.yellow("infrawise.yaml already exists.")} ${import_chalk2.default.dim("Use --force to overwrite.")}
2860
+ `);
2861
+ return;
2862
+ }
2863
+ printHeader("Initialize Infrawise");
2864
+ const repoType = detectRepoType(cwd);
2865
+ const repoName = path2.basename(cwd);
2866
+ const profiles = readAWSProfiles();
2867
+ const detectedRegion = detectAWSRegion();
2868
+ log.success(`Repository detected`, repoName);
2869
+ log.success(`Type`, repoType);
2870
+ log.success(`AWS profiles found`, String(profiles.length));
2871
+ console.log("");
2872
+ const answers = await import_inquirer.default.prompt([
2873
+ {
2874
+ type: "input",
2875
+ name: "project",
2876
+ message: "Project name:",
2877
+ default: repoName
2878
+ },
2879
+ {
2880
+ type: "list",
2881
+ name: "awsProfile",
2882
+ message: "AWS profile:",
2883
+ choices: profiles,
2884
+ default: profiles[0]
2885
+ },
2886
+ {
2887
+ type: "input",
2888
+ name: "region",
2889
+ message: "AWS region:",
2890
+ default: detectedRegion
2891
+ },
2892
+ {
2893
+ type: "input",
2894
+ name: "dynamoTables",
2895
+ message: "DynamoDB tables to include:",
2896
+ default: "",
2897
+ suffix: import_chalk2.default.dim(" (comma-separated, leave blank for all)")
2898
+ },
2899
+ {
2900
+ type: "confirm",
2901
+ name: "pgEnabled",
2902
+ message: "Enable PostgreSQL analysis?",
2903
+ default: false
2904
+ },
2905
+ {
2906
+ type: "input",
2907
+ name: "pgConnectionString",
2908
+ message: "PostgreSQL connection string:",
2909
+ default: "postgresql://localhost:5432/mydb",
2910
+ when: (a) => a.pgEnabled
2911
+ },
2912
+ {
2913
+ type: "confirm",
2914
+ name: "mysqlEnabled",
2915
+ message: "Enable MySQL analysis?",
2916
+ default: false
2917
+ },
2918
+ {
2919
+ type: "input",
2920
+ name: "mysqlConnectionString",
2921
+ message: "MySQL connection string:",
2922
+ default: "mysql://localhost:3306/mydb",
2923
+ when: (a) => a.mysqlEnabled
2924
+ },
2925
+ {
2926
+ type: "confirm",
2927
+ name: "mongoEnabled",
2928
+ message: "Enable MongoDB analysis?",
2929
+ default: false
2930
+ },
2931
+ {
2932
+ type: "input",
2933
+ name: "mongoConnectionString",
2934
+ message: "MongoDB connection string:",
2935
+ default: "mongodb://localhost:27017",
2936
+ when: (a) => a.mongoEnabled
2937
+ }
2938
+ ]);
2939
+ const includeTables = answers.dynamoTables ? answers.dynamoTables.split(",").map((t) => t.trim()).filter(Boolean) : [];
2940
+ const configContent = (0, import_core.generateDefaultConfig)(answers.project, {
2941
+ aws: { profile: answers.awsProfile, region: answers.region },
2942
+ dynamodb: { includeTables },
2943
+ postgres: {
2944
+ enabled: answers.pgEnabled,
2945
+ connectionString: answers.pgConnectionString ?? ""
2946
+ },
2947
+ mysql: {
2948
+ enabled: answers.mysqlEnabled,
2949
+ connectionString: answers.mysqlConnectionString ?? ""
2950
+ },
2951
+ mongodb: {
2952
+ enabled: answers.mongoEnabled,
2953
+ connectionString: answers.mongoConnectionString ?? ""
2954
+ }
2955
+ });
2956
+ fs2.writeFileSync(configPath, configContent, "utf-8");
2957
+ console.log("");
2958
+ log.success(`Created ${import_chalk2.default.bold("infrawise.yaml")}`);
2959
+ console.log("");
2960
+ console.log(import_chalk2.default.bold(" Next steps:"));
2961
+ log.info(`Run ${import_chalk2.default.cyan("infrawise doctor")} to validate your setup`);
2962
+ log.info(`Run ${import_chalk2.default.cyan("infrawise analyze")} to scan your infrastructure`);
2963
+ log.info(`Run ${import_chalk2.default.cyan("infrawise dev")} to start the MCP server`);
2964
+ console.log("");
2965
+ }
2966
+
2967
+ // src/commands/auth.ts
2968
+ var import_chalk3 = __toESM(require("chalk"));
2969
+ var import_inquirer2 = __toESM(require("inquirer"));
2970
+ var import_ora = __toESM(require("ora"));
2971
+ var import_adapters_dynamodb = __toESM(require_dist2());
2972
+ async function runAuth() {
2973
+ printHeader("AWS Authentication");
2974
+ const profiles = readAWSProfiles();
2975
+ if (profiles.length === 0) {
2976
+ log.fail("No AWS profiles found");
2977
+ console.log("");
2978
+ log.info("Run " + import_chalk3.default.cyan("aws configure") + " to set up credentials");
2979
+ log.info("Or manually edit " + import_chalk3.default.dim("~/.aws/credentials"));
2980
+ console.log("");
2981
+ return;
2982
+ }
2983
+ log.success(`Found ${profiles.length} profile(s)`);
2984
+ console.log("");
2985
+ const { selectedProfile } = await import_inquirer2.default.prompt([
2986
+ {
2987
+ type: "list",
2988
+ name: "selectedProfile",
2989
+ message: "Select a profile to validate:",
2990
+ choices: profiles
2991
+ }
2992
+ ]);
2993
+ console.log("");
2994
+ const spin = (0, import_ora.default)({ text: import_chalk3.default.dim(`Validating "${selectedProfile}"...`), color: "cyan" }).start();
2995
+ const testConfig = {
2996
+ project: "auth-test",
2997
+ aws: { profile: selectedProfile, region: "us-east-1" }
2998
+ };
2999
+ const isValid = await (0, import_adapters_dynamodb.validateDynamoAccess)(testConfig);
3000
+ if (isValid) {
3001
+ spin.succeed(import_chalk3.default.green(`Profile "${import_chalk3.default.bold(selectedProfile)}" is valid`));
3002
+ console.log("");
3003
+ console.log(import_chalk3.default.dim(" Update your infrawise.yaml:"));
3004
+ console.log(import_chalk3.default.cyan(` aws:
3005
+ profile: ${selectedProfile}`));
3006
+ } else {
3007
+ spin.fail(import_chalk3.default.red(`Profile "${import_chalk3.default.bold(selectedProfile)}" cannot access DynamoDB`));
3008
+ console.log("");
3009
+ log.warn("Possible causes:");
3010
+ log.dim("Missing IAM permissions \u2014 need dynamodb:ListTables, dynamodb:DescribeTable");
3011
+ log.dim("Expired SSO \u2014 run: aws sso login");
3012
+ log.dim("Wrong region \u2014 check your AWS config");
3013
+ console.log("");
3014
+ log.info(`Run ${import_chalk3.default.cyan("infrawise doctor")} for a full diagnostic`);
3015
+ }
3016
+ console.log("");
3017
+ }
3018
+
3019
+ // src/commands/analyze.ts
3020
+ var path3 = __toESM(require("path"));
3021
+ var import_chalk4 = __toESM(require("chalk"));
3022
+ var import_ora2 = __toESM(require("ora"));
3023
+ var import_core2 = __toESM(require_dist());
3024
+ var import_adapters_dynamodb2 = __toESM(require_dist2());
3025
+ var import_adapters_postgres = __toESM(require_dist3());
3026
+ var import_adapters_mysql = __toESM(require_dist4());
3027
+ var import_adapters_mongodb = __toESM(require_dist5());
3028
+ var import_adapters_terraform = __toESM(require_dist6());
3029
+ var import_context = __toESM(require_dist7());
3030
+ var import_graph = __toESM(require_dist8());
3031
+ var import_analyzers = __toESM(require_dist9());
3032
+ async function runAnalyze(options = {}) {
3033
+ printHeader("Running Analysis");
3034
+ let config;
3035
+ try {
3036
+ config = (0, import_core2.loadConfig)(options.config);
3037
+ log.success("Config loaded", options.config ?? "infrawise.yaml");
3038
+ } catch (err) {
3039
+ console.error((0, import_core2.formatError)(err));
3040
+ process.exit(1);
3041
+ }
3042
+ const repoPath = options.repo ?? process.cwd();
3043
+ const dynamoMeta = [];
3044
+ const postgresMeta = [];
3045
+ const mysqlMeta = [];
3046
+ const mongoMeta = [];
3047
+ {
3048
+ const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting DynamoDB table metadata..."), color: "cyan" }).start();
3049
+ try {
3050
+ const result = await (0, import_adapters_dynamodb2.extractDynamoMetadata)(config);
3051
+ dynamoMeta.push(...result);
3052
+ spin.succeed(import_chalk4.default.green("DynamoDB") + import_chalk4.default.dim(` ${result.length} table(s) found`));
3053
+ } catch (err) {
3054
+ spin.warn(import_chalk4.default.yellow("DynamoDB skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
3055
+ }
3056
+ }
3057
+ if (config.postgres?.enabled && config.postgres.connectionString) {
3058
+ const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting PostgreSQL schema..."), color: "cyan" }).start();
3059
+ try {
3060
+ const result = await (0, import_adapters_postgres.extractPostgresMetadata)(config.postgres.connectionString);
3061
+ postgresMeta.push(...result);
3062
+ spin.succeed(import_chalk4.default.green("PostgreSQL") + import_chalk4.default.dim(` ${result.length} table(s) found`));
3063
+ } catch (err) {
3064
+ spin.warn(import_chalk4.default.yellow("PostgreSQL skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
3065
+ }
3066
+ }
3067
+ if (config.mysql?.enabled && config.mysql.connectionString) {
3068
+ const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting MySQL schema..."), color: "cyan" }).start();
3069
+ try {
3070
+ const result = await (0, import_adapters_mysql.extractMySQLMetadata)(config.mysql.connectionString);
3071
+ mysqlMeta.push(...result);
3072
+ spin.succeed(import_chalk4.default.green("MySQL") + import_chalk4.default.dim(` ${result.length} table(s) found`));
3073
+ } catch (err) {
3074
+ spin.warn(import_chalk4.default.yellow("MySQL skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
3075
+ }
3076
+ }
3077
+ if (config.mongodb?.enabled && config.mongodb.connectionString) {
3078
+ const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting MongoDB schema..."), color: "cyan" }).start();
3079
+ try {
3080
+ const result = await (0, import_adapters_mongodb.extractMongoMetadata)(
3081
+ config.mongodb.connectionString,
3082
+ config.mongodb.databases
3083
+ );
3084
+ mongoMeta.push(...result);
3085
+ spin.succeed(import_chalk4.default.green("MongoDB") + import_chalk4.default.dim(` ${result.length} collection(s) found`));
3086
+ } catch (err) {
3087
+ spin.warn(import_chalk4.default.yellow("MongoDB skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
3088
+ }
3089
+ }
3090
+ let iacDriftAnalyzer;
3091
+ {
3092
+ const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Extracting IaC schema (Terraform/CloudFormation)..."), color: "cyan" }).start();
3093
+ try {
3094
+ const iacSchema = await (0, import_adapters_terraform.extractIaCSchema)(repoPath);
3095
+ const totalIaC = iacSchema.dynamoTables.length + iacSchema.rdsInstances.length + iacSchema.mongoClusters.length;
3096
+ iacDriftAnalyzer = new import_analyzers.IaCDriftAnalyzer();
3097
+ iacDriftAnalyzer.setIaCSchema(iacSchema);
3098
+ spin.succeed(import_chalk4.default.green("IaC schema") + import_chalk4.default.dim(` ${totalIaC} resource(s) found`));
3099
+ } catch (err) {
3100
+ spin.warn(import_chalk4.default.yellow("IaC scan skipped") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
3101
+ }
3102
+ }
3103
+ let operations;
3104
+ {
3105
+ const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim(`Scanning ${path3.basename(repoPath)}...`), color: "cyan" }).start();
3106
+ try {
3107
+ operations = await (0, import_context.scanRepository)(repoPath);
3108
+ spin.succeed(import_chalk4.default.green("Repository scanned") + import_chalk4.default.dim(` ${operations.length} database operation(s) found`));
3109
+ } catch (err) {
3110
+ spin.warn(import_chalk4.default.yellow("Repository scan failed") + import_chalk4.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
3111
+ operations = [];
3112
+ }
3113
+ }
3114
+ {
3115
+ const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Building infrastructure graph..."), color: "cyan" }).start();
3116
+ var graph = (0, import_graph.buildGraph)(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta);
3117
+ spin.succeed(import_chalk4.default.green("Graph built") + import_chalk4.default.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
3118
+ }
3119
+ const findings = await (async () => {
3120
+ const spin = (0, import_ora2.default)({ text: import_chalk4.default.dim("Running analyzers..."), color: "cyan" }).start();
3121
+ const {
3122
+ FullTableScanAnalyzer,
3123
+ MissingGSIAnalyzer,
3124
+ HotPartitionAnalyzer,
3125
+ MissingIndexAnalyzer,
3126
+ NplusOneAnalyzer,
3127
+ LargeSelectAnalyzer,
3128
+ MissingMySQLIndexAnalyzer,
3129
+ MySQLFullTableScanAnalyzer,
3130
+ MissingMongoIndexAnalyzer,
3131
+ MongoCollectionScanAnalyzer
3132
+ } = await Promise.resolve().then(() => __toESM(require_dist9()));
3133
+ const analyzers = [
3134
+ new FullTableScanAnalyzer(),
3135
+ new MissingGSIAnalyzer(),
3136
+ new HotPartitionAnalyzer(),
3137
+ new MissingIndexAnalyzer(),
3138
+ new NplusOneAnalyzer(),
3139
+ new LargeSelectAnalyzer(),
3140
+ new MissingMySQLIndexAnalyzer(),
3141
+ new MySQLFullTableScanAnalyzer(),
3142
+ new MissingMongoIndexAnalyzer(),
3143
+ new MongoCollectionScanAnalyzer(),
3144
+ ...iacDriftAnalyzer ? [iacDriftAnalyzer] : []
3145
+ ];
3146
+ const result = await (0, import_analyzers.runAllAnalyzers)(graph, analyzers);
3147
+ spin.succeed(import_chalk4.default.green("Analysis complete") + import_chalk4.default.dim(` ${result.length} finding(s)`));
3148
+ return result;
3149
+ })();
3150
+ (0, import_core2.writeCache)("graph", graph);
3151
+ (0, import_core2.writeCache)("findings", findings);
3152
+ (0, import_core2.writeCache)("operations", operations);
3153
+ console.log("");
3154
+ if (findings.length === 0) {
3155
+ console.log(` ${import_chalk4.default.green.bold("\u2713 No issues found!")} ${import_chalk4.default.dim("Your infrastructure looks clean.")}`);
3156
+ } else {
3157
+ console.log(import_chalk4.default.bold(` Findings`) + import_chalk4.default.dim(` ${findings.length} total`));
3158
+ findings.forEach((f, i) => printFinding(f, i));
3159
+ printSummaryBox(findings);
3160
+ if (findings.some((f) => f.severity === "high")) {
3161
+ console.log(`
3162
+ ${import_chalk4.default.red.bold("Action required:")} ${import_chalk4.default.red("High severity issues detected.")}`);
3163
+ }
3164
+ }
3165
+ console.log("");
3166
+ log.dim(`Results cached in .infrawise/cache/`);
3167
+ log.info(`Run ${import_chalk4.default.cyan("infrawise dev")} to explore via the MCP server`);
3168
+ console.log("");
3169
+ }
3170
+
3171
+ // src/commands/dev.ts
3172
+ var import_chalk5 = __toESM(require("chalk"));
3173
+ var import_ora3 = __toESM(require("ora"));
3174
+ var import_core3 = __toESM(require_dist());
3175
+ var import_server = __toESM(require_dist10());
3176
+ async function runDev(options = {}) {
3177
+ const port = options.port ?? 3e3;
3178
+ printHeader("MCP Server");
3179
+ let config;
3180
+ try {
3181
+ config = (0, import_core3.loadConfig)(options.config);
3182
+ log.success("Config loaded", options.config ?? "infrawise.yaml");
3183
+ } catch (err) {
3184
+ console.error((0, import_core3.formatError)(err));
3185
+ process.exit(1);
3186
+ }
3187
+ const cachedGraph = (0, import_core3.readCache)("graph");
3188
+ const cachedFindings = (0, import_core3.readCache)("findings");
3189
+ if (cachedGraph && cachedFindings) {
3190
+ log.success(
3191
+ "Cached analysis loaded",
3192
+ `${cachedGraph.nodes.length} nodes \xB7 ${cachedGraph.edges.length} edges \xB7 ${cachedFindings.length} finding(s)`
3193
+ );
3194
+ (0, import_server.setGraphState)(cachedGraph, cachedFindings);
3195
+ } else {
3196
+ log.warn("No cached analysis found");
3197
+ log.dim(`Run ${import_chalk5.default.cyan("infrawise analyze")} first for full results`);
3198
+ (0, import_server.setGraphState)({ nodes: [], edges: [] }, []);
3199
+ }
3200
+ console.log("");
3201
+ const spin = (0, import_ora3.default)({ text: import_chalk5.default.dim("Starting server..."), color: "cyan" }).start();
3202
+ const { start } = (0, import_server.createServer)(port);
3203
+ await start();
3204
+ spin.succeed(import_chalk5.default.green("Server running"));
3205
+ console.log("");
3206
+ 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"));
3207
+ console.log(import_chalk5.default.dim(" \u2502") + import_chalk5.default.bold(" MCP Server ") + import_chalk5.default.dim("\u2502"));
3208
+ 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"));
3209
+ 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"));
3210
+ 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"));
3211
+ 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"));
3212
+ 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"));
3213
+ console.log(import_chalk5.default.dim(" \u2502") + import_chalk5.default.dim(" Tools: get_graph_summary \xB7 analyze_function ") + import_chalk5.default.dim("\u2502"));
3214
+ console.log(import_chalk5.default.dim(" \u2502") + import_chalk5.default.dim(" suggest_gsi \xB7 postgres_index_suggestions ") + import_chalk5.default.dim("\u2502"));
3215
+ 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"));
3216
+ console.log("");
3217
+ console.log(import_chalk5.default.dim(" Add to .claude/settings.json:"));
3218
+ console.log(import_chalk5.default.dim(" {"));
3219
+ console.log(import_chalk5.default.dim(' "mcpServers": {'));
3220
+ console.log(import_chalk5.default.dim(' "infrawise": {'));
3221
+ console.log(import_chalk5.default.dim(` "url": "http://localhost:${port}/mcp"`));
3222
+ console.log(import_chalk5.default.dim(" }"));
3223
+ console.log(import_chalk5.default.dim(" }"));
3224
+ console.log(import_chalk5.default.dim(" }"));
3225
+ console.log("");
3226
+ console.log(import_chalk5.default.dim(" Press Ctrl+C to stop\n"));
3227
+ process.on("SIGINT", () => {
3228
+ console.log(import_chalk5.default.dim("\n Shutting down...\n"));
3229
+ process.exit(0);
3230
+ });
3231
+ await new Promise(() => {
3232
+ });
3233
+ }
3234
+
3235
+ // src/commands/doctor.ts
3236
+ var fs3 = __toESM(require("fs"));
3237
+ var path4 = __toESM(require("path"));
3238
+ var import_chalk6 = __toESM(require("chalk"));
3239
+ var import_ora4 = __toESM(require("ora"));
3240
+ var import_core4 = __toESM(require_dist());
3241
+ var import_adapters_dynamodb3 = __toESM(require_dist2());
3242
+ var import_adapters_postgres2 = __toESM(require_dist3());
3243
+ var import_adapters_mysql2 = __toESM(require_dist4());
3244
+ var import_adapters_mongodb2 = __toESM(require_dist5());
3245
+ var os2 = __toESM(require("os"));
3246
+ async function runCheck(label, fn) {
3247
+ const spin = (0, import_ora4.default)({ text: import_chalk6.default.dim(label), color: "cyan" }).start();
3248
+ const result = await fn();
3249
+ switch (result.status) {
3250
+ case "pass":
3251
+ spin.succeed(import_chalk6.default.green(result.name) + import_chalk6.default.dim(` ${result.message}`));
3252
+ break;
3253
+ case "fail":
3254
+ spin.fail(import_chalk6.default.red(result.name) + import_chalk6.default.dim(` ${result.message}`));
3255
+ break;
3256
+ case "warn":
3257
+ spin.warn(import_chalk6.default.yellow(result.name) + import_chalk6.default.dim(` ${result.message}`));
3258
+ break;
3259
+ case "skip":
3260
+ spin.info(import_chalk6.default.dim(`${result.name} ${result.message}`));
3261
+ break;
3262
+ }
3263
+ if (result.detail) {
3264
+ console.log(import_chalk6.default.dim(` ${result.detail}`));
3265
+ }
3266
+ return result;
3267
+ }
3268
+ async function runDoctor(options = {}) {
3269
+ printHeader("Infrawise Doctor");
3270
+ const configPath = path4.resolve(options.config ?? "infrawise.yaml");
3271
+ const results = [];
3272
+ results.push(await runCheck("Checking config file...", async () => {
3273
+ const exists = fs3.existsSync(configPath);
3274
+ return {
3275
+ name: "Config file",
3276
+ status: exists ? "pass" : "fail",
3277
+ message: exists ? configPath : `Not found at ${configPath}`,
3278
+ detail: exists ? void 0 : "Run: infrawise init"
3279
+ };
3280
+ }));
3281
+ let config;
3282
+ results.push(await runCheck("Validating config...", async () => {
3283
+ if (!fs3.existsSync(configPath)) {
3284
+ return { name: "Config validation", status: "skip", message: "No config file" };
3285
+ }
3286
+ try {
3287
+ config = (0, import_core4.loadConfig)(options.config);
3288
+ return { name: "Config validation", status: "pass", message: `project: ${config.project}` };
3289
+ } catch (err) {
3290
+ return {
3291
+ name: "Config validation",
3292
+ status: "fail",
3293
+ message: "Invalid config",
3294
+ detail: err instanceof Error ? err.message : String(err)
3295
+ };
3296
+ }
3297
+ }));
3298
+ results.push(await runCheck("Checking AWS credentials...", async () => {
3299
+ const home = process.env.HOME ?? os2.homedir();
3300
+ const hasCreds = fs3.existsSync(path4.join(home, ".aws", "credentials")) || fs3.existsSync(path4.join(home, ".aws", "config"));
3301
+ return {
3302
+ name: "AWS credentials",
3303
+ status: hasCreds ? "pass" : "warn",
3304
+ message: hasCreds ? "Found" : "Not found \u2014 may use env vars or IAM role",
3305
+ detail: hasCreds ? void 0 : "Run: aws configure"
3306
+ };
3307
+ }));
3308
+ results.push(await runCheck("Testing DynamoDB access...", async () => {
3309
+ if (!config) return { name: "DynamoDB access", status: "skip", message: "No valid config" };
3310
+ try {
3311
+ const ok = await (0, import_adapters_dynamodb3.validateDynamoAccess)(config);
3312
+ return {
3313
+ name: "DynamoDB access",
3314
+ status: ok ? "pass" : "fail",
3315
+ message: ok ? `Connected (profile: ${config.aws?.profile ?? "default"})` : "Cannot connect",
3316
+ detail: ok ? void 0 : "Check IAM: dynamodb:ListTables, dynamodb:DescribeTable"
3317
+ };
3318
+ } catch (err) {
3319
+ return {
3320
+ name: "DynamoDB access",
3321
+ status: "fail",
3322
+ message: err instanceof Error ? err.message : String(err)
3323
+ };
3324
+ }
3325
+ }));
3326
+ results.push(await runCheck("Testing PostgreSQL...", async () => {
3327
+ if (!config?.postgres?.enabled || !config.postgres.connectionString) {
3328
+ return { name: "PostgreSQL", status: "skip", message: "Not configured" };
3329
+ }
3330
+ try {
3331
+ const ok = await (0, import_adapters_postgres2.validatePostgresAccess)(config.postgres.connectionString);
3332
+ return {
3333
+ name: "PostgreSQL",
3334
+ status: ok ? "pass" : "fail",
3335
+ message: ok ? "Connected" : "Cannot connect",
3336
+ detail: ok ? void 0 : "Check connection string, security group, and credentials"
3337
+ };
3338
+ } catch (err) {
3339
+ return {
3340
+ name: "PostgreSQL",
3341
+ status: "fail",
3342
+ message: err instanceof Error ? err.message : String(err)
3343
+ };
3344
+ }
3345
+ }));
3346
+ results.push(await runCheck("Testing MySQL...", async () => {
3347
+ if (!config?.mysql?.enabled || !config.mysql.connectionString) {
3348
+ return { name: "MySQL", status: "skip", message: "Not configured" };
3349
+ }
3350
+ try {
3351
+ const ok = await (0, import_adapters_mysql2.validateMySQLAccess)(config.mysql.connectionString);
3352
+ return {
3353
+ name: "MySQL",
3354
+ status: ok ? "pass" : "fail",
3355
+ message: ok ? "Connected" : "Cannot connect",
3356
+ detail: ok ? void 0 : "Check connection string, host, port 3306, and credentials"
3357
+ };
3358
+ } catch (err) {
3359
+ return {
3360
+ name: "MySQL",
3361
+ status: "fail",
3362
+ message: err instanceof Error ? err.message : String(err)
3363
+ };
3364
+ }
3365
+ }));
3366
+ results.push(await runCheck("Testing MongoDB...", async () => {
3367
+ if (!config?.mongodb?.enabled || !config.mongodb.connectionString) {
3368
+ return { name: "MongoDB", status: "skip", message: "Not configured" };
3369
+ }
3370
+ try {
3371
+ const ok = await (0, import_adapters_mongodb2.validateMongoAccess)(config.mongodb.connectionString);
3372
+ return {
3373
+ name: "MongoDB",
3374
+ status: ok ? "pass" : "fail",
3375
+ message: ok ? "Connected" : "Cannot connect",
3376
+ detail: ok ? void 0 : "Check connection string, host, port 27017, and credentials"
3377
+ };
3378
+ } catch (err) {
3379
+ return {
3380
+ name: "MongoDB",
3381
+ status: "fail",
3382
+ message: err instanceof Error ? err.message : String(err)
3383
+ };
3384
+ }
3385
+ }));
3386
+ results.push(await runCheck("Detecting project type...", async () => {
3387
+ const hasTsConfig = fs3.existsSync(path4.join(process.cwd(), "tsconfig.json"));
3388
+ const hasPkg = fs3.existsSync(path4.join(process.cwd(), "package.json"));
3389
+ return {
3390
+ name: "Project type",
3391
+ status: hasTsConfig ? "pass" : "warn",
3392
+ message: hasTsConfig ? "TypeScript (tsconfig.json found)" : hasPkg ? "JavaScript (no tsconfig.json)" : "Unknown project",
3393
+ detail: hasTsConfig ? void 0 : "Scanner works best with TypeScript projects"
3394
+ };
3395
+ }));
3396
+ results.push(await runCheck("Checking analysis cache...", async () => {
3397
+ const cached = fs3.existsSync(path4.join(process.cwd(), ".infrawise", "cache", "graph.json"));
3398
+ return {
3399
+ name: "Analysis cache",
3400
+ status: cached ? "pass" : "warn",
3401
+ message: cached ? "Cached results found" : "No cache \u2014 run infrawise analyze first"
3402
+ };
3403
+ }));
3404
+ const passed = results.filter((r) => r.status === "pass").length;
3405
+ const failed = results.filter((r) => r.status === "fail").length;
3406
+ const warned = results.filter((r) => r.status === "warn").length;
3407
+ console.log("");
3408
+ console.log(import_chalk6.default.dim(" " + "\u2500".repeat(40)));
3409
+ if (failed === 0) {
3410
+ console.log(` ${import_chalk6.default.green.bold("All checks passed")} ${import_chalk6.default.dim(`${passed} passed, ${warned} warning(s)`)}`);
3411
+ } else {
3412
+ console.log(` ${import_chalk6.default.red.bold(`${failed} check(s) failed`)} ${import_chalk6.default.dim(`${passed} passed, ${warned} warning(s)`)}`);
3413
+ }
3414
+ console.log("");
3415
+ if (failed > 0) process.exit(1);
3416
+ }
3417
+
3418
+ // src/index.ts
3419
+ var program = new import_commander.Command();
3420
+ program.name("infrawise").description("CLI-first infrastructure intelligence platform for DynamoDB and PostgreSQL").version("0.1.0");
3421
+ program.command("init").description("Detect AWS profile/region, discover DynamoDB tables, and generate infrawise.yaml").option("--force", "Overwrite existing infrawise.yaml").action(async (options) => {
3422
+ printBanner();
3423
+ await runInit({ force: options.force });
3424
+ });
3425
+ program.command("auth").description("Validate and select AWS profile from ~/.aws/credentials").action(async () => {
3426
+ printBanner();
3427
+ await runAuth();
3428
+ });
3429
+ 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) => {
3430
+ printBanner();
3431
+ await runAnalyze({
3432
+ config: options.config !== "infrawise.yaml" ? options.config : void 0,
3433
+ repo: options.repo,
3434
+ noCache: !options.cache
3435
+ });
3436
+ });
3437
+ 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) => {
3438
+ printBanner();
3439
+ await runDev({
3440
+ config: options.config !== "infrawise.yaml" ? options.config : void 0,
3441
+ port: parseInt(options.port, 10)
3442
+ });
3443
+ });
3444
+ 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) => {
3445
+ printBanner();
3446
+ await runDoctor({
3447
+ config: options.config !== "infrawise.yaml" ? options.config : void 0
3448
+ });
3449
+ });
3450
+ program.exitOverride((err) => {
3451
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
3452
+ process.exit(0);
3453
+ }
3454
+ console.error(`
3455
+ Error: ${err.message}`);
3456
+ process.exit(1);
3457
+ });
3458
+ process.on("unhandledRejection", (reason) => {
3459
+ console.error("\nUnhandled error:", reason instanceof Error ? reason.message : String(reason));
3460
+ process.exit(1);
3461
+ });
3462
+ program.parse(process.argv);