infrawise 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +146 -74
- package/dist/adapters/aws.js +255 -0
- package/dist/adapters/dynamodb.js +97 -0
- package/dist/adapters/logs.js +119 -0
- package/dist/adapters/mongodb.js +109 -0
- package/dist/adapters/mysql.js +135 -0
- package/dist/adapters/postgres.js +131 -0
- package/dist/adapters/terraform.js +510 -0
- package/dist/analyzers/aws-services.js +168 -0
- package/dist/analyzers/dynamodb.js +144 -0
- package/dist/analyzers/index.js +59 -0
- package/dist/analyzers/mongodb.js +82 -0
- package/dist/analyzers/mysql.js +82 -0
- package/dist/analyzers/postgres.js +148 -0
- package/dist/analyzers/rds.js +109 -0
- package/dist/analyzers/terraform.js +95 -0
- package/dist/cli/commands/analyze.js +319 -0
- package/dist/cli/commands/auth.js +57 -0
- package/dist/cli/commands/dev.js +127 -0
- package/dist/cli/commands/doctor.js +338 -0
- package/dist/cli/commands/init.js +234 -0
- package/dist/cli/index.js +82 -0
- package/dist/cli/utils.js +165 -0
- package/dist/context/index.js +597 -0
- package/dist/core/cache.js +89 -0
- package/dist/core/config.js +163 -0
- package/dist/core/errors.js +97 -0
- package/dist/core/index.js +22 -0
- package/dist/core/logger.js +28 -0
- package/dist/graph/index.js +268 -0
- package/dist/server/index.js +347 -0
- package/dist/types.js +2 -0
- package/package.json +20 -30
- package/dist/index.js +0 -4800
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.scanRepository = scanRepository;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const ts_morph_1 = require("ts-morph");
|
|
40
|
+
const core_1 = require("../core");
|
|
41
|
+
const DYNAMO_OPERATIONS = new Set([
|
|
42
|
+
'query',
|
|
43
|
+
'scan',
|
|
44
|
+
'getItem',
|
|
45
|
+
'putItem',
|
|
46
|
+
'updateItem',
|
|
47
|
+
'deleteItem',
|
|
48
|
+
'batchGetItem',
|
|
49
|
+
'batchWriteItem',
|
|
50
|
+
'transactGetItems',
|
|
51
|
+
'transactWriteItems',
|
|
52
|
+
// Also handle command class names (AWS SDK v3)
|
|
53
|
+
'QueryCommand',
|
|
54
|
+
'ScanCommand',
|
|
55
|
+
'GetItemCommand',
|
|
56
|
+
'PutItemCommand',
|
|
57
|
+
'UpdateItemCommand',
|
|
58
|
+
'DeleteItemCommand',
|
|
59
|
+
]);
|
|
60
|
+
const DYNAMO_CLIENT_PATTERNS = ['DynamoDBClient', 'DynamoDB', 'dynamoDB', 'dynamo', 'ddb'];
|
|
61
|
+
const POSTGRES_QUERY_METHODS = new Set(['query', 'execute', 'exec']);
|
|
62
|
+
const KNEX_METHODS = new Set(['select', 'where', 'join', 'from', 'insert', 'update', 'delete', 'del']);
|
|
63
|
+
// MySQL-specific patterns
|
|
64
|
+
const MYSQL_QUERY_METHODS = new Set(['query', 'execute', 'exec']);
|
|
65
|
+
const MYSQL_CLIENT_PATTERNS = ['mysql', 'mysql2', 'connection', 'pool', 'conn'];
|
|
66
|
+
// MongoDB-specific patterns
|
|
67
|
+
const MONGO_READ_METHODS = new Set([
|
|
68
|
+
'find',
|
|
69
|
+
'findOne',
|
|
70
|
+
'findById',
|
|
71
|
+
'insertOne',
|
|
72
|
+
'insertMany',
|
|
73
|
+
'updateOne',
|
|
74
|
+
'updateMany',
|
|
75
|
+
'deleteOne',
|
|
76
|
+
'deleteMany',
|
|
77
|
+
'aggregate',
|
|
78
|
+
'countDocuments',
|
|
79
|
+
'estimatedDocumentCount',
|
|
80
|
+
]);
|
|
81
|
+
const MONGO_COLLECTION_METHODS = new Set(['collection']);
|
|
82
|
+
// ── AWS service patterns ──────────────────────────────────────────────────────
|
|
83
|
+
const SQS_COMMANDS = new Set(['SendMessageCommand', 'SendMessageBatchCommand', 'ReceiveMessageCommand', 'DeleteMessageCommand', 'sendMessage', 'sendMessageBatch', 'receiveMessage']);
|
|
84
|
+
const SNS_COMMANDS = new Set(['PublishCommand', 'PublishBatchCommand', 'publish', 'publishBatch']);
|
|
85
|
+
const SSM_COMMANDS = new Set(['GetParameterCommand', 'GetParametersCommand', 'GetParametersByPathCommand', 'getParameter', 'getParameters', 'getParametersByPath']);
|
|
86
|
+
const SECRETS_COMMANDS = new Set(['GetSecretValueCommand', 'getSecretValue']);
|
|
87
|
+
const LAMBDA_COMMANDS = new Set(['InvokeCommand', 'InvokeAsyncCommand', 'invoke', 'invokeAsync']);
|
|
88
|
+
const SQS_ARG_KEYS = ['QueueUrl', 'QueueName'];
|
|
89
|
+
const SNS_ARG_KEYS = ['TopicArn', 'TargetArn'];
|
|
90
|
+
const SSM_ARG_KEYS = ['Name', 'Path'];
|
|
91
|
+
const SECRETS_ARG_KEYS = ['SecretId', 'SecretName'];
|
|
92
|
+
const LAMBDA_ARG_KEYS = ['FunctionName'];
|
|
93
|
+
const PRISMA_METHODS = new Set([
|
|
94
|
+
'findMany',
|
|
95
|
+
'findFirst',
|
|
96
|
+
'findUnique',
|
|
97
|
+
'create',
|
|
98
|
+
'update',
|
|
99
|
+
'upsert',
|
|
100
|
+
'delete',
|
|
101
|
+
'deleteMany',
|
|
102
|
+
'updateMany',
|
|
103
|
+
]);
|
|
104
|
+
function getEnclosingFunctionName(node) {
|
|
105
|
+
let current = node.getParent();
|
|
106
|
+
while (current) {
|
|
107
|
+
if (ts_morph_1.Node.isFunctionDeclaration(current) ||
|
|
108
|
+
ts_morph_1.Node.isFunctionExpression(current) ||
|
|
109
|
+
ts_morph_1.Node.isArrowFunction(current) ||
|
|
110
|
+
ts_morph_1.Node.isMethodDeclaration(current)) {
|
|
111
|
+
if (ts_morph_1.Node.isFunctionDeclaration(current) || ts_morph_1.Node.isMethodDeclaration(current)) {
|
|
112
|
+
return current.getName() ?? '<anonymous>';
|
|
113
|
+
}
|
|
114
|
+
// Arrow function or function expression — check if assigned to a variable
|
|
115
|
+
const parent = current.getParent();
|
|
116
|
+
if (parent && ts_morph_1.Node.isVariableDeclaration(parent)) {
|
|
117
|
+
return parent.getName();
|
|
118
|
+
}
|
|
119
|
+
if (parent && ts_morph_1.Node.isPropertyAssignment(parent)) {
|
|
120
|
+
return parent.getName();
|
|
121
|
+
}
|
|
122
|
+
return '<anonymous>';
|
|
123
|
+
}
|
|
124
|
+
current = current.getParent();
|
|
125
|
+
}
|
|
126
|
+
return '<module>';
|
|
127
|
+
}
|
|
128
|
+
function extractTableNameFromArg(arg) {
|
|
129
|
+
if (ts_morph_1.Node.isStringLiteral(arg)) {
|
|
130
|
+
return arg.getLiteralValue();
|
|
131
|
+
}
|
|
132
|
+
if (ts_morph_1.Node.isObjectLiteralExpression(arg)) {
|
|
133
|
+
// Look for TableName property
|
|
134
|
+
for (const prop of arg.getProperties()) {
|
|
135
|
+
if (ts_morph_1.Node.isPropertyAssignment(prop) && prop.getName() === 'TableName') {
|
|
136
|
+
const init = prop.getInitializer();
|
|
137
|
+
if (init && ts_morph_1.Node.isStringLiteral(init)) {
|
|
138
|
+
return init.getLiteralValue();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return 'unknown';
|
|
144
|
+
}
|
|
145
|
+
function detectDynamoOperations(callExpr, filePath) {
|
|
146
|
+
const expr = callExpr.getExpression();
|
|
147
|
+
const args = callExpr.getArguments();
|
|
148
|
+
// Pattern 1: client.send(new QueryCommand({ TableName: '...' }))
|
|
149
|
+
if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
|
|
150
|
+
const methodName = expr.getName();
|
|
151
|
+
if (methodName === 'send' && args.length > 0) {
|
|
152
|
+
const firstArg = args[0];
|
|
153
|
+
if (ts_morph_1.Node.isNewExpression(firstArg)) {
|
|
154
|
+
const className = firstArg.getExpression().getText();
|
|
155
|
+
if (DYNAMO_OPERATIONS.has(className)) {
|
|
156
|
+
const cmdArgs = firstArg.getArguments();
|
|
157
|
+
const tableName = cmdArgs.length > 0 ? extractTableNameFromArg(cmdArgs[0]) : 'unknown';
|
|
158
|
+
return {
|
|
159
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
160
|
+
operationType: className,
|
|
161
|
+
databaseType: 'dynamodb',
|
|
162
|
+
target: tableName,
|
|
163
|
+
filePath,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Pattern 2: dynamoClient.query({ TableName: '...' }) (v2-style)
|
|
169
|
+
if (DYNAMO_OPERATIONS.has(methodName)) {
|
|
170
|
+
const objText = expr.getExpression().getText().toLowerCase();
|
|
171
|
+
if (DYNAMO_CLIENT_PATTERNS.some((p) => objText.includes(p.toLowerCase()))) {
|
|
172
|
+
const tableName = args.length > 0 ? extractTableNameFromArg(args[0]) : 'unknown';
|
|
173
|
+
return {
|
|
174
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
175
|
+
operationType: methodName,
|
|
176
|
+
databaseType: 'dynamodb',
|
|
177
|
+
target: tableName,
|
|
178
|
+
filePath,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function extractSqlTableName(sql) {
|
|
186
|
+
// Try to extract table name from common SQL patterns
|
|
187
|
+
const patterns = [
|
|
188
|
+
/FROM\s+["']?(\w+)["']?/i,
|
|
189
|
+
/INTO\s+["']?(\w+)["']?/i,
|
|
190
|
+
/UPDATE\s+["']?(\w+)["']?/i,
|
|
191
|
+
/JOIN\s+["']?(\w+)["']?/i,
|
|
192
|
+
];
|
|
193
|
+
for (const pattern of patterns) {
|
|
194
|
+
const match = sql.match(pattern);
|
|
195
|
+
if (match?.[1])
|
|
196
|
+
return match[1];
|
|
197
|
+
}
|
|
198
|
+
return 'unknown';
|
|
199
|
+
}
|
|
200
|
+
function detectPostgresOperations(callExpr, filePath) {
|
|
201
|
+
const expr = callExpr.getExpression();
|
|
202
|
+
const args = callExpr.getArguments();
|
|
203
|
+
if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
|
|
204
|
+
return null;
|
|
205
|
+
const methodName = expr.getName();
|
|
206
|
+
const objExpr = expr.getExpression();
|
|
207
|
+
// Pattern: pool.query(...) / client.query(...)
|
|
208
|
+
if (POSTGRES_QUERY_METHODS.has(methodName)) {
|
|
209
|
+
const objText = objExpr.getText().toLowerCase();
|
|
210
|
+
if (objText.includes('pool') ||
|
|
211
|
+
objText.includes('client') ||
|
|
212
|
+
objText.includes('db') ||
|
|
213
|
+
objText.includes('pg') ||
|
|
214
|
+
objText.includes('conn')) {
|
|
215
|
+
let tableName = 'unknown';
|
|
216
|
+
if (args.length > 0) {
|
|
217
|
+
const firstArg = args[0];
|
|
218
|
+
if (ts_morph_1.Node.isStringLiteral(firstArg)) {
|
|
219
|
+
tableName = extractSqlTableName(firstArg.getLiteralValue());
|
|
220
|
+
}
|
|
221
|
+
else if (ts_morph_1.Node.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
222
|
+
tableName = extractSqlTableName(firstArg.getLiteralText());
|
|
223
|
+
}
|
|
224
|
+
else if (ts_morph_1.Node.isTemplateExpression(firstArg)) {
|
|
225
|
+
// Template literal — extract what we can from the head
|
|
226
|
+
tableName = extractSqlTableName(firstArg.getHead().getLiteralText());
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
231
|
+
operationType: 'query',
|
|
232
|
+
databaseType: 'postgres',
|
|
233
|
+
target: tableName,
|
|
234
|
+
filePath,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Pattern: Prisma — prisma.user.findMany(), prisma.orders.create()
|
|
239
|
+
if (PRISMA_METHODS.has(methodName)) {
|
|
240
|
+
const accessChain = objExpr;
|
|
241
|
+
if (ts_morph_1.Node.isPropertyAccessExpression(accessChain)) {
|
|
242
|
+
const modelName = accessChain.getName();
|
|
243
|
+
const rootObj = accessChain.getExpression().getText().toLowerCase();
|
|
244
|
+
if (rootObj.includes('prisma')) {
|
|
245
|
+
return {
|
|
246
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
247
|
+
operationType: methodName,
|
|
248
|
+
databaseType: 'postgres',
|
|
249
|
+
target: modelName,
|
|
250
|
+
filePath,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Pattern: Knex — knex('tableName').select() or db('tableName').where()
|
|
256
|
+
if (KNEX_METHODS.has(methodName)) {
|
|
257
|
+
const calleeText = objExpr.getText();
|
|
258
|
+
// Check if it's a chained knex call
|
|
259
|
+
if (calleeText.includes('knex') ||
|
|
260
|
+
calleeText.includes('db(') ||
|
|
261
|
+
calleeText.includes('trx(')) {
|
|
262
|
+
return {
|
|
263
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
264
|
+
operationType: methodName,
|
|
265
|
+
databaseType: 'postgres',
|
|
266
|
+
target: 'unknown',
|
|
267
|
+
filePath,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// Knex call expression style: knex('users').select(...)
|
|
271
|
+
if (ts_morph_1.Node.isCallExpression(objExpr)) {
|
|
272
|
+
const innerExpr = objExpr.getExpression();
|
|
273
|
+
if (innerExpr.getText().toLowerCase().match(/knex|db|trx/)) {
|
|
274
|
+
const innerArgs = objExpr.getArguments();
|
|
275
|
+
const tableName = innerArgs.length > 0 && ts_morph_1.Node.isStringLiteral(innerArgs[0])
|
|
276
|
+
? innerArgs[0].getLiteralValue()
|
|
277
|
+
: 'unknown';
|
|
278
|
+
return {
|
|
279
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
280
|
+
operationType: methodName,
|
|
281
|
+
databaseType: 'postgres',
|
|
282
|
+
target: tableName,
|
|
283
|
+
filePath,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
function detectMySQLOperations(callExpr, filePath) {
|
|
291
|
+
const expr = callExpr.getExpression();
|
|
292
|
+
const args = callExpr.getArguments();
|
|
293
|
+
if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
|
|
294
|
+
return null;
|
|
295
|
+
const methodName = expr.getName();
|
|
296
|
+
const objExpr = expr.getExpression();
|
|
297
|
+
const objText = objExpr.getText().toLowerCase();
|
|
298
|
+
// Pattern: connection.query(...) / pool.query(...) / mysql.query(...)
|
|
299
|
+
if (MYSQL_QUERY_METHODS.has(methodName)) {
|
|
300
|
+
const isMysqlClient = MYSQL_CLIENT_PATTERNS.some((p) => objText.includes(p.toLowerCase()));
|
|
301
|
+
if (isMysqlClient) {
|
|
302
|
+
let tableName = 'unknown';
|
|
303
|
+
if (args.length > 0) {
|
|
304
|
+
const firstArg = args[0];
|
|
305
|
+
if (ts_morph_1.Node.isStringLiteral(firstArg)) {
|
|
306
|
+
tableName = extractSqlTableName(firstArg.getLiteralValue());
|
|
307
|
+
}
|
|
308
|
+
else if (ts_morph_1.Node.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
309
|
+
tableName = extractSqlTableName(firstArg.getLiteralText());
|
|
310
|
+
}
|
|
311
|
+
else if (ts_morph_1.Node.isTemplateExpression(firstArg)) {
|
|
312
|
+
tableName = extractSqlTableName(firstArg.getHead().getLiteralText());
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
317
|
+
operationType: 'query',
|
|
318
|
+
databaseType: 'mysql',
|
|
319
|
+
target: tableName,
|
|
320
|
+
filePath,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Pattern: knex with mysql dialect — same knex methods but with mysql hint in object text
|
|
325
|
+
if (KNEX_METHODS.has(methodName)) {
|
|
326
|
+
if (objText.includes('mysql') || objText.includes('knex')) {
|
|
327
|
+
let tableName = 'unknown';
|
|
328
|
+
if (ts_morph_1.Node.isCallExpression(objExpr)) {
|
|
329
|
+
const innerArgs = objExpr.getArguments();
|
|
330
|
+
if (innerArgs.length > 0 && ts_morph_1.Node.isStringLiteral(innerArgs[0])) {
|
|
331
|
+
tableName = innerArgs[0].getLiteralValue();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
336
|
+
operationType: methodName,
|
|
337
|
+
databaseType: 'mysql',
|
|
338
|
+
target: tableName,
|
|
339
|
+
filePath,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
function detectMongoOperations(callExpr, filePath) {
|
|
346
|
+
const expr = callExpr.getExpression();
|
|
347
|
+
if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
|
|
348
|
+
return null;
|
|
349
|
+
const methodName = expr.getName();
|
|
350
|
+
const objExpr = expr.getExpression();
|
|
351
|
+
// Pattern: collection.find(), collection.findOne(), collection.insertOne(), etc.
|
|
352
|
+
if (MONGO_READ_METHODS.has(methodName)) {
|
|
353
|
+
const objText = objExpr.getText().toLowerCase();
|
|
354
|
+
// Check if the receiver looks like a collection reference
|
|
355
|
+
if (objText.includes('collection') ||
|
|
356
|
+
objText.includes('col') ||
|
|
357
|
+
objText.includes('db.') ||
|
|
358
|
+
objText.includes('model') ||
|
|
359
|
+
// Mongoose: User.find(), Order.findOne()
|
|
360
|
+
/^[A-Z][a-zA-Z]+$/.test(objExpr.getText())) {
|
|
361
|
+
let collectionName = 'unknown';
|
|
362
|
+
// Try to get collection name from db.collection('name')
|
|
363
|
+
if (ts_morph_1.Node.isCallExpression(objExpr)) {
|
|
364
|
+
const innerExpr = objExpr.getExpression();
|
|
365
|
+
if (ts_morph_1.Node.isPropertyAccessExpression(innerExpr) &&
|
|
366
|
+
MONGO_COLLECTION_METHODS.has(innerExpr.getName())) {
|
|
367
|
+
const innerArgs = objExpr.getArguments();
|
|
368
|
+
if (innerArgs.length > 0 && ts_morph_1.Node.isStringLiteral(innerArgs[0])) {
|
|
369
|
+
collectionName = innerArgs[0].getLiteralValue();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else if (ts_morph_1.Node.isPropertyAccessExpression(objExpr)) {
|
|
374
|
+
// db.users.find() → collection name is the property
|
|
375
|
+
collectionName = objExpr.getName();
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// Mongoose model — use variable name as hint
|
|
379
|
+
collectionName = objExpr.getText();
|
|
380
|
+
}
|
|
381
|
+
const opType = methodName === 'find' || methodName === 'aggregate' ? 'scan' : 'query';
|
|
382
|
+
return {
|
|
383
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
384
|
+
operationType: opType,
|
|
385
|
+
databaseType: 'mongodb',
|
|
386
|
+
target: collectionName,
|
|
387
|
+
filePath,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Pattern: db.collection('name').find() — detect the db.collection() call itself
|
|
392
|
+
if (MONGO_COLLECTION_METHODS.has(methodName)) {
|
|
393
|
+
const args = callExpr.getArguments();
|
|
394
|
+
const objText = objExpr.getText().toLowerCase();
|
|
395
|
+
if (objText.includes('db') || objText.includes('mongo')) {
|
|
396
|
+
const collectionName = args.length > 0 && ts_morph_1.Node.isStringLiteral(args[0])
|
|
397
|
+
? args[0].getLiteralValue()
|
|
398
|
+
: 'unknown';
|
|
399
|
+
return {
|
|
400
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
401
|
+
operationType: 'query',
|
|
402
|
+
databaseType: 'mongodb',
|
|
403
|
+
target: collectionName,
|
|
404
|
+
filePath,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
function extractArgValue(arg, ...keys) {
|
|
411
|
+
if (ts_morph_1.Node.isObjectLiteralExpression(arg)) {
|
|
412
|
+
for (const prop of arg.getProperties()) {
|
|
413
|
+
if (ts_morph_1.Node.isPropertyAssignment(prop) && keys.includes(prop.getName())) {
|
|
414
|
+
const init = prop.getInitializer();
|
|
415
|
+
if (init && ts_morph_1.Node.isStringLiteral(init)) {
|
|
416
|
+
// For ARNs, extract the last segment as a readable name
|
|
417
|
+
const val = init.getLiteralValue();
|
|
418
|
+
return val.includes(':') ? (val.split(':').pop() ?? val) : val;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return 'unknown';
|
|
424
|
+
}
|
|
425
|
+
function detectAWSServiceOperations(callExpr, filePath) {
|
|
426
|
+
const expr = callExpr.getExpression();
|
|
427
|
+
const args = callExpr.getArguments();
|
|
428
|
+
// Pattern 1: client.send(new XCommand({ ... }))
|
|
429
|
+
if (ts_morph_1.Node.isPropertyAccessExpression(expr) && expr.getName() === 'send' && args.length > 0) {
|
|
430
|
+
const firstArg = args[0];
|
|
431
|
+
if (ts_morph_1.Node.isNewExpression(firstArg)) {
|
|
432
|
+
const className = firstArg.getExpression().getText();
|
|
433
|
+
const cmdArgs = firstArg.getArguments();
|
|
434
|
+
if (SQS_COMMANDS.has(className)) {
|
|
435
|
+
return {
|
|
436
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
437
|
+
operationType: className,
|
|
438
|
+
databaseType: 'sqs',
|
|
439
|
+
target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...SQS_ARG_KEYS) : 'unknown',
|
|
440
|
+
filePath,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
if (SNS_COMMANDS.has(className)) {
|
|
444
|
+
return {
|
|
445
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
446
|
+
operationType: className,
|
|
447
|
+
databaseType: 'sns',
|
|
448
|
+
target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...SNS_ARG_KEYS) : 'unknown',
|
|
449
|
+
filePath,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
if (SSM_COMMANDS.has(className)) {
|
|
453
|
+
return {
|
|
454
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
455
|
+
operationType: className,
|
|
456
|
+
databaseType: 'ssm',
|
|
457
|
+
target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...SSM_ARG_KEYS) : 'unknown',
|
|
458
|
+
filePath,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
if (SECRETS_COMMANDS.has(className)) {
|
|
462
|
+
return {
|
|
463
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
464
|
+
operationType: className,
|
|
465
|
+
databaseType: 'secretsmanager',
|
|
466
|
+
target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...SECRETS_ARG_KEYS) : 'unknown',
|
|
467
|
+
filePath,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
if (LAMBDA_COMMANDS.has(className)) {
|
|
471
|
+
return {
|
|
472
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
473
|
+
operationType: className,
|
|
474
|
+
databaseType: 'lambda',
|
|
475
|
+
target: cmdArgs.length > 0 ? extractArgValue(cmdArgs[0], ...LAMBDA_ARG_KEYS) : 'unknown',
|
|
476
|
+
filePath,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Pattern 2: awsService.method({ ... }) — v2-style or SDK-wrapped clients
|
|
482
|
+
if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
|
|
483
|
+
const methodName = expr.getName();
|
|
484
|
+
const objText = expr.getExpression().getText().toLowerCase();
|
|
485
|
+
if (SQS_COMMANDS.has(methodName) && (objText.includes('sqs') || objText.includes('queue'))) {
|
|
486
|
+
return {
|
|
487
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
488
|
+
operationType: methodName,
|
|
489
|
+
databaseType: 'sqs',
|
|
490
|
+
target: args.length > 0 ? extractArgValue(args[0], ...SQS_ARG_KEYS) : 'unknown',
|
|
491
|
+
filePath,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
if (SNS_COMMANDS.has(methodName) && (objText.includes('sns') || objText.includes('topic'))) {
|
|
495
|
+
return {
|
|
496
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
497
|
+
operationType: methodName,
|
|
498
|
+
databaseType: 'sns',
|
|
499
|
+
target: args.length > 0 ? extractArgValue(args[0], ...SNS_ARG_KEYS) : 'unknown',
|
|
500
|
+
filePath,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (SSM_COMMANDS.has(methodName) && (objText.includes('ssm') || objText.includes('parameter'))) {
|
|
504
|
+
return {
|
|
505
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
506
|
+
operationType: methodName,
|
|
507
|
+
databaseType: 'ssm',
|
|
508
|
+
target: args.length > 0 ? extractArgValue(args[0], ...SSM_ARG_KEYS) : 'unknown',
|
|
509
|
+
filePath,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if (SECRETS_COMMANDS.has(methodName) && (objText.includes('secret') || objText.includes('secrets'))) {
|
|
513
|
+
return {
|
|
514
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
515
|
+
operationType: methodName,
|
|
516
|
+
databaseType: 'secretsmanager',
|
|
517
|
+
target: args.length > 0 ? extractArgValue(args[0], ...SECRETS_ARG_KEYS) : 'unknown',
|
|
518
|
+
filePath,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
if (LAMBDA_COMMANDS.has(methodName) && (objText.includes('lambda') || objText.includes('fn'))) {
|
|
522
|
+
return {
|
|
523
|
+
functionName: getEnclosingFunctionName(callExpr),
|
|
524
|
+
operationType: methodName,
|
|
525
|
+
databaseType: 'lambda',
|
|
526
|
+
target: args.length > 0 ? extractArgValue(args[0], ...LAMBDA_ARG_KEYS) : 'unknown',
|
|
527
|
+
filePath,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
async function scanRepository(repoPath) {
|
|
534
|
+
const resolvedPath = path.resolve(repoPath);
|
|
535
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
536
|
+
throw new core_1.RepositoryScanError(`Path does not exist: ${resolvedPath}`);
|
|
537
|
+
}
|
|
538
|
+
const tsconfigPath = path.join(resolvedPath, 'tsconfig.json');
|
|
539
|
+
const hasTsConfig = fs.existsSync(tsconfigPath);
|
|
540
|
+
const project = new ts_morph_1.Project({
|
|
541
|
+
tsConfigFilePath: hasTsConfig ? tsconfigPath : undefined,
|
|
542
|
+
compilerOptions: hasTsConfig
|
|
543
|
+
? undefined
|
|
544
|
+
: {
|
|
545
|
+
target: 99,
|
|
546
|
+
allowJs: true,
|
|
547
|
+
},
|
|
548
|
+
skipAddingFilesFromTsConfig: !hasTsConfig,
|
|
549
|
+
});
|
|
550
|
+
if (!hasTsConfig) {
|
|
551
|
+
// Manually add .ts and .tsx files
|
|
552
|
+
project.addSourceFilesAtPaths([
|
|
553
|
+
path.join(resolvedPath, '**/*.ts'),
|
|
554
|
+
path.join(resolvedPath, '**/*.tsx'),
|
|
555
|
+
`!${path.join(resolvedPath, '**/node_modules/**')}`,
|
|
556
|
+
`!${path.join(resolvedPath, '**/dist/**')}`,
|
|
557
|
+
]);
|
|
558
|
+
}
|
|
559
|
+
const sourceFiles = project.getSourceFiles();
|
|
560
|
+
core_1.logger.debug(`Scanning ${sourceFiles.length} TypeScript file(s) in ${resolvedPath}`);
|
|
561
|
+
const operations = [];
|
|
562
|
+
for (const sourceFile of sourceFiles) {
|
|
563
|
+
const filePath = sourceFile.getFilePath();
|
|
564
|
+
// Skip node_modules and dist
|
|
565
|
+
if (filePath.includes('node_modules') || filePath.includes('/dist/'))
|
|
566
|
+
continue;
|
|
567
|
+
const callExpressions = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression);
|
|
568
|
+
for (const callExpr of callExpressions) {
|
|
569
|
+
const dynamoOp = detectDynamoOperations(callExpr, filePath);
|
|
570
|
+
if (dynamoOp) {
|
|
571
|
+
operations.push(dynamoOp);
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
const postgresOp = detectPostgresOperations(callExpr, filePath);
|
|
575
|
+
if (postgresOp) {
|
|
576
|
+
operations.push(postgresOp);
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const mysqlOp = detectMySQLOperations(callExpr, filePath);
|
|
580
|
+
if (mysqlOp) {
|
|
581
|
+
operations.push(mysqlOp);
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
const mongoOp = detectMongoOperations(callExpr, filePath);
|
|
585
|
+
if (mongoOp) {
|
|
586
|
+
operations.push(mongoOp);
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const awsOp = detectAWSServiceOperations(callExpr, filePath);
|
|
590
|
+
if (awsOp) {
|
|
591
|
+
operations.push(awsOp);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
core_1.logger.debug(`Extracted ${operations.length} database operation(s)`);
|
|
596
|
+
return operations;
|
|
597
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.writeCache = writeCache;
|
|
37
|
+
exports.readCache = readCache;
|
|
38
|
+
exports.clearCache = clearCache;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const CACHE_VERSION = '1.0.0';
|
|
42
|
+
const CACHE_DIR = path.join(process.cwd(), '.infrawise', 'cache');
|
|
43
|
+
function ensureCacheDir() {
|
|
44
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
45
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function writeCache(key, data) {
|
|
49
|
+
ensureCacheDir();
|
|
50
|
+
const entry = {
|
|
51
|
+
timestamp: Date.now(),
|
|
52
|
+
data,
|
|
53
|
+
version: CACHE_VERSION,
|
|
54
|
+
};
|
|
55
|
+
const filePath = path.join(CACHE_DIR, `${key}.json`);
|
|
56
|
+
fs.writeFileSync(filePath, JSON.stringify(entry, null, 2), 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
function readCache(key, maxAgeMs = 3600000) {
|
|
59
|
+
const filePath = path.join(CACHE_DIR, `${key}.json`);
|
|
60
|
+
if (!fs.existsSync(filePath))
|
|
61
|
+
return null;
|
|
62
|
+
try {
|
|
63
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
64
|
+
const entry = JSON.parse(raw);
|
|
65
|
+
if (entry.version !== CACHE_VERSION)
|
|
66
|
+
return null;
|
|
67
|
+
if (Date.now() - entry.timestamp > maxAgeMs)
|
|
68
|
+
return null;
|
|
69
|
+
return entry.data;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function clearCache(key) {
|
|
76
|
+
if (key) {
|
|
77
|
+
const filePath = path.join(CACHE_DIR, `${key}.json`);
|
|
78
|
+
if (fs.existsSync(filePath))
|
|
79
|
+
fs.unlinkSync(filePath);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
if (fs.existsSync(CACHE_DIR)) {
|
|
83
|
+
const files = fs.readdirSync(CACHE_DIR);
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
fs.unlinkSync(path.join(CACHE_DIR, file));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|