infrawise 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -11
- package/dist/adapters/aws.js +50 -64
- package/dist/adapters/dynamodb.js +17 -22
- package/dist/adapters/logs.js +12 -16
- package/dist/adapters/mongodb.js +10 -16
- package/dist/adapters/mysql.js +8 -17
- package/dist/adapters/postgres.js +9 -13
- package/dist/adapters/terraform.js +14 -56
- package/dist/analyzers/aws-services.js +7 -17
- package/dist/analyzers/dynamodb.js +6 -12
- package/dist/analyzers/index.js +13 -41
- package/dist/analyzers/mongodb.js +4 -9
- package/dist/analyzers/mysql.js +4 -9
- package/dist/analyzers/postgres.js +3 -9
- package/dist/analyzers/rds.js +5 -13
- package/dist/analyzers/terraform.js +1 -5
- package/dist/cli/commands/analyze.js +118 -158
- package/dist/cli/commands/auth.js +24 -30
- package/dist/cli/commands/dev.js +45 -84
- package/dist/cli/commands/doctor.js +35 -74
- package/dist/cli/commands/init.js +33 -72
- package/dist/cli/index.js +21 -23
- package/dist/cli/utils.js +40 -86
- package/dist/context/index.js +49 -85
- package/dist/core/cache.js +5 -43
- package/dist/core/config.js +43 -82
- package/dist/core/errors.js +7 -17
- package/dist/core/index.js +4 -22
- package/dist/core/logger.js +4 -10
- package/dist/graph/index.js +15 -32
- package/dist/server/index.js +84 -89
- package/dist/types.js +1 -2
- package/package.json +31 -30
package/dist/cli/index.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const { version } = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../../package.json'), 'utf8'));
|
|
14
|
-
const program = new commander_1.Command();
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { printBanner } from './utils.js';
|
|
6
|
+
import { runInit } from './commands/init.js';
|
|
7
|
+
import { runAuth } from './commands/auth.js';
|
|
8
|
+
import { runAnalyze } from './commands/analyze.js';
|
|
9
|
+
import { runDev } from './commands/dev.js';
|
|
10
|
+
import { runDoctor } from './commands/doctor.js';
|
|
11
|
+
const { version } = JSON.parse(readFileSync(join(import.meta.dirname, '../../package.json'), 'utf8'));
|
|
12
|
+
const program = new Command();
|
|
15
13
|
program
|
|
16
14
|
.name('infrawise')
|
|
17
15
|
.description('CLI-first infrastructure intelligence platform — analyze your databases, AWS services, and IaC')
|
|
@@ -21,15 +19,15 @@ program
|
|
|
21
19
|
.description('Detect AWS profile/region, discover DynamoDB tables, and generate infrawise.yaml')
|
|
22
20
|
.option('--force', 'Overwrite existing infrawise.yaml')
|
|
23
21
|
.action(async (options) => {
|
|
24
|
-
|
|
25
|
-
await
|
|
22
|
+
printBanner();
|
|
23
|
+
await runInit({ force: options.force });
|
|
26
24
|
});
|
|
27
25
|
program
|
|
28
26
|
.command('auth')
|
|
29
27
|
.description('Validate and select AWS profile from ~/.aws/credentials')
|
|
30
28
|
.action(async () => {
|
|
31
|
-
|
|
32
|
-
await
|
|
29
|
+
printBanner();
|
|
30
|
+
await runAuth();
|
|
33
31
|
});
|
|
34
32
|
program
|
|
35
33
|
.command('analyze')
|
|
@@ -38,8 +36,8 @@ program
|
|
|
38
36
|
.option('-r, --repo <path>', 'Path to repository to scan', process.cwd())
|
|
39
37
|
.option('--no-cache', 'Skip reading/writing the cache')
|
|
40
38
|
.action(async (options) => {
|
|
41
|
-
|
|
42
|
-
await
|
|
39
|
+
printBanner();
|
|
40
|
+
await runAnalyze({
|
|
43
41
|
config: options.config !== 'infrawise.yaml' ? options.config : undefined,
|
|
44
42
|
repo: options.repo,
|
|
45
43
|
noCache: !options.cache,
|
|
@@ -51,8 +49,8 @@ program
|
|
|
51
49
|
.option('-c, --config <path>', 'Path to infrawise.yaml', 'infrawise.yaml')
|
|
52
50
|
.option('-p, --port <number>', 'Port to listen on', '3000')
|
|
53
51
|
.action(async (options) => {
|
|
54
|
-
|
|
55
|
-
await
|
|
52
|
+
printBanner();
|
|
53
|
+
await runDev({
|
|
56
54
|
config: options.config !== 'infrawise.yaml' ? options.config : undefined,
|
|
57
55
|
port: parseInt(options.port, 10),
|
|
58
56
|
});
|
|
@@ -62,8 +60,8 @@ program
|
|
|
62
60
|
.description('Validate AWS access, postgres connectivity, config, and repo scan')
|
|
63
61
|
.option('-c, --config <path>', 'Path to infrawise.yaml', 'infrawise.yaml')
|
|
64
62
|
.action(async (options) => {
|
|
65
|
-
|
|
66
|
-
await
|
|
63
|
+
printBanner();
|
|
64
|
+
await runDoctor({
|
|
67
65
|
config: options.config !== 'infrawise.yaml' ? options.config : undefined,
|
|
68
66
|
});
|
|
69
67
|
});
|
package/dist/cli/utils.js
CHANGED
|
@@ -1,55 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.log = void 0;
|
|
40
|
-
exports.readAWSProfiles = readAWSProfiles;
|
|
41
|
-
exports.detectAWSRegion = detectAWSRegion;
|
|
42
|
-
exports.detectRepoType = detectRepoType;
|
|
43
|
-
exports.printBanner = printBanner;
|
|
44
|
-
exports.printHeader = printHeader;
|
|
45
|
-
exports.printFinding = printFinding;
|
|
46
|
-
exports.printSummaryBox = printSummaryBox;
|
|
47
|
-
const fs = __importStar(require("fs"));
|
|
48
|
-
const path = __importStar(require("path"));
|
|
49
|
-
const os = __importStar(require("os"));
|
|
50
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
51
5
|
// ─── AWS helpers ─────────────────────────────────────────────────────────────
|
|
52
|
-
function readAWSProfiles() {
|
|
6
|
+
export function readAWSProfiles() {
|
|
53
7
|
const credentialsPath = path.join(os.homedir(), '.aws', 'credentials');
|
|
54
8
|
const configPath = path.join(os.homedir(), '.aws', 'config');
|
|
55
9
|
const profiles = new Set();
|
|
@@ -70,7 +24,7 @@ function readAWSProfiles() {
|
|
|
70
24
|
parseFile(configPath);
|
|
71
25
|
return profiles.size > 0 ? [...profiles] : ['default'];
|
|
72
26
|
}
|
|
73
|
-
function detectAWSRegion() {
|
|
27
|
+
export function detectAWSRegion() {
|
|
74
28
|
if (process.env.AWS_DEFAULT_REGION)
|
|
75
29
|
return process.env.AWS_DEFAULT_REGION;
|
|
76
30
|
if (process.env.AWS_REGION)
|
|
@@ -83,7 +37,7 @@ function detectAWSRegion() {
|
|
|
83
37
|
}
|
|
84
38
|
return 'us-east-1';
|
|
85
39
|
}
|
|
86
|
-
function detectRepoType(repoPath) {
|
|
40
|
+
export function detectRepoType(repoPath) {
|
|
87
41
|
if (fs.existsSync(path.join(repoPath, 'tsconfig.json')))
|
|
88
42
|
return 'typescript';
|
|
89
43
|
if (fs.existsSync(path.join(repoPath, 'package.json')))
|
|
@@ -93,73 +47,73 @@ function detectRepoType(repoPath) {
|
|
|
93
47
|
// ─── Banner ──────────────────────────────────────────────────────────────────
|
|
94
48
|
function readVersion() {
|
|
95
49
|
try {
|
|
96
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(
|
|
50
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '../../package.json'), 'utf-8'));
|
|
97
51
|
return pkg.version;
|
|
98
52
|
}
|
|
99
53
|
catch {
|
|
100
54
|
return 'unknown';
|
|
101
55
|
}
|
|
102
56
|
}
|
|
103
|
-
function printBanner() {
|
|
104
|
-
const line1 =
|
|
105
|
-
const version =
|
|
106
|
-
const tagline =
|
|
57
|
+
export function printBanner() {
|
|
58
|
+
const line1 = chalk.bold.hex('#6366f1')(' infrawise');
|
|
59
|
+
const version = chalk.dim(` v${readVersion()}`);
|
|
60
|
+
const tagline = chalk.dim(' Infrastructure Intelligence Platform\n');
|
|
107
61
|
console.log(`\n${line1}${version}`);
|
|
108
62
|
console.log(tagline);
|
|
109
63
|
}
|
|
110
64
|
// ─── Section header ──────────────────────────────────────────────────────────
|
|
111
|
-
function printHeader(title) {
|
|
112
|
-
console.log(
|
|
113
|
-
console.log(
|
|
65
|
+
export function printHeader(title) {
|
|
66
|
+
console.log(chalk.bold(`\n${title}`));
|
|
67
|
+
console.log(chalk.dim('─'.repeat(title.length + 2)));
|
|
114
68
|
}
|
|
115
69
|
// ─── Status lines ────────────────────────────────────────────────────────────
|
|
116
|
-
|
|
70
|
+
export const log = {
|
|
117
71
|
success: (msg, detail) => {
|
|
118
|
-
console.log(` ${
|
|
72
|
+
console.log(` ${chalk.green('✓')} ${msg}${detail ? chalk.dim(` ${detail}`) : ''}`);
|
|
119
73
|
},
|
|
120
74
|
fail: (msg, detail) => {
|
|
121
|
-
console.log(` ${
|
|
75
|
+
console.log(` ${chalk.red('✗')} ${chalk.red(msg)}${detail ? chalk.dim(`\n ${detail}`) : ''}`);
|
|
122
76
|
},
|
|
123
77
|
warn: (msg, detail) => {
|
|
124
|
-
console.log(` ${
|
|
78
|
+
console.log(` ${chalk.yellow('⚠')} ${chalk.yellow(msg)}${detail ? chalk.dim(`\n ${detail}`) : ''}`);
|
|
125
79
|
},
|
|
126
80
|
skip: (msg, detail) => {
|
|
127
|
-
console.log(` ${
|
|
81
|
+
console.log(` ${chalk.dim('−')} ${chalk.dim(msg)}${detail ? chalk.dim(` ${detail}`) : ''}`);
|
|
128
82
|
},
|
|
129
83
|
info: (msg) => {
|
|
130
|
-
console.log(` ${
|
|
84
|
+
console.log(` ${chalk.cyan('›')} ${msg}`);
|
|
131
85
|
},
|
|
132
86
|
dim: (msg) => {
|
|
133
|
-
console.log(
|
|
87
|
+
console.log(chalk.dim(` ${msg}`));
|
|
134
88
|
},
|
|
135
89
|
};
|
|
136
90
|
// ─── Findings ────────────────────────────────────────────────────────────────
|
|
137
91
|
function severityBadge(severity) {
|
|
138
92
|
switch (severity) {
|
|
139
|
-
case 'high': return
|
|
140
|
-
case 'medium': return
|
|
141
|
-
case 'low': return
|
|
93
|
+
case 'high': return chalk.bgRed.white.bold(` HIGH `);
|
|
94
|
+
case 'medium': return chalk.bgYellow.black.bold(` MED `);
|
|
95
|
+
case 'low': return chalk.bgCyan.black.bold(` LOW `);
|
|
142
96
|
}
|
|
143
97
|
}
|
|
144
|
-
function printFinding(finding, index) {
|
|
98
|
+
export function printFinding(finding, index) {
|
|
145
99
|
const badge = severityBadge(finding.severity);
|
|
146
|
-
const num =
|
|
147
|
-
console.log(`\n ${num} ${badge} ${
|
|
148
|
-
console.log(
|
|
149
|
-
console.log(` ${
|
|
100
|
+
const num = chalk.dim(`${index + 1}.`);
|
|
101
|
+
console.log(`\n ${num} ${badge} ${chalk.bold(finding.issue)}`);
|
|
102
|
+
console.log(chalk.dim(` ${finding.description}`));
|
|
103
|
+
console.log(` ${chalk.green('→')} ${finding.recommendation}`);
|
|
150
104
|
}
|
|
151
|
-
function printSummaryBox(findings) {
|
|
105
|
+
export function printSummaryBox(findings) {
|
|
152
106
|
const high = findings.filter((f) => f.severity === 'high').length;
|
|
153
107
|
const medium = findings.filter((f) => f.severity === 'medium').length;
|
|
154
108
|
const low = findings.filter((f) => f.severity === 'low').length;
|
|
155
109
|
console.log('');
|
|
156
|
-
console.log(
|
|
157
|
-
console.log(
|
|
158
|
-
console.log(
|
|
159
|
-
console.log(
|
|
160
|
-
console.log(
|
|
161
|
-
console.log(
|
|
162
|
-
console.log(
|
|
163
|
-
console.log(
|
|
164
|
-
console.log(
|
|
110
|
+
console.log(chalk.dim(' ┌─────────────────────────────┐'));
|
|
111
|
+
console.log(chalk.dim(' │') + chalk.bold(' Analysis Summary ') + chalk.dim('│'));
|
|
112
|
+
console.log(chalk.dim(' ├─────────────────────────────┤'));
|
|
113
|
+
console.log(chalk.dim(' │') + ` ${chalk.red('●')} High ${chalk.red.bold(String(high).padStart(3))} ` + chalk.dim('│'));
|
|
114
|
+
console.log(chalk.dim(' │') + ` ${chalk.yellow('●')} Medium ${chalk.yellow.bold(String(medium).padStart(3))} ` + chalk.dim('│'));
|
|
115
|
+
console.log(chalk.dim(' │') + ` ${chalk.cyan('●')} Low ${chalk.cyan.bold(String(low).padStart(3))} ` + chalk.dim('│'));
|
|
116
|
+
console.log(chalk.dim(' ├─────────────────────────────┤'));
|
|
117
|
+
console.log(chalk.dim(' │') + ` Total ${chalk.bold(String(findings.length).padStart(3))} ` + chalk.dim('│'));
|
|
118
|
+
console.log(chalk.dim(' └─────────────────────────────┘'));
|
|
165
119
|
}
|
package/dist/context/index.js
CHANGED
|
@@ -1,43 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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");
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { Project, SyntaxKind, Node } from 'ts-morph';
|
|
4
|
+
import { RepositoryScanError, logger } from '../core/index.js';
|
|
41
5
|
const DYNAMO_OPERATIONS = new Set([
|
|
42
6
|
'query',
|
|
43
7
|
'scan',
|
|
@@ -109,19 +73,19 @@ const PRISMA_METHODS = new Set([
|
|
|
109
73
|
function getEnclosingFunctionName(node) {
|
|
110
74
|
let current = node.getParent();
|
|
111
75
|
while (current) {
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (
|
|
76
|
+
if (Node.isFunctionDeclaration(current) ||
|
|
77
|
+
Node.isFunctionExpression(current) ||
|
|
78
|
+
Node.isArrowFunction(current) ||
|
|
79
|
+
Node.isMethodDeclaration(current)) {
|
|
80
|
+
if (Node.isFunctionDeclaration(current) || Node.isMethodDeclaration(current)) {
|
|
117
81
|
return current.getName() ?? '<anonymous>';
|
|
118
82
|
}
|
|
119
83
|
// Arrow function or function expression — check if assigned to a variable
|
|
120
84
|
const parent = current.getParent();
|
|
121
|
-
if (parent &&
|
|
85
|
+
if (parent && Node.isVariableDeclaration(parent)) {
|
|
122
86
|
return parent.getName();
|
|
123
87
|
}
|
|
124
|
-
if (parent &&
|
|
88
|
+
if (parent && Node.isPropertyAssignment(parent)) {
|
|
125
89
|
return parent.getName();
|
|
126
90
|
}
|
|
127
91
|
return '<anonymous>';
|
|
@@ -131,15 +95,15 @@ function getEnclosingFunctionName(node) {
|
|
|
131
95
|
return '<module>';
|
|
132
96
|
}
|
|
133
97
|
function extractTableNameFromArg(arg) {
|
|
134
|
-
if (
|
|
98
|
+
if (Node.isStringLiteral(arg)) {
|
|
135
99
|
return arg.getLiteralValue();
|
|
136
100
|
}
|
|
137
|
-
if (
|
|
101
|
+
if (Node.isObjectLiteralExpression(arg)) {
|
|
138
102
|
// Look for TableName property
|
|
139
103
|
for (const prop of arg.getProperties()) {
|
|
140
|
-
if (
|
|
104
|
+
if (Node.isPropertyAssignment(prop) && prop.getName() === 'TableName') {
|
|
141
105
|
const init = prop.getInitializer();
|
|
142
|
-
if (init &&
|
|
106
|
+
if (init && Node.isStringLiteral(init)) {
|
|
143
107
|
return init.getLiteralValue();
|
|
144
108
|
}
|
|
145
109
|
}
|
|
@@ -151,11 +115,11 @@ function detectDynamoOperations(callExpr, filePath) {
|
|
|
151
115
|
const expr = callExpr.getExpression();
|
|
152
116
|
const args = callExpr.getArguments();
|
|
153
117
|
// Pattern 1: client.send(new QueryCommand({ TableName: '...' }))
|
|
154
|
-
if (
|
|
118
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
155
119
|
const methodName = expr.getName();
|
|
156
120
|
if (methodName === 'send' && args.length > 0) {
|
|
157
121
|
const firstArg = args[0];
|
|
158
|
-
if (
|
|
122
|
+
if (Node.isNewExpression(firstArg)) {
|
|
159
123
|
const className = firstArg.getExpression().getText();
|
|
160
124
|
if (DYNAMO_OPERATIONS.has(className)) {
|
|
161
125
|
const cmdArgs = firstArg.getArguments();
|
|
@@ -205,7 +169,7 @@ function extractSqlTableName(sql) {
|
|
|
205
169
|
function detectPostgresOperations(callExpr, filePath) {
|
|
206
170
|
const expr = callExpr.getExpression();
|
|
207
171
|
const args = callExpr.getArguments();
|
|
208
|
-
if (!
|
|
172
|
+
if (!Node.isPropertyAccessExpression(expr))
|
|
209
173
|
return null;
|
|
210
174
|
const methodName = expr.getName();
|
|
211
175
|
const objExpr = expr.getExpression();
|
|
@@ -220,13 +184,13 @@ function detectPostgresOperations(callExpr, filePath) {
|
|
|
220
184
|
let tableName = 'unknown';
|
|
221
185
|
if (args.length > 0) {
|
|
222
186
|
const firstArg = args[0];
|
|
223
|
-
if (
|
|
187
|
+
if (Node.isStringLiteral(firstArg)) {
|
|
224
188
|
tableName = extractSqlTableName(firstArg.getLiteralValue());
|
|
225
189
|
}
|
|
226
|
-
else if (
|
|
190
|
+
else if (Node.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
227
191
|
tableName = extractSqlTableName(firstArg.getLiteralText());
|
|
228
192
|
}
|
|
229
|
-
else if (
|
|
193
|
+
else if (Node.isTemplateExpression(firstArg)) {
|
|
230
194
|
// Template literal — extract what we can from the head
|
|
231
195
|
tableName = extractSqlTableName(firstArg.getHead().getLiteralText());
|
|
232
196
|
}
|
|
@@ -243,7 +207,7 @@ function detectPostgresOperations(callExpr, filePath) {
|
|
|
243
207
|
// Pattern: Prisma — prisma.user.findMany(), prisma.orders.create()
|
|
244
208
|
if (PRISMA_METHODS.has(methodName)) {
|
|
245
209
|
const accessChain = objExpr;
|
|
246
|
-
if (
|
|
210
|
+
if (Node.isPropertyAccessExpression(accessChain)) {
|
|
247
211
|
const modelName = accessChain.getName();
|
|
248
212
|
const rootObj = accessChain.getExpression().getText().toLowerCase();
|
|
249
213
|
if (rootObj.includes('prisma')) {
|
|
@@ -273,11 +237,11 @@ function detectPostgresOperations(callExpr, filePath) {
|
|
|
273
237
|
};
|
|
274
238
|
}
|
|
275
239
|
// Knex call expression style: knex('users').select(...)
|
|
276
|
-
if (
|
|
240
|
+
if (Node.isCallExpression(objExpr)) {
|
|
277
241
|
const innerExpr = objExpr.getExpression();
|
|
278
242
|
if (innerExpr.getText().toLowerCase().match(/knex|db|trx/)) {
|
|
279
243
|
const innerArgs = objExpr.getArguments();
|
|
280
|
-
const tableName = innerArgs.length > 0 &&
|
|
244
|
+
const tableName = innerArgs.length > 0 && Node.isStringLiteral(innerArgs[0])
|
|
281
245
|
? innerArgs[0].getLiteralValue()
|
|
282
246
|
: 'unknown';
|
|
283
247
|
return {
|
|
@@ -295,7 +259,7 @@ function detectPostgresOperations(callExpr, filePath) {
|
|
|
295
259
|
function detectMySQLOperations(callExpr, filePath) {
|
|
296
260
|
const expr = callExpr.getExpression();
|
|
297
261
|
const args = callExpr.getArguments();
|
|
298
|
-
if (!
|
|
262
|
+
if (!Node.isPropertyAccessExpression(expr))
|
|
299
263
|
return null;
|
|
300
264
|
const methodName = expr.getName();
|
|
301
265
|
const objExpr = expr.getExpression();
|
|
@@ -307,13 +271,13 @@ function detectMySQLOperations(callExpr, filePath) {
|
|
|
307
271
|
let tableName = 'unknown';
|
|
308
272
|
if (args.length > 0) {
|
|
309
273
|
const firstArg = args[0];
|
|
310
|
-
if (
|
|
274
|
+
if (Node.isStringLiteral(firstArg)) {
|
|
311
275
|
tableName = extractSqlTableName(firstArg.getLiteralValue());
|
|
312
276
|
}
|
|
313
|
-
else if (
|
|
277
|
+
else if (Node.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
314
278
|
tableName = extractSqlTableName(firstArg.getLiteralText());
|
|
315
279
|
}
|
|
316
|
-
else if (
|
|
280
|
+
else if (Node.isTemplateExpression(firstArg)) {
|
|
317
281
|
tableName = extractSqlTableName(firstArg.getHead().getLiteralText());
|
|
318
282
|
}
|
|
319
283
|
}
|
|
@@ -330,9 +294,9 @@ function detectMySQLOperations(callExpr, filePath) {
|
|
|
330
294
|
if (KNEX_METHODS.has(methodName)) {
|
|
331
295
|
if (objText.includes('mysql') || objText.includes('knex')) {
|
|
332
296
|
let tableName = 'unknown';
|
|
333
|
-
if (
|
|
297
|
+
if (Node.isCallExpression(objExpr)) {
|
|
334
298
|
const innerArgs = objExpr.getArguments();
|
|
335
|
-
if (innerArgs.length > 0 &&
|
|
299
|
+
if (innerArgs.length > 0 && Node.isStringLiteral(innerArgs[0])) {
|
|
336
300
|
tableName = innerArgs[0].getLiteralValue();
|
|
337
301
|
}
|
|
338
302
|
}
|
|
@@ -349,7 +313,7 @@ function detectMySQLOperations(callExpr, filePath) {
|
|
|
349
313
|
}
|
|
350
314
|
function detectMongoOperations(callExpr, filePath) {
|
|
351
315
|
const expr = callExpr.getExpression();
|
|
352
|
-
if (!
|
|
316
|
+
if (!Node.isPropertyAccessExpression(expr))
|
|
353
317
|
return null;
|
|
354
318
|
const methodName = expr.getName();
|
|
355
319
|
const objExpr = expr.getExpression();
|
|
@@ -365,17 +329,17 @@ function detectMongoOperations(callExpr, filePath) {
|
|
|
365
329
|
/^[A-Z][a-zA-Z]+$/.test(objExpr.getText())) {
|
|
366
330
|
let collectionName = 'unknown';
|
|
367
331
|
// Try to get collection name from db.collection('name')
|
|
368
|
-
if (
|
|
332
|
+
if (Node.isCallExpression(objExpr)) {
|
|
369
333
|
const innerExpr = objExpr.getExpression();
|
|
370
|
-
if (
|
|
334
|
+
if (Node.isPropertyAccessExpression(innerExpr) &&
|
|
371
335
|
MONGO_COLLECTION_METHODS.has(innerExpr.getName())) {
|
|
372
336
|
const innerArgs = objExpr.getArguments();
|
|
373
|
-
if (innerArgs.length > 0 &&
|
|
337
|
+
if (innerArgs.length > 0 && Node.isStringLiteral(innerArgs[0])) {
|
|
374
338
|
collectionName = innerArgs[0].getLiteralValue();
|
|
375
339
|
}
|
|
376
340
|
}
|
|
377
341
|
}
|
|
378
|
-
else if (
|
|
342
|
+
else if (Node.isPropertyAccessExpression(objExpr)) {
|
|
379
343
|
// db.users.find() → collection name is the property
|
|
380
344
|
collectionName = objExpr.getName();
|
|
381
345
|
}
|
|
@@ -398,7 +362,7 @@ function detectMongoOperations(callExpr, filePath) {
|
|
|
398
362
|
const args = callExpr.getArguments();
|
|
399
363
|
const objText = objExpr.getText().toLowerCase();
|
|
400
364
|
if (objText.includes('db') || objText.includes('mongo')) {
|
|
401
|
-
const collectionName = args.length > 0 &&
|
|
365
|
+
const collectionName = args.length > 0 && Node.isStringLiteral(args[0])
|
|
402
366
|
? args[0].getLiteralValue()
|
|
403
367
|
: 'unknown';
|
|
404
368
|
return {
|
|
@@ -413,11 +377,11 @@ function detectMongoOperations(callExpr, filePath) {
|
|
|
413
377
|
return null;
|
|
414
378
|
}
|
|
415
379
|
function extractArgValue(arg, ...keys) {
|
|
416
|
-
if (
|
|
380
|
+
if (Node.isObjectLiteralExpression(arg)) {
|
|
417
381
|
for (const prop of arg.getProperties()) {
|
|
418
|
-
if (
|
|
382
|
+
if (Node.isPropertyAssignment(prop) && keys.includes(prop.getName())) {
|
|
419
383
|
const init = prop.getInitializer();
|
|
420
|
-
if (init &&
|
|
384
|
+
if (init && Node.isStringLiteral(init)) {
|
|
421
385
|
// For ARNs, extract the last segment as a readable name
|
|
422
386
|
const val = init.getLiteralValue();
|
|
423
387
|
return val.includes(':') ? (val.split(':').pop() ?? val) : val;
|
|
@@ -430,7 +394,7 @@ function extractArgValue(arg, ...keys) {
|
|
|
430
394
|
function detectKafkaOperations(callExpr, filePath) {
|
|
431
395
|
const expr = callExpr.getExpression();
|
|
432
396
|
const args = callExpr.getArguments();
|
|
433
|
-
if (!
|
|
397
|
+
if (!Node.isPropertyAccessExpression(expr))
|
|
434
398
|
return null;
|
|
435
399
|
const methodName = expr.getName();
|
|
436
400
|
const objText = expr.getExpression().getText().toLowerCase();
|
|
@@ -460,9 +424,9 @@ function detectAWSServiceOperations(callExpr, filePath) {
|
|
|
460
424
|
const expr = callExpr.getExpression();
|
|
461
425
|
const args = callExpr.getArguments();
|
|
462
426
|
// Pattern 1: client.send(new XCommand({ ... }))
|
|
463
|
-
if (
|
|
427
|
+
if (Node.isPropertyAccessExpression(expr) && expr.getName() === 'send' && args.length > 0) {
|
|
464
428
|
const firstArg = args[0];
|
|
465
|
-
if (
|
|
429
|
+
if (Node.isNewExpression(firstArg)) {
|
|
466
430
|
const className = firstArg.getExpression().getText();
|
|
467
431
|
const cmdArgs = firstArg.getArguments();
|
|
468
432
|
if (SQS_COMMANDS.has(className)) {
|
|
@@ -513,7 +477,7 @@ function detectAWSServiceOperations(callExpr, filePath) {
|
|
|
513
477
|
}
|
|
514
478
|
}
|
|
515
479
|
// Pattern 2: awsService.method({ ... }) — v2-style or SDK-wrapped clients
|
|
516
|
-
if (
|
|
480
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
517
481
|
const methodName = expr.getName();
|
|
518
482
|
const objText = expr.getExpression().getText().toLowerCase();
|
|
519
483
|
if (SQS_COMMANDS.has(methodName) && (objText.includes('sqs') || objText.includes('queue'))) {
|
|
@@ -564,14 +528,14 @@ function detectAWSServiceOperations(callExpr, filePath) {
|
|
|
564
528
|
}
|
|
565
529
|
return null;
|
|
566
530
|
}
|
|
567
|
-
async function scanRepository(repoPath) {
|
|
531
|
+
export async function scanRepository(repoPath) {
|
|
568
532
|
const resolvedPath = path.resolve(repoPath);
|
|
569
533
|
if (!fs.existsSync(resolvedPath)) {
|
|
570
|
-
throw new
|
|
534
|
+
throw new RepositoryScanError(`Path does not exist: ${resolvedPath}`);
|
|
571
535
|
}
|
|
572
536
|
const tsconfigPath = path.join(resolvedPath, 'tsconfig.json');
|
|
573
537
|
const hasTsConfig = fs.existsSync(tsconfigPath);
|
|
574
|
-
const project = new
|
|
538
|
+
const project = new Project({
|
|
575
539
|
tsConfigFilePath: hasTsConfig ? tsconfigPath : undefined,
|
|
576
540
|
compilerOptions: hasTsConfig
|
|
577
541
|
? undefined
|
|
@@ -591,14 +555,14 @@ async function scanRepository(repoPath) {
|
|
|
591
555
|
]);
|
|
592
556
|
}
|
|
593
557
|
const sourceFiles = project.getSourceFiles();
|
|
594
|
-
|
|
558
|
+
logger.debug(`Scanning ${sourceFiles.length} TypeScript file(s) in ${resolvedPath}`);
|
|
595
559
|
const operations = [];
|
|
596
560
|
for (const sourceFile of sourceFiles) {
|
|
597
561
|
const filePath = sourceFile.getFilePath();
|
|
598
562
|
// Skip node_modules and dist
|
|
599
563
|
if (filePath.includes('node_modules') || filePath.includes('/dist/'))
|
|
600
564
|
continue;
|
|
601
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
565
|
+
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
602
566
|
for (const callExpr of callExpressions) {
|
|
603
567
|
const dynamoOp = detectDynamoOperations(callExpr, filePath);
|
|
604
568
|
if (dynamoOp) {
|
|
@@ -631,6 +595,6 @@ async function scanRepository(repoPath) {
|
|
|
631
595
|
}
|
|
632
596
|
}
|
|
633
597
|
}
|
|
634
|
-
|
|
598
|
+
logger.debug(`Extracted ${operations.length} database operation(s)`);
|
|
635
599
|
return operations;
|
|
636
600
|
}
|