@neat.is/core 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-T2U4U256.js → chunk-5KX7EI4F.js} +76 -13
- package/dist/chunk-5KX7EI4F.js.map +1 -0
- package/dist/{chunk-6JT6L2OV.js → chunk-BVF7MVR5.js} +8 -10
- package/dist/{chunk-6JT6L2OV.js.map → chunk-BVF7MVR5.js.map} +1 -1
- package/dist/{chunk-6SFEITLJ.js → chunk-IRPH6KL4.js} +541 -136
- package/dist/chunk-IRPH6KL4.js.map +1 -0
- package/dist/cli.cjs +1480 -479
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +22 -1
- package/dist/cli.d.ts +22 -1
- package/dist/cli.js +757 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +566 -339
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +16 -18
- package/dist/neatd.cjs +303 -153
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -3
- package/dist/neatd.js.map +1 -1
- package/dist/server.cjs +430 -168
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-6SFEITLJ.js.map +0 -1
- package/dist/chunk-T2U4U256.js.map +0 -1
- package/dist/chunk-WX55TLUT.js +0 -184
- package/dist/chunk-WX55TLUT.js.map +0 -1
package/dist/neatd.cjs
CHANGED
|
@@ -24,12 +24,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/neatd.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_node_fs19 = require("fs");
|
|
28
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
29
29
|
|
|
30
30
|
// src/daemon.ts
|
|
31
|
-
var
|
|
32
|
-
var
|
|
31
|
+
var import_node_fs18 = require("fs");
|
|
32
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
33
33
|
|
|
34
34
|
// src/graph.ts
|
|
35
35
|
var import_graphology = __toESM(require("graphology"), 1);
|
|
@@ -376,6 +376,17 @@ function deprecatedApis() {
|
|
|
376
376
|
return currentMatrix().deprecatedApis ?? [];
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
// src/events.ts
|
|
380
|
+
var import_node_events = require("events");
|
|
381
|
+
var EVENT_BUS_CHANNEL = "event";
|
|
382
|
+
var NeatEventBus = class extends import_node_events.EventEmitter {
|
|
383
|
+
};
|
|
384
|
+
var eventBus = new NeatEventBus();
|
|
385
|
+
eventBus.setMaxListeners(0);
|
|
386
|
+
function emitNeatEvent(envelope) {
|
|
387
|
+
eventBus.emit(EVENT_BUS_CHANNEL, envelope);
|
|
388
|
+
}
|
|
389
|
+
|
|
379
390
|
// src/traverse.ts
|
|
380
391
|
var import_types = require("@neat.is/types");
|
|
381
392
|
var BLAST_RADIUS_DEFAULT_DEPTH = 10;
|
|
@@ -918,10 +929,10 @@ function pickLater(a, b) {
|
|
|
918
929
|
}
|
|
919
930
|
|
|
920
931
|
// src/extract/services.ts
|
|
921
|
-
var
|
|
922
|
-
var
|
|
932
|
+
var import_node_fs7 = require("fs");
|
|
933
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
923
934
|
var import_ignore = __toESM(require("ignore"), 1);
|
|
924
|
-
var
|
|
935
|
+
var import_minimatch2 = require("minimatch");
|
|
925
936
|
var import_types5 = require("@neat.is/types");
|
|
926
937
|
|
|
927
938
|
// src/extract/shared.ts
|
|
@@ -1032,6 +1043,72 @@ function pythonToPackage(service) {
|
|
|
1032
1043
|
};
|
|
1033
1044
|
}
|
|
1034
1045
|
|
|
1046
|
+
// src/extract/owners.ts
|
|
1047
|
+
var import_node_fs6 = require("fs");
|
|
1048
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1049
|
+
var import_minimatch = require("minimatch");
|
|
1050
|
+
async function loadCodeowners(scanPath) {
|
|
1051
|
+
const candidates = [
|
|
1052
|
+
import_node_path6.default.join(scanPath, "CODEOWNERS"),
|
|
1053
|
+
import_node_path6.default.join(scanPath, ".github", "CODEOWNERS")
|
|
1054
|
+
];
|
|
1055
|
+
for (const file of candidates) {
|
|
1056
|
+
if (await exists(file)) {
|
|
1057
|
+
const raw = await import_node_fs6.promises.readFile(file, "utf8");
|
|
1058
|
+
return parseCodeowners(raw);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
function parseCodeowners(raw) {
|
|
1064
|
+
const rules = [];
|
|
1065
|
+
for (const line of raw.split("\n")) {
|
|
1066
|
+
const trimmed = line.trim();
|
|
1067
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1068
|
+
const match = /^(\S+)\s+(.+)$/.exec(trimmed);
|
|
1069
|
+
if (!match) continue;
|
|
1070
|
+
rules.push({ pattern: match[1], owners: match[2].trim() });
|
|
1071
|
+
}
|
|
1072
|
+
return { rules };
|
|
1073
|
+
}
|
|
1074
|
+
function matchOwner(file, repoPath) {
|
|
1075
|
+
const normalized = repoPath.split(import_node_path6.default.sep).join("/");
|
|
1076
|
+
for (const rule of file.rules) {
|
|
1077
|
+
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
1078
|
+
}
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
function matchesPattern(rawPattern, repoPath) {
|
|
1082
|
+
let pattern = rawPattern.startsWith("/") ? rawPattern.slice(1) : rawPattern;
|
|
1083
|
+
if (pattern === "*") return !repoPath.includes("/");
|
|
1084
|
+
if (pattern === "**" || pattern === "") return true;
|
|
1085
|
+
if (pattern.endsWith("/")) pattern = pattern + "**";
|
|
1086
|
+
if ((0, import_minimatch.minimatch)(repoPath, pattern, { dot: true })) return true;
|
|
1087
|
+
if (!pattern.includes("*") && (0, import_minimatch.minimatch)(repoPath, pattern + "/**", { dot: true })) return true;
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
async function readPackageJsonAuthor(serviceDir) {
|
|
1091
|
+
const pkgPath = import_node_path6.default.join(serviceDir, "package.json");
|
|
1092
|
+
if (!await exists(pkgPath)) return null;
|
|
1093
|
+
try {
|
|
1094
|
+
const pkg = await readJson(pkgPath);
|
|
1095
|
+
if (!pkg.author) return null;
|
|
1096
|
+
if (typeof pkg.author === "string") return pkg.author;
|
|
1097
|
+
if (typeof pkg.author === "object" && typeof pkg.author.name === "string") return pkg.author.name;
|
|
1098
|
+
return null;
|
|
1099
|
+
} catch {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
async function computeServiceOwner(codeowners, repoPath, serviceDir) {
|
|
1104
|
+
if (codeowners && repoPath !== void 0) {
|
|
1105
|
+
const owner = matchOwner(codeowners, repoPath);
|
|
1106
|
+
if (owner) return owner;
|
|
1107
|
+
}
|
|
1108
|
+
const author = await readPackageJsonAuthor(serviceDir);
|
|
1109
|
+
return author ?? void 0;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1035
1112
|
// src/extract/services.ts
|
|
1036
1113
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
1037
1114
|
function parseScanDepth() {
|
|
@@ -1048,21 +1125,21 @@ function workspaceGlobs(pkg) {
|
|
|
1048
1125
|
return null;
|
|
1049
1126
|
}
|
|
1050
1127
|
async function loadGitignore(scanPath) {
|
|
1051
|
-
const gitignorePath =
|
|
1128
|
+
const gitignorePath = import_node_path7.default.join(scanPath, ".gitignore");
|
|
1052
1129
|
if (!await exists(gitignorePath)) return null;
|
|
1053
|
-
const raw = await
|
|
1130
|
+
const raw = await import_node_fs7.promises.readFile(gitignorePath, "utf8");
|
|
1054
1131
|
return (0, import_ignore.default)().add(raw);
|
|
1055
1132
|
}
|
|
1056
1133
|
async function walkDirs(start, scanPath, options, visit) {
|
|
1057
1134
|
async function recurse(current, depth) {
|
|
1058
1135
|
if (depth > options.maxDepth) return;
|
|
1059
|
-
const entries = await
|
|
1136
|
+
const entries = await import_node_fs7.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
1060
1137
|
for (const entry2 of entries) {
|
|
1061
1138
|
if (!entry2.isDirectory()) continue;
|
|
1062
1139
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
1063
|
-
const child =
|
|
1140
|
+
const child = import_node_path7.default.join(current, entry2.name);
|
|
1064
1141
|
if (options.ig) {
|
|
1065
|
-
const rel =
|
|
1142
|
+
const rel = import_node_path7.default.relative(scanPath, child).split(import_node_path7.default.sep).join("/");
|
|
1066
1143
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
1067
1144
|
}
|
|
1068
1145
|
await visit(child);
|
|
@@ -1077,8 +1154,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
1077
1154
|
for (const raw of globs) {
|
|
1078
1155
|
const pattern = raw.replace(/^\.\//, "");
|
|
1079
1156
|
if (!pattern.includes("*")) {
|
|
1080
|
-
const candidate =
|
|
1081
|
-
if (await exists(
|
|
1157
|
+
const candidate = import_node_path7.default.join(scanPath, pattern);
|
|
1158
|
+
if (await exists(import_node_path7.default.join(candidate, "package.json"))) found.add(candidate);
|
|
1082
1159
|
continue;
|
|
1083
1160
|
}
|
|
1084
1161
|
const segments = pattern.split("/");
|
|
@@ -1087,13 +1164,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
1087
1164
|
if (seg.includes("*")) break;
|
|
1088
1165
|
staticSegments.push(seg);
|
|
1089
1166
|
}
|
|
1090
|
-
const start =
|
|
1167
|
+
const start = import_node_path7.default.join(scanPath, ...staticSegments);
|
|
1091
1168
|
if (!await exists(start)) continue;
|
|
1092
1169
|
const hasDoubleStar = pattern.includes("**");
|
|
1093
1170
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
1094
1171
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
1095
|
-
const rel =
|
|
1096
|
-
if ((0,
|
|
1172
|
+
const rel = import_node_path7.default.relative(scanPath, dir).split(import_node_path7.default.sep).join("/");
|
|
1173
|
+
if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path7.default.join(dir, "package.json"))) {
|
|
1097
1174
|
found.add(dir);
|
|
1098
1175
|
}
|
|
1099
1176
|
});
|
|
@@ -1101,9 +1178,17 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
1101
1178
|
return [...found];
|
|
1102
1179
|
}
|
|
1103
1180
|
async function discoverNodeService(scanPath, dir) {
|
|
1104
|
-
const pkgPath =
|
|
1181
|
+
const pkgPath = import_node_path7.default.join(dir, "package.json");
|
|
1105
1182
|
if (!await exists(pkgPath)) return null;
|
|
1106
|
-
|
|
1183
|
+
let pkg;
|
|
1184
|
+
try {
|
|
1185
|
+
pkg = await readJson(pkgPath);
|
|
1186
|
+
} catch (err) {
|
|
1187
|
+
console.warn(
|
|
1188
|
+
`[neat] services skipped ${import_node_path7.default.relative(scanPath, pkgPath)}: ${err.message}`
|
|
1189
|
+
);
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1107
1192
|
if (!pkg.name) return null;
|
|
1108
1193
|
const node = {
|
|
1109
1194
|
id: (0, import_types5.serviceId)(pkg.name),
|
|
@@ -1112,7 +1197,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
1112
1197
|
language: "javascript",
|
|
1113
1198
|
version: pkg.version,
|
|
1114
1199
|
dependencies: pkg.dependencies ?? {},
|
|
1115
|
-
repoPath:
|
|
1200
|
+
repoPath: import_node_path7.default.relative(scanPath, dir),
|
|
1116
1201
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
|
|
1117
1202
|
};
|
|
1118
1203
|
return { pkg, dir, node };
|
|
@@ -1128,13 +1213,22 @@ async function discoverPyService(scanPath, dir) {
|
|
|
1128
1213
|
language: "python",
|
|
1129
1214
|
version: py.version,
|
|
1130
1215
|
dependencies: py.dependencies,
|
|
1131
|
-
repoPath:
|
|
1216
|
+
repoPath: import_node_path7.default.relative(scanPath, dir)
|
|
1132
1217
|
};
|
|
1133
1218
|
return { pkg, dir, node };
|
|
1134
1219
|
}
|
|
1135
1220
|
async function discoverServices(scanPath) {
|
|
1136
|
-
const rootPkgPath =
|
|
1137
|
-
|
|
1221
|
+
const rootPkgPath = import_node_path7.default.join(scanPath, "package.json");
|
|
1222
|
+
let rootPkg = null;
|
|
1223
|
+
if (await exists(rootPkgPath)) {
|
|
1224
|
+
try {
|
|
1225
|
+
rootPkg = await readJson(rootPkgPath);
|
|
1226
|
+
} catch (err) {
|
|
1227
|
+
console.warn(
|
|
1228
|
+
`[neat] services workspaces skipped ${import_node_path7.default.relative(scanPath, rootPkgPath)}: ${err.message}`
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1138
1232
|
const wsGlobs = rootPkg ? workspaceGlobs(rootPkg) : null;
|
|
1139
1233
|
const candidateDirs = [];
|
|
1140
1234
|
if (wsGlobs) {
|
|
@@ -1147,9 +1241,9 @@ async function discoverServices(scanPath) {
|
|
|
1147
1241
|
scanPath,
|
|
1148
1242
|
{ maxDepth: parseScanDepth(), ig },
|
|
1149
1243
|
async (dir) => {
|
|
1150
|
-
if (await exists(
|
|
1244
|
+
if (await exists(import_node_path7.default.join(dir, "package.json"))) {
|
|
1151
1245
|
candidateDirs.push(dir);
|
|
1152
|
-
} else if (await exists(
|
|
1246
|
+
} else if (await exists(import_node_path7.default.join(dir, "pyproject.toml")) || await exists(import_node_path7.default.join(dir, "requirements.txt")) || await exists(import_node_path7.default.join(dir, "setup.py"))) {
|
|
1153
1247
|
candidateDirs.push(dir);
|
|
1154
1248
|
}
|
|
1155
1249
|
}
|
|
@@ -1163,8 +1257,8 @@ async function discoverServices(scanPath) {
|
|
|
1163
1257
|
if (!service) continue;
|
|
1164
1258
|
const existingDir = seen.get(service.node.name);
|
|
1165
1259
|
if (existingDir !== void 0) {
|
|
1166
|
-
const a =
|
|
1167
|
-
const b =
|
|
1260
|
+
const a = import_node_path7.default.relative(scanPath, existingDir) || ".";
|
|
1261
|
+
const b = import_node_path7.default.relative(scanPath, dir) || ".";
|
|
1168
1262
|
console.warn(
|
|
1169
1263
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
1170
1264
|
);
|
|
@@ -1173,6 +1267,11 @@ async function discoverServices(scanPath) {
|
|
|
1173
1267
|
seen.set(service.node.name, dir);
|
|
1174
1268
|
out.push(service);
|
|
1175
1269
|
}
|
|
1270
|
+
const codeowners = await loadCodeowners(scanPath);
|
|
1271
|
+
for (const service of out) {
|
|
1272
|
+
const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
|
|
1273
|
+
if (owner !== void 0) service.node.owner = owner;
|
|
1274
|
+
}
|
|
1176
1275
|
return out;
|
|
1177
1276
|
}
|
|
1178
1277
|
function addServiceNodes(graph, services) {
|
|
@@ -1195,8 +1294,8 @@ function addServiceNodes(graph, services) {
|
|
|
1195
1294
|
}
|
|
1196
1295
|
|
|
1197
1296
|
// src/extract/aliases.ts
|
|
1198
|
-
var
|
|
1199
|
-
var
|
|
1297
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1298
|
+
var import_node_fs8 = require("fs");
|
|
1200
1299
|
var import_yaml2 = require("yaml");
|
|
1201
1300
|
var import_types6 = require("@neat.is/types");
|
|
1202
1301
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
@@ -1223,21 +1322,29 @@ function indexServicesByName(services) {
|
|
|
1223
1322
|
const map = /* @__PURE__ */ new Map();
|
|
1224
1323
|
for (const s of services) {
|
|
1225
1324
|
map.set(s.node.name, s.node.id);
|
|
1226
|
-
map.set(
|
|
1325
|
+
map.set(import_node_path8.default.basename(s.dir), s.node.id);
|
|
1227
1326
|
}
|
|
1228
1327
|
return map;
|
|
1229
1328
|
}
|
|
1230
1329
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
1231
1330
|
let composePath = null;
|
|
1232
1331
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
1233
|
-
const abs =
|
|
1332
|
+
const abs = import_node_path8.default.join(scanPath, name);
|
|
1234
1333
|
if (await exists(abs)) {
|
|
1235
1334
|
composePath = abs;
|
|
1236
1335
|
break;
|
|
1237
1336
|
}
|
|
1238
1337
|
}
|
|
1239
1338
|
if (!composePath) return;
|
|
1240
|
-
|
|
1339
|
+
let compose;
|
|
1340
|
+
try {
|
|
1341
|
+
compose = await readYaml(composePath);
|
|
1342
|
+
} catch (err) {
|
|
1343
|
+
console.warn(
|
|
1344
|
+
`[neat] aliases compose skipped ${import_node_path8.default.relative(scanPath, composePath)}: ${err.message}`
|
|
1345
|
+
);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1241
1348
|
if (!compose?.services) return;
|
|
1242
1349
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
1243
1350
|
const serviceId3 = serviceIndex.get(composeName);
|
|
@@ -1276,9 +1383,17 @@ function parseDockerfileLabels(content) {
|
|
|
1276
1383
|
}
|
|
1277
1384
|
async function collectDockerfileAliases(graph, services) {
|
|
1278
1385
|
for (const service of services) {
|
|
1279
|
-
const dockerfilePath =
|
|
1386
|
+
const dockerfilePath = import_node_path8.default.join(service.dir, "Dockerfile");
|
|
1280
1387
|
if (!await exists(dockerfilePath)) continue;
|
|
1281
|
-
|
|
1388
|
+
let content;
|
|
1389
|
+
try {
|
|
1390
|
+
content = await import_node_fs8.promises.readFile(dockerfilePath, "utf8");
|
|
1391
|
+
} catch (err) {
|
|
1392
|
+
console.warn(
|
|
1393
|
+
`[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
|
|
1394
|
+
);
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1282
1397
|
const aliases = parseDockerfileLabels(content);
|
|
1283
1398
|
if (aliases.length > 0) addAliases(graph, service.node.id, aliases);
|
|
1284
1399
|
}
|
|
@@ -1286,13 +1401,13 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
1286
1401
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
1287
1402
|
if (depth > max) return [];
|
|
1288
1403
|
const out = [];
|
|
1289
|
-
const entries = await
|
|
1404
|
+
const entries = await import_node_fs8.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
1290
1405
|
for (const entry2 of entries) {
|
|
1291
1406
|
if (entry2.isDirectory()) {
|
|
1292
1407
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
1293
|
-
out.push(...await walkYamlFiles(
|
|
1294
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
1295
|
-
out.push(
|
|
1408
|
+
out.push(...await walkYamlFiles(import_node_path8.default.join(start, entry2.name), depth + 1, max));
|
|
1409
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path8.default.extname(entry2.name))) {
|
|
1410
|
+
out.push(import_node_path8.default.join(start, entry2.name));
|
|
1296
1411
|
}
|
|
1297
1412
|
}
|
|
1298
1413
|
return out;
|
|
@@ -1319,7 +1434,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
1319
1434
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
1320
1435
|
const files = await walkYamlFiles(scanPath);
|
|
1321
1436
|
for (const file of files) {
|
|
1322
|
-
const content = await
|
|
1437
|
+
const content = await import_node_fs8.promises.readFile(file, "utf8");
|
|
1323
1438
|
let docs;
|
|
1324
1439
|
try {
|
|
1325
1440
|
docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -1343,13 +1458,13 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
1343
1458
|
}
|
|
1344
1459
|
|
|
1345
1460
|
// src/extract/databases/index.ts
|
|
1346
|
-
var
|
|
1461
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
1347
1462
|
var import_types7 = require("@neat.is/types");
|
|
1348
1463
|
|
|
1349
1464
|
// src/extract/databases/db-config-yaml.ts
|
|
1350
|
-
var
|
|
1465
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1351
1466
|
async function parse(serviceDir) {
|
|
1352
|
-
const yamlPath =
|
|
1467
|
+
const yamlPath = import_node_path9.default.join(serviceDir, "db-config.yaml");
|
|
1353
1468
|
if (!await exists(yamlPath)) return [];
|
|
1354
1469
|
const raw = await readYaml(yamlPath);
|
|
1355
1470
|
return [
|
|
@@ -1366,12 +1481,12 @@ async function parse(serviceDir) {
|
|
|
1366
1481
|
var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
1367
1482
|
|
|
1368
1483
|
// src/extract/databases/dotenv.ts
|
|
1369
|
-
var
|
|
1370
|
-
var
|
|
1484
|
+
var import_node_fs10 = require("fs");
|
|
1485
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
1371
1486
|
|
|
1372
1487
|
// src/extract/databases/shared.ts
|
|
1373
|
-
var
|
|
1374
|
-
var
|
|
1488
|
+
var import_node_fs9 = require("fs");
|
|
1489
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1375
1490
|
function schemeToEngine(scheme) {
|
|
1376
1491
|
const s = scheme.toLowerCase().split("+")[0];
|
|
1377
1492
|
switch (s) {
|
|
@@ -1410,14 +1525,14 @@ function parseConnectionString(url) {
|
|
|
1410
1525
|
}
|
|
1411
1526
|
async function readIfExists(filePath) {
|
|
1412
1527
|
try {
|
|
1413
|
-
return await
|
|
1528
|
+
return await import_node_fs9.promises.readFile(filePath, "utf8");
|
|
1414
1529
|
} catch {
|
|
1415
1530
|
return null;
|
|
1416
1531
|
}
|
|
1417
1532
|
}
|
|
1418
1533
|
async function findFirst(serviceDir, candidates) {
|
|
1419
1534
|
for (const rel of candidates) {
|
|
1420
|
-
const abs =
|
|
1535
|
+
const abs = import_node_path10.default.join(serviceDir, rel);
|
|
1421
1536
|
const content = await readIfExists(abs);
|
|
1422
1537
|
if (content !== null) return abs;
|
|
1423
1538
|
}
|
|
@@ -1468,15 +1583,15 @@ function parseDotenvLine(line) {
|
|
|
1468
1583
|
return { key, value };
|
|
1469
1584
|
}
|
|
1470
1585
|
async function parse2(serviceDir) {
|
|
1471
|
-
const entries = await
|
|
1586
|
+
const entries = await import_node_fs10.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
1472
1587
|
const configs = [];
|
|
1473
1588
|
const seen = /* @__PURE__ */ new Set();
|
|
1474
1589
|
for (const entry2 of entries) {
|
|
1475
1590
|
if (!entry2.isFile()) continue;
|
|
1476
1591
|
const match = isConfigFile(entry2.name);
|
|
1477
1592
|
if (!match.match || match.fileType !== "env") continue;
|
|
1478
|
-
const filePath =
|
|
1479
|
-
const content = await
|
|
1593
|
+
const filePath = import_node_path11.default.join(serviceDir, entry2.name);
|
|
1594
|
+
const content = await import_node_fs10.promises.readFile(filePath, "utf8");
|
|
1480
1595
|
for (const line of content.split("\n")) {
|
|
1481
1596
|
const parsed = parseDotenvLine(line);
|
|
1482
1597
|
if (!parsed) continue;
|
|
@@ -1494,9 +1609,9 @@ async function parse2(serviceDir) {
|
|
|
1494
1609
|
var dotenvParser = { name: ".env", parse: parse2 };
|
|
1495
1610
|
|
|
1496
1611
|
// src/extract/databases/prisma.ts
|
|
1497
|
-
var
|
|
1612
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1498
1613
|
async function parse3(serviceDir) {
|
|
1499
|
-
const schemaPath =
|
|
1614
|
+
const schemaPath = import_node_path12.default.join(serviceDir, "prisma", "schema.prisma");
|
|
1500
1615
|
const content = await readIfExists(schemaPath);
|
|
1501
1616
|
if (!content) return [];
|
|
1502
1617
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -1625,10 +1740,10 @@ async function parse5(serviceDir) {
|
|
|
1625
1740
|
var knexParser = { name: "knex", parse: parse5 };
|
|
1626
1741
|
|
|
1627
1742
|
// src/extract/databases/ormconfig.ts
|
|
1628
|
-
var
|
|
1743
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
1629
1744
|
async function parse6(serviceDir) {
|
|
1630
1745
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
1631
|
-
const abs =
|
|
1746
|
+
const abs = import_node_path13.default.join(serviceDir, candidate);
|
|
1632
1747
|
if (!await exists(abs)) continue;
|
|
1633
1748
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
1634
1749
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -1686,9 +1801,9 @@ async function parse7(serviceDir) {
|
|
|
1686
1801
|
var typeormParser = { name: "typeorm", parse: parse7 };
|
|
1687
1802
|
|
|
1688
1803
|
// src/extract/databases/sequelize.ts
|
|
1689
|
-
var
|
|
1804
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
1690
1805
|
async function parse8(serviceDir) {
|
|
1691
|
-
const configPath =
|
|
1806
|
+
const configPath = import_node_path14.default.join(serviceDir, "config", "config.json");
|
|
1692
1807
|
if (!await exists(configPath)) return [];
|
|
1693
1808
|
const raw = await readJson(configPath);
|
|
1694
1809
|
const out = [];
|
|
@@ -1714,7 +1829,7 @@ async function parse8(serviceDir) {
|
|
|
1714
1829
|
var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
1715
1830
|
|
|
1716
1831
|
// src/extract/databases/docker-compose.ts
|
|
1717
|
-
var
|
|
1832
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
1718
1833
|
function portFromService(svc) {
|
|
1719
1834
|
for (const raw of svc.ports ?? []) {
|
|
1720
1835
|
const str = String(raw);
|
|
@@ -1741,7 +1856,7 @@ function databaseFromEnv(svc) {
|
|
|
1741
1856
|
}
|
|
1742
1857
|
async function parse9(serviceDir) {
|
|
1743
1858
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
1744
|
-
const abs =
|
|
1859
|
+
const abs = import_node_path15.default.join(serviceDir, name);
|
|
1745
1860
|
if (!await exists(abs)) continue;
|
|
1746
1861
|
const raw = await readYaml(abs);
|
|
1747
1862
|
if (!raw?.services) return [];
|
|
@@ -1921,7 +2036,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
1921
2036
|
provenance: import_types7.Provenance.EXTRACTED,
|
|
1922
2037
|
...config.sourceFile ? {
|
|
1923
2038
|
evidence: {
|
|
1924
|
-
file:
|
|
2039
|
+
file: import_node_path16.default.relative(scanPath, config.sourceFile).split(import_node_path16.default.sep).join("/")
|
|
1925
2040
|
}
|
|
1926
2041
|
} : {}
|
|
1927
2042
|
};
|
|
@@ -1948,15 +2063,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
1948
2063
|
}
|
|
1949
2064
|
|
|
1950
2065
|
// src/extract/configs.ts
|
|
1951
|
-
var
|
|
1952
|
-
var
|
|
2066
|
+
var import_node_fs11 = require("fs");
|
|
2067
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
1953
2068
|
var import_types8 = require("@neat.is/types");
|
|
1954
2069
|
async function walkConfigFiles(dir) {
|
|
1955
2070
|
const out = [];
|
|
1956
2071
|
async function walk(current) {
|
|
1957
|
-
const entries = await
|
|
2072
|
+
const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true });
|
|
1958
2073
|
for (const entry2 of entries) {
|
|
1959
|
-
const full =
|
|
2074
|
+
const full = import_node_path17.default.join(current, entry2.name);
|
|
1960
2075
|
if (entry2.isDirectory()) {
|
|
1961
2076
|
if (!IGNORED_DIRS.has(entry2.name)) await walk(full);
|
|
1962
2077
|
} else if (entry2.isFile() && isConfigFile(entry2.name).match) {
|
|
@@ -1973,13 +2088,13 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
1973
2088
|
for (const service of services) {
|
|
1974
2089
|
const configFiles = await walkConfigFiles(service.dir);
|
|
1975
2090
|
for (const file of configFiles) {
|
|
1976
|
-
const relPath =
|
|
2091
|
+
const relPath = import_node_path17.default.relative(scanPath, file);
|
|
1977
2092
|
const node = {
|
|
1978
2093
|
id: (0, import_types8.configId)(relPath),
|
|
1979
2094
|
type: import_types8.NodeType.ConfigNode,
|
|
1980
|
-
name:
|
|
2095
|
+
name: import_node_path17.default.basename(file),
|
|
1981
2096
|
path: relPath,
|
|
1982
|
-
fileType: isConfigFile(
|
|
2097
|
+
fileType: isConfigFile(import_node_path17.default.basename(file)).fileType
|
|
1983
2098
|
};
|
|
1984
2099
|
if (!graph.hasNode(node.id)) {
|
|
1985
2100
|
graph.addNode(node.id, node);
|
|
@@ -1991,7 +2106,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
1991
2106
|
target: node.id,
|
|
1992
2107
|
type: import_types8.EdgeType.CONFIGURED_BY,
|
|
1993
2108
|
provenance: import_types8.Provenance.EXTRACTED,
|
|
1994
|
-
evidence: { file: relPath.split(
|
|
2109
|
+
evidence: { file: relPath.split(import_node_path17.default.sep).join("/") }
|
|
1995
2110
|
};
|
|
1996
2111
|
if (!graph.hasEdge(edge.id)) {
|
|
1997
2112
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -2006,24 +2121,24 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
2006
2121
|
var import_types14 = require("@neat.is/types");
|
|
2007
2122
|
|
|
2008
2123
|
// src/extract/calls/http.ts
|
|
2009
|
-
var
|
|
2124
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
2010
2125
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
2011
2126
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
2012
2127
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
2013
2128
|
var import_types9 = require("@neat.is/types");
|
|
2014
2129
|
|
|
2015
2130
|
// src/extract/calls/shared.ts
|
|
2016
|
-
var
|
|
2017
|
-
var
|
|
2131
|
+
var import_node_fs12 = require("fs");
|
|
2132
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2018
2133
|
async function walkSourceFiles(dir) {
|
|
2019
2134
|
const out = [];
|
|
2020
2135
|
async function walk(current) {
|
|
2021
|
-
const entries = await
|
|
2136
|
+
const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2022
2137
|
for (const entry2 of entries) {
|
|
2023
|
-
const full =
|
|
2138
|
+
const full = import_node_path18.default.join(current, entry2.name);
|
|
2024
2139
|
if (entry2.isDirectory()) {
|
|
2025
2140
|
if (!IGNORED_DIRS.has(entry2.name)) await walk(full);
|
|
2026
|
-
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(
|
|
2141
|
+
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path18.default.extname(entry2.name))) {
|
|
2027
2142
|
out.push(full);
|
|
2028
2143
|
}
|
|
2029
2144
|
}
|
|
@@ -2036,7 +2151,7 @@ async function loadSourceFiles(dir) {
|
|
|
2036
2151
|
const out = [];
|
|
2037
2152
|
for (const p of paths) {
|
|
2038
2153
|
try {
|
|
2039
|
-
const content = await
|
|
2154
|
+
const content = await import_node_fs12.promises.readFile(p, "utf8");
|
|
2040
2155
|
out.push({ path: p, content });
|
|
2041
2156
|
} catch {
|
|
2042
2157
|
}
|
|
@@ -2092,9 +2207,9 @@ async function addHttpCallEdges(graph, services) {
|
|
|
2092
2207
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
2093
2208
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
2094
2209
|
for (const service of services) {
|
|
2095
|
-
knownHosts.add(
|
|
2210
|
+
knownHosts.add(import_node_path19.default.basename(service.dir));
|
|
2096
2211
|
knownHosts.add(service.pkg.name);
|
|
2097
|
-
hostToNodeId.set(
|
|
2212
|
+
hostToNodeId.set(import_node_path19.default.basename(service.dir), service.node.id);
|
|
2098
2213
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
2099
2214
|
}
|
|
2100
2215
|
let edgesAdded = 0;
|
|
@@ -2102,8 +2217,16 @@ async function addHttpCallEdges(graph, services) {
|
|
|
2102
2217
|
const files = await loadSourceFiles(service.dir);
|
|
2103
2218
|
const seenTargets = /* @__PURE__ */ new Map();
|
|
2104
2219
|
for (const file of files) {
|
|
2105
|
-
const parser =
|
|
2106
|
-
|
|
2220
|
+
const parser = import_node_path19.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
2221
|
+
let targets;
|
|
2222
|
+
try {
|
|
2223
|
+
targets = callsFromSource(file.content, parser, knownHosts);
|
|
2224
|
+
} catch (err) {
|
|
2225
|
+
console.warn(
|
|
2226
|
+
`[neat] http call extraction skipped ${file.path}: ${err.message}`
|
|
2227
|
+
);
|
|
2228
|
+
continue;
|
|
2229
|
+
}
|
|
2107
2230
|
for (const t of targets) {
|
|
2108
2231
|
const targetId = hostToNodeId.get(t);
|
|
2109
2232
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -2122,7 +2245,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
2122
2245
|
type: import_types9.EdgeType.CALLS,
|
|
2123
2246
|
provenance: import_types9.Provenance.EXTRACTED,
|
|
2124
2247
|
evidence: {
|
|
2125
|
-
file:
|
|
2248
|
+
file: import_node_path19.default.relative(service.dir, evidenceFile.file),
|
|
2126
2249
|
line,
|
|
2127
2250
|
snippet: snippet(fileContent, line)
|
|
2128
2251
|
}
|
|
@@ -2137,7 +2260,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
2137
2260
|
}
|
|
2138
2261
|
|
|
2139
2262
|
// src/extract/calls/kafka.ts
|
|
2140
|
-
var
|
|
2263
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
2141
2264
|
var import_types10 = require("@neat.is/types");
|
|
2142
2265
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
2143
2266
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -2164,7 +2287,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
2164
2287
|
kind: "kafka-topic",
|
|
2165
2288
|
edgeType,
|
|
2166
2289
|
evidence: {
|
|
2167
|
-
file:
|
|
2290
|
+
file: import_node_path20.default.relative(serviceDir, file.path),
|
|
2168
2291
|
line,
|
|
2169
2292
|
snippet: snippet(file.content, line)
|
|
2170
2293
|
}
|
|
@@ -2176,7 +2299,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
2176
2299
|
}
|
|
2177
2300
|
|
|
2178
2301
|
// src/extract/calls/redis.ts
|
|
2179
|
-
var
|
|
2302
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
2180
2303
|
var import_types11 = require("@neat.is/types");
|
|
2181
2304
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
2182
2305
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -2195,7 +2318,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
2195
2318
|
kind: "redis",
|
|
2196
2319
|
edgeType: "CALLS",
|
|
2197
2320
|
evidence: {
|
|
2198
|
-
file:
|
|
2321
|
+
file: import_node_path21.default.relative(serviceDir, file.path),
|
|
2199
2322
|
line,
|
|
2200
2323
|
snippet: snippet(file.content, line)
|
|
2201
2324
|
}
|
|
@@ -2205,7 +2328,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
2205
2328
|
}
|
|
2206
2329
|
|
|
2207
2330
|
// src/extract/calls/aws.ts
|
|
2208
|
-
var
|
|
2331
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
2209
2332
|
var import_types12 = require("@neat.is/types");
|
|
2210
2333
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
2211
2334
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -2235,7 +2358,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
2235
2358
|
kind,
|
|
2236
2359
|
edgeType: "CALLS",
|
|
2237
2360
|
evidence: {
|
|
2238
|
-
file:
|
|
2361
|
+
file: import_node_path22.default.relative(serviceDir, file.path),
|
|
2239
2362
|
line,
|
|
2240
2363
|
snippet: snippet(file.content, line)
|
|
2241
2364
|
}
|
|
@@ -2259,7 +2382,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
2259
2382
|
}
|
|
2260
2383
|
|
|
2261
2384
|
// src/extract/calls/grpc.ts
|
|
2262
|
-
var
|
|
2385
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
2263
2386
|
var import_types13 = require("@neat.is/types");
|
|
2264
2387
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
2265
2388
|
function isLikelyAddress(value) {
|
|
@@ -2284,7 +2407,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
2284
2407
|
kind: "grpc-service",
|
|
2285
2408
|
edgeType: "CALLS",
|
|
2286
2409
|
evidence: {
|
|
2287
|
-
file:
|
|
2410
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
2288
2411
|
line,
|
|
2289
2412
|
snippet: snippet(file.content, line)
|
|
2290
2413
|
}
|
|
@@ -2360,7 +2483,7 @@ async function addCallEdges(graph, services) {
|
|
|
2360
2483
|
}
|
|
2361
2484
|
|
|
2362
2485
|
// src/extract/infra/docker-compose.ts
|
|
2363
|
-
var
|
|
2486
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
2364
2487
|
var import_types16 = require("@neat.is/types");
|
|
2365
2488
|
|
|
2366
2489
|
// src/extract/infra/shared.ts
|
|
@@ -2397,7 +2520,7 @@ function dependsOnList(value) {
|
|
|
2397
2520
|
}
|
|
2398
2521
|
function serviceNameToServiceNode(name, services) {
|
|
2399
2522
|
for (const s of services) {
|
|
2400
|
-
if (s.node.name === name ||
|
|
2523
|
+
if (s.node.name === name || import_node_path24.default.basename(s.dir) === name) return s.node.id;
|
|
2401
2524
|
}
|
|
2402
2525
|
return null;
|
|
2403
2526
|
}
|
|
@@ -2406,16 +2529,24 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
2406
2529
|
let edgesAdded = 0;
|
|
2407
2530
|
let composePath = null;
|
|
2408
2531
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
2409
|
-
const abs =
|
|
2532
|
+
const abs = import_node_path24.default.join(scanPath, name);
|
|
2410
2533
|
if (await exists(abs)) {
|
|
2411
2534
|
composePath = abs;
|
|
2412
2535
|
break;
|
|
2413
2536
|
}
|
|
2414
2537
|
}
|
|
2415
2538
|
if (!composePath) return { nodesAdded, edgesAdded };
|
|
2416
|
-
|
|
2539
|
+
let compose;
|
|
2540
|
+
try {
|
|
2541
|
+
compose = await readYaml(composePath);
|
|
2542
|
+
} catch (err) {
|
|
2543
|
+
console.warn(
|
|
2544
|
+
`[neat] infra docker-compose skipped ${import_node_path24.default.relative(scanPath, composePath)}: ${err.message}`
|
|
2545
|
+
);
|
|
2546
|
+
return { nodesAdded, edgesAdded };
|
|
2547
|
+
}
|
|
2417
2548
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
2418
|
-
const evidenceFile =
|
|
2549
|
+
const evidenceFile = import_node_path24.default.relative(scanPath, composePath).split(import_node_path24.default.sep).join("/");
|
|
2419
2550
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
2420
2551
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
2421
2552
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -2455,8 +2586,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
2455
2586
|
}
|
|
2456
2587
|
|
|
2457
2588
|
// src/extract/infra/dockerfile.ts
|
|
2458
|
-
var
|
|
2459
|
-
var
|
|
2589
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
2590
|
+
var import_node_fs13 = require("fs");
|
|
2460
2591
|
var import_types17 = require("@neat.is/types");
|
|
2461
2592
|
function runtimeImage(content) {
|
|
2462
2593
|
const lines = content.split("\n");
|
|
@@ -2476,9 +2607,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
2476
2607
|
let nodesAdded = 0;
|
|
2477
2608
|
let edgesAdded = 0;
|
|
2478
2609
|
for (const service of services) {
|
|
2479
|
-
const dockerfilePath =
|
|
2610
|
+
const dockerfilePath = import_node_path25.default.join(service.dir, "Dockerfile");
|
|
2480
2611
|
if (!await exists(dockerfilePath)) continue;
|
|
2481
|
-
|
|
2612
|
+
let content;
|
|
2613
|
+
try {
|
|
2614
|
+
content = await import_node_fs13.promises.readFile(dockerfilePath, "utf8");
|
|
2615
|
+
} catch (err) {
|
|
2616
|
+
console.warn(
|
|
2617
|
+
`[neat] infra dockerfile skipped ${import_node_path25.default.relative(scanPath, dockerfilePath)}: ${err.message}`
|
|
2618
|
+
);
|
|
2619
|
+
continue;
|
|
2620
|
+
}
|
|
2482
2621
|
const image = runtimeImage(content);
|
|
2483
2622
|
if (!image) continue;
|
|
2484
2623
|
const node = makeInfraNode("container-image", image);
|
|
@@ -2495,7 +2634,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
2495
2634
|
type: import_types17.EdgeType.RUNS_ON,
|
|
2496
2635
|
provenance: import_types17.Provenance.EXTRACTED,
|
|
2497
2636
|
evidence: {
|
|
2498
|
-
file:
|
|
2637
|
+
file: import_node_path25.default.relative(scanPath, dockerfilePath).split(import_node_path25.default.sep).join("/")
|
|
2499
2638
|
}
|
|
2500
2639
|
};
|
|
2501
2640
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -2506,19 +2645,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
2506
2645
|
}
|
|
2507
2646
|
|
|
2508
2647
|
// src/extract/infra/terraform.ts
|
|
2509
|
-
var
|
|
2510
|
-
var
|
|
2648
|
+
var import_node_fs14 = require("fs");
|
|
2649
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
2511
2650
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
2512
2651
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
2513
2652
|
if (depth > max) return [];
|
|
2514
2653
|
const out = [];
|
|
2515
|
-
const entries = await
|
|
2654
|
+
const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
2516
2655
|
for (const entry2 of entries) {
|
|
2517
2656
|
if (entry2.isDirectory()) {
|
|
2518
2657
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
2519
|
-
out.push(...await walkTfFiles(
|
|
2658
|
+
out.push(...await walkTfFiles(import_node_path26.default.join(start, entry2.name), depth + 1, max));
|
|
2520
2659
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
2521
|
-
out.push(
|
|
2660
|
+
out.push(import_node_path26.default.join(start, entry2.name));
|
|
2522
2661
|
}
|
|
2523
2662
|
}
|
|
2524
2663
|
return out;
|
|
@@ -2527,7 +2666,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
2527
2666
|
let nodesAdded = 0;
|
|
2528
2667
|
const files = await walkTfFiles(scanPath);
|
|
2529
2668
|
for (const file of files) {
|
|
2530
|
-
const content = await
|
|
2669
|
+
const content = await import_node_fs14.promises.readFile(file, "utf8");
|
|
2531
2670
|
RESOURCE_RE.lastIndex = 0;
|
|
2532
2671
|
let m;
|
|
2533
2672
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -2544,8 +2683,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
2544
2683
|
}
|
|
2545
2684
|
|
|
2546
2685
|
// src/extract/infra/k8s.ts
|
|
2547
|
-
var
|
|
2548
|
-
var
|
|
2686
|
+
var import_node_fs15 = require("fs");
|
|
2687
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
2549
2688
|
var import_yaml3 = require("yaml");
|
|
2550
2689
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
2551
2690
|
Service: "k8s-service",
|
|
@@ -2559,13 +2698,13 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
2559
2698
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
2560
2699
|
if (depth > max) return [];
|
|
2561
2700
|
const out = [];
|
|
2562
|
-
const entries = await
|
|
2701
|
+
const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
2563
2702
|
for (const entry2 of entries) {
|
|
2564
2703
|
if (entry2.isDirectory()) {
|
|
2565
2704
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2566
|
-
out.push(...await walkYamlFiles2(
|
|
2567
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
2568
|
-
out.push(
|
|
2705
|
+
out.push(...await walkYamlFiles2(import_node_path27.default.join(start, entry2.name), depth + 1, max));
|
|
2706
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path27.default.extname(entry2.name))) {
|
|
2707
|
+
out.push(import_node_path27.default.join(start, entry2.name));
|
|
2569
2708
|
}
|
|
2570
2709
|
}
|
|
2571
2710
|
return out;
|
|
@@ -2574,7 +2713,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
2574
2713
|
let nodesAdded = 0;
|
|
2575
2714
|
const files = await walkYamlFiles2(scanPath);
|
|
2576
2715
|
for (const file of files) {
|
|
2577
|
-
const content = await
|
|
2716
|
+
const content = await import_node_fs15.promises.readFile(file, "utf8");
|
|
2578
2717
|
let docs;
|
|
2579
2718
|
try {
|
|
2580
2719
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -2620,16 +2759,27 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
2620
2759
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
2621
2760
|
const frontiersPromoted = promoteFrontierNodes(graph);
|
|
2622
2761
|
if (opts.onPolicyTrigger) await opts.onPolicyTrigger(graph);
|
|
2623
|
-
|
|
2762
|
+
const result = {
|
|
2624
2763
|
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
2625
2764
|
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
2626
2765
|
frontiersPromoted
|
|
2627
2766
|
};
|
|
2767
|
+
emitNeatEvent({
|
|
2768
|
+
type: "extraction-complete",
|
|
2769
|
+
project: opts.project ?? DEFAULT_PROJECT,
|
|
2770
|
+
payload: {
|
|
2771
|
+
project: opts.project ?? DEFAULT_PROJECT,
|
|
2772
|
+
fileCount: services.length,
|
|
2773
|
+
nodesAdded: result.nodesAdded,
|
|
2774
|
+
edgesAdded: result.edgesAdded
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
return result;
|
|
2628
2778
|
}
|
|
2629
2779
|
|
|
2630
2780
|
// src/persist.ts
|
|
2631
|
-
var
|
|
2632
|
-
var
|
|
2781
|
+
var import_node_fs16 = require("fs");
|
|
2782
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
2633
2783
|
var SCHEMA_VERSION = 2;
|
|
2634
2784
|
function migrateV1ToV2(payload) {
|
|
2635
2785
|
const nodes = payload.graph.nodes;
|
|
@@ -2643,7 +2793,7 @@ function migrateV1ToV2(payload) {
|
|
|
2643
2793
|
return { ...payload, schemaVersion: 2 };
|
|
2644
2794
|
}
|
|
2645
2795
|
async function ensureDir(filePath) {
|
|
2646
|
-
await
|
|
2796
|
+
await import_node_fs16.promises.mkdir(import_node_path28.default.dirname(filePath), { recursive: true });
|
|
2647
2797
|
}
|
|
2648
2798
|
async function saveGraphToDisk(graph, outPath) {
|
|
2649
2799
|
await ensureDir(outPath);
|
|
@@ -2653,13 +2803,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
2653
2803
|
graph: graph.export()
|
|
2654
2804
|
};
|
|
2655
2805
|
const tmp = `${outPath}.tmp`;
|
|
2656
|
-
await
|
|
2657
|
-
await
|
|
2806
|
+
await import_node_fs16.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
2807
|
+
await import_node_fs16.promises.rename(tmp, outPath);
|
|
2658
2808
|
}
|
|
2659
2809
|
async function loadGraphFromDisk(graph, outPath) {
|
|
2660
2810
|
let raw;
|
|
2661
2811
|
try {
|
|
2662
|
-
raw = await
|
|
2812
|
+
raw = await import_node_fs16.promises.readFile(outPath, "utf8");
|
|
2663
2813
|
} catch (err) {
|
|
2664
2814
|
if (err.code === "ENOENT") return;
|
|
2665
2815
|
throw err;
|
|
@@ -2711,62 +2861,62 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
2711
2861
|
}
|
|
2712
2862
|
|
|
2713
2863
|
// src/projects.ts
|
|
2714
|
-
var
|
|
2864
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
2715
2865
|
function pathsForProject(project, baseDir) {
|
|
2716
2866
|
if (project === DEFAULT_PROJECT) {
|
|
2717
2867
|
return {
|
|
2718
|
-
snapshotPath:
|
|
2719
|
-
errorsPath:
|
|
2720
|
-
staleEventsPath:
|
|
2721
|
-
embeddingsCachePath:
|
|
2722
|
-
policyViolationsPath:
|
|
2868
|
+
snapshotPath: import_node_path29.default.join(baseDir, "graph.json"),
|
|
2869
|
+
errorsPath: import_node_path29.default.join(baseDir, "errors.ndjson"),
|
|
2870
|
+
staleEventsPath: import_node_path29.default.join(baseDir, "stale-events.ndjson"),
|
|
2871
|
+
embeddingsCachePath: import_node_path29.default.join(baseDir, "embeddings.json"),
|
|
2872
|
+
policyViolationsPath: import_node_path29.default.join(baseDir, "policy-violations.ndjson")
|
|
2723
2873
|
};
|
|
2724
2874
|
}
|
|
2725
2875
|
return {
|
|
2726
|
-
snapshotPath:
|
|
2727
|
-
errorsPath:
|
|
2728
|
-
staleEventsPath:
|
|
2729
|
-
embeddingsCachePath:
|
|
2730
|
-
policyViolationsPath:
|
|
2876
|
+
snapshotPath: import_node_path29.default.join(baseDir, `${project}.json`),
|
|
2877
|
+
errorsPath: import_node_path29.default.join(baseDir, `errors.${project}.ndjson`),
|
|
2878
|
+
staleEventsPath: import_node_path29.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
2879
|
+
embeddingsCachePath: import_node_path29.default.join(baseDir, `embeddings.${project}.json`),
|
|
2880
|
+
policyViolationsPath: import_node_path29.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
2731
2881
|
};
|
|
2732
2882
|
}
|
|
2733
2883
|
|
|
2734
2884
|
// src/registry.ts
|
|
2735
|
-
var
|
|
2885
|
+
var import_node_fs17 = require("fs");
|
|
2736
2886
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
2737
|
-
var
|
|
2887
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
2738
2888
|
var import_types18 = require("@neat.is/types");
|
|
2739
2889
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
2740
2890
|
var LOCK_RETRY_MS = 50;
|
|
2741
2891
|
function neatHome() {
|
|
2742
2892
|
const override = process.env.NEAT_HOME;
|
|
2743
|
-
if (override && override.length > 0) return
|
|
2744
|
-
return
|
|
2893
|
+
if (override && override.length > 0) return import_node_path30.default.resolve(override);
|
|
2894
|
+
return import_node_path30.default.join(import_node_os2.default.homedir(), ".neat");
|
|
2745
2895
|
}
|
|
2746
2896
|
function registryPath() {
|
|
2747
|
-
return
|
|
2897
|
+
return import_node_path30.default.join(neatHome(), "projects.json");
|
|
2748
2898
|
}
|
|
2749
2899
|
function registryLockPath() {
|
|
2750
|
-
return
|
|
2900
|
+
return import_node_path30.default.join(neatHome(), "projects.json.lock");
|
|
2751
2901
|
}
|
|
2752
2902
|
async function writeAtomically(target, contents) {
|
|
2753
|
-
await
|
|
2903
|
+
await import_node_fs17.promises.mkdir(import_node_path30.default.dirname(target), { recursive: true });
|
|
2754
2904
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
2755
|
-
const fd = await
|
|
2905
|
+
const fd = await import_node_fs17.promises.open(tmp, "w");
|
|
2756
2906
|
try {
|
|
2757
2907
|
await fd.writeFile(contents, "utf8");
|
|
2758
2908
|
await fd.sync();
|
|
2759
2909
|
} finally {
|
|
2760
2910
|
await fd.close();
|
|
2761
2911
|
}
|
|
2762
|
-
await
|
|
2912
|
+
await import_node_fs17.promises.rename(tmp, target);
|
|
2763
2913
|
}
|
|
2764
2914
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
2765
2915
|
const deadline = Date.now() + timeoutMs;
|
|
2766
|
-
await
|
|
2916
|
+
await import_node_fs17.promises.mkdir(import_node_path30.default.dirname(lockPath), { recursive: true });
|
|
2767
2917
|
while (true) {
|
|
2768
2918
|
try {
|
|
2769
|
-
const fd = await
|
|
2919
|
+
const fd = await import_node_fs17.promises.open(lockPath, "wx");
|
|
2770
2920
|
await fd.close();
|
|
2771
2921
|
return;
|
|
2772
2922
|
} catch (err) {
|
|
@@ -2782,7 +2932,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
|
2782
2932
|
}
|
|
2783
2933
|
}
|
|
2784
2934
|
async function releaseLock(lockPath) {
|
|
2785
|
-
await
|
|
2935
|
+
await import_node_fs17.promises.unlink(lockPath).catch(() => {
|
|
2786
2936
|
});
|
|
2787
2937
|
}
|
|
2788
2938
|
async function withLock(fn) {
|
|
@@ -2798,7 +2948,7 @@ async function readRegistry() {
|
|
|
2798
2948
|
const file = registryPath();
|
|
2799
2949
|
let raw;
|
|
2800
2950
|
try {
|
|
2801
|
-
raw = await
|
|
2951
|
+
raw = await import_node_fs17.promises.readFile(file, "utf8");
|
|
2802
2952
|
} catch (err) {
|
|
2803
2953
|
if (err.code === "ENOENT") {
|
|
2804
2954
|
return { version: 1, projects: [] };
|
|
@@ -2838,15 +2988,15 @@ async function touchLastSeen(name, at = (/* @__PURE__ */ new Date()).toISOString
|
|
|
2838
2988
|
|
|
2839
2989
|
// src/daemon.ts
|
|
2840
2990
|
function neatHomeFor(opts) {
|
|
2841
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
2991
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path31.default.resolve(opts.neatHome);
|
|
2842
2992
|
const env = process.env.NEAT_HOME;
|
|
2843
|
-
if (env && env.length > 0) return
|
|
2993
|
+
if (env && env.length > 0) return import_node_path31.default.resolve(env);
|
|
2844
2994
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
2845
|
-
return
|
|
2995
|
+
return import_node_path31.default.join(home, ".neat");
|
|
2846
2996
|
}
|
|
2847
2997
|
async function bootstrapProject(entry2) {
|
|
2848
2998
|
try {
|
|
2849
|
-
const stat = await
|
|
2999
|
+
const stat = await import_node_fs18.promises.stat(entry2.path);
|
|
2850
3000
|
if (!stat.isDirectory()) {
|
|
2851
3001
|
throw new Error(`registered path ${entry2.path} is not a directory`);
|
|
2852
3002
|
}
|
|
@@ -2869,7 +3019,7 @@ async function bootstrapProject(entry2) {
|
|
|
2869
3019
|
const graph = getGraph(entry2.name);
|
|
2870
3020
|
const outPath = pathsForProject(
|
|
2871
3021
|
entry2.name,
|
|
2872
|
-
|
|
3022
|
+
import_node_path31.default.join(entry2.path, "neat-out")
|
|
2873
3023
|
).snapshotPath;
|
|
2874
3024
|
await loadGraphFromDisk(graph, outPath);
|
|
2875
3025
|
await extractFromDirectory(graph, entry2.path);
|
|
@@ -2888,13 +3038,13 @@ async function startDaemon(opts = {}) {
|
|
|
2888
3038
|
const home = neatHomeFor(opts);
|
|
2889
3039
|
const regPath = registryPath();
|
|
2890
3040
|
try {
|
|
2891
|
-
await
|
|
3041
|
+
await import_node_fs18.promises.access(regPath);
|
|
2892
3042
|
} catch {
|
|
2893
3043
|
throw new Error(
|
|
2894
3044
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
2895
3045
|
);
|
|
2896
3046
|
}
|
|
2897
|
-
const pidPath =
|
|
3047
|
+
const pidPath = import_node_path31.default.join(home, "neatd.pid");
|
|
2898
3048
|
await writeAtomically(pidPath, `${process.pid}
|
|
2899
3049
|
`);
|
|
2900
3050
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -2960,7 +3110,7 @@ async function startDaemon(opts = {}) {
|
|
|
2960
3110
|
} catch {
|
|
2961
3111
|
}
|
|
2962
3112
|
}
|
|
2963
|
-
await
|
|
3113
|
+
await import_node_fs18.promises.unlink(pidPath).catch(() => {
|
|
2964
3114
|
});
|
|
2965
3115
|
};
|
|
2966
3116
|
return { slots, reload, stop, pidPath };
|
|
@@ -2969,14 +3119,14 @@ async function startDaemon(opts = {}) {
|
|
|
2969
3119
|
// src/neatd.ts
|
|
2970
3120
|
function neatHome2() {
|
|
2971
3121
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
2972
|
-
return
|
|
3122
|
+
return import_node_path32.default.resolve(process.env.NEAT_HOME);
|
|
2973
3123
|
}
|
|
2974
3124
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
2975
|
-
return
|
|
3125
|
+
return import_node_path32.default.join(home, ".neat");
|
|
2976
3126
|
}
|
|
2977
3127
|
async function readPid() {
|
|
2978
3128
|
try {
|
|
2979
|
-
const raw = await
|
|
3129
|
+
const raw = await import_node_fs19.promises.readFile(import_node_path32.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
2980
3130
|
const n = Number.parseInt(raw.trim(), 10);
|
|
2981
3131
|
return Number.isFinite(n) ? n : null;
|
|
2982
3132
|
} catch {
|