infrawise 0.1.0 → 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 +152 -75
- 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 +31 -26
- package/dist/index.js +0 -3462
|
@@ -0,0 +1,163 @@
|
|
|
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.ConfigError = exports.InfrawiseConfigSchema = void 0;
|
|
37
|
+
exports.loadConfig = loadConfig;
|
|
38
|
+
exports.generateDefaultConfig = generateDefaultConfig;
|
|
39
|
+
const zod_1 = require("zod");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const yaml = __importStar(require("js-yaml"));
|
|
43
|
+
exports.InfrawiseConfigSchema = zod_1.z.object({
|
|
44
|
+
project: zod_1.z.string().min(1, 'Project name is required'),
|
|
45
|
+
aws: zod_1.z
|
|
46
|
+
.object({
|
|
47
|
+
profile: zod_1.z.string().optional().default('default'),
|
|
48
|
+
region: zod_1.z.string().optional().default('us-east-1'),
|
|
49
|
+
})
|
|
50
|
+
.optional()
|
|
51
|
+
.default({}),
|
|
52
|
+
dynamodb: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true), includeTables: zod_1.z.array(zod_1.z.string()).optional() }).optional(),
|
|
53
|
+
postgres: zod_1.z.object({
|
|
54
|
+
enabled: zod_1.z.boolean().optional().default(false),
|
|
55
|
+
connectionString: zod_1.z.string().optional(),
|
|
56
|
+
}).optional(),
|
|
57
|
+
mysql: zod_1.z.object({
|
|
58
|
+
enabled: zod_1.z.boolean().optional().default(false),
|
|
59
|
+
connectionString: zod_1.z.string().optional(),
|
|
60
|
+
}).optional(),
|
|
61
|
+
mongodb: zod_1.z.object({
|
|
62
|
+
enabled: zod_1.z.boolean().optional().default(false),
|
|
63
|
+
connectionString: zod_1.z.string().optional(),
|
|
64
|
+
databases: zod_1.z.array(zod_1.z.string()).optional(),
|
|
65
|
+
}).optional(),
|
|
66
|
+
terraform: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
|
|
67
|
+
sqs: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
|
|
68
|
+
sns: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
|
|
69
|
+
ssm: zod_1.z.object({
|
|
70
|
+
enabled: zod_1.z.boolean().optional().default(true),
|
|
71
|
+
paths: zod_1.z.array(zod_1.z.string()).optional(),
|
|
72
|
+
}).optional(),
|
|
73
|
+
secretsManager: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
|
|
74
|
+
lambda: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(true) }).optional(),
|
|
75
|
+
rds: zod_1.z.object({ enabled: zod_1.z.boolean().optional().default(false) }).optional(),
|
|
76
|
+
cloudwatchLogs: zod_1.z.object({
|
|
77
|
+
enabled: zod_1.z.boolean().optional().default(false),
|
|
78
|
+
logGroupPrefixes: zod_1.z.array(zod_1.z.string()).optional(),
|
|
79
|
+
windowHours: zod_1.z.number().int().positive().optional().default(24),
|
|
80
|
+
}).optional(),
|
|
81
|
+
analysis: zod_1.z.object({
|
|
82
|
+
sampleSize: zod_1.z.number().int().positive().optional().default(100),
|
|
83
|
+
}).optional(),
|
|
84
|
+
});
|
|
85
|
+
class ConfigError extends Error {
|
|
86
|
+
details;
|
|
87
|
+
constructor(message, details) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.details = details;
|
|
90
|
+
this.name = 'ConfigError';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.ConfigError = ConfigError;
|
|
94
|
+
function loadConfig(configPath) {
|
|
95
|
+
const resolvedPath = configPath
|
|
96
|
+
? path.resolve(configPath)
|
|
97
|
+
: path.resolve(process.cwd(), 'infrawise.yaml');
|
|
98
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
99
|
+
throw new ConfigError(`Configuration file not found at: ${resolvedPath}`, [
|
|
100
|
+
'Run `infrawise init` to generate a configuration file',
|
|
101
|
+
`Or specify a path with --config <path>`,
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
let rawContent;
|
|
105
|
+
try {
|
|
106
|
+
rawContent = fs.readFileSync(resolvedPath, 'utf-8');
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
throw new ConfigError(`Unable to read configuration file: ${resolvedPath}`, [String(err)]);
|
|
110
|
+
}
|
|
111
|
+
let parsedYaml;
|
|
112
|
+
try {
|
|
113
|
+
parsedYaml = yaml.load(rawContent);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
throw new ConfigError(`Invalid YAML in configuration file: ${resolvedPath}`, [String(err)]);
|
|
117
|
+
}
|
|
118
|
+
const result = exports.InfrawiseConfigSchema.safeParse(parsedYaml);
|
|
119
|
+
if (!result.success) {
|
|
120
|
+
const details = result.error.errors.map((e) => ` - ${e.path.join('.')}: ${e.message}`);
|
|
121
|
+
throw new ConfigError('Configuration validation failed', details);
|
|
122
|
+
}
|
|
123
|
+
return result.data;
|
|
124
|
+
}
|
|
125
|
+
function generateDefaultConfig(projectName, options) {
|
|
126
|
+
const config = {
|
|
127
|
+
project: projectName,
|
|
128
|
+
aws: {
|
|
129
|
+
profile: options?.aws?.profile ?? 'default',
|
|
130
|
+
region: options?.aws?.region ?? 'us-east-1',
|
|
131
|
+
},
|
|
132
|
+
dynamodb: { enabled: options?.dynamodb?.enabled ?? true, includeTables: options?.dynamodb?.includeTables ?? [] },
|
|
133
|
+
postgres: {
|
|
134
|
+
enabled: options?.postgres?.enabled ?? false,
|
|
135
|
+
connectionString: options?.postgres?.connectionString ?? '',
|
|
136
|
+
},
|
|
137
|
+
mysql: {
|
|
138
|
+
enabled: options?.mysql?.enabled ?? false,
|
|
139
|
+
connectionString: options?.mysql?.connectionString ?? '',
|
|
140
|
+
},
|
|
141
|
+
mongodb: {
|
|
142
|
+
enabled: options?.mongodb?.enabled ?? false,
|
|
143
|
+
connectionString: options?.mongodb?.connectionString ?? '',
|
|
144
|
+
databases: options?.mongodb?.databases ?? [],
|
|
145
|
+
},
|
|
146
|
+
terraform: { enabled: options?.terraform?.enabled ?? true },
|
|
147
|
+
sqs: { enabled: options?.sqs?.enabled ?? true },
|
|
148
|
+
sns: { enabled: options?.sns?.enabled ?? true },
|
|
149
|
+
ssm: {
|
|
150
|
+
enabled: options?.ssm?.enabled ?? true,
|
|
151
|
+
paths: options?.ssm?.paths ?? [],
|
|
152
|
+
},
|
|
153
|
+
secretsManager: { enabled: options?.secretsManager?.enabled ?? true },
|
|
154
|
+
lambda: { enabled: options?.lambda?.enabled ?? true },
|
|
155
|
+
cloudwatchLogs: {
|
|
156
|
+
enabled: options?.cloudwatchLogs?.enabled ?? false,
|
|
157
|
+
logGroupPrefixes: options?.cloudwatchLogs?.logGroupPrefixes ?? [],
|
|
158
|
+
windowHours: options?.cloudwatchLogs?.windowHours ?? 24,
|
|
159
|
+
},
|
|
160
|
+
analysis: { sampleSize: options?.analysis?.sampleSize ?? 100 },
|
|
161
|
+
};
|
|
162
|
+
return yaml.dump(config, { lineWidth: 120 });
|
|
163
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigError = exports.RepositoryScanError = exports.PostgresConnectionError = exports.DynamoDBError = exports.AWSConnectionError = exports.InfrawiseError = void 0;
|
|
4
|
+
exports.formatError = formatError;
|
|
5
|
+
class InfrawiseError extends Error {
|
|
6
|
+
reasons;
|
|
7
|
+
remediation;
|
|
8
|
+
constructor(message, reasons, remediation) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.reasons = reasons;
|
|
11
|
+
this.remediation = remediation;
|
|
12
|
+
this.name = 'InfrawiseError';
|
|
13
|
+
}
|
|
14
|
+
format() {
|
|
15
|
+
const lines = [`\n${this.message}\n`];
|
|
16
|
+
if (this.reasons && this.reasons.length > 0) {
|
|
17
|
+
lines.push('Possible reasons:');
|
|
18
|
+
for (const reason of this.reasons) {
|
|
19
|
+
lines.push(` - ${reason}`);
|
|
20
|
+
}
|
|
21
|
+
lines.push('');
|
|
22
|
+
}
|
|
23
|
+
if (this.remediation) {
|
|
24
|
+
lines.push(`Run: ${this.remediation}`);
|
|
25
|
+
}
|
|
26
|
+
return lines.join('\n');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.InfrawiseError = InfrawiseError;
|
|
30
|
+
class AWSConnectionError extends InfrawiseError {
|
|
31
|
+
constructor(details) {
|
|
32
|
+
super('Unable to connect to AWS.', [
|
|
33
|
+
'Invalid or missing AWS credentials',
|
|
34
|
+
'Incorrect AWS profile specified',
|
|
35
|
+
'Network connectivity issues',
|
|
36
|
+
details ?? 'Unexpected AWS error',
|
|
37
|
+
], 'infrawise doctor');
|
|
38
|
+
this.name = 'AWSConnectionError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.AWSConnectionError = AWSConnectionError;
|
|
42
|
+
class DynamoDBError extends InfrawiseError {
|
|
43
|
+
constructor(details) {
|
|
44
|
+
super('Unable to access DynamoDB.', [
|
|
45
|
+
'Insufficient IAM permissions (need dynamodb:ListTables, dynamodb:DescribeTable)',
|
|
46
|
+
'Wrong AWS region configured',
|
|
47
|
+
'DynamoDB endpoint not reachable',
|
|
48
|
+
details ?? 'Unexpected DynamoDB error',
|
|
49
|
+
], 'infrawise doctor');
|
|
50
|
+
this.name = 'DynamoDBError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.DynamoDBError = DynamoDBError;
|
|
54
|
+
class PostgresConnectionError extends InfrawiseError {
|
|
55
|
+
constructor(details) {
|
|
56
|
+
super('Unable to connect to PostgreSQL.', [
|
|
57
|
+
'Invalid connection string',
|
|
58
|
+
'Security group restrictions',
|
|
59
|
+
'Expired credentials',
|
|
60
|
+
details ?? 'Unexpected PostgreSQL error',
|
|
61
|
+
], 'infrawise doctor');
|
|
62
|
+
this.name = 'PostgresConnectionError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.PostgresConnectionError = PostgresConnectionError;
|
|
66
|
+
class RepositoryScanError extends InfrawiseError {
|
|
67
|
+
constructor(details) {
|
|
68
|
+
super('Unable to scan repository.', [
|
|
69
|
+
'Path does not exist or is not accessible',
|
|
70
|
+
'Not a valid TypeScript project',
|
|
71
|
+
'tsconfig.json not found',
|
|
72
|
+
details ?? 'Unexpected scan error',
|
|
73
|
+
], 'infrawise doctor');
|
|
74
|
+
this.name = 'RepositoryScanError';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.RepositoryScanError = RepositoryScanError;
|
|
78
|
+
class ConfigError extends InfrawiseError {
|
|
79
|
+
constructor(details) {
|
|
80
|
+
super('Invalid or missing configuration.', [
|
|
81
|
+
'infrawise.yaml not found in current directory',
|
|
82
|
+
'Missing required fields in configuration',
|
|
83
|
+
details ?? 'Unexpected config error',
|
|
84
|
+
], 'infrawise init');
|
|
85
|
+
this.name = 'ConfigError';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.ConfigError = ConfigError;
|
|
89
|
+
function formatError(err) {
|
|
90
|
+
if (err instanceof InfrawiseError) {
|
|
91
|
+
return err.format();
|
|
92
|
+
}
|
|
93
|
+
if (err instanceof Error) {
|
|
94
|
+
return `\nUnexpected error: ${err.message}\n`;
|
|
95
|
+
}
|
|
96
|
+
return `\nUnexpected error: ${String(err)}\n`;
|
|
97
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearCache = exports.readCache = exports.writeCache = exports.formatError = exports.ConfigError = exports.RepositoryScanError = exports.PostgresConnectionError = exports.DynamoDBError = exports.AWSConnectionError = exports.InfrawiseError = exports.logger = exports.ConfigValidationError = exports.InfrawiseConfigSchema = exports.generateDefaultConfig = exports.loadConfig = void 0;
|
|
4
|
+
var config_1 = require("./config");
|
|
5
|
+
Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return config_1.loadConfig; } });
|
|
6
|
+
Object.defineProperty(exports, "generateDefaultConfig", { enumerable: true, get: function () { return config_1.generateDefaultConfig; } });
|
|
7
|
+
Object.defineProperty(exports, "InfrawiseConfigSchema", { enumerable: true, get: function () { return config_1.InfrawiseConfigSchema; } });
|
|
8
|
+
Object.defineProperty(exports, "ConfigValidationError", { enumerable: true, get: function () { return config_1.ConfigError; } });
|
|
9
|
+
var logger_1 = require("./logger");
|
|
10
|
+
Object.defineProperty(exports, "logger", { enumerable: true, get: function () { return logger_1.logger; } });
|
|
11
|
+
var errors_1 = require("./errors");
|
|
12
|
+
Object.defineProperty(exports, "InfrawiseError", { enumerable: true, get: function () { return errors_1.InfrawiseError; } });
|
|
13
|
+
Object.defineProperty(exports, "AWSConnectionError", { enumerable: true, get: function () { return errors_1.AWSConnectionError; } });
|
|
14
|
+
Object.defineProperty(exports, "DynamoDBError", { enumerable: true, get: function () { return errors_1.DynamoDBError; } });
|
|
15
|
+
Object.defineProperty(exports, "PostgresConnectionError", { enumerable: true, get: function () { return errors_1.PostgresConnectionError; } });
|
|
16
|
+
Object.defineProperty(exports, "RepositoryScanError", { enumerable: true, get: function () { return errors_1.RepositoryScanError; } });
|
|
17
|
+
Object.defineProperty(exports, "ConfigError", { enumerable: true, get: function () { return errors_1.ConfigError; } });
|
|
18
|
+
Object.defineProperty(exports, "formatError", { enumerable: true, get: function () { return errors_1.formatError; } });
|
|
19
|
+
var cache_1 = require("./cache");
|
|
20
|
+
Object.defineProperty(exports, "writeCache", { enumerable: true, get: function () { return cache_1.writeCache; } });
|
|
21
|
+
Object.defineProperty(exports, "readCache", { enumerable: true, get: function () { return cache_1.readCache; } });
|
|
22
|
+
Object.defineProperty(exports, "clearCache", { enumerable: true, get: function () { return cache_1.clearCache; } });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logger = void 0;
|
|
7
|
+
const pino_1 = __importDefault(require("pino"));
|
|
8
|
+
function createLogger() {
|
|
9
|
+
const isDevelopment = process.env.NODE_ENV !== 'production';
|
|
10
|
+
if (isDevelopment) {
|
|
11
|
+
return (0, pino_1.default)({
|
|
12
|
+
level: process.env.LOG_LEVEL ?? 'info',
|
|
13
|
+
transport: {
|
|
14
|
+
target: 'pino-pretty',
|
|
15
|
+
options: {
|
|
16
|
+
colorize: true,
|
|
17
|
+
translateTime: 'HH:MM:ss',
|
|
18
|
+
ignore: 'pid,hostname',
|
|
19
|
+
messageFormat: '{msg}',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return (0, pino_1.default)({
|
|
25
|
+
level: process.env.LOG_LEVEL ?? 'info',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
exports.logger = createLogger();
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildGraph = buildGraph;
|
|
4
|
+
exports.getTableNodes = getTableNodes;
|
|
5
|
+
exports.getFunctionNodes = getFunctionNodes;
|
|
6
|
+
exports.getIndexNodes = getIndexNodes;
|
|
7
|
+
exports.getQueueNodes = getQueueNodes;
|
|
8
|
+
exports.getTopicNodes = getTopicNodes;
|
|
9
|
+
exports.getSecretNodes = getSecretNodes;
|
|
10
|
+
exports.getParameterNodes = getParameterNodes;
|
|
11
|
+
exports.getLogGroupNodes = getLogGroupNodes;
|
|
12
|
+
exports.getLambdaNodes = getLambdaNodes;
|
|
13
|
+
exports.getEdgesForNode = getEdgesForNode;
|
|
14
|
+
exports.getOutgoingEdges = getOutgoingEdges;
|
|
15
|
+
exports.getIncomingEdges = getIncomingEdges;
|
|
16
|
+
exports.getScanEdges = getScanEdges;
|
|
17
|
+
exports.getEdgeFrequency = getEdgeFrequency;
|
|
18
|
+
function buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta = [], mongoMeta = [], servicesMeta = {}) {
|
|
19
|
+
const nodes = [];
|
|
20
|
+
const edges = [];
|
|
21
|
+
const nodeIds = new Set();
|
|
22
|
+
function addNode(node) {
|
|
23
|
+
if (!nodeIds.has(node.id)) {
|
|
24
|
+
nodes.push(node);
|
|
25
|
+
nodeIds.add(node.id);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// ── Database tables ──────────────────────────────────────────────────────
|
|
29
|
+
for (const table of dynamoMeta) {
|
|
30
|
+
const nodeId = `table:dynamo:${table.tableName}`;
|
|
31
|
+
addNode({ id: nodeId, type: 'table', name: table.tableName, databaseType: 'dynamodb' });
|
|
32
|
+
for (const indexName of table.indexes) {
|
|
33
|
+
const indexNodeId = `index:${table.tableName}:${indexName}`;
|
|
34
|
+
addNode({ id: indexNodeId, type: 'index', name: indexName });
|
|
35
|
+
edges.push({ from: nodeId, to: indexNodeId, type: 'uses_index' });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const table of postgresMeta) {
|
|
39
|
+
const nodeId = `table:postgres:${table.schema}.${table.table}`;
|
|
40
|
+
addNode({ id: nodeId, type: 'table', name: `${table.schema}.${table.table}`, databaseType: 'postgres' });
|
|
41
|
+
for (const indexName of table.indexes) {
|
|
42
|
+
const indexNodeId = `index:${table.schema}.${table.table}:${indexName}`;
|
|
43
|
+
addNode({ id: indexNodeId, type: 'index', name: indexName });
|
|
44
|
+
edges.push({ from: nodeId, to: indexNodeId, type: 'uses_index' });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const table of mysqlMeta) {
|
|
48
|
+
const nodeId = `table:mysql:${table.schema}.${table.table}`;
|
|
49
|
+
addNode({ id: nodeId, type: 'table', name: `${table.schema}.${table.table}`, databaseType: 'mysql' });
|
|
50
|
+
for (const indexName of table.indexes) {
|
|
51
|
+
const indexNodeId = `index:${table.schema}.${table.table}:${indexName}`;
|
|
52
|
+
addNode({ id: indexNodeId, type: 'index', name: indexName });
|
|
53
|
+
edges.push({ from: nodeId, to: indexNodeId, type: 'uses_index' });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const coll of mongoMeta) {
|
|
57
|
+
const nodeId = `table:mongodb:${coll.database}.${coll.collection}`;
|
|
58
|
+
addNode({ id: nodeId, type: 'table', name: `${coll.database}.${coll.collection}`, databaseType: 'mongodb' });
|
|
59
|
+
for (const idx of coll.indexes) {
|
|
60
|
+
if (idx.name === '_id_')
|
|
61
|
+
continue;
|
|
62
|
+
const indexNodeId = `index:${coll.database}.${coll.collection}:${idx.name}`;
|
|
63
|
+
addNode({ id: indexNodeId, type: 'index', name: idx.name });
|
|
64
|
+
edges.push({ from: nodeId, to: indexNodeId, type: 'uses_index' });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ── AWS services ──────────────────────────────────────────────────────────
|
|
68
|
+
for (const q of servicesMeta.sqs ?? []) {
|
|
69
|
+
addNode({
|
|
70
|
+
id: `queue:aws:${q.name}`,
|
|
71
|
+
type: 'queue',
|
|
72
|
+
name: q.name,
|
|
73
|
+
provider: 'aws',
|
|
74
|
+
hasDLQ: q.hasDLQ,
|
|
75
|
+
encrypted: q.encrypted,
|
|
76
|
+
approximateMessages: q.approximateMessages,
|
|
77
|
+
retentionDays: q.retentionDays,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
for (const t of servicesMeta.sns ?? []) {
|
|
81
|
+
addNode({
|
|
82
|
+
id: `topic:aws:${t.name}`,
|
|
83
|
+
type: 'topic',
|
|
84
|
+
name: t.name,
|
|
85
|
+
provider: 'aws',
|
|
86
|
+
subscriptionCount: t.subscriptionCount,
|
|
87
|
+
encrypted: t.encrypted,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const s of servicesMeta.secrets ?? []) {
|
|
91
|
+
addNode({
|
|
92
|
+
id: `secret:aws:${s.name}`,
|
|
93
|
+
type: 'secret',
|
|
94
|
+
name: s.name,
|
|
95
|
+
provider: 'aws',
|
|
96
|
+
rotationEnabled: s.rotationEnabled,
|
|
97
|
+
rotationDays: s.rotationDays,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
for (const p of servicesMeta.ssm ?? []) {
|
|
101
|
+
addNode({
|
|
102
|
+
id: `parameter:aws:${p.name}`,
|
|
103
|
+
type: 'parameter',
|
|
104
|
+
name: p.name,
|
|
105
|
+
provider: 'aws',
|
|
106
|
+
paramType: p.type,
|
|
107
|
+
tier: p.tier,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
for (const lg of servicesMeta.logs ?? []) {
|
|
111
|
+
addNode({
|
|
112
|
+
id: `log_group:aws:${lg.logGroupName}`,
|
|
113
|
+
type: 'log_group',
|
|
114
|
+
name: lg.logGroupName,
|
|
115
|
+
provider: 'aws',
|
|
116
|
+
retentionDays: lg.retentionDays,
|
|
117
|
+
errorCount: lg.errorCount,
|
|
118
|
+
topErrorPatterns: lg.topErrorPatterns,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
for (const fn of servicesMeta.lambda ?? []) {
|
|
122
|
+
addNode({
|
|
123
|
+
id: `lambda:aws:${fn.name}`,
|
|
124
|
+
type: 'lambda',
|
|
125
|
+
name: fn.name,
|
|
126
|
+
runtime: fn.runtime,
|
|
127
|
+
memoryMB: fn.memoryMB,
|
|
128
|
+
timeoutSec: fn.timeoutSec,
|
|
129
|
+
envVarKeys: fn.envVarKeys,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
for (const db of servicesMeta.rds ?? []) {
|
|
133
|
+
addNode({
|
|
134
|
+
id: `database_instance:aws:${db.dbInstanceIdentifier}`,
|
|
135
|
+
type: 'database_instance',
|
|
136
|
+
name: db.dbInstanceIdentifier,
|
|
137
|
+
provider: 'aws',
|
|
138
|
+
engine: db.engine,
|
|
139
|
+
engineVersion: db.engineVersion,
|
|
140
|
+
instanceClass: db.instanceClass,
|
|
141
|
+
publiclyAccessible: db.publiclyAccessible,
|
|
142
|
+
storageEncrypted: db.storageEncrypted,
|
|
143
|
+
backupRetentionDays: db.backupRetentionPeriod,
|
|
144
|
+
deletionProtection: db.deletionProtection,
|
|
145
|
+
multiAZ: db.multiAZ,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// ── Code operations (functions + edges) ───────────────────────────────────
|
|
149
|
+
for (const op of operations) {
|
|
150
|
+
const funcNodeId = `function:${op.filePath}:${op.functionName}`;
|
|
151
|
+
if (!nodeIds.has(funcNodeId)) {
|
|
152
|
+
nodes.push({ id: funcNodeId, type: 'function', name: op.functionName, file: op.filePath });
|
|
153
|
+
nodeIds.add(funcNodeId);
|
|
154
|
+
}
|
|
155
|
+
// AWS service operations create edges to service nodes
|
|
156
|
+
if (op.databaseType === 'sqs') {
|
|
157
|
+
const queueId = `queue:aws:${op.target}`;
|
|
158
|
+
addNode({ id: queueId, type: 'queue', name: op.target, provider: 'aws', hasDLQ: false, encrypted: false });
|
|
159
|
+
edges.push({ from: funcNodeId, to: queueId, type: 'publishes_to' });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (op.databaseType === 'sns') {
|
|
163
|
+
const topicId = `topic:aws:${op.target}`;
|
|
164
|
+
addNode({ id: topicId, type: 'topic', name: op.target, provider: 'aws', encrypted: false });
|
|
165
|
+
edges.push({ from: funcNodeId, to: topicId, type: 'publishes_to' });
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (op.databaseType === 'ssm') {
|
|
169
|
+
const paramId = `parameter:aws:${op.target}`;
|
|
170
|
+
addNode({ id: paramId, type: 'parameter', name: op.target, provider: 'aws', paramType: 'String', tier: 'Standard' });
|
|
171
|
+
edges.push({ from: funcNodeId, to: paramId, type: 'reads_parameter' });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (op.databaseType === 'secretsmanager') {
|
|
175
|
+
const secretId = `secret:aws:${op.target}`;
|
|
176
|
+
addNode({ id: secretId, type: 'secret', name: op.target, provider: 'aws', rotationEnabled: false });
|
|
177
|
+
edges.push({ from: funcNodeId, to: secretId, type: 'reads_secret' });
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (op.databaseType === 'lambda') {
|
|
181
|
+
const lambdaId = `lambda:aws:${op.target}`;
|
|
182
|
+
addNode({ id: lambdaId, type: 'lambda', name: op.target });
|
|
183
|
+
edges.push({ from: funcNodeId, to: lambdaId, type: 'triggers' });
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// Database operations
|
|
187
|
+
let tableNodeId;
|
|
188
|
+
if (op.databaseType === 'dynamodb') {
|
|
189
|
+
tableNodeId = `table:dynamo:${op.target}`;
|
|
190
|
+
addNode({ id: tableNodeId, type: 'table', name: op.target, databaseType: 'dynamodb' });
|
|
191
|
+
}
|
|
192
|
+
else if (op.databaseType === 'mysql') {
|
|
193
|
+
const q = op.target.includes('.') ? op.target : `default.${op.target}`;
|
|
194
|
+
tableNodeId = `table:mysql:${q}`;
|
|
195
|
+
addNode({ id: tableNodeId, type: 'table', name: q, databaseType: 'mysql' });
|
|
196
|
+
}
|
|
197
|
+
else if (op.databaseType === 'mongodb') {
|
|
198
|
+
const q = op.target.includes('.') ? op.target : `default.${op.target}`;
|
|
199
|
+
tableNodeId = `table:mongodb:${q}`;
|
|
200
|
+
addNode({ id: tableNodeId, type: 'table', name: q, databaseType: 'mongodb' });
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// postgres
|
|
204
|
+
const q = op.target.includes('.') ? op.target : `public.${op.target}`;
|
|
205
|
+
tableNodeId = `table:postgres:${q}`;
|
|
206
|
+
addNode({ id: tableNodeId, type: 'table', name: q, databaseType: 'postgres' });
|
|
207
|
+
}
|
|
208
|
+
const edgeType = resolveEdgeType(op.operationType);
|
|
209
|
+
edges.push({ from: funcNodeId, to: tableNodeId, type: edgeType });
|
|
210
|
+
}
|
|
211
|
+
return { nodes, edges };
|
|
212
|
+
}
|
|
213
|
+
function resolveEdgeType(operationType) {
|
|
214
|
+
const op = operationType.toLowerCase();
|
|
215
|
+
if (op === 'scan' || op === 'scancommand')
|
|
216
|
+
return 'scan';
|
|
217
|
+
if (op === 'join' || op === 'joins')
|
|
218
|
+
return 'joins';
|
|
219
|
+
return 'query';
|
|
220
|
+
}
|
|
221
|
+
// ── Typed node selectors ─────────────────────────────────────────────────────
|
|
222
|
+
function getTableNodes(graph) {
|
|
223
|
+
return graph.nodes.filter((n) => n.type === 'table');
|
|
224
|
+
}
|
|
225
|
+
function getFunctionNodes(graph) {
|
|
226
|
+
return graph.nodes.filter((n) => n.type === 'function');
|
|
227
|
+
}
|
|
228
|
+
function getIndexNodes(graph) {
|
|
229
|
+
return graph.nodes.filter((n) => n.type === 'index');
|
|
230
|
+
}
|
|
231
|
+
function getQueueNodes(graph) {
|
|
232
|
+
return graph.nodes.filter((n) => n.type === 'queue');
|
|
233
|
+
}
|
|
234
|
+
function getTopicNodes(graph) {
|
|
235
|
+
return graph.nodes.filter((n) => n.type === 'topic');
|
|
236
|
+
}
|
|
237
|
+
function getSecretNodes(graph) {
|
|
238
|
+
return graph.nodes.filter((n) => n.type === 'secret');
|
|
239
|
+
}
|
|
240
|
+
function getParameterNodes(graph) {
|
|
241
|
+
return graph.nodes.filter((n) => n.type === 'parameter');
|
|
242
|
+
}
|
|
243
|
+
function getLogGroupNodes(graph) {
|
|
244
|
+
return graph.nodes.filter((n) => n.type === 'log_group');
|
|
245
|
+
}
|
|
246
|
+
function getLambdaNodes(graph) {
|
|
247
|
+
return graph.nodes.filter((n) => n.type === 'lambda');
|
|
248
|
+
}
|
|
249
|
+
function getEdgesForNode(graph, nodeId) {
|
|
250
|
+
return graph.edges.filter((e) => e.from === nodeId || e.to === nodeId);
|
|
251
|
+
}
|
|
252
|
+
function getOutgoingEdges(graph, nodeId) {
|
|
253
|
+
return graph.edges.filter((e) => e.from === nodeId);
|
|
254
|
+
}
|
|
255
|
+
function getIncomingEdges(graph, nodeId) {
|
|
256
|
+
return graph.edges.filter((e) => e.to === nodeId);
|
|
257
|
+
}
|
|
258
|
+
function getScanEdges(graph) {
|
|
259
|
+
return graph.edges.filter((e) => e.type === 'scan');
|
|
260
|
+
}
|
|
261
|
+
function getEdgeFrequency(graph) {
|
|
262
|
+
const freq = new Map();
|
|
263
|
+
for (const edge of graph.edges) {
|
|
264
|
+
const key = `${edge.from}->${edge.to}`;
|
|
265
|
+
freq.set(key, (freq.get(key) ?? 0) + 1);
|
|
266
|
+
}
|
|
267
|
+
return freq;
|
|
268
|
+
}
|